@hlw-uni/mp-vue 1.0.32 → 1.0.34

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,22 @@
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
+ };
package/dist/index.d.ts CHANGED
@@ -10,5 +10,6 @@ export { default as HlwLoading } from './components/hlw-loading/index.vue';
10
10
  export { default as HlwMenu } from './components/hlw-menu/index.vue';
11
11
  export type { HlwMenuItem } from './components/hlw-menu/types';
12
12
  export { default as HlwPage } from './components/hlw-page/index.vue';
13
- export type { FontScale, FontPreset } from './components/hlw-page/font-presets';
14
- export { FONT_PRESETS, FONT_SCALE_KEY, FONT_SCALE_EVENT, getCurrentFontScale, getCurrentFontStyle, useFontPageStyle } from './components/hlw-page/font-presets';
13
+ export type { FontScale, FontPreset, ThemeColor } from './composables/theme';
14
+ export { FONT_PRESETS, FONT_SCALE_KEY, DEFAULT_THEMES, THEME_COLOR_KEY, THEME_CHANGE_EVENT, getCurrentFontScale, getCurrentFontVars, getCurrentThemeColor, getCurrentThemeVars, buildThemeStyle, useThemePageStyle } from './composables/theme';
15
+ export { useThemeStore } from './stores/theme';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  (function(global, factory) {
2
- typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("vue")) : typeof define === "function" && define.amd ? define(["exports", "vue"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.HlwUniVue = {}, global.vue));
3
- })(this, function(exports2, vue) {
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("vue"), require("pinia")) : typeof define === "function" && define.amd ? define(["exports", "vue", "pinia"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.HlwUniVue = {}, global.vue, global.pinia));
3
+ })(this, function(exports2, vue, pinia) {
4
4
  "use strict";
5
5
  const _sfc_main$7 = /* @__PURE__ */ vue.defineComponent({
6
6
  ...{ name: "HlwAd" },
@@ -79,67 +79,108 @@
79
79
  return target;
80
80
  };
81
81
  const index$6 = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["__scopeId", "data-v-89dcbc96"]]);
82
- const _hoisted_1$5 = { class: "hlw-card" };
83
- const _hoisted_2$5 = {
82
+ const _hoisted_1$5 = {
84
83
  key: 0,
85
84
  class: "hlw-card-header"
86
85
  };
87
- const _hoisted_3$3 = { class: "flex items-center justify-between px-5 py-4 border-0 border-b border-dashed border-slate-200 bg-slate-50/30" };
88
- const _hoisted_4$2 = { key: 0 };
89
- const _hoisted_5$1 = { class: "text-sm font-bold text-slate-800 flex items-center gap-2 tracking-wide" };
90
- const _hoisted_6$1 = { key: 1 };
86
+ const _hoisted_2$5 = { class: "hlw-card-header-inner" };
87
+ const _hoisted_3$3 = { class: "hlw-card-header-left" };
88
+ const _hoisted_4$2 = {
89
+ key: 0,
90
+ class: "hlw-card-title"
91
+ };
92
+ const _hoisted_5$1 = {
93
+ key: 0,
94
+ class: "hlw-card-header-right"
95
+ };
96
+ const _hoisted_6$1 = {
97
+ key: 0,
98
+ class: "hlw-card-extra"
99
+ };
91
100
  const _hoisted_7$1 = {
101
+ key: 1,
102
+ class: "hlw-card-divider"
103
+ };
104
+ const _hoisted_8$1 = {
105
+ key: 2,
106
+ class: "hlw-card-footer"
107
+ };
108
+ const _hoisted_9$1 = { class: "hlw-card-footer-inner" };
109
+ const _hoisted_10$1 = { class: "hlw-card-footer-left" };
110
+ const _hoisted_11$1 = {
92
111
  key: 0,
93
- class: "text-[10px] text-slate-400 bg-slate-50 px-2 py-1 rounded border border-slate-100 tracking-wide"
112
+ class: "hlw-card-footer-right"
94
113
  };
95
- const _hoisted_8$1 = { class: "hlw-card-body" };
96
114
  const _sfc_main$5 = /* @__PURE__ */ vue.defineComponent({
97
- ...{
98
- options: {
99
- styleIsolation: "shared",
100
- virtualHost: true
101
- }
102
- },
103
115
  __name: "index",
104
116
  props: {
105
117
  title: { default: "" },
106
- icon: { default: "" },
107
- iconColor: { default: "" },
108
- extra: { default: "" }
118
+ extra: { default: "" },
119
+ border: { type: Boolean, default: true },
120
+ radius: { default: "xl" },
121
+ divider: { type: Boolean, default: void 0 },
122
+ padding: { type: Boolean, default: true }
109
123
  },
110
124
  setup(__props) {
125
+ const props = __props;
126
+ const slots = vue.useSlots();
127
+ const hasHeader = vue.computed(
128
+ () => !!(props.title || props.extra || slots.header || slots["header-left"] || slots["header-right"])
129
+ );
130
+ const hasFooter = vue.computed(
131
+ () => !!(slots.footer || slots["footer-left"] || slots["footer-right"])
132
+ );
133
+ const showDivider = vue.computed(() => {
134
+ if (props.divider !== void 0)
135
+ return props.divider;
136
+ return hasHeader.value;
137
+ });
111
138
  return (_ctx, _cache) => {
112
- return vue.openBlock(), vue.createElementBlock("view", _hoisted_1$5, [
113
- _ctx.$slots.header || __props.title || __props.icon || _ctx.$slots["header-left"] || _ctx.$slots["header-right"] ? (vue.openBlock(), vue.createElementBlock("view", _hoisted_2$5, [
139
+ return vue.openBlock(), vue.createElementBlock("view", {
140
+ class: vue.normalizeClass(["hlw-card", [
141
+ `hlw-card--radius-${__props.radius}`,
142
+ __props.border ? "hlw-card--bordered" : ""
143
+ ]])
144
+ }, [
145
+ hasHeader.value ? (vue.openBlock(), vue.createElementBlock("view", _hoisted_1$5, [
114
146
  vue.renderSlot(_ctx.$slots, "header", {}, () => [
115
- vue.createElementVNode("view", _hoisted_3$3, [
116
- _ctx.$slots["header-left"] || __props.title || __props.icon ? (vue.openBlock(), vue.createElementBlock("view", _hoisted_4$2, [
147
+ vue.createElementVNode("view", _hoisted_2$5, [
148
+ vue.createElementVNode("view", _hoisted_3$3, [
117
149
  vue.renderSlot(_ctx.$slots, "header-left", {}, () => [
118
- vue.createElementVNode("view", _hoisted_5$1, [
119
- __props.icon ? (vue.openBlock(), vue.createElementBlock("text", {
120
- key: 0,
121
- class: vue.normalizeClass([__props.icon, __props.iconColor])
122
- }, null, 2)) : vue.createCommentVNode("", true),
123
- vue.createElementVNode("text", null, vue.toDisplayString(__props.title), 1)
124
- ])
150
+ __props.title ? (vue.openBlock(), vue.createElementBlock("text", _hoisted_4$2, vue.toDisplayString(__props.title), 1)) : vue.createCommentVNode("", true)
125
151
  ], true)
126
- ])) : vue.createCommentVNode("", true),
127
- _ctx.$slots["header-right"] || __props.extra ? (vue.openBlock(), vue.createElementBlock("view", _hoisted_6$1, [
152
+ ]),
153
+ _ctx.$slots["header-right"] || __props.extra ? (vue.openBlock(), vue.createElementBlock("view", _hoisted_5$1, [
128
154
  vue.renderSlot(_ctx.$slots, "header-right", {}, () => [
129
- __props.extra ? (vue.openBlock(), vue.createElementBlock("text", _hoisted_7$1, vue.toDisplayString(__props.extra), 1)) : vue.createCommentVNode("", true)
155
+ __props.extra ? (vue.openBlock(), vue.createElementBlock("text", _hoisted_6$1, vue.toDisplayString(__props.extra), 1)) : vue.createCommentVNode("", true)
130
156
  ], true)
131
157
  ])) : vue.createCommentVNode("", true)
132
158
  ])
133
159
  ], true)
134
160
  ])) : vue.createCommentVNode("", true),
135
- vue.createElementVNode("view", _hoisted_8$1, [
161
+ showDivider.value ? (vue.openBlock(), vue.createElementBlock("view", _hoisted_7$1)) : vue.createCommentVNode("", true),
162
+ vue.createElementVNode("view", {
163
+ class: vue.normalizeClass(["hlw-card-body", { "hlw-card-body--padded": __props.padding }])
164
+ }, [
136
165
  vue.renderSlot(_ctx.$slots, "default", {}, void 0, true)
137
- ])
138
- ]);
166
+ ], 2),
167
+ hasFooter.value ? (vue.openBlock(), vue.createElementBlock("view", _hoisted_8$1, [
168
+ vue.renderSlot(_ctx.$slots, "footer", {}, () => [
169
+ vue.createElementVNode("view", _hoisted_9$1, [
170
+ vue.createElementVNode("view", _hoisted_10$1, [
171
+ vue.renderSlot(_ctx.$slots, "footer-left", {}, void 0, true)
172
+ ]),
173
+ _ctx.$slots["footer-right"] ? (vue.openBlock(), vue.createElementBlock("view", _hoisted_11$1, [
174
+ vue.renderSlot(_ctx.$slots, "footer-right", {}, void 0, true)
175
+ ])) : vue.createCommentVNode("", true)
176
+ ])
177
+ ], true)
178
+ ])) : vue.createCommentVNode("", true)
179
+ ], 2);
139
180
  };
140
181
  }
141
182
  });
142
- const index$5 = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-787fc3a7"]]);
183
+ const index$5 = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-cf55252e"]]);
143
184
  const _hoisted_1$4 = { class: "hlw-empty" };
144
185
  const _hoisted_2$4 = ["src"];
145
186
  const _hoisted_3$2 = {
@@ -523,45 +564,161 @@
523
564
  }
524
565
  });
525
566
  const index = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-d8833363"]]);
567
+ function varsToStyle(vars) {
568
+ return Object.entries(vars).map(([k, v]) => `${k}:${v}`).join(";") + ";";
569
+ }
570
+ function hexToRgba(hex, alpha) {
571
+ const r = parseInt(hex.slice(1, 3), 16);
572
+ const g = parseInt(hex.slice(3, 5), 16);
573
+ const b = parseInt(hex.slice(5, 7), 16);
574
+ return `rgba(${r},${g},${b},${alpha})`;
575
+ }
576
+ function darkenHex(hex, amount = 0.15) {
577
+ const r = Math.max(0, Math.round(parseInt(hex.slice(1, 3), 16) * (1 - amount)));
578
+ const g = Math.max(0, Math.round(parseInt(hex.slice(3, 5), 16) * (1 - amount)));
579
+ const b = Math.max(0, Math.round(parseInt(hex.slice(5, 7), 16) * (1 - amount)));
580
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
581
+ }
526
582
  const FONT_SCALE_KEY = "hlw_font_scale";
527
- const FONT_SCALE_EVENT = "hlw:font-scale-change";
583
+ const THEME_COLOR_KEY = "hlw_theme_color";
584
+ const THEME_CHANGE_EVENT = "hlw:theme-change";
528
585
  const FONT_PRESETS = {
586
+ small: {
587
+ label: "小字体",
588
+ vars: {
589
+ "--font-xs": "16rpx",
590
+ "--font-sm": "20rpx",
591
+ "--font-base": "24rpx",
592
+ "--font-md": "28rpx",
593
+ "--font-lg": "32rpx",
594
+ "--font-xl": "36rpx"
595
+ }
596
+ },
529
597
  normal: {
530
598
  label: "标准",
531
- style: "--font-xs:20rpx;--font-sm:24rpx;--font-base:28rpx;--font-md:32rpx;--font-lg:36rpx;--font-xl:40rpx;"
599
+ vars: {
600
+ "--font-xs": "20rpx",
601
+ "--font-sm": "24rpx",
602
+ "--font-base": "28rpx",
603
+ "--font-md": "32rpx",
604
+ "--font-lg": "36rpx",
605
+ "--font-xl": "40rpx"
606
+ }
532
607
  },
533
608
  large: {
534
609
  label: "大字体",
535
- style: "--font-xs:24rpx;--font-sm:30rpx;--font-base:34rpx;--font-md:40rpx;--font-lg:46rpx;--font-xl:52rpx;"
610
+ vars: {
611
+ "--font-xs": "24rpx",
612
+ "--font-sm": "30rpx",
613
+ "--font-base": "34rpx",
614
+ "--font-md": "40rpx",
615
+ "--font-lg": "46rpx",
616
+ "--font-xl": "52rpx"
617
+ }
536
618
  },
537
619
  xlarge: {
538
620
  label: "超大字体",
539
- style: "--font-xs:28rpx;--font-sm:36rpx;--font-base:42rpx;--font-md:48rpx;--font-lg:56rpx;--font-xl:64rpx;"
621
+ vars: {
622
+ "--font-xs": "28rpx",
623
+ "--font-sm": "36rpx",
624
+ "--font-base": "42rpx",
625
+ "--font-md": "48rpx",
626
+ "--font-lg": "56rpx",
627
+ "--font-xl": "64rpx"
628
+ }
540
629
  }
541
630
  };
542
631
  function getCurrentFontScale() {
543
632
  try {
544
633
  const v = uni.getStorageSync(FONT_SCALE_KEY);
545
- if (v === "large" || v === "xlarge")
634
+ if (v === "small" || v === "large" || v === "xlarge")
546
635
  return v;
547
636
  } catch {
548
637
  }
549
638
  return "normal";
550
639
  }
551
- function getCurrentFontStyle() {
552
- return FONT_PRESETS[getCurrentFontScale()].style;
640
+ function getCurrentFontVars() {
641
+ return FONT_PRESETS[getCurrentFontScale()].vars;
553
642
  }
554
- function useFontPageStyle() {
555
- const fontPageStyle = vue.ref(getCurrentFontStyle());
556
- const onFontChange = () => {
557
- fontPageStyle.value = getCurrentFontStyle();
643
+ const DEFAULT_THEMES = [
644
+ { label: "默认蓝", value: "#3b82f6" },
645
+ { label: "活力橙", value: "#f97316" },
646
+ { label: "翡翠绿", value: "#10b981" },
647
+ { label: "玫瑰红", value: "#f43f5e" },
648
+ { label: "紫罗兰", value: "#8b5cf6" },
649
+ { label: "青石灰", value: "#64748b" }
650
+ ];
651
+ function getCurrentThemeColor() {
652
+ try {
653
+ const v = uni.getStorageSync(THEME_COLOR_KEY);
654
+ if (v && typeof v === "string")
655
+ return v;
656
+ } catch {
657
+ }
658
+ return DEFAULT_THEMES[0].value;
659
+ }
660
+ function getCurrentThemeVars() {
661
+ const color = getCurrentThemeColor();
662
+ return {
663
+ "--primary-color": color,
664
+ "--primary-light": hexToRgba(color, 0.12),
665
+ "--primary-dark": darkenHex(color)
666
+ };
667
+ }
668
+ function buildThemeStyle() {
669
+ return varsToStyle({
670
+ ...getCurrentFontVars(),
671
+ ...getCurrentThemeVars()
672
+ });
673
+ }
674
+ function useThemePageStyle() {
675
+ const themePageStyle = vue.ref(buildThemeStyle());
676
+ const onThemeChange = () => {
677
+ themePageStyle.value = buildThemeStyle();
558
678
  };
559
- vue.onMounted(() => uni.$on(FONT_SCALE_EVENT, onFontChange));
560
- vue.onUnmounted(() => uni.$off(FONT_SCALE_EVENT, onFontChange));
561
- return { fontPageStyle };
679
+ vue.onMounted(() => uni.$on(THEME_CHANGE_EVENT, onThemeChange));
680
+ vue.onUnmounted(() => uni.$off(THEME_CHANGE_EVENT, onThemeChange));
681
+ return { themePageStyle };
562
682
  }
683
+ const useThemeStore = pinia.defineStore(
684
+ "theme",
685
+ () => {
686
+ const scale = vue.ref("normal");
687
+ function setScale(s) {
688
+ scale.value = s;
689
+ uni.setStorageSync(FONT_SCALE_KEY, s);
690
+ uni.$emit(THEME_CHANGE_EVENT);
691
+ }
692
+ const fontOptions = Object.keys(FONT_PRESETS).map((key) => ({
693
+ value: key,
694
+ label: FONT_PRESETS[key].label
695
+ }));
696
+ const primaryColor = vue.ref(DEFAULT_THEMES[0].value);
697
+ const themes = DEFAULT_THEMES;
698
+ const activeTheme = vue.computed(
699
+ () => themes.find((t) => t.value === primaryColor.value) ?? { label: "自定义", value: primaryColor.value }
700
+ );
701
+ function setTheme(color) {
702
+ primaryColor.value = color;
703
+ uni.setStorageSync(THEME_COLOR_KEY, color);
704
+ uni.$emit(THEME_CHANGE_EVENT);
705
+ }
706
+ return {
707
+ // 字体
708
+ scale,
709
+ fontOptions,
710
+ setScale,
711
+ // 主题色
712
+ primaryColor,
713
+ themes,
714
+ activeTheme,
715
+ setTheme
716
+ };
717
+ },
718
+ { unistorage: true }
719
+ );
720
+ exports2.DEFAULT_THEMES = DEFAULT_THEMES;
563
721
  exports2.FONT_PRESETS = FONT_PRESETS;
564
- exports2.FONT_SCALE_EVENT = FONT_SCALE_EVENT;
565
722
  exports2.FONT_SCALE_KEY = FONT_SCALE_KEY;
566
723
  exports2.HlwAd = _sfc_main$7;
567
724
  exports2.HlwAvatar = index$6;
@@ -571,8 +728,14 @@
571
728
  exports2.HlwLoading = index$2;
572
729
  exports2.HlwMenu = index$1;
573
730
  exports2.HlwPage = index;
731
+ exports2.THEME_CHANGE_EVENT = THEME_CHANGE_EVENT;
732
+ exports2.THEME_COLOR_KEY = THEME_COLOR_KEY;
733
+ exports2.buildThemeStyle = buildThemeStyle;
574
734
  exports2.getCurrentFontScale = getCurrentFontScale;
575
- exports2.getCurrentFontStyle = getCurrentFontStyle;
576
- exports2.useFontPageStyle = useFontPageStyle;
735
+ exports2.getCurrentFontVars = getCurrentFontVars;
736
+ exports2.getCurrentThemeColor = getCurrentThemeColor;
737
+ exports2.getCurrentThemeVars = getCurrentThemeVars;
738
+ exports2.useThemePageStyle = useThemePageStyle;
739
+ exports2.useThemeStore = useThemeStore;
577
740
  Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
578
741
  });
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
- import { defineComponent, resolveComponent, openBlock, createBlock, ref, computed, createElementBlock, normalizeClass, createElementVNode, toDisplayString, renderSlot, createCommentVNode, useSlots, normalizeStyle, unref, Fragment, renderList, withCtx, createVNode, onMounted, onUnmounted } from "vue";
1
+ import { defineComponent, resolveComponent, openBlock, createBlock, ref, computed, createElementBlock, normalizeClass, createElementVNode, toDisplayString, useSlots, renderSlot, createCommentVNode, normalizeStyle, unref, Fragment, renderList, withCtx, createVNode, onMounted, onUnmounted } from "vue";
2
+ import { defineStore } from "pinia";
2
3
  const _sfc_main$7 = /* @__PURE__ */ defineComponent({
3
4
  ...{ name: "HlwAd" },
4
5
  __name: "index",
@@ -76,67 +77,108 @@ const _export_sfc = (sfc, props) => {
76
77
  return target;
77
78
  };
78
79
  const index$6 = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["__scopeId", "data-v-89dcbc96"]]);
79
- const _hoisted_1$5 = { class: "hlw-card" };
80
- const _hoisted_2$5 = {
80
+ const _hoisted_1$5 = {
81
81
  key: 0,
82
82
  class: "hlw-card-header"
83
83
  };
84
- const _hoisted_3$3 = { class: "flex items-center justify-between px-5 py-4 border-0 border-b border-dashed border-slate-200 bg-slate-50/30" };
85
- const _hoisted_4$2 = { key: 0 };
86
- const _hoisted_5$1 = { class: "text-sm font-bold text-slate-800 flex items-center gap-2 tracking-wide" };
87
- const _hoisted_6$1 = { key: 1 };
84
+ const _hoisted_2$5 = { class: "hlw-card-header-inner" };
85
+ const _hoisted_3$3 = { class: "hlw-card-header-left" };
86
+ const _hoisted_4$2 = {
87
+ key: 0,
88
+ class: "hlw-card-title"
89
+ };
90
+ const _hoisted_5$1 = {
91
+ key: 0,
92
+ class: "hlw-card-header-right"
93
+ };
94
+ const _hoisted_6$1 = {
95
+ key: 0,
96
+ class: "hlw-card-extra"
97
+ };
88
98
  const _hoisted_7$1 = {
99
+ key: 1,
100
+ class: "hlw-card-divider"
101
+ };
102
+ const _hoisted_8$1 = {
103
+ key: 2,
104
+ class: "hlw-card-footer"
105
+ };
106
+ const _hoisted_9$1 = { class: "hlw-card-footer-inner" };
107
+ const _hoisted_10$1 = { class: "hlw-card-footer-left" };
108
+ const _hoisted_11$1 = {
89
109
  key: 0,
90
- class: "text-[10px] text-slate-400 bg-slate-50 px-2 py-1 rounded border border-slate-100 tracking-wide"
110
+ class: "hlw-card-footer-right"
91
111
  };
92
- const _hoisted_8$1 = { class: "hlw-card-body" };
93
112
  const _sfc_main$5 = /* @__PURE__ */ defineComponent({
94
- ...{
95
- options: {
96
- styleIsolation: "shared",
97
- virtualHost: true
98
- }
99
- },
100
113
  __name: "index",
101
114
  props: {
102
115
  title: { default: "" },
103
- icon: { default: "" },
104
- iconColor: { default: "" },
105
- extra: { default: "" }
116
+ extra: { default: "" },
117
+ border: { type: Boolean, default: true },
118
+ radius: { default: "xl" },
119
+ divider: { type: Boolean, default: void 0 },
120
+ padding: { type: Boolean, default: true }
106
121
  },
107
122
  setup(__props) {
123
+ const props = __props;
124
+ const slots = useSlots();
125
+ const hasHeader = computed(
126
+ () => !!(props.title || props.extra || slots.header || slots["header-left"] || slots["header-right"])
127
+ );
128
+ const hasFooter = computed(
129
+ () => !!(slots.footer || slots["footer-left"] || slots["footer-right"])
130
+ );
131
+ const showDivider = computed(() => {
132
+ if (props.divider !== void 0)
133
+ return props.divider;
134
+ return hasHeader.value;
135
+ });
108
136
  return (_ctx, _cache) => {
109
- return openBlock(), createElementBlock("view", _hoisted_1$5, [
110
- _ctx.$slots.header || __props.title || __props.icon || _ctx.$slots["header-left"] || _ctx.$slots["header-right"] ? (openBlock(), createElementBlock("view", _hoisted_2$5, [
137
+ return openBlock(), createElementBlock("view", {
138
+ class: normalizeClass(["hlw-card", [
139
+ `hlw-card--radius-${__props.radius}`,
140
+ __props.border ? "hlw-card--bordered" : ""
141
+ ]])
142
+ }, [
143
+ hasHeader.value ? (openBlock(), createElementBlock("view", _hoisted_1$5, [
111
144
  renderSlot(_ctx.$slots, "header", {}, () => [
112
- createElementVNode("view", _hoisted_3$3, [
113
- _ctx.$slots["header-left"] || __props.title || __props.icon ? (openBlock(), createElementBlock("view", _hoisted_4$2, [
145
+ createElementVNode("view", _hoisted_2$5, [
146
+ createElementVNode("view", _hoisted_3$3, [
114
147
  renderSlot(_ctx.$slots, "header-left", {}, () => [
115
- createElementVNode("view", _hoisted_5$1, [
116
- __props.icon ? (openBlock(), createElementBlock("text", {
117
- key: 0,
118
- class: normalizeClass([__props.icon, __props.iconColor])
119
- }, null, 2)) : createCommentVNode("", true),
120
- createElementVNode("text", null, toDisplayString(__props.title), 1)
121
- ])
148
+ __props.title ? (openBlock(), createElementBlock("text", _hoisted_4$2, toDisplayString(__props.title), 1)) : createCommentVNode("", true)
122
149
  ], true)
123
- ])) : createCommentVNode("", true),
124
- _ctx.$slots["header-right"] || __props.extra ? (openBlock(), createElementBlock("view", _hoisted_6$1, [
150
+ ]),
151
+ _ctx.$slots["header-right"] || __props.extra ? (openBlock(), createElementBlock("view", _hoisted_5$1, [
125
152
  renderSlot(_ctx.$slots, "header-right", {}, () => [
126
- __props.extra ? (openBlock(), createElementBlock("text", _hoisted_7$1, toDisplayString(__props.extra), 1)) : createCommentVNode("", true)
153
+ __props.extra ? (openBlock(), createElementBlock("text", _hoisted_6$1, toDisplayString(__props.extra), 1)) : createCommentVNode("", true)
127
154
  ], true)
128
155
  ])) : createCommentVNode("", true)
129
156
  ])
130
157
  ], true)
131
158
  ])) : createCommentVNode("", true),
132
- createElementVNode("view", _hoisted_8$1, [
159
+ showDivider.value ? (openBlock(), createElementBlock("view", _hoisted_7$1)) : createCommentVNode("", true),
160
+ createElementVNode("view", {
161
+ class: normalizeClass(["hlw-card-body", { "hlw-card-body--padded": __props.padding }])
162
+ }, [
133
163
  renderSlot(_ctx.$slots, "default", {}, void 0, true)
134
- ])
135
- ]);
164
+ ], 2),
165
+ hasFooter.value ? (openBlock(), createElementBlock("view", _hoisted_8$1, [
166
+ renderSlot(_ctx.$slots, "footer", {}, () => [
167
+ createElementVNode("view", _hoisted_9$1, [
168
+ createElementVNode("view", _hoisted_10$1, [
169
+ renderSlot(_ctx.$slots, "footer-left", {}, void 0, true)
170
+ ]),
171
+ _ctx.$slots["footer-right"] ? (openBlock(), createElementBlock("view", _hoisted_11$1, [
172
+ renderSlot(_ctx.$slots, "footer-right", {}, void 0, true)
173
+ ])) : createCommentVNode("", true)
174
+ ])
175
+ ], true)
176
+ ])) : createCommentVNode("", true)
177
+ ], 2);
136
178
  };
137
179
  }
138
180
  });
139
- const index$5 = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-787fc3a7"]]);
181
+ const index$5 = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-cf55252e"]]);
140
182
  const _hoisted_1$4 = { class: "hlw-empty" };
141
183
  const _hoisted_2$4 = ["src"];
142
184
  const _hoisted_3$2 = {
@@ -520,46 +562,162 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
520
562
  }
521
563
  });
522
564
  const index = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-d8833363"]]);
565
+ function varsToStyle(vars) {
566
+ return Object.entries(vars).map(([k, v]) => `${k}:${v}`).join(";") + ";";
567
+ }
568
+ function hexToRgba(hex, alpha) {
569
+ const r = parseInt(hex.slice(1, 3), 16);
570
+ const g = parseInt(hex.slice(3, 5), 16);
571
+ const b = parseInt(hex.slice(5, 7), 16);
572
+ return `rgba(${r},${g},${b},${alpha})`;
573
+ }
574
+ function darkenHex(hex, amount = 0.15) {
575
+ const r = Math.max(0, Math.round(parseInt(hex.slice(1, 3), 16) * (1 - amount)));
576
+ const g = Math.max(0, Math.round(parseInt(hex.slice(3, 5), 16) * (1 - amount)));
577
+ const b = Math.max(0, Math.round(parseInt(hex.slice(5, 7), 16) * (1 - amount)));
578
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
579
+ }
523
580
  const FONT_SCALE_KEY = "hlw_font_scale";
524
- const FONT_SCALE_EVENT = "hlw:font-scale-change";
581
+ const THEME_COLOR_KEY = "hlw_theme_color";
582
+ const THEME_CHANGE_EVENT = "hlw:theme-change";
525
583
  const FONT_PRESETS = {
584
+ small: {
585
+ label: "小字体",
586
+ vars: {
587
+ "--font-xs": "16rpx",
588
+ "--font-sm": "20rpx",
589
+ "--font-base": "24rpx",
590
+ "--font-md": "28rpx",
591
+ "--font-lg": "32rpx",
592
+ "--font-xl": "36rpx"
593
+ }
594
+ },
526
595
  normal: {
527
596
  label: "标准",
528
- style: "--font-xs:20rpx;--font-sm:24rpx;--font-base:28rpx;--font-md:32rpx;--font-lg:36rpx;--font-xl:40rpx;"
597
+ vars: {
598
+ "--font-xs": "20rpx",
599
+ "--font-sm": "24rpx",
600
+ "--font-base": "28rpx",
601
+ "--font-md": "32rpx",
602
+ "--font-lg": "36rpx",
603
+ "--font-xl": "40rpx"
604
+ }
529
605
  },
530
606
  large: {
531
607
  label: "大字体",
532
- style: "--font-xs:24rpx;--font-sm:30rpx;--font-base:34rpx;--font-md:40rpx;--font-lg:46rpx;--font-xl:52rpx;"
608
+ vars: {
609
+ "--font-xs": "24rpx",
610
+ "--font-sm": "30rpx",
611
+ "--font-base": "34rpx",
612
+ "--font-md": "40rpx",
613
+ "--font-lg": "46rpx",
614
+ "--font-xl": "52rpx"
615
+ }
533
616
  },
534
617
  xlarge: {
535
618
  label: "超大字体",
536
- style: "--font-xs:28rpx;--font-sm:36rpx;--font-base:42rpx;--font-md:48rpx;--font-lg:56rpx;--font-xl:64rpx;"
619
+ vars: {
620
+ "--font-xs": "28rpx",
621
+ "--font-sm": "36rpx",
622
+ "--font-base": "42rpx",
623
+ "--font-md": "48rpx",
624
+ "--font-lg": "56rpx",
625
+ "--font-xl": "64rpx"
626
+ }
537
627
  }
538
628
  };
539
629
  function getCurrentFontScale() {
540
630
  try {
541
631
  const v = uni.getStorageSync(FONT_SCALE_KEY);
542
- if (v === "large" || v === "xlarge")
632
+ if (v === "small" || v === "large" || v === "xlarge")
543
633
  return v;
544
634
  } catch {
545
635
  }
546
636
  return "normal";
547
637
  }
548
- function getCurrentFontStyle() {
549
- return FONT_PRESETS[getCurrentFontScale()].style;
638
+ function getCurrentFontVars() {
639
+ return FONT_PRESETS[getCurrentFontScale()].vars;
550
640
  }
551
- function useFontPageStyle() {
552
- const fontPageStyle = ref(getCurrentFontStyle());
553
- const onFontChange = () => {
554
- fontPageStyle.value = getCurrentFontStyle();
641
+ const DEFAULT_THEMES = [
642
+ { label: "默认蓝", value: "#3b82f6" },
643
+ { label: "活力橙", value: "#f97316" },
644
+ { label: "翡翠绿", value: "#10b981" },
645
+ { label: "玫瑰红", value: "#f43f5e" },
646
+ { label: "紫罗兰", value: "#8b5cf6" },
647
+ { label: "青石灰", value: "#64748b" }
648
+ ];
649
+ function getCurrentThemeColor() {
650
+ try {
651
+ const v = uni.getStorageSync(THEME_COLOR_KEY);
652
+ if (v && typeof v === "string")
653
+ return v;
654
+ } catch {
655
+ }
656
+ return DEFAULT_THEMES[0].value;
657
+ }
658
+ function getCurrentThemeVars() {
659
+ const color = getCurrentThemeColor();
660
+ return {
661
+ "--primary-color": color,
662
+ "--primary-light": hexToRgba(color, 0.12),
663
+ "--primary-dark": darkenHex(color)
664
+ };
665
+ }
666
+ function buildThemeStyle() {
667
+ return varsToStyle({
668
+ ...getCurrentFontVars(),
669
+ ...getCurrentThemeVars()
670
+ });
671
+ }
672
+ function useThemePageStyle() {
673
+ const themePageStyle = ref(buildThemeStyle());
674
+ const onThemeChange = () => {
675
+ themePageStyle.value = buildThemeStyle();
555
676
  };
556
- onMounted(() => uni.$on(FONT_SCALE_EVENT, onFontChange));
557
- onUnmounted(() => uni.$off(FONT_SCALE_EVENT, onFontChange));
558
- return { fontPageStyle };
677
+ onMounted(() => uni.$on(THEME_CHANGE_EVENT, onThemeChange));
678
+ onUnmounted(() => uni.$off(THEME_CHANGE_EVENT, onThemeChange));
679
+ return { themePageStyle };
559
680
  }
681
+ const useThemeStore = defineStore(
682
+ "theme",
683
+ () => {
684
+ const scale = ref("normal");
685
+ function setScale(s) {
686
+ scale.value = s;
687
+ uni.setStorageSync(FONT_SCALE_KEY, s);
688
+ uni.$emit(THEME_CHANGE_EVENT);
689
+ }
690
+ const fontOptions = Object.keys(FONT_PRESETS).map((key) => ({
691
+ value: key,
692
+ label: FONT_PRESETS[key].label
693
+ }));
694
+ const primaryColor = ref(DEFAULT_THEMES[0].value);
695
+ const themes = DEFAULT_THEMES;
696
+ const activeTheme = computed(
697
+ () => themes.find((t) => t.value === primaryColor.value) ?? { label: "自定义", value: primaryColor.value }
698
+ );
699
+ function setTheme(color) {
700
+ primaryColor.value = color;
701
+ uni.setStorageSync(THEME_COLOR_KEY, color);
702
+ uni.$emit(THEME_CHANGE_EVENT);
703
+ }
704
+ return {
705
+ // 字体
706
+ scale,
707
+ fontOptions,
708
+ setScale,
709
+ // 主题色
710
+ primaryColor,
711
+ themes,
712
+ activeTheme,
713
+ setTheme
714
+ };
715
+ },
716
+ { unistorage: true }
717
+ );
560
718
  export {
719
+ DEFAULT_THEMES,
561
720
  FONT_PRESETS,
562
- FONT_SCALE_EVENT,
563
721
  FONT_SCALE_KEY,
564
722
  _sfc_main$7 as HlwAd,
565
723
  index$6 as HlwAvatar,
@@ -569,7 +727,13 @@ export {
569
727
  index$2 as HlwLoading,
570
728
  index$1 as HlwMenu,
571
729
  index as HlwPage,
730
+ THEME_CHANGE_EVENT,
731
+ THEME_COLOR_KEY,
732
+ buildThemeStyle,
572
733
  getCurrentFontScale,
573
- getCurrentFontStyle,
574
- useFontPageStyle
734
+ getCurrentFontVars,
735
+ getCurrentThemeColor,
736
+ getCurrentThemeVars,
737
+ useThemePageStyle,
738
+ useThemeStore
575
739
  };
@@ -0,0 +1 @@
1
+ export declare const useThemeStore: any;
package/dist/style.css CHANGED
@@ -33,10 +33,95 @@
33
33
  .hlw-avatar--large .hlw-avatar__initial[data-v-89dcbc96] { font-size: var(--font-xl, 40rpx);
34
34
  }
35
35
 
36
- .hlw-card[data-v-787fc3a7] {
37
- @apply bg-white rounded-xl border border-solid border-slate-200 overflow-hidden w-full;
36
+ .hlw-card[data-v-cf55252e] {
37
+ width: 100%;
38
+ background: #fff;
39
+ overflow: hidden;
40
+ /* 圆角档位 */
41
+ }
42
+ .hlw-card--radius-none[data-v-cf55252e] {
43
+ border-radius: 0;
44
+ }
45
+ .hlw-card--radius-sm[data-v-cf55252e] {
46
+ border-radius: var(--radius-sm, 8rpx);
47
+ }
48
+ .hlw-card--radius-md[data-v-cf55252e] {
49
+ border-radius: var(--radius-md, 16rpx);
50
+ }
51
+ .hlw-card--radius-lg[data-v-cf55252e] {
52
+ border-radius: var(--radius-lg, 24rpx);
53
+ }
54
+ .hlw-card--radius-xl[data-v-cf55252e] {
55
+ border-radius: var(--radius-xl, 32rpx);
56
+ }
57
+ .hlw-card[data-v-cf55252e] {
58
+ /* 边框 */
59
+ }
60
+ .hlw-card--bordered[data-v-cf55252e] {
61
+ border: 1rpx solid var(--border-color, #e2e8f0);
62
+ }
63
+
64
+ /* 头部 */
65
+ .hlw-card-header[data-v-cf55252e] {
66
+ width: 100%;
67
+ }
68
+ .hlw-card-header-inner[data-v-cf55252e] {
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: space-between;
72
+ padding: 24rpx 28rpx;
73
+ }
74
+ .hlw-card-header-left[data-v-cf55252e] {
75
+ flex: 1;
76
+ min-width: 0;
77
+ }
78
+ .hlw-card-header-right[data-v-cf55252e] {
79
+ flex-shrink: 0;
80
+ margin-left: 16rpx;
81
+ }
82
+ .hlw-card-title[data-v-cf55252e] {
83
+ font-size: var(--font-sm, 24rpx);
84
+ font-weight: 700;
85
+ color: #1e293b;
86
+ letter-spacing: 0.02em;
87
+ }
88
+ .hlw-card-extra[data-v-cf55252e] {
89
+ font-size: var(--font-xs, 20rpx);
90
+ color: #94a3b8;
38
91
  }
39
92
 
93
+ /* 虚线分隔 */
94
+ .hlw-card-divider[data-v-cf55252e] {
95
+ width: 100%;
96
+ border-bottom: 1rpx dashed var(--border-color, #e2e8f0);
97
+ }
98
+
99
+ /* 内容区 */
100
+ .hlw-card-body[data-v-cf55252e] {
101
+ width: 100%;
102
+ }
103
+ .hlw-card-body--padded[data-v-cf55252e] {
104
+ padding: 24rpx 28rpx;
105
+ }
106
+
107
+ /* 底部 */
108
+ .hlw-card-footer[data-v-cf55252e] {
109
+ width: 100%;
110
+ border-top: 1rpx solid var(--border-color-light, #f1f5f9);
111
+ }
112
+ .hlw-card-footer-inner[data-v-cf55252e] {
113
+ display: flex;
114
+ align-items: center;
115
+ padding: 20rpx 28rpx;
116
+ }
117
+ .hlw-card-footer-left[data-v-cf55252e] {
118
+ flex: 1;
119
+ min-width: 0;
120
+ }
121
+ .hlw-card-footer-right[data-v-cf55252e] {
122
+ flex-shrink: 0;
123
+ margin-left: 16rpx;
124
+ }
40
125
  .hlw-empty[data-v-08b8d8fe] {
41
126
  display: flex;
42
127
  flex-direction: column;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hlw-uni/mp-vue",
3
- "version": "1.0.32",
3
+ "version": "1.0.34",
4
4
  "description": "hlw-uni Vue 组件库 — 供小程序业务方使用的 UI 组件集合",
5
5
  "main": "src/index.ts",
6
6
  "module": "src/index.ts",
@@ -0,0 +1,142 @@
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
+ }
package/src/index.ts CHANGED
@@ -11,6 +11,6 @@ export { default as HlwLoading } from "./components/hlw-loading/index.vue";
11
11
  export { default as HlwMenu } from "./components/hlw-menu/index.vue";
12
12
  export type { HlwMenuItem } from "./components/hlw-menu/types";
13
13
  export { default as HlwPage } from "./components/hlw-page/index.vue";
14
- export type { FontScale, FontPreset } from "./composables/font-presets";
15
- export { FONT_PRESETS, FONT_SCALE_KEY, FONT_SCALE_EVENT, getCurrentFontScale, getCurrentFontStyle, useFontPageStyle } from "./composables/font-presets";
16
- export { useFontStore } from "./stores/font";
14
+ export type { FontScale, FontPreset, ThemeColor } from "./composables/theme";
15
+ export { FONT_PRESETS, FONT_SCALE_KEY, DEFAULT_THEMES, THEME_COLOR_KEY, THEME_CHANGE_EVENT, getCurrentFontScale, getCurrentFontVars, getCurrentThemeColor, getCurrentThemeVars, buildThemeStyle, useThemePageStyle } from "./composables/theme";
16
+ export { useThemeStore } from "./stores/theme";
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Theme Store — 全局主题设置(字体档位 + 主题色)
3
+ * 持久化到 storage,变更时广播 THEME_CHANGE_EVENT 通知所有页面实时刷新
4
+ */
5
+ import { defineStore } from "pinia";
6
+ import { ref, computed } from "vue";
7
+ import {
8
+ FONT_PRESETS,
9
+ FONT_SCALE_KEY,
10
+ DEFAULT_THEMES,
11
+ THEME_COLOR_KEY,
12
+ THEME_CHANGE_EVENT,
13
+ } from "../composables/theme";
14
+ import type { FontScale, ThemeColor } from "../composables/theme";
15
+
16
+ export const useThemeStore = defineStore(
17
+ "theme",
18
+ () => {
19
+ // ─── 字体档位 ────────────────────────────
20
+ const scale = ref<FontScale>("normal");
21
+
22
+ function setScale(s: FontScale) {
23
+ scale.value = s;
24
+ uni.setStorageSync(FONT_SCALE_KEY, s);
25
+ uni.$emit(THEME_CHANGE_EVENT);
26
+ }
27
+
28
+ const fontOptions = (Object.keys(FONT_PRESETS) as FontScale[]).map((key) => ({
29
+ value: key,
30
+ label: FONT_PRESETS[key].label,
31
+ }));
32
+
33
+ // ─── 主题色 ─────────────────────────────
34
+ const primaryColor = ref(DEFAULT_THEMES[0].value);
35
+
36
+ /** 内置预设主题 */
37
+ const themes = DEFAULT_THEMES;
38
+
39
+ /** 当前激活的主题信息 */
40
+ const activeTheme = computed<ThemeColor>(() =>
41
+ themes.find((t) => t.value === primaryColor.value)
42
+ ?? { label: "自定义", value: primaryColor.value },
43
+ );
44
+
45
+ /** 设置主题色(预设或自定义颜色值) */
46
+ function setTheme(color: string) {
47
+ primaryColor.value = color;
48
+ uni.setStorageSync(THEME_COLOR_KEY, color);
49
+ uni.$emit(THEME_CHANGE_EVENT);
50
+ }
51
+
52
+ return {
53
+ // 字体
54
+ scale,
55
+ fontOptions,
56
+ setScale,
57
+ // 主题色
58
+ primaryColor,
59
+ themes,
60
+ activeTheme,
61
+ setTheme,
62
+ };
63
+ },
64
+ { unistorage: true },
65
+ );
@@ -1,54 +0,0 @@
1
- /**
2
- * hlw-page 字体档位预设
3
- *
4
- * 三档:标准 / 大字体 / 超大字体
5
- * 通过 <page-meta page-style="..."> 注入到页面根节点,
6
- * 覆盖 CSS 变量,所有使用 var(--font-*) 的组件自动跟随。
7
- */
8
- export type FontScale = "normal" | "large" | "xlarge";
9
- /** storage key,与 qz2 font store 保持一致 */
10
- export declare const FONT_SCALE_KEY = "hlw_font_scale";
11
- /** 全局事件名,store.setScale 触发后 hlw-page 实时响应 */
12
- export declare const FONT_SCALE_EVENT = "hlw:font-scale-change";
13
- export interface FontPreset {
14
- /** 展示名称 */
15
- label: string;
16
- /** 注入到 page-meta 的 CSS 变量字符串 */
17
- style: string;
18
- }
19
- /**
20
- * 三档字体预设
21
- *
22
- * 变量说明:
23
- * --font-xs 极小文字(角标、辅助标注)
24
- * --font-sm 小文字(次要说明、标签、grid-label)
25
- * --font-base 正文(菜单项、内容主体)
26
- * --font-md 中等(次级标题)
27
- * --font-lg 大号(页面标题、导航标题)
28
- * --font-xl 特大(数字展示)
29
- */
30
- export declare const FONT_PRESETS: Record<FontScale, FontPreset>;
31
- /** 读取当前档位(同步,从 storage 取) */
32
- export declare function getCurrentFontScale(): FontScale;
33
- /** 读取当前档位对应的 page-style 字符串 */
34
- export declare function getCurrentFontStyle(): string;
35
- /**
36
- * 在页面根节点使用,配合 <page-meta :page-style="fontPageStyle"> 实现全局字体缩放。
37
- *
38
- * 注意:<page-meta> 必须作为页面 .vue 文件 template 的第一个根节点,不可放在子组件内。
39
- *
40
- * @example
41
- * ```vue
42
- * <template>
43
- * <page-meta :page-style="fontPageStyle" />
44
- * <hlw-page title="xxx">...</hlw-page>
45
- * </template>
46
- * <script setup>
47
- * import { useFontPageStyle } from '@hlw-uni/mp-vue';
48
- * const { fontPageStyle } = useFontPageStyle();
49
- * </script>
50
- * ```
51
- */
52
- export declare function useFontPageStyle(): {
53
- fontPageStyle: any;
54
- };
@@ -1 +0,0 @@
1
- export * from "../../composables/font-presets";
@@ -1,98 +0,0 @@
1
- import { ref, onMounted, onUnmounted } from "vue";
2
-
3
- /**
4
- * hlw-page 字体档位预设
5
- *
6
- * 三档:标准 / 大字体 / 超大字体
7
- * 通过 <page-meta page-style="..."> 注入到页面根节点,
8
- * 覆盖 CSS 变量,所有使用 var(--font-*) 的组件自动跟随。
9
- */
10
-
11
- export type FontScale = "small" | "normal" | "large" | "xlarge";
12
-
13
- /** storage key,与 qz2 font store 保持一致 */
14
- export const FONT_SCALE_KEY = "hlw_font_scale";
15
-
16
- /** 全局事件名,store.setScale 触发后 hlw-page 实时响应 */
17
- export const FONT_SCALE_EVENT = "hlw:font-scale-change";
18
-
19
- export interface FontPreset {
20
- /** 展示名称 */
21
- label: string;
22
- /** 注入到 page-meta 的 CSS 变量字符串 */
23
- style: string;
24
- }
25
-
26
- /**
27
- * 三档字体预设
28
- *
29
- * 变量说明:
30
- * --font-xs 极小文字(角标、辅助标注)
31
- * --font-sm 小文字(次要说明、标签、grid-label)
32
- * --font-base 正文(菜单项、内容主体)
33
- * --font-md 中等(次级标题)
34
- * --font-lg 大号(页面标题、导航标题)
35
- * --font-xl 特大(数字展示)
36
- */
37
- export const FONT_PRESETS: Record<FontScale, FontPreset> = {
38
- small: {
39
- label: "小字体",
40
- style: "--font-xs:16rpx;--font-sm:20rpx;--font-base:24rpx;--font-md:28rpx;--font-lg:32rpx;--font-xl:36rpx;",
41
- },
42
- normal: {
43
- label: "标准",
44
- style: "--font-xs:20rpx;--font-sm:24rpx;--font-base:28rpx;--font-md:32rpx;--font-lg:36rpx;--font-xl:40rpx;",
45
- },
46
- large: {
47
- label: "大字体",
48
- style: "--font-xs:24rpx;--font-sm:30rpx;--font-base:34rpx;--font-md:40rpx;--font-lg:46rpx;--font-xl:52rpx;",
49
- },
50
- xlarge: {
51
- label: "超大字体",
52
- style: "--font-xs:28rpx;--font-sm:36rpx;--font-base:42rpx;--font-md:48rpx;--font-lg:56rpx;--font-xl:64rpx;",
53
- },
54
- };
55
-
56
- /** 读取当前档位(同步,从 storage 取) */
57
- export function getCurrentFontScale(): FontScale {
58
- try {
59
- const v = uni.getStorageSync(FONT_SCALE_KEY);
60
- if (v === "small" || v === "large" || v === "xlarge") return v;
61
- } catch {}
62
- return "normal";
63
- }
64
-
65
- /** 读取当前档位对应的 page-style 字符串 */
66
- export function getCurrentFontStyle(): string {
67
- return FONT_PRESETS[getCurrentFontScale()].style;
68
- }
69
-
70
- /**
71
- * 在页面根节点使用,配合 <page-meta :page-style="fontPageStyle"> 实现全局字体缩放。
72
- *
73
- * 注意:<page-meta> 必须作为页面 .vue 文件 template 的第一个根节点,不可放在子组件内。
74
- *
75
- * @example
76
- * ```vue
77
- * <template>
78
- * <page-meta :page-style="fontPageStyle" />
79
- * <hlw-page title="xxx">...</hlw-page>
80
- * </template>
81
- * <script setup>
82
- * import { useFontPageStyle } from '@hlw-uni/mp-vue';
83
- * const { fontPageStyle } = useFontPageStyle();
84
- * </script>
85
- * ```
86
- */
87
- export function useFontPageStyle() {
88
- const fontPageStyle = ref(getCurrentFontStyle());
89
-
90
- const onFontChange = () => {
91
- fontPageStyle.value = getCurrentFontStyle();
92
- };
93
-
94
- onMounted(() => uni.$on(FONT_SCALE_EVENT, onFontChange));
95
- onUnmounted(() => uni.$off(FONT_SCALE_EVENT, onFontChange));
96
-
97
- return { fontPageStyle };
98
- }
@@ -1,31 +0,0 @@
1
- /**
2
- * Font Store — 全局字体档位设置
3
- * 持久化到 storage,变更时广播 FONT_SCALE_EVENT 通知所有页面实例实时刷新
4
- */
5
- import { defineStore } from "pinia";
6
- import { ref } from "vue";
7
- import { FONT_PRESETS, FONT_SCALE_EVENT, FONT_SCALE_KEY } from "../composables/font-presets";
8
- import type { FontScale } from "../composables/font-presets";
9
-
10
- export const useFontStore = defineStore(
11
- "font",
12
- () => {
13
- const scale = ref<FontScale>("normal");
14
-
15
- /** 切换字体档位,自动持久化并通知页面刷新 */
16
- function setScale(s: FontScale) {
17
- scale.value = s;
18
- uni.setStorageSync(FONT_SCALE_KEY, s);
19
- uni.$emit(FONT_SCALE_EVENT);
20
- }
21
-
22
- /** 所有可选档位列表,供 UI 遍历 */
23
- const options = (Object.keys(FONT_PRESETS) as FontScale[]).map((key) => ({
24
- value: key,
25
- label: FONT_PRESETS[key].label,
26
- }));
27
-
28
- return { scale, options, setScale };
29
- },
30
- { unistorage: true },
31
- );