@hlw-uni/mp-vue 1.0.34 → 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,104 @@
1
+ <template>
2
+ <scroll-view class="hlw-tabs" :scroll-x="scrollable" :enhanced="true" :show-scrollbar="false">
3
+ <view class="hlw-tabs-wrap">
4
+ <view
5
+ v-for="(item, index) in items"
6
+ :key="index"
7
+ class="hlw-tab"
8
+ :class="{ 'hlw-tab--active': modelValue === index }"
9
+ @tap="onChange(index)"
10
+ >
11
+ <text class="hlw-tab-text">{{ typeof item === "string" ? item : item.label }}</text>
12
+ <view
13
+ v-if="typeof item !== 'string' && item.badge"
14
+ class="hlw-tab-badge"
15
+ >{{ item.badge }}</view>
16
+ <view v-if="modelValue === index" class="hlw-tab-line" :style="{ width: lineWidth }" />
17
+ </view>
18
+ </view>
19
+ </scroll-view>
20
+ </template>
21
+
22
+ <script setup lang="ts">
23
+ export interface HlwTabItem {
24
+ label: string;
25
+ badge?: string;
26
+ }
27
+
28
+ interface Props {
29
+ modelValue?: number;
30
+ items?: (string | HlwTabItem)[];
31
+ scrollable?: boolean;
32
+ lineWidth?: string;
33
+ }
34
+
35
+ withDefaults(defineProps<Props>(), {
36
+ modelValue: 0,
37
+ items: () => [],
38
+ scrollable: false,
39
+ lineWidth: "40rpx",
40
+ });
41
+
42
+ const emit = defineEmits<{ "update:modelValue": [index: number]; change: [index: number] }>();
43
+
44
+ function onChange(index: number) {
45
+ emit("update:modelValue", index);
46
+ emit("change", index);
47
+ }
48
+ </script>
49
+
50
+ <style lang="scss" scoped>
51
+ .hlw-tabs {
52
+ background: #fff;
53
+ white-space: nowrap;
54
+ }
55
+
56
+ .hlw-tabs-wrap {
57
+ display: inline-flex;
58
+ min-width: 100%;
59
+ }
60
+
61
+ .hlw-tab {
62
+ position: relative;
63
+ flex: 1;
64
+ display: flex;
65
+ align-items: center;
66
+ justify-content: center;
67
+ padding: 24rpx 28rpx;
68
+ gap: 6rpx;
69
+ transition: color 0.2s;
70
+
71
+ &--active .hlw-tab-text {
72
+ color: var(--primary-color, #3b82f6);
73
+ font-weight: 600;
74
+ }
75
+ }
76
+
77
+ .hlw-tab-text {
78
+ font-size: var(--font-base, 28rpx);
79
+ color: #64748b;
80
+ }
81
+
82
+ .hlw-tab-badge {
83
+ padding: 0 8rpx;
84
+ min-width: 28rpx;
85
+ height: 28rpx;
86
+ line-height: 28rpx;
87
+ font-size: 18rpx;
88
+ text-align: center;
89
+ color: #fff;
90
+ background: #ef4444;
91
+ border-radius: 999rpx;
92
+ }
93
+
94
+ .hlw-tab-line {
95
+ position: absolute;
96
+ bottom: 4rpx;
97
+ left: 50%;
98
+ transform: translateX(-50%);
99
+ height: 6rpx;
100
+ border-radius: 6rpx;
101
+ background: var(--primary-color, #3b82f6);
102
+ transition: width 0.2s;
103
+ }
104
+ </style>
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <view
3
+ class="hlw-tag"
4
+ :class="[`hlw-tag--${type}`, `hlw-tag--${size}`, { 'hlw-tag--plain': plain, 'hlw-tag--round': round }]"
5
+ :style="customStyle"
6
+ @tap="$emit('click')"
7
+ >
8
+ <slot />
9
+ <view v-if="closable" class="hlw-tag-close" @tap.stop="$emit('close')">&#215;</view>
10
+ </view>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ import { computed } from "vue";
15
+
16
+ interface Props {
17
+ type?: "primary" | "success" | "warning" | "danger" | "info";
18
+ plain?: boolean;
19
+ closable?: boolean;
20
+ size?: "small" | "medium";
21
+ round?: boolean;
22
+ color?: string;
23
+ }
24
+
25
+ const props = withDefaults(defineProps<Props>(), {
26
+ type: "primary",
27
+ plain: false,
28
+ closable: false,
29
+ size: "medium",
30
+ round: false,
31
+ color: "",
32
+ });
33
+
34
+ defineEmits<{ click: []; close: [] }>();
35
+
36
+ const customStyle = computed(() => {
37
+ if (!props.color) return {};
38
+ return props.plain
39
+ ? { color: props.color, borderColor: props.color, background: "transparent" }
40
+ : { background: props.color, color: "#fff", borderColor: props.color };
41
+ });
42
+ </script>
43
+
44
+ <style lang="scss" scoped>
45
+ $colors: (
46
+ primary: var(--primary-color, #3b82f6),
47
+ success: #10b981,
48
+ warning: #f59e0b,
49
+ danger: #ef4444,
50
+ info: #64748b,
51
+ );
52
+
53
+ .hlw-tag {
54
+ display: inline-flex;
55
+ align-items: center;
56
+ gap: 4rpx;
57
+ font-weight: 500;
58
+ border: 2rpx solid transparent;
59
+
60
+ &--medium { padding: 4rpx 16rpx; font-size: var(--font-xs, 20rpx); border-radius: var(--radius-sm, 8rpx); }
61
+ &--small { padding: 2rpx 10rpx; font-size: 18rpx; border-radius: 6rpx; }
62
+ &--round { border-radius: 999rpx; }
63
+
64
+ @each $name, $c in $colors {
65
+ &--#{$name} { background: #{$c}; color: #fff; border-color: #{$c}; }
66
+ &--#{$name}.hlw-tag--plain { background: transparent; color: #{$c}; }
67
+ }
68
+ }
69
+
70
+ .hlw-tag-close {
71
+ font-size: 1.2em;
72
+ line-height: 1;
73
+ margin-left: 2rpx;
74
+ opacity: 0.8;
75
+ }
76
+ </style>
@@ -0,0 +1,51 @@
1
+ export type FontScale = "small" | "normal" | "large" | "xlarge";
2
+
3
+ export interface FontPreset {
4
+ label: string;
5
+ vars: Record<string, string>;
6
+ }
7
+
8
+ export const FONT_SCALE_KEY = "hlw_font_scale";
9
+
10
+ export const FONT_PRESETS: Record<FontScale, FontPreset> = {
11
+ small: {
12
+ label: "小字体",
13
+ vars: {
14
+ "--font-xs": "16rpx", "--font-sm": "20rpx", "--font-base": "24rpx",
15
+ "--font-md": "28rpx", "--font-lg": "32rpx", "--font-xl": "36rpx",
16
+ },
17
+ },
18
+ normal: {
19
+ label: "标准",
20
+ vars: {
21
+ "--font-xs": "20rpx", "--font-sm": "24rpx", "--font-base": "28rpx",
22
+ "--font-md": "32rpx", "--font-lg": "36rpx", "--font-xl": "40rpx",
23
+ },
24
+ },
25
+ large: {
26
+ label: "大字体",
27
+ vars: {
28
+ "--font-xs": "24rpx", "--font-sm": "30rpx", "--font-base": "34rpx",
29
+ "--font-md": "40rpx", "--font-lg": "46rpx", "--font-xl": "52rpx",
30
+ },
31
+ },
32
+ xlarge: {
33
+ label: "超大字体",
34
+ vars: {
35
+ "--font-xs": "28rpx", "--font-sm": "36rpx", "--font-base": "42rpx",
36
+ "--font-md": "48rpx", "--font-lg": "56rpx", "--font-xl": "64rpx",
37
+ },
38
+ },
39
+ };
40
+
41
+ export function getCurrentFontScale(): FontScale {
42
+ try {
43
+ const v = uni.getStorageSync(FONT_SCALE_KEY);
44
+ if (v === "small" || v === "large" || v === "xlarge") return v;
45
+ } catch {}
46
+ return "normal";
47
+ }
48
+
49
+ export function getCurrentFontVars(): Record<string, string> {
50
+ return FONT_PRESETS[getCurrentFontScale()].vars;
51
+ }
@@ -0,0 +1,33 @@
1
+ import { ref, onMounted, onUnmounted } from "vue";
2
+ import { useColor } from "@hlw-uni/mp-core";
3
+
4
+ const { varsToStyle } = useColor();
5
+ import { getCurrentFontVars } from "./font";
6
+ import { getCurrentThemeVars } from "./palette";
7
+
8
+ export const THEME_CHANGE_EVENT = "hlw:theme-change";
9
+
10
+ export function buildThemeStyle(): string {
11
+ return varsToStyle({
12
+ ...getCurrentFontVars(),
13
+ ...getCurrentThemeVars(),
14
+ });
15
+ }
16
+
17
+ export function useThemePageStyle() {
18
+ const themePageStyle = ref(buildThemeStyle());
19
+
20
+ const onThemeChange = () => {
21
+ themePageStyle.value = buildThemeStyle();
22
+ };
23
+
24
+ onMounted(() => uni.$on(THEME_CHANGE_EVENT, onThemeChange));
25
+ onUnmounted(() => uni.$off(THEME_CHANGE_EVENT, onThemeChange));
26
+
27
+ return { themePageStyle };
28
+ }
29
+
30
+ export type { FontScale, FontPreset } from "./font";
31
+ export { FONT_SCALE_KEY, FONT_PRESETS, getCurrentFontScale, getCurrentFontVars } from "./font";
32
+ export type { ThemeColor } from "./palette";
33
+ export { THEME_COLOR_KEY, DEFAULT_THEMES, getCurrentThemeColor, getCurrentThemeVars } from "./palette";
@@ -0,0 +1,36 @@
1
+ import { useColor } from "@hlw-uni/mp-core";
2
+
3
+ const { hexToRgba, darkenHex } = useColor();
4
+
5
+ export interface ThemeColor {
6
+ label: string;
7
+ value: string;
8
+ }
9
+
10
+ export const THEME_COLOR_KEY = "hlw_theme_color";
11
+
12
+ export const DEFAULT_THEMES: ThemeColor[] = [
13
+ { label: "默认蓝", value: "#3b82f6" },
14
+ { label: "活力橙", value: "#f97316" },
15
+ { label: "翡翠绿", value: "#10b981" },
16
+ { label: "玫瑰红", value: "#f43f5e" },
17
+ { label: "紫罗兰", value: "#8b5cf6" },
18
+ { label: "青石灰", value: "#64748b" },
19
+ ];
20
+
21
+ export function getCurrentThemeColor(): string {
22
+ try {
23
+ const v = uni.getStorageSync(THEME_COLOR_KEY);
24
+ if (v && typeof v === "string") return v;
25
+ } catch {}
26
+ return DEFAULT_THEMES[0].value;
27
+ }
28
+
29
+ export function getCurrentThemeVars(): Record<string, string> {
30
+ const color = getCurrentThemeColor();
31
+ return {
32
+ "--primary-color": color,
33
+ "--primary-light": hexToRgba(color, 0.12),
34
+ "--primary-dark": darkenHex(color),
35
+ };
36
+ }
package/src/index.ts CHANGED
@@ -4,13 +4,24 @@
4
4
 
5
5
  export { default as HlwAd } from "./components/hlw-ad/index.vue";
6
6
  export { default as HlwAvatar } from "./components/hlw-avatar/index.vue";
7
+ export { default as HlwButton } from "./components/hlw-button/index.vue";
7
8
  export { default as HlwCard } from "./components/hlw-card/index.vue";
9
+ export { default as HlwCell } from "./components/hlw-cell/index.vue";
10
+ export { default as HlwDivider } from "./components/hlw-divider/index.vue";
8
11
  export { default as HlwEmpty } from "./components/hlw-empty/index.vue";
9
12
  export { default as HlwHeader } from "./components/hlw-header/index.vue";
10
13
  export { default as HlwLoading } from "./components/hlw-loading/index.vue";
11
14
  export { default as HlwMenu } from "./components/hlw-menu/index.vue";
12
15
  export type { HlwMenuItem } from "./components/hlw-menu/types";
16
+ export { default as HlwModal } from "./components/hlw-modal/index.vue";
17
+ export { default as HlwNoticeBar } from "./components/hlw-notice-bar/index.vue";
13
18
  export { default as HlwPage } from "./components/hlw-page/index.vue";
19
+ export { default as HlwPopup } from "./components/hlw-popup/index.vue";
20
+ export { default as HlwSearch } from "./components/hlw-search/index.vue";
21
+ export { default as HlwSkeleton } from "./components/hlw-skeleton/index.vue";
22
+ export { default as HlwTabs } from "./components/hlw-tabs/index.vue";
23
+ export { default as HlwTag } from "./components/hlw-tag/index.vue";
24
+ export type { HlwTabItem } from "./components/hlw-tabs/index.vue";
14
25
  export type { FontScale, FontPreset, ThemeColor } from "./composables/theme";
15
26
  export { FONT_PRESETS, FONT_SCALE_KEY, DEFAULT_THEMES, THEME_COLOR_KEY, THEME_CHANGE_EVENT, getCurrentFontScale, getCurrentFontVars, getCurrentThemeColor, getCurrentThemeVars, buildThemeStyle, useThemePageStyle } from "./composables/theme";
16
27
  export { useThemeStore } from "./stores/theme";
@@ -1,22 +0,0 @@
1
- export type FontScale = "small" | "normal" | "large" | "xlarge";
2
- export interface FontPreset {
3
- label: string;
4
- vars: Record<string, string>;
5
- }
6
- export interface ThemeColor {
7
- label: string;
8
- value: string;
9
- }
10
- export declare const FONT_SCALE_KEY = "hlw_font_scale";
11
- export declare const THEME_COLOR_KEY = "hlw_theme_color";
12
- export declare const THEME_CHANGE_EVENT = "hlw:theme-change";
13
- export declare const FONT_PRESETS: Record<FontScale, FontPreset>;
14
- export declare function getCurrentFontScale(): FontScale;
15
- export declare function getCurrentFontVars(): Record<string, string>;
16
- export declare const DEFAULT_THEMES: ThemeColor[];
17
- export declare function getCurrentThemeColor(): string;
18
- export declare function getCurrentThemeVars(): Record<string, string>;
19
- export declare function buildThemeStyle(): string;
20
- export declare function useThemePageStyle(): {
21
- themePageStyle: any;
22
- };
@@ -1,142 +0,0 @@
1
- import { ref, onMounted, onUnmounted } from "vue";
2
-
3
- // ─── 工具函数 ───────────────────────────────────
4
-
5
- function varsToStyle(vars: Record<string, string>): string {
6
- return Object.entries(vars).map(([k, v]) => `${k}:${v}`).join(";") + ";";
7
- }
8
-
9
- function hexToRgba(hex: string, alpha: number): string {
10
- const r = parseInt(hex.slice(1, 3), 16);
11
- const g = parseInt(hex.slice(3, 5), 16);
12
- const b = parseInt(hex.slice(5, 7), 16);
13
- return `rgba(${r},${g},${b},${alpha})`;
14
- }
15
-
16
- function darkenHex(hex: string, amount = 0.15): string {
17
- const r = Math.max(0, Math.round(parseInt(hex.slice(1, 3), 16) * (1 - amount)));
18
- const g = Math.max(0, Math.round(parseInt(hex.slice(3, 5), 16) * (1 - amount)));
19
- const b = Math.max(0, Math.round(parseInt(hex.slice(5, 7), 16) * (1 - amount)));
20
- return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
21
- }
22
-
23
- // ─── 类型 ──────────────────────────────────────
24
-
25
- export type FontScale = "small" | "normal" | "large" | "xlarge";
26
-
27
- export interface FontPreset {
28
- label: string;
29
- vars: Record<string, string>;
30
- }
31
-
32
- export interface ThemeColor {
33
- label: string;
34
- value: string;
35
- }
36
-
37
- // ─── 存储 Key ──────────────────────────────────
38
-
39
- export const FONT_SCALE_KEY = "hlw_font_scale";
40
- export const THEME_COLOR_KEY = "hlw_theme_color";
41
-
42
- // ─── 事件 ──────────────────────────────────────
43
-
44
- export const THEME_CHANGE_EVENT = "hlw:theme-change";
45
-
46
- // ─── 字体档位 ──────────────────────────────────
47
-
48
- export const FONT_PRESETS: Record<FontScale, FontPreset> = {
49
- small: {
50
- label: "小字体",
51
- vars: {
52
- "--font-xs": "16rpx", "--font-sm": "20rpx", "--font-base": "24rpx",
53
- "--font-md": "28rpx", "--font-lg": "32rpx", "--font-xl": "36rpx",
54
- },
55
- },
56
- normal: {
57
- label: "标准",
58
- vars: {
59
- "--font-xs": "20rpx", "--font-sm": "24rpx", "--font-base": "28rpx",
60
- "--font-md": "32rpx", "--font-lg": "36rpx", "--font-xl": "40rpx",
61
- },
62
- },
63
- large: {
64
- label: "大字体",
65
- vars: {
66
- "--font-xs": "24rpx", "--font-sm": "30rpx", "--font-base": "34rpx",
67
- "--font-md": "40rpx", "--font-lg": "46rpx", "--font-xl": "52rpx",
68
- },
69
- },
70
- xlarge: {
71
- label: "超大字体",
72
- vars: {
73
- "--font-xs": "28rpx", "--font-sm": "36rpx", "--font-base": "42rpx",
74
- "--font-md": "48rpx", "--font-lg": "56rpx", "--font-xl": "64rpx",
75
- },
76
- },
77
- };
78
-
79
- export function getCurrentFontScale(): FontScale {
80
- try {
81
- const v = uni.getStorageSync(FONT_SCALE_KEY);
82
- if (v === "small" || v === "large" || v === "xlarge") return v;
83
- } catch {}
84
- return "normal";
85
- }
86
-
87
- export function getCurrentFontVars(): Record<string, string> {
88
- return FONT_PRESETS[getCurrentFontScale()].vars;
89
- }
90
-
91
- // ─── 主题色 ────────────────────────────────────
92
-
93
- export const DEFAULT_THEMES: ThemeColor[] = [
94
- { label: "默认蓝", value: "#3b82f6" },
95
- { label: "活力橙", value: "#f97316" },
96
- { label: "翡翠绿", value: "#10b981" },
97
- { label: "玫瑰红", value: "#f43f5e" },
98
- { label: "紫罗兰", value: "#8b5cf6" },
99
- { label: "青石灰", value: "#64748b" },
100
- ];
101
-
102
- export function getCurrentThemeColor(): string {
103
- try {
104
- const v = uni.getStorageSync(THEME_COLOR_KEY);
105
- if (v && typeof v === "string") return v;
106
- } catch {}
107
- return DEFAULT_THEMES[0].value;
108
- }
109
-
110
- export function getCurrentThemeVars(): Record<string, string> {
111
- const color = getCurrentThemeColor();
112
- return {
113
- "--primary-color": color,
114
- "--primary-light": hexToRgba(color, 0.12),
115
- "--primary-dark": darkenHex(color),
116
- };
117
- }
118
-
119
- // ─── 统一样式构建 ──────────────────────────────
120
- // 后续新增主题维度时,只需添加 getCurrentXxxVars() 并在此展开
121
-
122
- export function buildThemeStyle(): string {
123
- return varsToStyle({
124
- ...getCurrentFontVars(),
125
- ...getCurrentThemeVars(),
126
- });
127
- }
128
-
129
- // ─── 组合式函数 ────────────────────────────────
130
-
131
- export function useThemePageStyle() {
132
- const themePageStyle = ref(buildThemeStyle());
133
-
134
- const onThemeChange = () => {
135
- themePageStyle.value = buildThemeStyle();
136
- };
137
-
138
- onMounted(() => uni.$on(THEME_CHANGE_EVENT, onThemeChange));
139
- onUnmounted(() => uni.$off(THEME_CHANGE_EVENT, onThemeChange));
140
-
141
- return { themePageStyle };
142
- }