@hlw-uni/mp-vue 1.0.13 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ export type HlwMenuIconTheme = "orange" | "purple" | "cyan" | "emerald" | "slate" | "wechat" | "rose" | "blue" | "red";
2
+ export type HlwMenuTagTheme = "orange" | "red" | "wechat" | "rose" | "blue";
3
+ export interface HlwMenuItem {
4
+ /** 图标 class,如 'i-fa6-solid-gear' */
5
+ icon: string;
6
+ /** 图标主题色 */
7
+ iconTheme?: HlwMenuIconTheme;
8
+ /** 菜单文字 */
9
+ label: string;
10
+ /** 跳转路径,有值则用 navigator 包裹 */
11
+ url?: string;
12
+ /** 右侧标签文字(列表模式)/ 角标文字(宫格模式) */
13
+ tag?: string;
14
+ /** 标签主题色 */
15
+ tagTheme?: HlwMenuTagTheme;
16
+ /** 标签是否闪烁 */
17
+ tagPulse?: boolean;
18
+ /** 加载中状态(列表模式) */
19
+ loading?: boolean;
20
+ /** 是否显示,默认 true */
21
+ visible?: boolean;
22
+ }
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@ export { default as HlwEmpty } from './components/hlw-empty/index.vue';
8
8
  export { default as HlwHeader } from './components/hlw-header/index.vue';
9
9
  export { default as HlwLoading } from './components/hlw-loading/index.vue';
10
10
  export { default as HlwMenu } from './components/hlw-menu/index.vue';
11
- export type { HlwMenuItem } from './components/hlw-menu/index.vue';
11
+ export type { HlwMenuItem } from './components/hlw-menu/types';
12
12
  export { default as HlwMenuList } from './components/hlw-menu-list/index.vue';
13
13
  export { default as HlwPage } from './components/hlw-page/index.vue';
14
14
  export type { MenuItem } from './components/hlw-menu-list/types';
package/dist/index.js CHANGED
@@ -85,14 +85,14 @@
85
85
  class: "hlw-card-header"
86
86
  };
87
87
  const _hoisted_3$4 = { 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 };
91
- const _hoisted_7$1 = {
88
+ const _hoisted_4$3 = { key: 0 };
89
+ const _hoisted_5$2 = { class: "text-sm font-bold text-slate-800 flex items-center gap-2 tracking-wide" };
90
+ const _hoisted_6$2 = { key: 1 };
91
+ const _hoisted_7$2 = {
92
92
  key: 0,
93
93
  class: "text-[10px] text-slate-400 bg-slate-50 px-2 py-1 rounded border border-slate-100 tracking-wide"
94
94
  };
95
- const _hoisted_8 = { class: "hlw-card-body" };
95
+ const _hoisted_8$1 = { class: "hlw-card-body" };
96
96
  const _sfc_main$6 = /* @__PURE__ */ vue.defineComponent({
97
97
  ...{
98
98
  options: {
@@ -113,9 +113,9 @@
113
113
  _ctx.$slots.header || __props.title || __props.icon || _ctx.$slots["header-left"] || _ctx.$slots["header-right"] ? (vue.openBlock(), vue.createElementBlock("view", _hoisted_2$6, [
114
114
  vue.renderSlot(_ctx.$slots, "header", {}, () => [
115
115
  vue.createElementVNode("view", _hoisted_3$4, [
116
- _ctx.$slots["header-left"] || __props.title || __props.icon ? (vue.openBlock(), vue.createElementBlock("view", _hoisted_4$2, [
116
+ _ctx.$slots["header-left"] || __props.title || __props.icon ? (vue.openBlock(), vue.createElementBlock("view", _hoisted_4$3, [
117
117
  vue.renderSlot(_ctx.$slots, "header-left", {}, () => [
118
- vue.createElementVNode("view", _hoisted_5$1, [
118
+ vue.createElementVNode("view", _hoisted_5$2, [
119
119
  __props.icon ? (vue.openBlock(), vue.createElementBlock("text", {
120
120
  key: 0,
121
121
  class: vue.normalizeClass([__props.icon, __props.iconColor])
@@ -124,15 +124,15 @@
124
124
  ])
125
125
  ], true)
126
126
  ])) : vue.createCommentVNode("", true),
127
- _ctx.$slots["header-right"] || __props.extra ? (vue.openBlock(), vue.createElementBlock("view", _hoisted_6$1, [
127
+ _ctx.$slots["header-right"] || __props.extra ? (vue.openBlock(), vue.createElementBlock("view", _hoisted_6$2, [
128
128
  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)
129
+ __props.extra ? (vue.openBlock(), vue.createElementBlock("text", _hoisted_7$2, vue.toDisplayString(__props.extra), 1)) : vue.createCommentVNode("", true)
130
130
  ], true)
131
131
  ])) : vue.createCommentVNode("", true)
132
132
  ])
133
133
  ], true)
134
134
  ])) : vue.createCommentVNode("", true),
135
- vue.createElementVNode("view", _hoisted_8, [
135
+ vue.createElementVNode("view", _hoisted_8$1, [
136
136
  vue.renderSlot(_ctx.$slots, "default", {}, void 0, true)
137
137
  ])
138
138
  ]);
@@ -146,7 +146,7 @@
146
146
  key: 1,
147
147
  class: "hlw-empty__icon"
148
148
  };
149
- const _hoisted_4$1 = { class: "hlw-empty__text" };
149
+ const _hoisted_4$2 = { class: "hlw-empty__text" };
150
150
  const _sfc_main$5 = /* @__PURE__ */ vue.defineComponent({
151
151
  __name: "index",
152
152
  props: {
@@ -162,7 +162,7 @@
162
162
  src: __props.image,
163
163
  mode: "aspectFit"
164
164
  }, null, 8, _hoisted_2$5)) : (vue.openBlock(), vue.createElementBlock("text", _hoisted_3$3, "📦")),
165
- vue.createElementVNode("text", _hoisted_4$1, vue.toDisplayString(__props.text || "暂无数据"), 1),
165
+ vue.createElementVNode("text", _hoisted_4$2, vue.toDisplayString(__props.text || "暂无数据"), 1),
166
166
  vue.renderSlot(_ctx.$slots, "default", {}, void 0, true)
167
167
  ]);
168
168
  };
@@ -300,45 +300,37 @@
300
300
  });
301
301
  const index$3 = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__scopeId", "data-v-14242381"]]);
302
302
  const _hoisted_1$2 = { class: "hlw-menu" };
303
- const _hoisted_2$2 = ["onClick"];
304
- const _hoisted_3$2 = {
303
+ const _hoisted_2$2 = {
304
+ key: 0,
305
+ class: "hlw-menu-title"
306
+ };
307
+ const _hoisted_3$2 = { class: "hlw-menu-left" };
308
+ const _hoisted_4$1 = { class: "hlw-menu-label" };
309
+ const _hoisted_5$1 = { class: "hlw-menu-right" };
310
+ const _hoisted_6$1 = ["onClick"];
311
+ const _hoisted_7$1 = { class: "hlw-menu-left" };
312
+ const _hoisted_8 = { class: "hlw-menu-label" };
313
+ const _hoisted_9 = { class: "hlw-menu-right" };
314
+ const _hoisted_10 = {
315
+ key: 0,
316
+ class: "i-fa6-solid-circle-notch hlw-menu-spin hlw-menu-muted"
317
+ };
318
+ const _hoisted_11 = {
305
319
  key: 2,
306
320
  class: "hlw-menu-divider"
307
321
  };
308
- const HlwMenuItemContent = vue.defineComponent({
309
- name: "HlwMenuItemContent",
310
- props: {
311
- item: { type: Object, required: true }
312
- },
313
- setup(props) {
314
- return () => {
315
- const item = props.item;
316
- return vue.h("view", { class: "hlw-menu-item-inner" }, [
317
- // 左侧
318
- vue.h("view", { class: "hlw-menu-left" }, [
319
- vue.h("view", { class: `hlw-menu-icon hlw-menu-icon--${item.iconTheme || "slate"}` }, [
320
- vue.h("text", { class: item.icon })
321
- ]),
322
- vue.h("text", { class: "hlw-menu-label" }, item.label)
323
- ]),
324
- // 右侧
325
- vue.h("view", { class: "hlw-menu-right" }, [
326
- item.loading ? vue.h("text", { class: "i-fa6-solid-circle-notch hlw-menu-spin hlw-menu-muted" }) : null,
327
- item.tag ? vue.h(
328
- "text",
329
- { class: `hlw-menu-tag hlw-menu-tag--${item.tagTheme || "rose"} ${item.tagPulse ? "hlw-menu-tag-pulse" : ""}` },
330
- item.tag
331
- ) : null,
332
- vue.h("text", { class: "i-fa6-solid-chevron-right hlw-menu-arrow" })
333
- ])
334
- ]);
335
- };
336
- }
337
- });
322
+ const _hoisted_12 = { class: "hlw-menu-grid-icon-wrap" };
323
+ const _hoisted_13 = { class: "hlw-menu-grid-label" };
324
+ const _hoisted_14 = ["onClick"];
325
+ const _hoisted_15 = { class: "hlw-menu-grid-icon-wrap" };
326
+ const _hoisted_16 = { class: "hlw-menu-grid-label" };
338
327
  const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({
339
328
  __name: "index",
340
329
  props: {
341
- items: {}
330
+ items: {},
331
+ title: { default: "" },
332
+ mode: { default: "list" },
333
+ columns: { default: 4 }
342
334
  },
343
335
  emits: ["click"],
344
336
  setup(__props, { emit: __emit }) {
@@ -351,7 +343,10 @@
351
343
  return (_ctx, _cache) => {
352
344
  const _component_navigator = vue.resolveComponent("navigator");
353
345
  return vue.openBlock(), vue.createElementBlock("view", _hoisted_1$2, [
354
- (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(visibleItems.value, (item, index2) => {
346
+ props.title ? (vue.openBlock(), vue.createElementBlock("view", _hoisted_2$2, [
347
+ vue.createElementVNode("text", null, vue.toDisplayString(props.title), 1)
348
+ ])) : vue.createCommentVNode("", true),
349
+ props.mode === "list" ? (vue.openBlock(true), vue.createElementBlock(vue.Fragment, { key: 1 }, vue.renderList(visibleItems.value, (item, index2) => {
355
350
  return vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: index2 }, [
356
351
  item.url ? (vue.openBlock(), vue.createBlock(_component_navigator, {
357
352
  key: 0,
@@ -360,9 +355,23 @@
360
355
  "hover-class": "hlw-menu-item--active"
361
356
  }, {
362
357
  default: vue.withCtx(() => [
363
- vue.renderSlot(_ctx.$slots, "item", { item }, () => [
364
- vue.createVNode(vue.unref(HlwMenuItemContent), { item }, null, 8, ["item"])
365
- ], true)
358
+ vue.createElementVNode("view", _hoisted_3$2, [
359
+ vue.createElementVNode("view", {
360
+ class: vue.normalizeClass(["hlw-menu-icon", `hlw-menu-icon--${item.iconTheme || "slate"}`])
361
+ }, [
362
+ vue.createElementVNode("text", {
363
+ class: vue.normalizeClass(item.icon)
364
+ }, null, 2)
365
+ ], 2),
366
+ vue.createElementVNode("text", _hoisted_4$1, vue.toDisplayString(item.label), 1)
367
+ ]),
368
+ vue.createElementVNode("view", _hoisted_5$1, [
369
+ item.tag ? (vue.openBlock(), vue.createElementBlock("text", {
370
+ key: 0,
371
+ class: vue.normalizeClass(["hlw-menu-tag", [`hlw-menu-tag--${item.tagTheme || "rose"}`, item.tagPulse ? "hlw-menu-tag-pulse" : ""]])
372
+ }, vue.toDisplayString(item.tag), 3)) : vue.createCommentVNode("", true),
373
+ _cache[0] || (_cache[0] = vue.createElementVNode("text", { class: "i-fa6-solid-chevron-right hlw-menu-arrow" }, null, -1))
374
+ ])
366
375
  ]),
367
376
  _: 2
368
377
  }, 1032, ["url"])) : (vue.openBlock(), vue.createElementBlock("view", {
@@ -371,18 +380,86 @@
371
380
  "hover-class": "hlw-menu-item--active",
372
381
  onClick: ($event) => handleClick(item)
373
382
  }, [
374
- vue.renderSlot(_ctx.$slots, "item", { item }, () => [
375
- vue.createVNode(vue.unref(HlwMenuItemContent), { item }, null, 8, ["item"])
376
- ], true)
377
- ], 8, _hoisted_2$2)),
378
- index2 < visibleItems.value.length - 1 ? (vue.openBlock(), vue.createElementBlock("view", _hoisted_3$2)) : vue.createCommentVNode("", true)
383
+ vue.createElementVNode("view", _hoisted_7$1, [
384
+ vue.createElementVNode("view", {
385
+ class: vue.normalizeClass(["hlw-menu-icon", `hlw-menu-icon--${item.iconTheme || "slate"}`])
386
+ }, [
387
+ vue.createElementVNode("text", {
388
+ class: vue.normalizeClass(item.icon)
389
+ }, null, 2)
390
+ ], 2),
391
+ vue.createElementVNode("text", _hoisted_8, vue.toDisplayString(item.label), 1)
392
+ ]),
393
+ vue.createElementVNode("view", _hoisted_9, [
394
+ item.loading ? (vue.openBlock(), vue.createElementBlock("text", _hoisted_10)) : vue.createCommentVNode("", true),
395
+ item.tag ? (vue.openBlock(), vue.createElementBlock("text", {
396
+ key: 1,
397
+ class: vue.normalizeClass(["hlw-menu-tag", [`hlw-menu-tag--${item.tagTheme || "rose"}`, item.tagPulse ? "hlw-menu-tag-pulse" : ""]])
398
+ }, vue.toDisplayString(item.tag), 3)) : vue.createCommentVNode("", true),
399
+ _cache[1] || (_cache[1] = vue.createElementVNode("text", { class: "i-fa6-solid-chevron-right hlw-menu-arrow" }, null, -1))
400
+ ])
401
+ ], 8, _hoisted_6$1)),
402
+ index2 < visibleItems.value.length - 1 ? (vue.openBlock(), vue.createElementBlock("view", _hoisted_11)) : vue.createCommentVNode("", true)
379
403
  ], 64);
380
- }), 128))
404
+ }), 128)) : (vue.openBlock(), vue.createElementBlock("view", {
405
+ key: 2,
406
+ class: "hlw-menu-grid",
407
+ style: vue.normalizeStyle({ gridTemplateColumns: `repeat(${props.columns}, 1fr)` })
408
+ }, [
409
+ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(visibleItems.value, (item, index2) => {
410
+ return vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: index2 }, [
411
+ item.url ? (vue.openBlock(), vue.createBlock(_component_navigator, {
412
+ key: 0,
413
+ url: item.url,
414
+ class: "hlw-menu-grid-item",
415
+ "hover-class": "hlw-menu-grid-item--active"
416
+ }, {
417
+ default: vue.withCtx(() => [
418
+ vue.createElementVNode("view", _hoisted_12, [
419
+ vue.createElementVNode("view", {
420
+ class: vue.normalizeClass(["hlw-menu-icon hlw-menu-icon--grid", `hlw-menu-icon--${item.iconTheme || "slate"}`])
421
+ }, [
422
+ vue.createElementVNode("text", {
423
+ class: vue.normalizeClass(item.icon)
424
+ }, null, 2)
425
+ ], 2),
426
+ item.tag ? (vue.openBlock(), vue.createElementBlock("text", {
427
+ key: 0,
428
+ class: vue.normalizeClass(["hlw-menu-badge", [`hlw-menu-tag--${item.tagTheme || "rose"}`, item.tagPulse ? "hlw-menu-tag-pulse" : ""]])
429
+ }, vue.toDisplayString(item.tag), 3)) : vue.createCommentVNode("", true)
430
+ ]),
431
+ vue.createElementVNode("text", _hoisted_13, vue.toDisplayString(item.label), 1)
432
+ ]),
433
+ _: 2
434
+ }, 1032, ["url"])) : (vue.openBlock(), vue.createElementBlock("view", {
435
+ key: 1,
436
+ class: "hlw-menu-grid-item",
437
+ "hover-class": "hlw-menu-grid-item--active",
438
+ onClick: ($event) => handleClick(item)
439
+ }, [
440
+ vue.createElementVNode("view", _hoisted_15, [
441
+ vue.createElementVNode("view", {
442
+ class: vue.normalizeClass(["hlw-menu-icon hlw-menu-icon--grid", `hlw-menu-icon--${item.iconTheme || "slate"}`])
443
+ }, [
444
+ vue.createElementVNode("text", {
445
+ class: vue.normalizeClass(item.icon)
446
+ }, null, 2)
447
+ ], 2),
448
+ item.tag ? (vue.openBlock(), vue.createElementBlock("text", {
449
+ key: 0,
450
+ class: vue.normalizeClass(["hlw-menu-badge", [`hlw-menu-tag--${item.tagTheme || "rose"}`, item.tagPulse ? "hlw-menu-tag-pulse" : ""]])
451
+ }, vue.toDisplayString(item.tag), 3)) : vue.createCommentVNode("", true)
452
+ ]),
453
+ vue.createElementVNode("text", _hoisted_16, vue.toDisplayString(item.label), 1)
454
+ ], 8, _hoisted_14))
455
+ ], 64);
456
+ }), 128))
457
+ ], 4))
381
458
  ]);
382
459
  };
383
460
  }
384
461
  });
385
- const index$2 = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-48deaf95"]]);
462
+ const index$2 = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-f4bdcedc"]]);
386
463
  const _hoisted_1$1 = { class: "hlw-menu-list" };
387
464
  const _hoisted_2$1 = ["onTap"];
388
465
  const _hoisted_3$1 = { class: "hlw-menu-list__left" };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { defineComponent, resolveComponent, openBlock, createBlock, ref, computed, createElementBlock, normalizeClass, createElementVNode, toDisplayString, renderSlot, createCommentVNode, useSlots, normalizeStyle, unref, Fragment, h, renderList, withCtx, createVNode } from "vue";
1
+ import { defineComponent, resolveComponent, openBlock, createBlock, ref, computed, createElementBlock, normalizeClass, createElementVNode, toDisplayString, renderSlot, createCommentVNode, useSlots, normalizeStyle, unref, Fragment, renderList, withCtx, createVNode } from "vue";
2
2
  const _sfc_main$8 = /* @__PURE__ */ defineComponent({
3
3
  ...{ name: "HlwAd" },
4
4
  __name: "index",
@@ -82,14 +82,14 @@ const _hoisted_2$6 = {
82
82
  class: "hlw-card-header"
83
83
  };
84
84
  const _hoisted_3$4 = { 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 };
88
- const _hoisted_7$1 = {
85
+ const _hoisted_4$3 = { key: 0 };
86
+ const _hoisted_5$2 = { class: "text-sm font-bold text-slate-800 flex items-center gap-2 tracking-wide" };
87
+ const _hoisted_6$2 = { key: 1 };
88
+ const _hoisted_7$2 = {
89
89
  key: 0,
90
90
  class: "text-[10px] text-slate-400 bg-slate-50 px-2 py-1 rounded border border-slate-100 tracking-wide"
91
91
  };
92
- const _hoisted_8 = { class: "hlw-card-body" };
92
+ const _hoisted_8$1 = { class: "hlw-card-body" };
93
93
  const _sfc_main$6 = /* @__PURE__ */ defineComponent({
94
94
  ...{
95
95
  options: {
@@ -110,9 +110,9 @@ const _sfc_main$6 = /* @__PURE__ */ defineComponent({
110
110
  _ctx.$slots.header || __props.title || __props.icon || _ctx.$slots["header-left"] || _ctx.$slots["header-right"] ? (openBlock(), createElementBlock("view", _hoisted_2$6, [
111
111
  renderSlot(_ctx.$slots, "header", {}, () => [
112
112
  createElementVNode("view", _hoisted_3$4, [
113
- _ctx.$slots["header-left"] || __props.title || __props.icon ? (openBlock(), createElementBlock("view", _hoisted_4$2, [
113
+ _ctx.$slots["header-left"] || __props.title || __props.icon ? (openBlock(), createElementBlock("view", _hoisted_4$3, [
114
114
  renderSlot(_ctx.$slots, "header-left", {}, () => [
115
- createElementVNode("view", _hoisted_5$1, [
115
+ createElementVNode("view", _hoisted_5$2, [
116
116
  __props.icon ? (openBlock(), createElementBlock("text", {
117
117
  key: 0,
118
118
  class: normalizeClass([__props.icon, __props.iconColor])
@@ -121,15 +121,15 @@ const _sfc_main$6 = /* @__PURE__ */ defineComponent({
121
121
  ])
122
122
  ], true)
123
123
  ])) : createCommentVNode("", true),
124
- _ctx.$slots["header-right"] || __props.extra ? (openBlock(), createElementBlock("view", _hoisted_6$1, [
124
+ _ctx.$slots["header-right"] || __props.extra ? (openBlock(), createElementBlock("view", _hoisted_6$2, [
125
125
  renderSlot(_ctx.$slots, "header-right", {}, () => [
126
- __props.extra ? (openBlock(), createElementBlock("text", _hoisted_7$1, toDisplayString(__props.extra), 1)) : createCommentVNode("", true)
126
+ __props.extra ? (openBlock(), createElementBlock("text", _hoisted_7$2, toDisplayString(__props.extra), 1)) : createCommentVNode("", true)
127
127
  ], true)
128
128
  ])) : createCommentVNode("", true)
129
129
  ])
130
130
  ], true)
131
131
  ])) : createCommentVNode("", true),
132
- createElementVNode("view", _hoisted_8, [
132
+ createElementVNode("view", _hoisted_8$1, [
133
133
  renderSlot(_ctx.$slots, "default", {}, void 0, true)
134
134
  ])
135
135
  ]);
@@ -143,7 +143,7 @@ const _hoisted_3$3 = {
143
143
  key: 1,
144
144
  class: "hlw-empty__icon"
145
145
  };
146
- const _hoisted_4$1 = { class: "hlw-empty__text" };
146
+ const _hoisted_4$2 = { class: "hlw-empty__text" };
147
147
  const _sfc_main$5 = /* @__PURE__ */ defineComponent({
148
148
  __name: "index",
149
149
  props: {
@@ -159,7 +159,7 @@ const _sfc_main$5 = /* @__PURE__ */ defineComponent({
159
159
  src: __props.image,
160
160
  mode: "aspectFit"
161
161
  }, null, 8, _hoisted_2$5)) : (openBlock(), createElementBlock("text", _hoisted_3$3, "📦")),
162
- createElementVNode("text", _hoisted_4$1, toDisplayString(__props.text || "暂无数据"), 1),
162
+ createElementVNode("text", _hoisted_4$2, toDisplayString(__props.text || "暂无数据"), 1),
163
163
  renderSlot(_ctx.$slots, "default", {}, void 0, true)
164
164
  ]);
165
165
  };
@@ -297,45 +297,37 @@ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
297
297
  });
298
298
  const index$3 = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__scopeId", "data-v-14242381"]]);
299
299
  const _hoisted_1$2 = { class: "hlw-menu" };
300
- const _hoisted_2$2 = ["onClick"];
301
- const _hoisted_3$2 = {
300
+ const _hoisted_2$2 = {
301
+ key: 0,
302
+ class: "hlw-menu-title"
303
+ };
304
+ const _hoisted_3$2 = { class: "hlw-menu-left" };
305
+ const _hoisted_4$1 = { class: "hlw-menu-label" };
306
+ const _hoisted_5$1 = { class: "hlw-menu-right" };
307
+ const _hoisted_6$1 = ["onClick"];
308
+ const _hoisted_7$1 = { class: "hlw-menu-left" };
309
+ const _hoisted_8 = { class: "hlw-menu-label" };
310
+ const _hoisted_9 = { class: "hlw-menu-right" };
311
+ const _hoisted_10 = {
312
+ key: 0,
313
+ class: "i-fa6-solid-circle-notch hlw-menu-spin hlw-menu-muted"
314
+ };
315
+ const _hoisted_11 = {
302
316
  key: 2,
303
317
  class: "hlw-menu-divider"
304
318
  };
305
- const HlwMenuItemContent = defineComponent({
306
- name: "HlwMenuItemContent",
307
- props: {
308
- item: { type: Object, required: true }
309
- },
310
- setup(props) {
311
- return () => {
312
- const item = props.item;
313
- return h("view", { class: "hlw-menu-item-inner" }, [
314
- // 左侧
315
- h("view", { class: "hlw-menu-left" }, [
316
- h("view", { class: `hlw-menu-icon hlw-menu-icon--${item.iconTheme || "slate"}` }, [
317
- h("text", { class: item.icon })
318
- ]),
319
- h("text", { class: "hlw-menu-label" }, item.label)
320
- ]),
321
- // 右侧
322
- h("view", { class: "hlw-menu-right" }, [
323
- item.loading ? h("text", { class: "i-fa6-solid-circle-notch hlw-menu-spin hlw-menu-muted" }) : null,
324
- item.tag ? h(
325
- "text",
326
- { class: `hlw-menu-tag hlw-menu-tag--${item.tagTheme || "rose"} ${item.tagPulse ? "hlw-menu-tag-pulse" : ""}` },
327
- item.tag
328
- ) : null,
329
- h("text", { class: "i-fa6-solid-chevron-right hlw-menu-arrow" })
330
- ])
331
- ]);
332
- };
333
- }
334
- });
319
+ const _hoisted_12 = { class: "hlw-menu-grid-icon-wrap" };
320
+ const _hoisted_13 = { class: "hlw-menu-grid-label" };
321
+ const _hoisted_14 = ["onClick"];
322
+ const _hoisted_15 = { class: "hlw-menu-grid-icon-wrap" };
323
+ const _hoisted_16 = { class: "hlw-menu-grid-label" };
335
324
  const _sfc_main$2 = /* @__PURE__ */ defineComponent({
336
325
  __name: "index",
337
326
  props: {
338
- items: {}
327
+ items: {},
328
+ title: { default: "" },
329
+ mode: { default: "list" },
330
+ columns: { default: 4 }
339
331
  },
340
332
  emits: ["click"],
341
333
  setup(__props, { emit: __emit }) {
@@ -348,7 +340,10 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
348
340
  return (_ctx, _cache) => {
349
341
  const _component_navigator = resolveComponent("navigator");
350
342
  return openBlock(), createElementBlock("view", _hoisted_1$2, [
351
- (openBlock(true), createElementBlock(Fragment, null, renderList(visibleItems.value, (item, index2) => {
343
+ props.title ? (openBlock(), createElementBlock("view", _hoisted_2$2, [
344
+ createElementVNode("text", null, toDisplayString(props.title), 1)
345
+ ])) : createCommentVNode("", true),
346
+ props.mode === "list" ? (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(visibleItems.value, (item, index2) => {
352
347
  return openBlock(), createElementBlock(Fragment, { key: index2 }, [
353
348
  item.url ? (openBlock(), createBlock(_component_navigator, {
354
349
  key: 0,
@@ -357,9 +352,23 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
357
352
  "hover-class": "hlw-menu-item--active"
358
353
  }, {
359
354
  default: withCtx(() => [
360
- renderSlot(_ctx.$slots, "item", { item }, () => [
361
- createVNode(unref(HlwMenuItemContent), { item }, null, 8, ["item"])
362
- ], true)
355
+ createElementVNode("view", _hoisted_3$2, [
356
+ createElementVNode("view", {
357
+ class: normalizeClass(["hlw-menu-icon", `hlw-menu-icon--${item.iconTheme || "slate"}`])
358
+ }, [
359
+ createElementVNode("text", {
360
+ class: normalizeClass(item.icon)
361
+ }, null, 2)
362
+ ], 2),
363
+ createElementVNode("text", _hoisted_4$1, toDisplayString(item.label), 1)
364
+ ]),
365
+ createElementVNode("view", _hoisted_5$1, [
366
+ item.tag ? (openBlock(), createElementBlock("text", {
367
+ key: 0,
368
+ class: normalizeClass(["hlw-menu-tag", [`hlw-menu-tag--${item.tagTheme || "rose"}`, item.tagPulse ? "hlw-menu-tag-pulse" : ""]])
369
+ }, toDisplayString(item.tag), 3)) : createCommentVNode("", true),
370
+ _cache[0] || (_cache[0] = createElementVNode("text", { class: "i-fa6-solid-chevron-right hlw-menu-arrow" }, null, -1))
371
+ ])
363
372
  ]),
364
373
  _: 2
365
374
  }, 1032, ["url"])) : (openBlock(), createElementBlock("view", {
@@ -368,18 +377,86 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
368
377
  "hover-class": "hlw-menu-item--active",
369
378
  onClick: ($event) => handleClick(item)
370
379
  }, [
371
- renderSlot(_ctx.$slots, "item", { item }, () => [
372
- createVNode(unref(HlwMenuItemContent), { item }, null, 8, ["item"])
373
- ], true)
374
- ], 8, _hoisted_2$2)),
375
- index2 < visibleItems.value.length - 1 ? (openBlock(), createElementBlock("view", _hoisted_3$2)) : createCommentVNode("", true)
380
+ createElementVNode("view", _hoisted_7$1, [
381
+ createElementVNode("view", {
382
+ class: normalizeClass(["hlw-menu-icon", `hlw-menu-icon--${item.iconTheme || "slate"}`])
383
+ }, [
384
+ createElementVNode("text", {
385
+ class: normalizeClass(item.icon)
386
+ }, null, 2)
387
+ ], 2),
388
+ createElementVNode("text", _hoisted_8, toDisplayString(item.label), 1)
389
+ ]),
390
+ createElementVNode("view", _hoisted_9, [
391
+ item.loading ? (openBlock(), createElementBlock("text", _hoisted_10)) : createCommentVNode("", true),
392
+ item.tag ? (openBlock(), createElementBlock("text", {
393
+ key: 1,
394
+ class: normalizeClass(["hlw-menu-tag", [`hlw-menu-tag--${item.tagTheme || "rose"}`, item.tagPulse ? "hlw-menu-tag-pulse" : ""]])
395
+ }, toDisplayString(item.tag), 3)) : createCommentVNode("", true),
396
+ _cache[1] || (_cache[1] = createElementVNode("text", { class: "i-fa6-solid-chevron-right hlw-menu-arrow" }, null, -1))
397
+ ])
398
+ ], 8, _hoisted_6$1)),
399
+ index2 < visibleItems.value.length - 1 ? (openBlock(), createElementBlock("view", _hoisted_11)) : createCommentVNode("", true)
376
400
  ], 64);
377
- }), 128))
401
+ }), 128)) : (openBlock(), createElementBlock("view", {
402
+ key: 2,
403
+ class: "hlw-menu-grid",
404
+ style: normalizeStyle({ gridTemplateColumns: `repeat(${props.columns}, 1fr)` })
405
+ }, [
406
+ (openBlock(true), createElementBlock(Fragment, null, renderList(visibleItems.value, (item, index2) => {
407
+ return openBlock(), createElementBlock(Fragment, { key: index2 }, [
408
+ item.url ? (openBlock(), createBlock(_component_navigator, {
409
+ key: 0,
410
+ url: item.url,
411
+ class: "hlw-menu-grid-item",
412
+ "hover-class": "hlw-menu-grid-item--active"
413
+ }, {
414
+ default: withCtx(() => [
415
+ createElementVNode("view", _hoisted_12, [
416
+ createElementVNode("view", {
417
+ class: normalizeClass(["hlw-menu-icon hlw-menu-icon--grid", `hlw-menu-icon--${item.iconTheme || "slate"}`])
418
+ }, [
419
+ createElementVNode("text", {
420
+ class: normalizeClass(item.icon)
421
+ }, null, 2)
422
+ ], 2),
423
+ item.tag ? (openBlock(), createElementBlock("text", {
424
+ key: 0,
425
+ class: normalizeClass(["hlw-menu-badge", [`hlw-menu-tag--${item.tagTheme || "rose"}`, item.tagPulse ? "hlw-menu-tag-pulse" : ""]])
426
+ }, toDisplayString(item.tag), 3)) : createCommentVNode("", true)
427
+ ]),
428
+ createElementVNode("text", _hoisted_13, toDisplayString(item.label), 1)
429
+ ]),
430
+ _: 2
431
+ }, 1032, ["url"])) : (openBlock(), createElementBlock("view", {
432
+ key: 1,
433
+ class: "hlw-menu-grid-item",
434
+ "hover-class": "hlw-menu-grid-item--active",
435
+ onClick: ($event) => handleClick(item)
436
+ }, [
437
+ createElementVNode("view", _hoisted_15, [
438
+ createElementVNode("view", {
439
+ class: normalizeClass(["hlw-menu-icon hlw-menu-icon--grid", `hlw-menu-icon--${item.iconTheme || "slate"}`])
440
+ }, [
441
+ createElementVNode("text", {
442
+ class: normalizeClass(item.icon)
443
+ }, null, 2)
444
+ ], 2),
445
+ item.tag ? (openBlock(), createElementBlock("text", {
446
+ key: 0,
447
+ class: normalizeClass(["hlw-menu-badge", [`hlw-menu-tag--${item.tagTheme || "rose"}`, item.tagPulse ? "hlw-menu-tag-pulse" : ""]])
448
+ }, toDisplayString(item.tag), 3)) : createCommentVNode("", true)
449
+ ]),
450
+ createElementVNode("text", _hoisted_16, toDisplayString(item.label), 1)
451
+ ], 8, _hoisted_14))
452
+ ], 64);
453
+ }), 128))
454
+ ], 4))
378
455
  ]);
379
456
  };
380
457
  }
381
458
  });
382
- const index$2 = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-48deaf95"]]);
459
+ const index$2 = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-f4bdcedc"]]);
383
460
  const _hoisted_1$1 = { class: "hlw-menu-list" };
384
461
  const _hoisted_2$1 = ["onTap"];
385
462
  const _hoisted_3$1 = { class: "hlw-menu-list__left" };
package/dist/style.css CHANGED
@@ -145,46 +145,90 @@ to { transform: rotate(360deg);
145
145
  color: #999;
146
146
  }
147
147
 
148
- .hlw-menu[data-v-48deaf95] {
148
+ .hlw-menu[data-v-f4bdcedc] {
149
149
  background: #fff;
150
150
  border-radius: var(--radius-lg, 24rpx);
151
151
  border: 1rpx solid var(--border-color, #e2e8f0);
152
152
  overflow: hidden;
153
153
  width: 100%;
154
- padding: 8rpx 0;
155
154
  }
156
- .hlw-menu-item[data-v-48deaf95] {
155
+
156
+ /* 标题 */
157
+ .hlw-menu-title[data-v-f4bdcedc] {
158
+ padding: 24rpx 32rpx 0;
159
+ }
160
+ .hlw-menu-title text[data-v-f4bdcedc] {
161
+ font-size: 24rpx;
162
+ font-weight: 600;
163
+ color: #94a3b8;
164
+ letter-spacing: 1rpx;
165
+ }
166
+
167
+ /* ========== 列表模式 ========== */
168
+ .hlw-menu-item[data-v-f4bdcedc] {
157
169
  display: flex;
158
170
  align-items: center;
159
171
  justify-content: space-between;
160
- padding: 16rpx 32rpx;
172
+ padding: 24rpx 32rpx;
161
173
  }
162
- .hlw-menu-item--active[data-v-48deaf95] {
174
+ .hlw-menu-item--active[data-v-f4bdcedc] {
163
175
  background: #f8fafc;
164
176
  }
165
- .hlw-menu-divider[data-v-48deaf95] {
166
- margin: 8rpx 0;
177
+ .hlw-menu-divider[data-v-f4bdcedc] {
178
+ margin: 0 32rpx;
167
179
  height: 0;
168
180
  border-bottom: 1rpx dashed var(--border-color, #e2e8f0);
169
181
  }
170
-
171
- /* 下面的样式供 HlwMenuItemContent 内部渲染用(非 scoped) */.hlw-menu-item-inner {
182
+ .hlw-menu-left[data-v-f4bdcedc] {
172
183
  display: flex;
173
184
  align-items: center;
174
- justify-content: space-between;
175
- width: 100%;
185
+ gap: 24rpx;
176
186
  }
177
- .hlw-menu-left {
187
+ .hlw-menu-right[data-v-f4bdcedc] {
178
188
  display: flex;
179
189
  align-items: center;
180
- gap: 24rpx;
190
+ gap: 16rpx;
191
+ }
192
+
193
+ /* ========== 宫格模式 ========== */
194
+ .hlw-menu-grid[data-v-f4bdcedc] {
195
+ display: grid;
196
+ padding: 16rpx 0 24rpx;
181
197
  }
182
- .hlw-menu-right {
198
+ .hlw-menu-grid-item[data-v-f4bdcedc] {
183
199
  display: flex;
200
+ flex-direction: column;
184
201
  align-items: center;
185
- gap: 16rpx;
202
+ gap: 12rpx;
203
+ padding: 16rpx 8rpx;
204
+ }
205
+ .hlw-menu-grid-item--active[data-v-f4bdcedc] {
206
+ background: #f8fafc;
207
+ }
208
+ .hlw-menu-grid-icon-wrap[data-v-f4bdcedc] {
209
+ position: relative;
186
210
  }
187
- .hlw-menu-icon {
211
+ .hlw-menu-badge[data-v-f4bdcedc] {
212
+ position: absolute;
213
+ top: -8rpx;
214
+ right: -8rpx;
215
+ font-size: 18rpx;
216
+ color: #fff;
217
+ padding: 0 8rpx;
218
+ border-radius: 9999rpx;
219
+ min-width: 28rpx;
220
+ text-align: center;
221
+ line-height: 28rpx;
222
+ height: 28rpx;
223
+ }
224
+ .hlw-menu-grid-label[data-v-f4bdcedc] {
225
+ font-size: 24rpx;
226
+ color: #334155;
227
+ text-align: center;
228
+ }
229
+
230
+ /* ========== 图标 ========== */
231
+ .hlw-menu-icon[data-v-f4bdcedc] {
188
232
  width: 64rpx;
189
233
  height: 64rpx;
190
234
  border-radius: var(--radius-md, 16rpx);
@@ -193,76 +237,86 @@ to { transform: rotate(360deg);
193
237
  justify-content: center;
194
238
  flex-shrink: 0;
195
239
  }
196
- .hlw-menu-icon text {
240
+ .hlw-menu-icon text[data-v-f4bdcedc] {
197
241
  font-size: 20rpx;
198
242
  }
199
- .hlw-menu-icon--orange {
243
+ .hlw-menu-icon--grid[data-v-f4bdcedc] {
244
+ width: 88rpx;
245
+ height: 88rpx;
246
+ border-radius: var(--radius-lg, 24rpx);
247
+ }
248
+ .hlw-menu-icon--grid text[data-v-f4bdcedc] {
249
+ font-size: 32rpx;
250
+ }
251
+ .hlw-menu-icon--orange[data-v-f4bdcedc] {
200
252
  background: #fff7ed;
201
253
  color: #f97316;
202
254
  }
203
- .hlw-menu-icon--purple {
255
+ .hlw-menu-icon--purple[data-v-f4bdcedc] {
204
256
  background: #faf5ff;
205
257
  color: #a855f7;
206
258
  }
207
- .hlw-menu-icon--wechat {
259
+ .hlw-menu-icon--wechat[data-v-f4bdcedc] {
208
260
  background: #f0fdf4;
209
- color: #07C160;
261
+ color: #07c160;
210
262
  }
211
- .hlw-menu-icon--cyan {
263
+ .hlw-menu-icon--cyan[data-v-f4bdcedc] {
212
264
  background: #ecfeff;
213
265
  color: #06b6d4;
214
266
  }
215
- .hlw-menu-icon--emerald {
267
+ .hlw-menu-icon--emerald[data-v-f4bdcedc] {
216
268
  background: #ecfdf5;
217
269
  color: #10b981;
218
270
  }
219
- .hlw-menu-icon--slate {
271
+ .hlw-menu-icon--slate[data-v-f4bdcedc] {
220
272
  background: #f1f5f9;
221
273
  color: #64748b;
222
274
  }
223
- .hlw-menu-icon--rose {
275
+ .hlw-menu-icon--rose[data-v-f4bdcedc] {
224
276
  background: #fff1f2;
225
277
  color: #f43f5e;
226
278
  }
227
- .hlw-menu-icon--blue {
279
+ .hlw-menu-icon--blue[data-v-f4bdcedc] {
228
280
  background: #eff6ff;
229
281
  color: #3b82f6;
230
282
  }
231
- .hlw-menu-icon--red {
283
+ .hlw-menu-icon--red[data-v-f4bdcedc] {
232
284
  background: #fef2f2;
233
285
  color: #ef4444;
234
286
  }
235
- .hlw-menu-label {
287
+
288
+ /* ========== 标签 / 角标 ========== */
289
+ .hlw-menu-label[data-v-f4bdcedc] {
236
290
  font-size: 28rpx;
237
291
  font-weight: 500;
238
292
  color: #334155;
239
293
  }
240
- .hlw-menu-tag {
294
+ .hlw-menu-tag[data-v-f4bdcedc] {
241
295
  font-size: 20rpx;
242
296
  color: #fff;
243
297
  padding: 2rpx 12rpx;
244
298
  border-radius: 9999rpx;
245
299
  box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
246
300
  }
247
- .hlw-menu-tag--orange {
301
+ .hlw-menu-tag--orange[data-v-f4bdcedc] {
248
302
  background: #fb923c;
249
303
  }
250
- .hlw-menu-tag--red {
304
+ .hlw-menu-tag--red[data-v-f4bdcedc] {
251
305
  background: #ef4444;
252
306
  }
253
- .hlw-menu-tag--wechat {
254
- background: #07C160;
307
+ .hlw-menu-tag--wechat[data-v-f4bdcedc] {
308
+ background: #07c160;
255
309
  }
256
- .hlw-menu-tag--rose {
310
+ .hlw-menu-tag--rose[data-v-f4bdcedc] {
257
311
  background: #f43f5e;
258
312
  }
259
- .hlw-menu-tag--blue {
313
+ .hlw-menu-tag--blue[data-v-f4bdcedc] {
260
314
  background: #3b82f6;
261
315
  }
262
- .hlw-menu-tag-pulse {
263
- animation: hlw-menu-tag-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
316
+ .hlw-menu-tag-pulse[data-v-f4bdcedc] {
317
+ animation: tag-pulse-f4bdcedc 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
264
318
  }
265
- @keyframes hlw-menu-tag-pulse {
319
+ @keyframes tag-pulse-f4bdcedc {
266
320
  0%, 100% {
267
321
  opacity: 1;
268
322
  }
@@ -270,18 +324,18 @@ to { transform: rotate(360deg);
270
324
  opacity: 0.5;
271
325
  }
272
326
  }
273
- .hlw-menu-arrow {
327
+ .hlw-menu-arrow[data-v-f4bdcedc] {
274
328
  color: #d1d5db;
275
329
  font-size: 20rpx;
276
330
  }
277
- .hlw-menu-spin {
278
- animation: hlw-menu-spin 1s linear infinite;
331
+ .hlw-menu-spin[data-v-f4bdcedc] {
332
+ animation: icon-spin-f4bdcedc 1s linear infinite;
279
333
  }
280
- .hlw-menu-muted {
334
+ .hlw-menu-muted[data-v-f4bdcedc] {
281
335
  color: #94a3b8;
282
336
  font-size: 20rpx;
283
337
  }
284
- @keyframes hlw-menu-spin {
338
+ @keyframes icon-spin-f4bdcedc {
285
339
  from {
286
340
  transform: rotate(0deg);
287
341
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hlw-uni/mp-vue",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "hlw-uni Vue 组件库 — 供小程序业务方使用的 UI 组件集合",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -1,50 +1,92 @@
1
1
  <template>
2
2
  <view class="hlw-menu">
3
- <template v-for="(item, index) in visibleItems" :key="index">
4
- <navigator v-if="item.url" :url="item.url" class="hlw-menu-item" hover-class="hlw-menu-item--active">
5
- <slot name="item" :item="item">
6
- <hlw-menu-item-content :item="item" />
7
- </slot>
8
- </navigator>
9
- <view v-else class="hlw-menu-item" hover-class="hlw-menu-item--active" @click="handleClick(item)">
10
- <slot name="item" :item="item">
11
- <hlw-menu-item-content :item="item" />
12
- </slot>
13
- </view>
14
- <view v-if="index < visibleItems.length - 1" class="hlw-menu-divider"></view>
3
+ <!-- 标题 -->
4
+ <view v-if="props.title" class="hlw-menu-title">
5
+ <text>{{ props.title }}</text>
6
+ </view>
7
+
8
+ <!-- 列表模式 -->
9
+ <template v-if="props.mode === 'list'">
10
+ <template v-for="(item, index) in visibleItems" :key="index">
11
+ <navigator v-if="item.url" :url="item.url" class="hlw-menu-item" hover-class="hlw-menu-item--active">
12
+ <view class="hlw-menu-left">
13
+ <view class="hlw-menu-icon" :class="`hlw-menu-icon--${item.iconTheme || 'slate'}`">
14
+ <text :class="item.icon"></text>
15
+ </view>
16
+ <text class="hlw-menu-label">{{ item.label }}</text>
17
+ </view>
18
+ <view class="hlw-menu-right">
19
+ <text v-if="item.tag" class="hlw-menu-tag" :class="[`hlw-menu-tag--${item.tagTheme || 'rose'}`, item.tagPulse ? 'hlw-menu-tag-pulse' : '']">{{ item.tag }}</text>
20
+ <text class="i-fa6-solid-chevron-right hlw-menu-arrow"></text>
21
+ </view>
22
+ </navigator>
23
+
24
+ <view v-else class="hlw-menu-item" hover-class="hlw-menu-item--active" @click="handleClick(item)">
25
+ <view class="hlw-menu-left">
26
+ <view class="hlw-menu-icon" :class="`hlw-menu-icon--${item.iconTheme || 'slate'}`">
27
+ <text :class="item.icon"></text>
28
+ </view>
29
+ <text class="hlw-menu-label">{{ item.label }}</text>
30
+ </view>
31
+ <view class="hlw-menu-right">
32
+ <text v-if="item.loading" class="i-fa6-solid-circle-notch hlw-menu-spin hlw-menu-muted"></text>
33
+ <text v-if="item.tag" class="hlw-menu-tag" :class="[`hlw-menu-tag--${item.tagTheme || 'rose'}`, item.tagPulse ? 'hlw-menu-tag-pulse' : '']">{{ item.tag }}</text>
34
+ <text class="i-fa6-solid-chevron-right hlw-menu-arrow"></text>
35
+ </view>
36
+ </view>
37
+
38
+ <view v-if="index < visibleItems.length - 1" class="hlw-menu-divider"></view>
39
+ </template>
15
40
  </template>
41
+
42
+ <!-- 宫格模式 -->
43
+ <view v-else class="hlw-menu-grid" :style="{ gridTemplateColumns: `repeat(${props.columns}, 1fr)` }">
44
+ <template v-for="(item, index) in visibleItems" :key="index">
45
+ <navigator v-if="item.url" :url="item.url" class="hlw-menu-grid-item" hover-class="hlw-menu-grid-item--active">
46
+ <view class="hlw-menu-grid-icon-wrap">
47
+ <view class="hlw-menu-icon hlw-menu-icon--grid" :class="`hlw-menu-icon--${item.iconTheme || 'slate'}`">
48
+ <text :class="item.icon"></text>
49
+ </view>
50
+ <text v-if="item.tag" class="hlw-menu-badge" :class="[`hlw-menu-tag--${item.tagTheme || 'rose'}`, item.tagPulse ? 'hlw-menu-tag-pulse' : '']">{{ item.tag }}</text>
51
+ </view>
52
+ <text class="hlw-menu-grid-label">{{ item.label }}</text>
53
+ </navigator>
54
+
55
+ <view v-else class="hlw-menu-grid-item" hover-class="hlw-menu-grid-item--active" @click="handleClick(item)">
56
+ <view class="hlw-menu-grid-icon-wrap">
57
+ <view class="hlw-menu-icon hlw-menu-icon--grid" :class="`hlw-menu-icon--${item.iconTheme || 'slate'}`">
58
+ <text :class="item.icon"></text>
59
+ </view>
60
+ <text v-if="item.tag" class="hlw-menu-badge" :class="[`hlw-menu-tag--${item.tagTheme || 'rose'}`, item.tagPulse ? 'hlw-menu-tag-pulse' : '']">{{ item.tag }}</text>
61
+ </view>
62
+ <text class="hlw-menu-grid-label">{{ item.label }}</text>
63
+ </view>
64
+ </template>
65
+ </view>
16
66
  </view>
17
67
  </template>
18
68
 
19
69
  <script setup lang="ts">
20
70
  import { computed } from "vue";
21
-
22
- export interface HlwMenuItem {
23
- /** 图标 class,如 'i-fa6-solid-gear' */
24
- icon: string;
25
- /** 图标主题色 */
26
- iconTheme?: "orange" | "purple" | "cyan" | "emerald" | "slate" | "wechat" | "rose" | "blue" | "red";
27
- /** 菜单文字 */
28
- label: string;
29
- /** 跳转路径,有值则用 navigator 包裹 */
30
- url?: string;
31
- /** 右侧标签文字 */
32
- tag?: string;
33
- /** 标签主题色 */
34
- tagTheme?: "orange" | "red" | "wechat" | "rose" | "blue";
35
- /** 标签是否闪烁 */
36
- tagPulse?: boolean;
37
- /** 加载中状态(右侧显示 loading 图标) */
38
- loading?: boolean;
39
- /** 是否显示,默认 true */
40
- visible?: boolean;
41
- }
71
+ import type { HlwMenuItem } from "./types";
72
+ export type { HlwMenuItem } from "./types";
42
73
 
43
74
  interface Props {
44
75
  items: HlwMenuItem[];
76
+ /** 标题 */
77
+ title?: string;
78
+ /** 布局模式,默认 list */
79
+ mode?: "list" | "grid";
80
+ /** 宫格列数,默认 4 */
81
+ columns?: number;
45
82
  }
46
83
 
47
- const props = defineProps<Props>();
84
+ const props = withDefaults(defineProps<Props>(), {
85
+ title: "",
86
+ mode: "list",
87
+ columns: 4,
88
+ });
89
+
48
90
  const emit = defineEmits<{
49
91
  click: [item: HlwMenuItem];
50
92
  }>();
@@ -56,49 +98,6 @@ const handleClick = (item: HlwMenuItem) => {
56
98
  };
57
99
  </script>
58
100
 
59
- <!-- 子组件:菜单项内容(内部使用) -->
60
- <script lang="ts">
61
- // 注册内部组件
62
- import { defineComponent, h } from "vue";
63
-
64
- const HlwMenuItemContent = defineComponent({
65
- name: "HlwMenuItemContent",
66
- props: {
67
- item: { type: Object, required: true },
68
- },
69
- setup(props) {
70
- return () => {
71
- const item = props.item as import("./index.vue").HlwMenuItem;
72
- return h("view", { class: "hlw-menu-item-inner" }, [
73
- // 左侧
74
- h("view", { class: "hlw-menu-left" }, [
75
- h("view", { class: `hlw-menu-icon hlw-menu-icon--${item.iconTheme || "slate"}` }, [
76
- h("text", { class: item.icon }),
77
- ]),
78
- h("text", { class: "hlw-menu-label" }, item.label),
79
- ]),
80
- // 右侧
81
- h("view", { class: "hlw-menu-right" }, [
82
- item.loading
83
- ? h("text", { class: "i-fa6-solid-circle-notch hlw-menu-spin hlw-menu-muted" })
84
- : null,
85
- item.tag
86
- ? h(
87
- "text",
88
- { class: `hlw-menu-tag hlw-menu-tag--${item.tagTheme || "rose"} ${item.tagPulse ? "hlw-menu-tag-pulse" : ""}` },
89
- item.tag,
90
- )
91
- : null,
92
- h("text", { class: "i-fa6-solid-chevron-right hlw-menu-arrow" }),
93
- ]),
94
- ]);
95
- };
96
- },
97
- });
98
-
99
- export { HlwMenuItemContent };
100
- </script>
101
-
102
101
  <style lang="scss" scoped>
103
102
  .hlw-menu {
104
103
  background: #fff;
@@ -106,14 +105,25 @@ export { HlwMenuItemContent };
106
105
  border: 1rpx solid var(--border-color, #e2e8f0);
107
106
  overflow: hidden;
108
107
  width: 100%;
109
- padding: 8rpx 0;
110
108
  }
111
109
 
110
+ /* 标题 */
111
+ .hlw-menu-title {
112
+ padding: 24rpx 32rpx 0;
113
+ text {
114
+ font-size: 24rpx;
115
+ font-weight: 600;
116
+ color: #94a3b8;
117
+ letter-spacing: 1rpx;
118
+ }
119
+ }
120
+
121
+ /* ========== 列表模式 ========== */
112
122
  .hlw-menu-item {
113
123
  display: flex;
114
124
  align-items: center;
115
125
  justify-content: space-between;
116
- padding: 16rpx 32rpx;
126
+ padding: 24rpx 32rpx;
117
127
 
118
128
  &--active {
119
129
  background: #f8fafc;
@@ -121,22 +131,11 @@ export { HlwMenuItemContent };
121
131
  }
122
132
 
123
133
  .hlw-menu-divider {
124
- margin: 8rpx 0;
134
+ margin: 0 32rpx;
125
135
  height: 0;
126
136
  border-bottom: 1rpx dashed var(--border-color, #e2e8f0);
127
137
  }
128
138
 
129
- /* 下面的样式供 HlwMenuItemContent 内部渲染用(非 scoped) */
130
- </style>
131
-
132
- <style lang="scss">
133
- .hlw-menu-item-inner {
134
- display: flex;
135
- align-items: center;
136
- justify-content: space-between;
137
- width: 100%;
138
- }
139
-
140
139
  .hlw-menu-left {
141
140
  display: flex;
142
141
  align-items: center;
@@ -149,6 +148,49 @@ export { HlwMenuItemContent };
149
148
  gap: 16rpx;
150
149
  }
151
150
 
151
+ /* ========== 宫格模式 ========== */
152
+ .hlw-menu-grid {
153
+ display: grid;
154
+ padding: 16rpx 0 24rpx;
155
+ }
156
+
157
+ .hlw-menu-grid-item {
158
+ display: flex;
159
+ flex-direction: column;
160
+ align-items: center;
161
+ gap: 12rpx;
162
+ padding: 16rpx 8rpx;
163
+
164
+ &--active {
165
+ background: #f8fafc;
166
+ }
167
+ }
168
+
169
+ .hlw-menu-grid-icon-wrap {
170
+ position: relative;
171
+ }
172
+
173
+ .hlw-menu-badge {
174
+ position: absolute;
175
+ top: -8rpx;
176
+ right: -8rpx;
177
+ font-size: 18rpx;
178
+ color: #fff;
179
+ padding: 0 8rpx;
180
+ border-radius: 9999rpx;
181
+ min-width: 28rpx;
182
+ text-align: center;
183
+ line-height: 28rpx;
184
+ height: 28rpx;
185
+ }
186
+
187
+ .hlw-menu-grid-label {
188
+ font-size: 24rpx;
189
+ color: #334155;
190
+ text-align: center;
191
+ }
192
+
193
+ /* ========== 图标 ========== */
152
194
  .hlw-menu-icon {
153
195
  width: 64rpx;
154
196
  height: 64rpx;
@@ -160,9 +202,16 @@ export { HlwMenuItemContent };
160
202
 
161
203
  text { font-size: 20rpx; }
162
204
 
205
+ &--grid {
206
+ width: 88rpx;
207
+ height: 88rpx;
208
+ border-radius: var(--radius-lg, 24rpx);
209
+ text { font-size: 32rpx; }
210
+ }
211
+
163
212
  &--orange { background: #fff7ed; color: #f97316; }
164
213
  &--purple { background: #faf5ff; color: #a855f7; }
165
- &--wechat { background: #f0fdf4; color: #07C160; }
214
+ &--wechat { background: #f0fdf4; color: #07c160; }
166
215
  &--cyan { background: #ecfeff; color: #06b6d4; }
167
216
  &--emerald { background: #ecfdf5; color: #10b981; }
168
217
  &--slate { background: #f1f5f9; color: #64748b; }
@@ -171,6 +220,7 @@ export { HlwMenuItemContent };
171
220
  &--red { background: #fef2f2; color: #ef4444; }
172
221
  }
173
222
 
223
+ /* ========== 标签 / 角标 ========== */
174
224
  .hlw-menu-label {
175
225
  font-size: 28rpx;
176
226
  font-weight: 500;
@@ -183,38 +233,28 @@ export { HlwMenuItemContent };
183
233
  padding: 2rpx 12rpx;
184
234
  border-radius: 9999rpx;
185
235
  box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
186
-
187
- &--orange { background: #fb923c; }
188
- &--red { background: #ef4444; }
189
- &--wechat { background: #07C160; }
190
- &--rose { background: #f43f5e; }
191
- &--blue { background: #3b82f6; }
192
236
  }
193
237
 
238
+ .hlw-menu-tag--orange { background: #fb923c; }
239
+ .hlw-menu-tag--red { background: #ef4444; }
240
+ .hlw-menu-tag--wechat { background: #07c160; }
241
+ .hlw-menu-tag--rose { background: #f43f5e; }
242
+ .hlw-menu-tag--blue { background: #3b82f6; }
243
+
194
244
  .hlw-menu-tag-pulse {
195
- animation: hlw-menu-tag-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
245
+ animation: tag-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
196
246
  }
197
247
 
198
- @keyframes hlw-menu-tag-pulse {
248
+ @keyframes tag-pulse {
199
249
  0%, 100% { opacity: 1; }
200
250
  50% { opacity: 0.5; }
201
251
  }
202
252
 
203
- .hlw-menu-arrow {
204
- color: #d1d5db;
205
- font-size: 20rpx;
206
- }
207
-
208
- .hlw-menu-spin {
209
- animation: hlw-menu-spin 1s linear infinite;
210
- }
211
-
212
- .hlw-menu-muted {
213
- color: #94a3b8;
214
- font-size: 20rpx;
215
- }
253
+ .hlw-menu-arrow { color: #d1d5db; font-size: 20rpx; }
254
+ .hlw-menu-spin { animation: icon-spin 1s linear infinite; }
255
+ .hlw-menu-muted { color: #94a3b8; font-size: 20rpx; }
216
256
 
217
- @keyframes hlw-menu-spin {
257
+ @keyframes icon-spin {
218
258
  from { transform: rotate(0deg); }
219
259
  to { transform: rotate(360deg); }
220
260
  }
@@ -0,0 +1,23 @@
1
+ export type HlwMenuIconTheme = "orange" | "purple" | "cyan" | "emerald" | "slate" | "wechat" | "rose" | "blue" | "red";
2
+ export type HlwMenuTagTheme = "orange" | "red" | "wechat" | "rose" | "blue";
3
+
4
+ export interface HlwMenuItem {
5
+ /** 图标 class,如 'i-fa6-solid-gear' */
6
+ icon: string;
7
+ /** 图标主题色 */
8
+ iconTheme?: HlwMenuIconTheme;
9
+ /** 菜单文字 */
10
+ label: string;
11
+ /** 跳转路径,有值则用 navigator 包裹 */
12
+ url?: string;
13
+ /** 右侧标签文字(列表模式)/ 角标文字(宫格模式) */
14
+ tag?: string;
15
+ /** 标签主题色 */
16
+ tagTheme?: HlwMenuTagTheme;
17
+ /** 标签是否闪烁 */
18
+ tagPulse?: boolean;
19
+ /** 加载中状态(列表模式) */
20
+ loading?: boolean;
21
+ /** 是否显示,默认 true */
22
+ visible?: boolean;
23
+ }
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@ export { default as HlwEmpty } from './components/hlw-empty/index.vue';
9
9
  export { default as HlwHeader } from './components/hlw-header/index.vue';
10
10
  export { default as HlwLoading } from './components/hlw-loading/index.vue';
11
11
  export { default as HlwMenu } from './components/hlw-menu/index.vue';
12
- export type { HlwMenuItem } from './components/hlw-menu/index.vue';
12
+ export type { HlwMenuItem } from './components/hlw-menu/types';
13
13
  export { default as HlwMenuList } from './components/hlw-menu-list/index.vue';
14
14
  export { default as HlwPage } from './components/hlw-page/index.vue';
15
15
  export type { MenuItem } from './components/hlw-menu-list/types';