@hlw-uni/mp-vue 1.0.3 → 1.0.5

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.
@@ -1,4 +1,65 @@
1
- <script lang="ts">
2
- import Avatar from './index';
3
- export default Avatar;
1
+ <template>
2
+ <view :class="`hlw-avatar hlw-avatar--${size ?? 'medium'}`">
3
+ <image
4
+ v-if="src && !loadError"
5
+ class="hlw-avatar__image"
6
+ :src="src"
7
+ mode="aspectFill"
8
+ @error="loadError = true"
9
+ />
10
+ <view v-else class="hlw-avatar__placeholder">
11
+ <text class="hlw-avatar__initial">{{ initial }}</text>
12
+ </view>
13
+ </view>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ import { ref, computed } from 'vue';
18
+
19
+ const props = defineProps<{
20
+ src?: string;
21
+ name?: string;
22
+ size?: 'small' | 'medium' | 'large';
23
+ }>();
24
+
25
+ const loadError = ref(false);
26
+ const initial = computed(() => {
27
+ if (!props.name) return '?';
28
+ return props.name.charAt(0).toUpperCase();
29
+ });
4
30
  </script>
31
+
32
+ <style scoped>
33
+ .hlw-avatar {
34
+ border-radius: 50%;
35
+ overflow: hidden;
36
+ flex-shrink: 0;
37
+ }
38
+
39
+ .hlw-avatar--small { width: 56rpx; height: 56rpx; }
40
+ .hlw-avatar--medium { width: 80rpx; height: 80rpx; }
41
+ .hlw-avatar--large { width: 120rpx; height: 120rpx; }
42
+
43
+ .hlw-avatar__image {
44
+ width: 100%;
45
+ height: 100%;
46
+ }
47
+
48
+ .hlw-avatar__placeholder {
49
+ width: 100%;
50
+ height: 100%;
51
+ background: #07c160;
52
+ display: flex;
53
+ align-items: center;
54
+ justify-content: center;
55
+ }
56
+
57
+ .hlw-avatar__initial {
58
+ color: #fff;
59
+ font-weight: bold;
60
+ }
61
+
62
+ .hlw-avatar--small .hlw-avatar__initial { font-size: 22rpx; }
63
+ .hlw-avatar--medium .hlw-avatar__initial { font-size: 30rpx; }
64
+ .hlw-avatar--large .hlw-avatar__initial { font-size: 46rpx; }
65
+ </style>
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <view class="hlw-card">
3
+ <view
4
+ v-if="$slots.header || title || icon || $slots['header-left'] || $slots['header-right']"
5
+ class="hlw-card-header"
6
+ >
7
+ <slot name="header">
8
+ <view class="flex items-center justify-between px-5 py-4 border-0 border-b border-dashed border-slate-200 bg-slate-50/30">
9
+ <view v-if="$slots['header-left'] || title || icon">
10
+ <slot name="header-left">
11
+ <view class="text-sm font-bold text-slate-800 flex items-center gap-2 tracking-wide">
12
+ <text v-if="icon" :class="[icon, iconColor]"></text>
13
+ <text>{{ title }}</text>
14
+ </view>
15
+ </slot>
16
+ </view>
17
+ <view v-if="$slots['header-right'] || extra">
18
+ <slot name="header-right">
19
+ <text
20
+ v-if="extra"
21
+ class="text-[10px] text-slate-400 bg-slate-50 px-2 py-1 rounded border border-slate-100 tracking-wide"
22
+ >{{ extra }}</text>
23
+ </slot>
24
+ </view>
25
+ </view>
26
+ </slot>
27
+ </view>
28
+ <view class="hlw-card-body">
29
+ <slot></slot>
30
+ </view>
31
+ </view>
32
+ </template>
33
+
34
+ <script setup lang="ts">
35
+ interface Props {
36
+ title?: string;
37
+ icon?: string;
38
+ iconColor?: string;
39
+ extra?: string;
40
+ }
41
+
42
+ withDefaults(defineProps<Props>(), {
43
+ title: '',
44
+ icon: '',
45
+ iconColor: '',
46
+ extra: '',
47
+ });
48
+
49
+ defineOptions({
50
+ options: {
51
+ styleIsolation: 'shared',
52
+ virtualHost: true,
53
+ },
54
+ });
55
+ </script>
56
+
57
+ <style scoped>
58
+ .hlw-card {
59
+ @apply bg-white rounded-xl border border-solid border-slate-200 overflow-hidden w-full;
60
+ }
61
+ </style>
@@ -1,4 +1,41 @@
1
- <script lang="ts">
2
- import Empty from './index';
3
- export default Empty;
1
+ <template>
2
+ <view class="hlw-empty">
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>
6
+ <slot />
7
+ </view>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ defineProps<{
12
+ text?: string;
13
+ image?: string;
14
+ }>();
4
15
  </script>
16
+
17
+ <style scoped>
18
+ .hlw-empty {
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ justify-content: center;
23
+ padding: 80rpx 40rpx;
24
+ }
25
+
26
+ .hlw-empty__image {
27
+ width: 200rpx;
28
+ height: 200rpx;
29
+ margin-bottom: 24rpx;
30
+ }
31
+
32
+ .hlw-empty__icon {
33
+ font-size: 100rpx;
34
+ margin-bottom: 20rpx;
35
+ }
36
+
37
+ .hlw-empty__text {
38
+ font-size: 28rpx;
39
+ color: #bbb;
40
+ }
41
+ </style>
@@ -0,0 +1,138 @@
1
+ <template>
2
+ <view class="hlw-header" :style="{ height: totalNavBarHeight + 'px' }">
3
+ <view class="header-bg-layer" :class="hasBgSlot ? '' : props.bgClass">
4
+ <slot v-if="!props.isBack" name="bg"></slot>
5
+ </view>
6
+ <view class="status-bar-spacer" :style="{ height: statusBarHeight + 'px' }"></view>
7
+ <view class="header-content-area" :style="{ height: NAV_BAR_CONTENT_HEIGHT + 'px' }">
8
+ <template v-if="props.isBack">
9
+ <view class="header-back" @click="handleBack">
10
+ <text class="iconfont icon-back"></text>
11
+ </view>
12
+ <view class="header-title">{{ props.title }}</view>
13
+ <view class="header-placeholder"></view>
14
+ </template>
15
+ <slot v-else>
16
+ <view v-if="props.title" class="header-title">{{ props.title }}</view>
17
+ </slot>
18
+ </view>
19
+ </view>
20
+ </template>
21
+
22
+ <script setup lang="ts">
23
+ import { ref, computed, useSlots } from 'vue';
24
+
25
+ const getNavBarContentHeight = (): number => {
26
+ try {
27
+ const menuInfo = uni.getMenuButtonBoundingClientRect?.();
28
+ if (!menuInfo) return 44;
29
+ const systemInfo = uni.getSystemInfoSync();
30
+ return (menuInfo.top - systemInfo.statusBarHeight!) * 2 + menuInfo.height;
31
+ } catch {
32
+ return 44;
33
+ }
34
+ };
35
+
36
+ const getStatusBarHeight = (): number => {
37
+ try {
38
+ const systemInfo = uni.getSystemInfoSync();
39
+ return systemInfo.statusBarHeight || 20;
40
+ } catch {
41
+ return 20;
42
+ }
43
+ };
44
+
45
+ const NAV_BAR_CONTENT_HEIGHT = getNavBarContentHeight();
46
+
47
+ interface Props {
48
+ extraHeight?: number;
49
+ bgClass?: string;
50
+ isBack?: boolean;
51
+ title?: string;
52
+ }
53
+
54
+ const props = withDefaults(defineProps<Props>(), {
55
+ extraHeight: 0,
56
+ bgClass: '',
57
+ isBack: false,
58
+ title: '',
59
+ });
60
+
61
+ const emit = defineEmits<{
62
+ back: [];
63
+ }>();
64
+
65
+ const handleBack = () => {
66
+ emit('back');
67
+ uni.navigateBack({ delta: 1 });
68
+ };
69
+
70
+ const slots = useSlots();
71
+ const hasBgSlot = computed(() => !props.isBack && !!slots.bg);
72
+ const statusBarHeight = ref(getStatusBarHeight());
73
+ const totalNavBarHeight = computed(() => statusBarHeight.value + NAV_BAR_CONTENT_HEIGHT + props.extraHeight);
74
+ </script>
75
+
76
+ <style lang="scss" scoped>
77
+ .hlw-header {
78
+ position: sticky;
79
+ top: 0;
80
+ z-index: 999;
81
+ display: flex;
82
+ flex-direction: column;
83
+ overflow: hidden;
84
+ }
85
+
86
+ .header-bg-layer {
87
+ position: absolute;
88
+ top: 0;
89
+ left: 0;
90
+ right: 0;
91
+ bottom: 0;
92
+ z-index: 0;
93
+ }
94
+
95
+ .status-bar-spacer {
96
+ flex-shrink: 0;
97
+ width: 100%;
98
+ position: relative;
99
+ z-index: 1;
100
+ }
101
+
102
+ .header-content-area {
103
+ flex-shrink: 0;
104
+ width: 100%;
105
+ display: flex;
106
+ align-items: center;
107
+ position: relative;
108
+ z-index: 1;
109
+ }
110
+
111
+ .header-back {
112
+ width: 88rpx;
113
+ height: 100%;
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ flex-shrink: 0;
118
+
119
+ .iconfont {
120
+ font-size: 40rpx;
121
+ }
122
+ }
123
+
124
+ .header-title {
125
+ flex: 1;
126
+ text-align: center;
127
+ font-size: 28rpx;
128
+ font-weight: 500;
129
+ overflow: hidden;
130
+ text-overflow: ellipsis;
131
+ white-space: nowrap;
132
+ }
133
+
134
+ .header-placeholder {
135
+ width: 88rpx;
136
+ flex-shrink: 0;
137
+ }
138
+ </style>
@@ -1,4 +1,41 @@
1
- <script lang="ts">
2
- import Loading from './index';
3
- export default Loading;
1
+ <template>
2
+ <view class="hlw-loading">
3
+ <view class="hlw-loading__spinner" />
4
+ <text v-if="text" class="hlw-loading__text">{{ text }}</text>
5
+ </view>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ defineProps<{
10
+ text?: string;
11
+ }>();
4
12
  </script>
13
+
14
+ <style scoped>
15
+ .hlw-loading {
16
+ display: flex;
17
+ flex-direction: column;
18
+ align-items: center;
19
+ justify-content: center;
20
+ padding: 24rpx;
21
+ }
22
+
23
+ .hlw-loading__spinner {
24
+ width: 56rpx;
25
+ height: 56rpx;
26
+ border: 6rpx solid #e8e8e8;
27
+ border-top-color: #07c160;
28
+ border-radius: 50%;
29
+ animation: hlw-spin 0.8s linear infinite;
30
+ }
31
+
32
+ @keyframes hlw-spin {
33
+ to { transform: rotate(360deg); }
34
+ }
35
+
36
+ .hlw-loading__text {
37
+ margin-top: 16rpx;
38
+ font-size: 26rpx;
39
+ color: #999;
40
+ }
41
+ </style>
@@ -1,4 +1,94 @@
1
- <script lang="ts">
2
- import MenuList from './index';
3
- export default MenuList;
1
+ <template>
2
+ <view class="hlw-menu-list">
3
+ <view
4
+ v-for="item in items"
5
+ :key="item.key"
6
+ class="hlw-menu-list__item"
7
+ @tap="onTap(item)"
8
+ >
9
+ <view class="hlw-menu-list__left">
10
+ <text v-if="item.icon" class="hlw-menu-list__icon">{{ item.icon }}</text>
11
+ <text class="hlw-menu-list__label">{{ item.label }}</text>
12
+ </view>
13
+ <view class="hlw-menu-list__right">
14
+ <text v-if="item.value" class="hlw-menu-list__value">{{ item.value }}</text>
15
+ <text class="hlw-menu-list__arrow">›</text>
16
+ </view>
17
+ </view>
18
+ </view>
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ import type { MenuItem } from './types';
23
+
24
+ const props = defineProps<{
25
+ items: MenuItem[];
26
+ }>();
27
+
28
+ const emit = defineEmits<{
29
+ (e: 'click', item: MenuItem): void;
30
+ }>();
31
+
32
+ function onTap(item: MenuItem) {
33
+ if (item.url) {
34
+ uni.navigateTo({ url: item.url });
35
+ } else if (item.action) {
36
+ item.action();
37
+ }
38
+ emit('click', item);
39
+ }
4
40
  </script>
41
+
42
+ <style scoped>
43
+ .hlw-menu-list {
44
+ background: #fff;
45
+ border-radius: 16rpx;
46
+ overflow: hidden;
47
+ }
48
+
49
+ .hlw-menu-list__item {
50
+ display: flex;
51
+ align-items: center;
52
+ justify-content: space-between;
53
+ padding: 28rpx 32rpx;
54
+ border-bottom: 1rpx solid #f5f5f5;
55
+ }
56
+
57
+ .hlw-menu-list__item:last-child {
58
+ border-bottom: none;
59
+ }
60
+
61
+ .hlw-menu-list__left {
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 16rpx;
65
+ }
66
+
67
+ .hlw-menu-list__icon {
68
+ font-size: 36rpx;
69
+ width: 44rpx;
70
+ text-align: center;
71
+ }
72
+
73
+ .hlw-menu-list__label {
74
+ font-size: 30rpx;
75
+ color: #333;
76
+ }
77
+
78
+ .hlw-menu-list__right {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 8rpx;
82
+ }
83
+
84
+ .hlw-menu-list__value {
85
+ font-size: 28rpx;
86
+ color: #999;
87
+ }
88
+
89
+ .hlw-menu-list__arrow {
90
+ font-size: 36rpx;
91
+ color: #ccc;
92
+ line-height: 1;
93
+ }
94
+ </style>
@@ -0,0 +1,8 @@
1
+ export interface MenuItem {
2
+ key: string;
3
+ label: string;
4
+ icon?: string;
5
+ value?: string;
6
+ url?: string;
7
+ action?: () => void;
8
+ }
@@ -0,0 +1,64 @@
1
+ <template>
2
+ <view class="hlw-page">
3
+ <view class="hlw-page-header">
4
+ <slot name="header">
5
+ <hlw-header
6
+ v-if="title || isBack"
7
+ :title="title"
8
+ :is-back="isBack"
9
+ :bg-class="bgClass"
10
+ />
11
+ </slot>
12
+ </view>
13
+ <scroll-view
14
+ class="hlw-page-content"
15
+ :scroll-y="true"
16
+ :enable-flex="true"
17
+ :enhanced="true"
18
+ :show-scrollbar="true"
19
+ >
20
+ <slot></slot>
21
+ </scroll-view>
22
+ <view class="hlw-page-footer">
23
+ <slot name="footer"></slot>
24
+ </view>
25
+ </view>
26
+ </template>
27
+
28
+ <script setup lang="ts">
29
+ interface Props {
30
+ title?: string;
31
+ isBack?: boolean;
32
+ bgClass?: string;
33
+ }
34
+
35
+ withDefaults(defineProps<Props>(), {
36
+ title: '',
37
+ isBack: false,
38
+ bgClass: '',
39
+ });
40
+ </script>
41
+
42
+ <style lang="scss" scoped>
43
+ .hlw-page {
44
+ width: 100%;
45
+ height: 100vh;
46
+ display: flex;
47
+ flex-direction: column;
48
+ overflow: hidden;
49
+ }
50
+
51
+ .hlw-page-header {
52
+ flex-shrink: 0;
53
+ }
54
+
55
+ .hlw-page-content {
56
+ flex: 1;
57
+ height: 0;
58
+ width: 100%;
59
+ }
60
+
61
+ .hlw-page-footer {
62
+ flex-shrink: 0;
63
+ }
64
+ </style>
package/src/index.ts CHANGED
@@ -3,7 +3,11 @@
3
3
  */
4
4
 
5
5
  export { default as HlwAd } from './components/hlw-ad/index.vue';
6
- export { default as HlwAvatar } from './components/hlw-avatar/index';
7
- export { default as HlwEmpty } from './components/hlw-empty/index';
8
- export { default as HlwLoading } from './components/hlw-loading/index';
9
- export { default as HlwMenuList, type MenuItem } from './components/hlw-menu-list/index';
6
+ export { default as HlwAvatar } from './components/hlw-avatar/index.vue';
7
+ export { default as HlwCard } from './components/hlw-card/index.vue';
8
+ export { default as HlwEmpty } from './components/hlw-empty/index.vue';
9
+ export { default as HlwHeader } from './components/hlw-header/index.vue';
10
+ export { default as HlwLoading } from './components/hlw-loading/index.vue';
11
+ export { default as HlwMenuList } from './components/hlw-menu-list/index.vue';
12
+ export { default as HlwPage } from './components/hlw-page/index.vue';
13
+ export type { MenuItem } from './components/hlw-menu-list/types';
@@ -1,7 +0,0 @@
1
- export interface AvatarProps {
2
- src?: string;
3
- name?: string;
4
- size?: 'small' | 'medium' | 'large';
5
- }
6
- declare const _default: any;
7
- export default _default;
@@ -1,2 +0,0 @@
1
- declare const _default: any;
2
- export default _default;
@@ -1,2 +0,0 @@
1
- declare const _default: any;
2
- export default _default;
@@ -1,52 +0,0 @@
1
- import { defineComponent, ref, computed } from 'vue';
2
-
3
- export interface AvatarProps {
4
- src?: string;
5
- name?: string;
6
- size?: 'small' | 'medium' | 'large';
7
- }
8
-
9
- export default defineComponent({
10
- name: 'Avatar',
11
- props: {
12
- src: { type: String },
13
- name: { type: String },
14
- size: { type: String as () => 'small' | 'medium' | 'large', default: 'medium' },
15
- },
16
- setup(props) {
17
- const loadError = ref(false);
18
-
19
- const initial = computed(() => {
20
- if (!props.name) return '?';
21
- return props.name.charAt(0).toUpperCase();
22
- });
23
-
24
- function onError() { loadError.value = true; }
25
-
26
- return () => {
27
- const sizeClass = `hlw-avatar--${props.size}`;
28
- return (
29
- // @ts-ignore - uni app nodes
30
- h('view', { class: `hlw-avatar ${sizeClass}` }, [
31
- props.src && !loadError.value
32
- ? (
33
- // @ts-ignore
34
- h('image', {
35
- class: 'hlw-avatar__image',
36
- src: props.src,
37
- mode: 'aspectFill',
38
- onError,
39
- })
40
- )
41
- : (
42
- // @ts-ignore
43
- h('view', { class: 'hlw-avatar__placeholder' }, [
44
- // @ts-ignore
45
- h('text', { class: 'hlw-avatar__initial' }, initial.value),
46
- ])
47
- ),
48
- ])
49
- );
50
- };
51
- },
52
- });
@@ -1,34 +0,0 @@
1
- import { defineComponent, useSlots } from 'vue';
2
-
3
- export default defineComponent({
4
- name: 'Empty',
5
- props: {
6
- text: { type: String },
7
- image: { type: String },
8
- },
9
- setup(props) {
10
- const slots = useSlots();
11
- return () => {
12
- return (
13
- // @ts-ignore
14
- h('view', { class: 'hlw-empty' }, [
15
- props.image
16
- ? (
17
- // @ts-ignore
18
- h('image', { class: 'hlw-empty__image', src: props.image, mode: 'aspectFit' })
19
- )
20
- : (
21
- // @ts-ignore
22
- h('view', { class: 'hlw-empty__icon' }, [
23
- // @ts-ignore
24
- h('text', '📦'),
25
- ])
26
- ),
27
- // @ts-ignore
28
- h('text', { class: 'hlw-empty__text' }, props.text || '暂无数据'),
29
- slots.default?.(),
30
- ])
31
- );
32
- };
33
- },
34
- });
@@ -1,29 +0,0 @@
1
- import { defineComponent } from 'vue';
2
-
3
- export default defineComponent({
4
- name: 'Loading',
5
- props: {
6
- text: { type: String },
7
- },
8
- setup(props) {
9
- return () => {
10
- const dots = Array.from({ length: 12 }, (_, i) =>
11
- // @ts-ignore
12
- h('view', { key: i + 1, class: 'hlw-loading__dot' }),
13
- );
14
- return (
15
- // @ts-ignore
16
- h('view', { class: 'hlw-loading' }, [
17
- // @ts-ignore
18
- h('view', { class: 'hlw-loading__spinner' }, dots),
19
- props.text
20
- ? (
21
- // @ts-ignore
22
- h('text', { class: 'hlw-loading__text' }, props.text)
23
- )
24
- : null,
25
- ])
26
- );
27
- };
28
- },
29
- });