@hlw-uni/mp-vue 1.1.7 → 1.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,7 +32,7 @@ pnpm add @hlw-uni/mp-vue
32
32
 
33
33
  ```ts
34
34
  import { HlwAd, HlwAvatar, HlwEmpty, HlwLoading, HlwMenuList } from '@hlw-uni/mp-vue';
35
- import type { MenuItem } from '@hlw-uni/mp-vue';
35
+ import type { MenuItem, HlwPagingRef } from '@hlw-uni/mp-vue';
36
36
  ```
37
37
 
38
38
  可在页面或组件中直接注册并使用:
@@ -288,6 +288,95 @@ mp-vue/
288
288
  └── vite.config.ts
289
289
  ```
290
290
 
291
+ ## HlwPaging
292
+
293
+ 基于 `z-paging` 的包装组件。保留 `z-paging` 原始 props、事件和大部分插槽,同时默认接入 `hlw-loading` 和 `hlw-empty` 作为加载态与空态展示。
294
+
295
+ **额外 Props**
296
+
297
+ | 属性 | 类型 | 默认值 | 说明 |
298
+ | --- | --- | --- | --- |
299
+ | `loadingText` | `string` | `'加载中...'` | 默认 loading 文案 |
300
+ | `emptyText` | `string` | `'暂无数据'` | 默认空态文案 |
301
+ | `errorText` | `string` | `'加载失败,请稍后重试'` | 默认失败文案 |
302
+ | `emptyImage` | `string` | `''` | 自定义空态图片 |
303
+ | `useDefaultLoading` | `boolean` | `true` | 是否启用默认 loading 插槽 |
304
+ | `useDefaultEmpty` | `boolean` | `true` | 是否启用默认 empty 插槽 |
305
+
306
+ **插槽**
307
+
308
+ - 继续支持 `z-paging` 的原始插槽,如 `loading`、`empty`、`refresher`、`top`、`bottom`
309
+ - 额外支持 `empty-extra`,用于在默认空态下方插入自定义内容
310
+
311
+ **Ref 类型**
312
+
313
+ ```ts
314
+ import type { HlwPagingRef } from '@hlw-uni/mp-vue';
315
+ ```
316
+
317
+ **基础示例**
318
+
319
+ ```vue
320
+ <script setup lang="ts">
321
+ import { ref } from 'vue';
322
+ import type { HlwPagingRef } from '@hlw-uni/mp-vue';
323
+
324
+ interface ListItem {
325
+ id: number;
326
+ title: string;
327
+ }
328
+
329
+ const paging = ref<HlwPagingRef<ListItem> | null>(null);
330
+ const dataList = ref<ListItem[]>([]);
331
+
332
+ const queryList = async (pageNo: number, pageSize: number) => {
333
+ const list = Array.from({ length: pageSize }, (_, index) => ({
334
+ id: (pageNo - 1) * pageSize + index + 1,
335
+ title: `第 ${pageNo} 页数据 ${index + 1}`,
336
+ }));
337
+
338
+ paging.value?.completeByNoMore(list, pageNo >= 3);
339
+ };
340
+ </script>
341
+
342
+ <template>
343
+ <hlw-paging
344
+ ref="paging"
345
+ v-model="dataList"
346
+ @query="queryList"
347
+ loading-text="正在同步数据"
348
+ empty-text="还没有任何记录"
349
+ >
350
+ <view v-for="item in dataList" :key="item.id" class="demo-row">
351
+ {{ item.title }}
352
+ </view>
353
+ </hlw-paging>
354
+ </template>
355
+ ```
356
+
357
+ **自定义空态**
358
+
359
+ ```vue
360
+ <hlw-paging v-model="dataList" @query="queryList" :use-default-empty="false">
361
+ <template #empty="{ isLoadFailed }">
362
+ <hlw-empty :text="isLoadFailed ? '请求失败,请重试' : '这里暂时还是空的'" />
363
+ </template>
364
+ </hlw-paging>
365
+ ```
366
+
367
+ **常用 Ref 方法**
368
+
369
+ - `reload(animate?)`
370
+ - `refresh()`
371
+ - `complete(list)`
372
+ - `completeByTotal(list, total)`
373
+ - `completeByNoMore(list, noMore)`
374
+ - `completeByError(cause)`
375
+ - `clear()`
376
+ - `scrollToTop()`
377
+
378
+ 更多分页能力和原始参数可参考 `z-paging` 文档:https://z-paging.zxlee.cn
379
+
291
380
  ## License
292
381
 
293
382
  内部组件库,仅供 hlw-uni 项目使用。
@@ -0,0 +1,49 @@
1
+ export interface HlwPagingReturnData<T = any> {
2
+ totalList: T[];
3
+ noMore: boolean;
4
+ }
5
+ export interface HlwPagingRef<T = any> {
6
+ reload: (animate?: boolean) => Promise<HlwPagingReturnData<T>> | undefined;
7
+ refresh: () => Promise<HlwPagingReturnData<T>> | undefined;
8
+ refreshToPage: (page: number) => Promise<HlwPagingReturnData<T>> | undefined;
9
+ complete: (data?: T[] | false, success?: boolean) => Promise<HlwPagingReturnData<T>> | undefined;
10
+ completeByTotal: (data: T[], total: number, success?: boolean) => Promise<HlwPagingReturnData<T>> | undefined;
11
+ completeByNoMore: (data: T[], noMore: boolean, success?: boolean) => Promise<HlwPagingReturnData<T>> | undefined;
12
+ completeByError: (cause: string) => Promise<HlwPagingReturnData<T>> | undefined;
13
+ completeByKey: (data: T[], key: string, success?: boolean) => Promise<HlwPagingReturnData<T>> | undefined;
14
+ clear: () => void;
15
+ addDataFromTop: (data: T | T[], scrollToTop?: boolean, animate?: boolean) => void;
16
+ resetTotalData: (data: T[]) => void;
17
+ endRefresh: () => void;
18
+ updateCustomRefresherHeight: () => void;
19
+ goF2: () => void;
20
+ closeF2: () => void;
21
+ doLoadMore: (source?: "click" | "toBottom") => void;
22
+ updatePageScrollTop: (scrollTop: number) => void;
23
+ updatePageScrollTopHeight: () => void;
24
+ updatePageScrollBottomHeight: () => void;
25
+ updateLeftAndRightWidth: () => void;
26
+ updateFixedLayout: () => void;
27
+ doInsertVirtualListItem: (item: T, index: number) => void;
28
+ didUpdateVirtualListCell: (index: number) => void;
29
+ didDeleteVirtualListCell: (index: number) => void;
30
+ updateVirtualListRender: () => void;
31
+ setLocalPaging: (data: T[], success?: boolean) => Promise<HlwPagingReturnData<T>> | undefined;
32
+ doChatRecordLoadMore: () => void;
33
+ addChatRecordData: (data: T | T[], scrollToBottom?: boolean, animate?: boolean) => void;
34
+ addKeyboardHeightChangeListener: () => void;
35
+ scrollToTop: (animate?: boolean) => void;
36
+ scrollToBottom: (animate?: boolean) => void;
37
+ scrollIntoViewById: (id: string, offset?: number, animate?: boolean) => void;
38
+ scrollIntoViewByNodeTop: (top: number, offset?: number, animate?: boolean) => void;
39
+ scrollToY: (y: number, offset?: number, animate?: boolean) => void;
40
+ scrollToX: (x: number, offset?: number, animate?: boolean) => void;
41
+ scrollIntoViewByIndex: (index: number, offset?: number, animate?: boolean) => void;
42
+ scrollIntoViewByView: (view: unknown, offset?: number, animate?: boolean) => void;
43
+ setSpecialEffects: (args: Record<string, any>) => void;
44
+ setListSpecialEffects: (args: Record<string, any>) => void;
45
+ updateCache: () => void;
46
+ getVersion: () => string | undefined;
47
+ }
48
+ export interface HlwPagingInstance<T = any> extends HlwPagingRef<T> {
49
+ }
@@ -1,9 +1,10 @@
1
+ import { Ref } from 'vue';
1
2
  export declare const THEME_CHANGE_EVENT = "hlw:theme-change";
2
3
  export declare function buildThemeStyle(): string;
3
4
  export declare function useThemePageStyle(): {
4
- themePageStyle: any;
5
+ themePageStyle: Ref<string, string>;
5
6
  };
6
7
  export type { FontScale, FontPreset } from './font';
7
8
  export { FONT_SCALE_KEY, FONT_PRESETS, getCurrentFontScale, getCurrentFontVars } from './font';
8
9
  export type { ThemeColor } from './palette';
9
- export { THEME_COLOR_KEY, DEFAULT_THEMES, getCurrentThemeColor, getCurrentThemeVars } from './palette';
10
+ export { THEME_COLOR_KEY, THEME_SEMANTIC_COLORS, DEFAULT_THEMES, getCurrentThemeColor, getCurrentThemeVars, } from './palette';
@@ -3,6 +3,12 @@ export interface ThemeColor {
3
3
  value: string;
4
4
  }
5
5
  export declare const THEME_COLOR_KEY = "hlw_theme_color";
6
+ export declare const THEME_SEMANTIC_COLORS: {
7
+ readonly success: "#10b981";
8
+ readonly warning: "#f59e0b";
9
+ readonly error: "#ef4444";
10
+ readonly info: "#64748b";
11
+ };
6
12
  export declare const DEFAULT_THEMES: ThemeColor[];
7
13
  export declare function getCurrentThemeColor(): string;
8
14
  export declare function getCurrentThemeVars(): Record<string, string>;
package/dist/index.d.ts CHANGED
@@ -7,5 +7,7 @@
7
7
  * compiled SFC runtime helpers from the library entry.
8
8
  */
9
9
  export type { FontScale, FontPreset, ThemeColor } from './composables/theme';
10
- export { FONT_PRESETS, FONT_SCALE_KEY, DEFAULT_THEMES, THEME_COLOR_KEY, THEME_CHANGE_EVENT, getCurrentFontScale, getCurrentFontVars, getCurrentThemeColor, getCurrentThemeVars, buildThemeStyle, useThemePageStyle, } from './composables/theme';
10
+ export type { HlwMenuItem } from './components/hlw-menu/types';
11
+ export type { HlwPagingRef, HlwPagingInstance } from './components/hlw-paging/types';
12
+ export { FONT_PRESETS, FONT_SCALE_KEY, DEFAULT_THEMES, THEME_COLOR_KEY, THEME_SEMANTIC_COLORS, THEME_CHANGE_EVENT, getCurrentFontScale, getCurrentFontVars, getCurrentThemeColor, getCurrentThemeVars, buildThemeStyle, useThemePageStyle, } from './composables/theme';
11
13
  export { useThemeStore } from './stores/theme';
package/dist/index.js CHANGED
@@ -63,11 +63,17 @@
63
63
  }
64
64
  const { hexToRgba, darkenHex } = mpCore.useColor();
65
65
  const THEME_COLOR_KEY = "hlw_theme_color";
66
+ const THEME_SEMANTIC_COLORS = {
67
+ success: "#10b981",
68
+ warning: "#f59e0b",
69
+ error: "#ef4444",
70
+ info: "#64748b"
71
+ };
66
72
  const DEFAULT_THEMES = [
67
- { label: "默认蓝", value: "#3b82f6" },
68
- { label: "活力橙", value: "#f97316" },
69
73
  { label: "翡翠绿", value: "#10b981" },
70
- { label: "玫瑰红", value: "#f43f5e" },
74
+ { label: "活力橙", value: "#f97316" },
75
+ { label: "默认蓝", value: "#3b82f6" },
76
+ { label: "玫瑰粉", value: "#f43f5e" },
71
77
  { label: "紫罗兰", value: "#8b5cf6" },
72
78
  { label: "青石灰", value: "#64748b" }
73
79
  ];
@@ -85,7 +91,31 @@
85
91
  return {
86
92
  "--primary-color": color,
87
93
  "--primary-light": hexToRgba(color, 0.12),
88
- "--primary-dark": darkenHex(color)
94
+ "--primary-dark": darkenHex(color),
95
+ "--primary-success": THEME_SEMANTIC_COLORS.success,
96
+ "--primary-success-light": hexToRgba(THEME_SEMANTIC_COLORS.success, 0.12),
97
+ "--primary-success-dark": darkenHex(THEME_SEMANTIC_COLORS.success),
98
+ "--primary-warning": THEME_SEMANTIC_COLORS.warning,
99
+ "--primary-warning-light": hexToRgba(THEME_SEMANTIC_COLORS.warning, 0.12),
100
+ "--primary-warning-dark": darkenHex(THEME_SEMANTIC_COLORS.warning),
101
+ "--primary-error": THEME_SEMANTIC_COLORS.error,
102
+ "--primary-error-light": hexToRgba(THEME_SEMANTIC_COLORS.error, 0.12),
103
+ "--primary-error-dark": darkenHex(THEME_SEMANTIC_COLORS.error),
104
+ "--primary-info": THEME_SEMANTIC_COLORS.info,
105
+ "--primary-info-light": hexToRgba(THEME_SEMANTIC_COLORS.info, 0.12),
106
+ "--primary-info-dark": darkenHex(THEME_SEMANTIC_COLORS.info),
107
+ "--success-color": THEME_SEMANTIC_COLORS.success,
108
+ "--success-light": hexToRgba(THEME_SEMANTIC_COLORS.success, 0.12),
109
+ "--success-dark": darkenHex(THEME_SEMANTIC_COLORS.success),
110
+ "--warning-color": THEME_SEMANTIC_COLORS.warning,
111
+ "--warning-light": hexToRgba(THEME_SEMANTIC_COLORS.warning, 0.12),
112
+ "--warning-dark": darkenHex(THEME_SEMANTIC_COLORS.warning),
113
+ "--error-color": THEME_SEMANTIC_COLORS.error,
114
+ "--error-light": hexToRgba(THEME_SEMANTIC_COLORS.error, 0.12),
115
+ "--error-dark": darkenHex(THEME_SEMANTIC_COLORS.error),
116
+ "--info-color": THEME_SEMANTIC_COLORS.info,
117
+ "--info-light": hexToRgba(THEME_SEMANTIC_COLORS.info, 0.12),
118
+ "--info-dark": darkenHex(THEME_SEMANTIC_COLORS.info)
89
119
  };
90
120
  }
91
121
  const { varsToStyle } = mpCore.useColor();
@@ -147,6 +177,7 @@
147
177
  exports2.FONT_SCALE_KEY = FONT_SCALE_KEY;
148
178
  exports2.THEME_CHANGE_EVENT = THEME_CHANGE_EVENT;
149
179
  exports2.THEME_COLOR_KEY = THEME_COLOR_KEY;
180
+ exports2.THEME_SEMANTIC_COLORS = THEME_SEMANTIC_COLORS;
150
181
  exports2.buildThemeStyle = buildThemeStyle;
151
182
  exports2.getCurrentFontScale = getCurrentFontScale;
152
183
  exports2.getCurrentFontVars = getCurrentFontVars;
package/dist/index.mjs CHANGED
@@ -62,11 +62,17 @@ function getCurrentFontVars() {
62
62
  }
63
63
  const { hexToRgba, darkenHex } = useColor();
64
64
  const THEME_COLOR_KEY = "hlw_theme_color";
65
+ const THEME_SEMANTIC_COLORS = {
66
+ success: "#10b981",
67
+ warning: "#f59e0b",
68
+ error: "#ef4444",
69
+ info: "#64748b"
70
+ };
65
71
  const DEFAULT_THEMES = [
66
- { label: "默认蓝", value: "#3b82f6" },
67
- { label: "活力橙", value: "#f97316" },
68
72
  { label: "翡翠绿", value: "#10b981" },
69
- { label: "玫瑰红", value: "#f43f5e" },
73
+ { label: "活力橙", value: "#f97316" },
74
+ { label: "默认蓝", value: "#3b82f6" },
75
+ { label: "玫瑰粉", value: "#f43f5e" },
70
76
  { label: "紫罗兰", value: "#8b5cf6" },
71
77
  { label: "青石灰", value: "#64748b" }
72
78
  ];
@@ -84,7 +90,31 @@ function getCurrentThemeVars() {
84
90
  return {
85
91
  "--primary-color": color,
86
92
  "--primary-light": hexToRgba(color, 0.12),
87
- "--primary-dark": darkenHex(color)
93
+ "--primary-dark": darkenHex(color),
94
+ "--primary-success": THEME_SEMANTIC_COLORS.success,
95
+ "--primary-success-light": hexToRgba(THEME_SEMANTIC_COLORS.success, 0.12),
96
+ "--primary-success-dark": darkenHex(THEME_SEMANTIC_COLORS.success),
97
+ "--primary-warning": THEME_SEMANTIC_COLORS.warning,
98
+ "--primary-warning-light": hexToRgba(THEME_SEMANTIC_COLORS.warning, 0.12),
99
+ "--primary-warning-dark": darkenHex(THEME_SEMANTIC_COLORS.warning),
100
+ "--primary-error": THEME_SEMANTIC_COLORS.error,
101
+ "--primary-error-light": hexToRgba(THEME_SEMANTIC_COLORS.error, 0.12),
102
+ "--primary-error-dark": darkenHex(THEME_SEMANTIC_COLORS.error),
103
+ "--primary-info": THEME_SEMANTIC_COLORS.info,
104
+ "--primary-info-light": hexToRgba(THEME_SEMANTIC_COLORS.info, 0.12),
105
+ "--primary-info-dark": darkenHex(THEME_SEMANTIC_COLORS.info),
106
+ "--success-color": THEME_SEMANTIC_COLORS.success,
107
+ "--success-light": hexToRgba(THEME_SEMANTIC_COLORS.success, 0.12),
108
+ "--success-dark": darkenHex(THEME_SEMANTIC_COLORS.success),
109
+ "--warning-color": THEME_SEMANTIC_COLORS.warning,
110
+ "--warning-light": hexToRgba(THEME_SEMANTIC_COLORS.warning, 0.12),
111
+ "--warning-dark": darkenHex(THEME_SEMANTIC_COLORS.warning),
112
+ "--error-color": THEME_SEMANTIC_COLORS.error,
113
+ "--error-light": hexToRgba(THEME_SEMANTIC_COLORS.error, 0.12),
114
+ "--error-dark": darkenHex(THEME_SEMANTIC_COLORS.error),
115
+ "--info-color": THEME_SEMANTIC_COLORS.info,
116
+ "--info-light": hexToRgba(THEME_SEMANTIC_COLORS.info, 0.12),
117
+ "--info-dark": darkenHex(THEME_SEMANTIC_COLORS.info)
88
118
  };
89
119
  }
90
120
  const { varsToStyle } = useColor();
@@ -147,6 +177,7 @@ export {
147
177
  FONT_SCALE_KEY,
148
178
  THEME_CHANGE_EVENT,
149
179
  THEME_COLOR_KEY,
180
+ THEME_SEMANTIC_COLORS,
150
181
  buildThemeStyle,
151
182
  getCurrentFontScale,
152
183
  getCurrentFontVars,
@@ -1 +1,38 @@
1
- export declare const useThemeStore: any;
1
+ import { FontScale, ThemeColor } from '../composables/theme';
2
+ import { StoreDefinition } from 'pinia';
3
+ import { Ref, ComputedRef } from 'vue';
4
+
5
+ export declare const useThemeStore: StoreDefinition<"theme", Pick<{
6
+ scale: Ref<FontScale, FontScale>;
7
+ fontOptions: {
8
+ value: FontScale;
9
+ label: string;
10
+ }[];
11
+ setScale: (s: FontScale) => void;
12
+ primaryColor: Ref<string, string>;
13
+ themes: ThemeColor[];
14
+ activeTheme: ComputedRef<ThemeColor>;
15
+ setTheme: (color: string) => void;
16
+ }, "scale" | "fontOptions" | "primaryColor" | "themes">, Pick<{
17
+ scale: Ref<FontScale, FontScale>;
18
+ fontOptions: {
19
+ value: FontScale;
20
+ label: string;
21
+ }[];
22
+ setScale: (s: FontScale) => void;
23
+ primaryColor: Ref<string, string>;
24
+ themes: ThemeColor[];
25
+ activeTheme: ComputedRef<ThemeColor>;
26
+ setTheme: (color: string) => void;
27
+ }, "activeTheme">, Pick<{
28
+ scale: Ref<FontScale, FontScale>;
29
+ fontOptions: {
30
+ value: FontScale;
31
+ label: string;
32
+ }[];
33
+ setScale: (s: FontScale) => void;
34
+ primaryColor: Ref<string, string>;
35
+ themes: ThemeColor[];
36
+ activeTheme: ComputedRef<ThemeColor>;
37
+ setTheme: (color: string) => void;
38
+ }, "setScale" | "setTheme">>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hlw-uni/mp-vue",
3
- "version": "1.1.7",
3
+ "version": "1.1.11",
4
4
  "description": "hlw-uni Vue 组件库 — 供小程序业务方使用的 UI 组件集合",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -40,6 +40,7 @@
40
40
  "vue": "^3.5.32"
41
41
  },
42
42
  "dependencies": {
43
- "@hlw-uni/mp-core": "^1.0.28"
43
+ "@hlw-uni/mp-core": "^1.0.28",
44
+ "z-paging": "^2.8.8"
44
45
  }
45
46
  }
@@ -2,48 +2,52 @@
2
2
  <button
3
3
  class="hlw-btn"
4
4
  :class="[
5
- `hlw-btn--${type}`,
6
- `hlw-btn--${size}`,
7
- { 'hlw-btn--block': block, 'hlw-btn--round': round, 'hlw-btn--disabled': disabled, 'hlw-btn--loading': loading },
5
+ `hlw-btn--${props.type}`,
6
+ `hlw-btn--${props.size}`,
7
+ {
8
+ 'hlw-btn--block': props.block,
9
+ 'hlw-btn--round': props.round,
10
+ 'hlw-btn--disabled': props.disabled,
11
+ 'hlw-btn--loading': props.loading,
12
+ },
8
13
  ]"
9
- :disabled="disabled || loading"
10
- :open-type="openType"
11
- @tap="$emit('click')"
14
+ :style="buttonStyle"
15
+ :disabled="props.disabled || props.loading"
16
+ :open-type="props.openType"
17
+ @tap="handleTap"
12
18
  >
13
- <view v-if="loading" class="hlw-btn-spinner" />
14
- <view v-else-if="icon" :class="icon" class="hlw-btn-icon" />
15
- <slot />
19
+ <view v-if="props.loading" class="hlw-btn-spinner" />
20
+ <template v-else>
21
+ <view v-if="hasIcon" class="hlw-btn-icon-wrap">
22
+ <slot name="icon">
23
+ <view v-if="props.icon" :class="props.icon" class="hlw-btn-icon" />
24
+ </slot>
25
+ </view>
26
+ <view class="hlw-btn-content">
27
+ <slot />
28
+ </view>
29
+ </template>
16
30
  </button>
17
31
  </template>
18
32
 
19
33
  <script setup lang="ts">
20
- /**
21
- * HlwButton — 主题按钮
22
- *
23
- * 跟随 --primary-color 主题色,支持多种类型、尺寸和状态。
24
- *
25
- * @props
26
- * type - 按钮类型:primary / outline / text / ghost,默认 primary
27
- * size - 尺寸:small / medium / large,默认 medium
28
- * loading - 加载状态(显示 spinner 并禁止点击),默认 false
29
- * disabled - 禁用状态,默认 false
30
- * block - 块级按钮(占满父容器宽度),默认 false
31
- * round - 圆角药丸形状,默认 false
32
- * icon - 左侧图标 class(如 i-fa6-solid-plus)
33
- * openType - 微信原生 open-type(如 share、getPhoneNumber)
34
- *
35
- * @events
36
- * click - 点击事件
37
- *
38
- * @example
39
- * ```vue
40
- * <HlwButton type="primary" @click="submit">提交</HlwButton>
41
- * <HlwButton type="outline" loading>加载中</HlwButton>
42
- * <HlwButton type="text" icon="i-fa6-solid-plus">新增</HlwButton>
43
- * ```
44
- */
34
+ import { computed, useSlots } from "vue";
35
+
36
+ type ButtonType =
37
+ | "primary"
38
+ | "success"
39
+ | "warning"
40
+ | "danger"
41
+ | "error"
42
+ | "info"
43
+ | "outline"
44
+ | "text"
45
+ | "ghost";
46
+
47
+ type NavigateType = "navigateTo" | "redirectTo" | "switchTab" | "reLaunch" | "navigateBack";
48
+
45
49
  interface Props {
46
- type?: "primary" | "outline" | "text" | "ghost";
50
+ type?: ButtonType;
47
51
  size?: "small" | "medium" | "large";
48
52
  loading?: boolean;
49
53
  disabled?: boolean;
@@ -51,9 +55,15 @@ interface Props {
51
55
  round?: boolean;
52
56
  icon?: string;
53
57
  openType?: string;
58
+ bgColor?: string;
59
+ textColor?: string;
60
+ borderColor?: string;
61
+ url?: string;
62
+ navigateType?: NavigateType;
63
+ delta?: number;
54
64
  }
55
65
 
56
- withDefaults(defineProps<Props>(), {
66
+ const props = withDefaults(defineProps<Props>(), {
57
67
  type: "primary",
58
68
  size: "medium",
59
69
  loading: false,
@@ -62,38 +72,107 @@ withDefaults(defineProps<Props>(), {
62
72
  round: false,
63
73
  icon: "",
64
74
  openType: "",
75
+ bgColor: "",
76
+ textColor: "",
77
+ borderColor: "",
78
+ url: "",
79
+ navigateType: "navigateTo",
80
+ delta: 1,
81
+ });
82
+
83
+ const emit = defineEmits<{ click: [] }>();
84
+
85
+ const slots = useSlots();
86
+ const hasIcon = computed(() => Boolean(props.icon || slots.icon));
87
+
88
+ const buttonStyle = computed(() => {
89
+ const style: Record<string, string> = {};
90
+
91
+ if (props.bgColor) style.background = props.bgColor;
92
+ if (props.textColor) style.color = props.textColor;
93
+ if (props.borderColor) style.borderColor = props.borderColor;
94
+
95
+ return style;
65
96
  });
66
97
 
67
- defineEmits<{ click: [] }>();
98
+ const handleTap = () => {
99
+ if (props.disabled || props.loading) return;
100
+
101
+ emit("click");
102
+
103
+ if (!props.url && props.navigateType !== "navigateBack") return;
104
+
105
+ switch (props.navigateType) {
106
+ case "redirectTo":
107
+ if (props.url) uni.redirectTo({ url: props.url });
108
+ break;
109
+ case "switchTab":
110
+ if (props.url) uni.switchTab({ url: props.url });
111
+ break;
112
+ case "reLaunch":
113
+ if (props.url) uni.reLaunch({ url: props.url });
114
+ break;
115
+ case "navigateBack":
116
+ uni.navigateBack({ delta: props.delta });
117
+ break;
118
+ default:
119
+ if (props.url) uni.navigateTo({ url: props.url });
120
+ break;
121
+ }
122
+ };
68
123
  </script>
69
124
 
70
125
  <style lang="scss" scoped>
126
+ $semantic-colors: (
127
+ primary: var(--primary-color, #3b82f6),
128
+ success: var(--primary-success, #10b981),
129
+ warning: var(--primary-warning, #f59e0b),
130
+ danger: var(--primary-error, #ef4444),
131
+ error: var(--primary-error, #ef4444),
132
+ info: var(--primary-info, #64748b),
133
+ );
134
+
71
135
  .hlw-btn {
72
136
  display: inline-flex;
73
137
  align-items: center;
74
138
  justify-content: center;
75
139
  gap: 8rpx;
140
+ flex-shrink: 0;
76
141
  border: none;
77
142
  font-weight: 500;
78
143
  line-height: 1;
79
144
  transition: opacity 0.2s;
145
+ box-sizing: border-box;
146
+ background: transparent;
147
+ white-space: nowrap;
80
148
 
81
- &::after { display: none; }
82
- &:active { opacity: 0.7; }
149
+ &::after {
150
+ display: none;
151
+ }
83
152
 
84
- &--primary {
85
- background: var(--primary-color);
86
- color: #fff;
153
+ &:active {
154
+ opacity: 0.7;
155
+ }
156
+
157
+ @each $name, $color in $semantic-colors {
158
+ &--#{$name} {
159
+ background: #{$color};
160
+ color: #fff;
161
+ border: none;
162
+ }
87
163
  }
164
+
88
165
  &--outline {
89
166
  background: transparent;
90
167
  color: var(--primary-color);
91
168
  border: 2rpx solid var(--primary-color);
92
169
  }
170
+
93
171
  &--text {
94
172
  background: transparent;
95
173
  color: var(--primary-color);
96
174
  }
175
+
97
176
  &--ghost {
98
177
  background: transparent;
99
178
  color: #fff;
@@ -101,25 +180,54 @@ defineEmits<{ click: [] }>();
101
180
  }
102
181
 
103
182
  &--small {
104
- padding: 8rpx 20rpx;
183
+ min-height: 56rpx;
184
+ padding: 12rpx 20rpx;
105
185
  font-size: var(--font-xs, 20rpx);
106
186
  border-radius: var(--radius-sm, 8rpx);
107
187
  }
188
+
108
189
  &--medium {
109
- padding: 16rpx 32rpx;
190
+ min-height: 72rpx;
191
+ padding: 18rpx 32rpx;
110
192
  font-size: var(--font-sm, 24rpx);
111
193
  border-radius: var(--radius-md, 16rpx);
112
194
  }
195
+
113
196
  &--large {
114
- padding: 24rpx 48rpx;
197
+ min-height: 88rpx;
198
+ padding: 22rpx 48rpx;
115
199
  font-size: var(--font-base, 28rpx);
116
200
  border-radius: var(--radius-md, 16rpx);
117
201
  }
118
202
 
119
- &--block { display: flex; width: 100%; }
120
- &--round { border-radius: 999rpx; }
121
- &--disabled { opacity: 0.4; &:active { opacity: 0.4; } }
122
- &--loading { opacity: 0.7; }
203
+ &--block {
204
+ display: flex;
205
+ width: 100%;
206
+ }
207
+
208
+ &--round {
209
+ border-radius: 999rpx;
210
+ }
211
+
212
+ &--disabled {
213
+ opacity: 0.4;
214
+
215
+ &:active {
216
+ opacity: 0.4;
217
+ }
218
+ }
219
+
220
+ &--loading {
221
+ opacity: 0.7;
222
+ }
223
+ }
224
+
225
+ .hlw-btn-icon-wrap,
226
+ .hlw-btn-content {
227
+ display: inline-flex;
228
+ align-items: center;
229
+ justify-content: center;
230
+ white-space: nowrap;
123
231
  }
124
232
 
125
233
  .hlw-btn-icon {
@@ -136,6 +244,8 @@ defineEmits<{ click: [] }>();
136
244
  }
137
245
 
138
246
  @keyframes hlw-spin {
139
- to { transform: rotate(360deg); }
247
+ to {
248
+ transform: rotate(360deg);
249
+ }
140
250
  }
141
251
  </style>
@@ -118,7 +118,20 @@ const emit = defineEmits<{
118
118
 
119
119
  const handleBack = () => {
120
120
  emit("back");
121
- uni.navigateBack({ delta: 1 });
121
+
122
+ const pages = getCurrentPages();
123
+ if (pages.length > 1) {
124
+ uni.navigateBack({ delta: 1 });
125
+ return;
126
+ }
127
+
128
+ const homeUrl = "/pages/index/index";
129
+ uni.switchTab({
130
+ url: homeUrl,
131
+ fail: () => {
132
+ uni.reLaunch({ url: homeUrl });
133
+ },
134
+ });
122
135
  };
123
136
 
124
137
  const titleStyle = computed(() => ({
@@ -180,7 +193,7 @@ const totalNavBarHeight = computed(() => statusBarHeight.value + NAV_BAR_CONTENT
180
193
  }
181
194
 
182
195
  .header-back-icon {
183
- font-size: var(--font-md, 32rpx);
196
+ font-size: var(--font-base, 28rpx);
184
197
  color: inherit;
185
198
  }
186
199
 
@@ -152,6 +152,10 @@ const handleClick = (item: HlwMenuItem) => {
152
152
 
153
153
  <style lang="scss" scoped>
154
154
  .hlw-menu {
155
+ --hlw-menu-icon-size: var(--font-sm, 24rpx);
156
+ --hlw-menu-icon-box-size: calc(var(--hlw-menu-icon-size) + 40rpx);
157
+ --hlw-menu-grid-icon-size: var(--font-lg, 36rpx);
158
+ --hlw-menu-grid-icon-box-size: calc(var(--hlw-menu-grid-icon-size) + 52rpx);
155
159
  background: #fff;
156
160
  border-radius: var(--radius-lg, 24rpx);
157
161
  border: 1rpx solid var(--border-color, #e2e8f0);
@@ -254,8 +258,8 @@ const handleClick = (item: HlwMenuItem) => {
254
258
 
255
259
  /* ========== 图标 ========== */
256
260
  .hlw-menu-icon {
257
- width: 64rpx;
258
- height: 64rpx;
261
+ width: var(--hlw-menu-icon-box-size);
262
+ height: var(--hlw-menu-icon-box-size);
259
263
  border-radius: var(--radius-md, 16rpx);
260
264
  display: flex;
261
265
  align-items: center;
@@ -263,15 +267,15 @@ const handleClick = (item: HlwMenuItem) => {
263
267
  flex-shrink: 0;
264
268
 
265
269
  text {
266
- font-size: 20rpx;
270
+ font-size: var(--hlw-menu-icon-size);
267
271
  }
268
272
 
269
273
  &--grid {
270
- width: 88rpx;
271
- height: 88rpx;
274
+ width: var(--hlw-menu-grid-icon-box-size);
275
+ height: var(--hlw-menu-grid-icon-box-size);
272
276
  border-radius: var(--radius-lg, 24rpx);
273
277
  text {
274
- font-size: 32rpx;
278
+ font-size: var(--hlw-menu-grid-icon-size);
275
279
  }
276
280
  }
277
281
 
@@ -365,14 +369,14 @@ const handleClick = (item: HlwMenuItem) => {
365
369
 
366
370
  .hlw-menu-arrow {
367
371
  color: #d1d5db;
368
- font-size: var(--font-xs, 20rpx);
372
+ font-size: var(--font-sm, 24rpx);
369
373
  }
370
374
  .hlw-menu-spin {
371
375
  animation: icon-spin 1s linear infinite;
372
376
  }
373
377
  .hlw-menu-muted {
374
378
  color: #94a3b8;
375
- font-size: var(--font-xs, 20rpx);
379
+ font-size: var(--font-sm, 24rpx);
376
380
  }
377
381
 
378
382
  @keyframes icon-spin {
@@ -10,7 +10,42 @@
10
10
  />
11
11
  </slot>
12
12
  </view>
13
+
14
+ <hlw-paging
15
+ v-if="props.usePaging"
16
+ ref="pagingRef"
17
+ class="hlw-page-content hlw-page-content--paging"
18
+ :model-value="props.modelValue"
19
+ :fixed="false"
20
+ :use-page-scroll="false"
21
+ :height="'100%'"
22
+ :refresher-enabled="props.refresherEnabled"
23
+ :loading-more-enabled="props.loadingMoreEnabled"
24
+ :default-page-size="props.defaultPageSize"
25
+ :loading-text="props.loadingText"
26
+ :empty-text="props.emptyText"
27
+ :error-text="props.errorText"
28
+ :empty-image="props.emptyImage"
29
+ :use-default-loading="props.useDefaultLoading"
30
+ :use-default-empty="props.useDefaultEmpty"
31
+ @update:model-value="handleUpdateModelValue"
32
+ @query="handleQuery"
33
+ >
34
+ <slot />
35
+
36
+ <template v-if="$slots.loading" #loading="slotProps">
37
+ <slot name="loading" :isLoadFailed="slotProps && slotProps.isLoadFailed" />
38
+ </template>
39
+ <template v-if="$slots.empty" #empty="slotProps">
40
+ <slot name="empty" :isLoadFailed="slotProps && slotProps.isLoadFailed" />
41
+ </template>
42
+ <template v-if="$slots['empty-extra']" #empty-extra="slotProps">
43
+ <slot name="empty-extra" :isLoadFailed="slotProps && slotProps.isLoadFailed" />
44
+ </template>
45
+ </hlw-paging>
46
+
13
47
  <scroll-view
48
+ v-else
14
49
  class="hlw-page-content"
15
50
  :scroll-y="true"
16
51
  :enable-flex="true"
@@ -18,53 +53,131 @@
18
53
  :show-scrollbar="false"
19
54
  :style="themePageStyle"
20
55
  >
21
- <slot></slot>
56
+ <slot />
22
57
  </scroll-view>
58
+
23
59
  <view class="hlw-page-footer">
24
- <slot name="footer"></slot>
60
+ <slot name="footer" />
25
61
  </view>
26
62
  </view>
27
63
  </template>
28
64
 
29
65
  <script setup lang="ts">
66
+ import { ref } from "vue";
67
+ import type { HlwPagingRef } from "../hlw-paging/types";
30
68
  import { useThemePageStyle } from "../../composables/theme";
31
- /**
32
- * HlwPage — 页面布局容器
33
- *
34
- * 全屏 flex 布局:固定 header + 可滚动 content + 固定 footer。
35
- * 传入 title/isBack 自动渲染 HlwHeader,也可通过 header 插槽完全自定义。
36
- *
37
- * @props
38
- * title - 页面标题(自动渲染 HlwHeader)
39
- * isBack - 是否显示返回按钮,默认 false
40
- * bgClass - header 背景 CSS class
41
- *
42
- * @slots
43
- * header - 自定义顶部(覆盖默认 HlwHeader)
44
- * default - 主体可滚动内容
45
- * footer - 固定底部
46
- *
47
- * @example
48
- * ```vue
49
- * <HlwPage title="首页" bg-class="header-bg">
50
- * <view>页面内容</view>
51
- * <template #footer><view>底部栏</view></template>
52
- * </HlwPage>
53
- * ```
54
- */
69
+
70
+ defineOptions({
71
+ name: "HlwPage",
72
+ inheritAttrs: false,
73
+ });
74
+
55
75
  interface Props {
56
76
  title?: string;
57
77
  isBack?: boolean;
58
78
  bgClass?: string;
79
+ usePaging?: boolean;
80
+ modelValue?: any[];
81
+ refresherEnabled?: boolean;
82
+ loadingMoreEnabled?: boolean;
83
+ defaultPageSize?: number;
84
+ loadingText?: string;
85
+ emptyText?: string;
86
+ errorText?: string;
87
+ emptyImage?: string;
88
+ useDefaultLoading?: boolean;
89
+ useDefaultEmpty?: boolean;
59
90
  }
60
91
 
61
92
  const props = withDefaults(defineProps<Props>(), {
62
93
  title: "",
63
94
  isBack: false,
64
95
  bgClass: "",
96
+ usePaging: false,
97
+ modelValue: () => [],
98
+ useDefaultLoading: true,
99
+ useDefaultEmpty: true,
65
100
  });
66
101
 
102
+ const emit = defineEmits<{
103
+ "update:modelValue": [value: any[]];
104
+ query: [pageNo: number, pageSize: number, ...args: any[]];
105
+ }>();
106
+
107
+ const pagingRef = ref<HlwPagingRef<any> | null>(null);
67
108
  const { themePageStyle } = useThemePageStyle();
109
+
110
+ function handleUpdateModelValue(value: any[]) {
111
+ emit("update:modelValue", value ?? []);
112
+ }
113
+
114
+ function handleQuery(pageNo: number, pageSize: number, ...args: any[]) {
115
+ emit("query", pageNo, pageSize, ...args);
116
+ }
117
+
118
+ const getPagingRef = () => pagingRef.value;
119
+
120
+ const pagingMethodNames = [
121
+ "reload",
122
+ "refresh",
123
+ "refreshToPage",
124
+ "complete",
125
+ "completeByTotal",
126
+ "completeByNoMore",
127
+ "completeByError",
128
+ "completeByKey",
129
+ "clear",
130
+ "addDataFromTop",
131
+ "resetTotalData",
132
+ "endRefresh",
133
+ "updateCustomRefresherHeight",
134
+ "goF2",
135
+ "closeF2",
136
+ "doLoadMore",
137
+ "updatePageScrollTop",
138
+ "updatePageScrollTopHeight",
139
+ "updatePageScrollBottomHeight",
140
+ "updateLeftAndRightWidth",
141
+ "updateFixedLayout",
142
+ "doInsertVirtualListItem",
143
+ "didUpdateVirtualListCell",
144
+ "didDeleteVirtualListCell",
145
+ "updateVirtualListRender",
146
+ "setLocalPaging",
147
+ "doChatRecordLoadMore",
148
+ "addChatRecordData",
149
+ "addKeyboardHeightChangeListener",
150
+ "scrollToTop",
151
+ "scrollToBottom",
152
+ "scrollIntoViewById",
153
+ "scrollIntoViewByNodeTop",
154
+ "scrollToY",
155
+ "scrollToX",
156
+ "scrollIntoViewByIndex",
157
+ "scrollIntoViewByView",
158
+ "setSpecialEffects",
159
+ "setListSpecialEffects",
160
+ "updateCache",
161
+ "getVersion",
162
+ ] as const;
163
+
164
+ type PagingMethodName = (typeof pagingMethodNames)[number];
165
+
166
+ const exposed = pagingMethodNames.reduce((result, methodName) => {
167
+ result[methodName] = (...args: any[]) => {
168
+ return pagingRef.value?.[methodName]?.(...args);
169
+ };
170
+
171
+ return result;
172
+ }, {
173
+ pagingRef,
174
+ getPagingRef,
175
+ } as Record<string, any>);
176
+
177
+ defineExpose(exposed as {
178
+ pagingRef: typeof pagingRef;
179
+ getPagingRef: () => HlwPagingRef<any> | null;
180
+ } & Record<PagingMethodName, (...args: any[]) => any>);
68
181
  </script>
69
182
 
70
183
  <style lang="scss" scoped>
@@ -86,6 +199,10 @@ const { themePageStyle } = useThemePageStyle();
86
199
  width: 100%;
87
200
  }
88
201
 
202
+ .hlw-page-content--paging {
203
+ min-height: 0;
204
+ }
205
+
89
206
  .hlw-page-footer {
90
207
  flex-shrink: 0;
91
208
  }
@@ -0,0 +1,192 @@
1
+ <template>
2
+ <z-paging
3
+ ref="pagingRef"
4
+ class="hlw-paging"
5
+ :model-value="props.modelValue"
6
+ :fixed="props.fixed"
7
+ :use-page-scroll="props.usePageScroll"
8
+ :height="props.height"
9
+ :refresher-enabled="props.refresherEnabled"
10
+ :loading-more-enabled="props.loadingMoreEnabled"
11
+ :default-page-size="props.defaultPageSize"
12
+ @update:model-value="handleUpdateModelValue"
13
+ @query="handleQuery"
14
+ >
15
+ <slot />
16
+
17
+ <template v-if="$slots.loading && !showDefaultLoading" #loading="slotProps">
18
+ <slot name="loading" :isLoadFailed="slotProps && slotProps.isLoadFailed" />
19
+ </template>
20
+
21
+ <template v-if="$slots.empty && !showDefaultEmpty" #empty="slotProps">
22
+ <slot name="empty" :isLoadFailed="slotProps && slotProps.isLoadFailed" />
23
+ </template>
24
+
25
+ <template v-if="$slots['empty-extra']" #empty-extra="slotProps">
26
+ <slot name="empty-extra" :isLoadFailed="slotProps && slotProps.isLoadFailed" />
27
+ </template>
28
+
29
+ <template v-if="showDefaultLoading" #loading>
30
+ <view class="hlw-paging__state hlw-paging__state--loading">
31
+ <hlw-loading :text="props.loadingText" />
32
+ </view>
33
+ </template>
34
+
35
+ <template v-if="showDefaultEmpty" #empty="{ isLoadFailed }">
36
+ <view class="hlw-paging__state hlw-paging__state--empty">
37
+ <hlw-empty
38
+ :image="props.emptyImage"
39
+ :text="isLoadFailed ? props.errorText : props.emptyText"
40
+ >
41
+ <slot name="empty-extra" :isLoadFailed="isLoadFailed" />
42
+ </hlw-empty>
43
+ </view>
44
+ </template>
45
+ </z-paging>
46
+ </template>
47
+
48
+ <script setup lang="ts">
49
+ import { computed, ref, useSlots } from "vue";
50
+ import ZPaging from "z-paging/components/z-paging/z-paging.vue";
51
+ import type { HlwPagingRef } from "./types";
52
+
53
+ defineOptions({
54
+ name: "HlwPaging",
55
+ inheritAttrs: false,
56
+ });
57
+
58
+ interface Props {
59
+ modelValue?: any[];
60
+ fixed?: boolean;
61
+ usePageScroll?: boolean;
62
+ height?: string;
63
+ refresherEnabled?: boolean;
64
+ loadingMoreEnabled?: boolean;
65
+ defaultPageSize?: number;
66
+ loadingText?: string;
67
+ emptyText?: string;
68
+ errorText?: string;
69
+ emptyImage?: string;
70
+ useDefaultLoading?: boolean;
71
+ useDefaultEmpty?: boolean;
72
+ }
73
+
74
+ const props = withDefaults(defineProps<Props>(), {
75
+ modelValue: () => [],
76
+ loadingText: "加载中...",
77
+ emptyText: "暂无数据",
78
+ errorText: "加载失败,请稍后重试",
79
+ emptyImage: "",
80
+ useDefaultLoading: true,
81
+ useDefaultEmpty: true,
82
+ });
83
+
84
+ const emit = defineEmits<{
85
+ "update:modelValue": [value: any[]];
86
+ query: [pageNo: number, pageSize: number, ...args: any[]];
87
+ }>();
88
+
89
+ const slots = useSlots();
90
+ const pagingRef = ref<HlwPagingRef<any> | null>(null);
91
+
92
+ function handleUpdateModelValue(value: any[]) {
93
+ emit("update:modelValue", value ?? []);
94
+ }
95
+
96
+ function handleQuery(pageNo: number, pageSize: number, ...args: any[]) {
97
+ emit("query", pageNo, pageSize, ...args);
98
+ }
99
+
100
+ const showDefaultLoading = computed(() => {
101
+ return props.useDefaultLoading && !slots.loading;
102
+ });
103
+
104
+ const showDefaultEmpty = computed(() => {
105
+ return props.useDefaultEmpty && !slots.empty;
106
+ });
107
+
108
+ const getPagingRef = () => pagingRef.value;
109
+
110
+ const pagingMethodNames = [
111
+ "reload",
112
+ "refresh",
113
+ "refreshToPage",
114
+ "complete",
115
+ "completeByTotal",
116
+ "completeByNoMore",
117
+ "completeByError",
118
+ "completeByKey",
119
+ "clear",
120
+ "addDataFromTop",
121
+ "resetTotalData",
122
+ "endRefresh",
123
+ "updateCustomRefresherHeight",
124
+ "goF2",
125
+ "closeF2",
126
+ "doLoadMore",
127
+ "updatePageScrollTop",
128
+ "updatePageScrollTopHeight",
129
+ "updatePageScrollBottomHeight",
130
+ "updateLeftAndRightWidth",
131
+ "updateFixedLayout",
132
+ "doInsertVirtualListItem",
133
+ "didUpdateVirtualListCell",
134
+ "didDeleteVirtualListCell",
135
+ "updateVirtualListRender",
136
+ "setLocalPaging",
137
+ "doChatRecordLoadMore",
138
+ "addChatRecordData",
139
+ "addKeyboardHeightChangeListener",
140
+ "scrollToTop",
141
+ "scrollToBottom",
142
+ "scrollIntoViewById",
143
+ "scrollIntoViewByNodeTop",
144
+ "scrollToY",
145
+ "scrollToX",
146
+ "scrollIntoViewByIndex",
147
+ "scrollIntoViewByView",
148
+ "setSpecialEffects",
149
+ "setListSpecialEffects",
150
+ "updateCache",
151
+ "getVersion",
152
+ ] as const;
153
+
154
+ type PagingMethodName = (typeof pagingMethodNames)[number];
155
+
156
+ const exposed = pagingMethodNames.reduce((result, methodName) => {
157
+ result[methodName] = (...args: any[]) => {
158
+ return pagingRef.value?.[methodName]?.(...args);
159
+ };
160
+
161
+ return result;
162
+ }, {
163
+ pagingRef,
164
+ getPagingRef,
165
+ } as Record<string, any>);
166
+
167
+ defineExpose(exposed as {
168
+ pagingRef: typeof pagingRef;
169
+ getPagingRef: () => HlwPagingRef<any> | null;
170
+ } & Record<PagingMethodName, (...args: any[]) => any>);
171
+ </script>
172
+
173
+ <style scoped lang="scss">
174
+ .hlw-paging {
175
+ width: 100%;
176
+ }
177
+
178
+ .hlw-paging__state {
179
+ width: 100%;
180
+ display: flex;
181
+ justify-content: center;
182
+ }
183
+
184
+ .hlw-paging__state--loading {
185
+ padding: 24rpx 0;
186
+ }
187
+
188
+ .hlw-paging__state--empty {
189
+ min-height: 360rpx;
190
+ padding: 32rpx 0;
191
+ }
192
+ </style>
@@ -0,0 +1,50 @@
1
+ export interface HlwPagingReturnData<T = any> {
2
+ totalList: T[];
3
+ noMore: boolean;
4
+ }
5
+
6
+ export interface HlwPagingRef<T = any> {
7
+ reload: (animate?: boolean) => Promise<HlwPagingReturnData<T>> | undefined;
8
+ refresh: () => Promise<HlwPagingReturnData<T>> | undefined;
9
+ refreshToPage: (page: number) => Promise<HlwPagingReturnData<T>> | undefined;
10
+ complete: (data?: T[] | false, success?: boolean) => Promise<HlwPagingReturnData<T>> | undefined;
11
+ completeByTotal: (data: T[], total: number, success?: boolean) => Promise<HlwPagingReturnData<T>> | undefined;
12
+ completeByNoMore: (data: T[], noMore: boolean, success?: boolean) => Promise<HlwPagingReturnData<T>> | undefined;
13
+ completeByError: (cause: string) => Promise<HlwPagingReturnData<T>> | undefined;
14
+ completeByKey: (data: T[], key: string, success?: boolean) => Promise<HlwPagingReturnData<T>> | undefined;
15
+ clear: () => void;
16
+ addDataFromTop: (data: T | T[], scrollToTop?: boolean, animate?: boolean) => void;
17
+ resetTotalData: (data: T[]) => void;
18
+ endRefresh: () => void;
19
+ updateCustomRefresherHeight: () => void;
20
+ goF2: () => void;
21
+ closeF2: () => void;
22
+ doLoadMore: (source?: "click" | "toBottom") => void;
23
+ updatePageScrollTop: (scrollTop: number) => void;
24
+ updatePageScrollTopHeight: () => void;
25
+ updatePageScrollBottomHeight: () => void;
26
+ updateLeftAndRightWidth: () => void;
27
+ updateFixedLayout: () => void;
28
+ doInsertVirtualListItem: (item: T, index: number) => void;
29
+ didUpdateVirtualListCell: (index: number) => void;
30
+ didDeleteVirtualListCell: (index: number) => void;
31
+ updateVirtualListRender: () => void;
32
+ setLocalPaging: (data: T[], success?: boolean) => Promise<HlwPagingReturnData<T>> | undefined;
33
+ doChatRecordLoadMore: () => void;
34
+ addChatRecordData: (data: T | T[], scrollToBottom?: boolean, animate?: boolean) => void;
35
+ addKeyboardHeightChangeListener: () => void;
36
+ scrollToTop: (animate?: boolean) => void;
37
+ scrollToBottom: (animate?: boolean) => void;
38
+ scrollIntoViewById: (id: string, offset?: number, animate?: boolean) => void;
39
+ scrollIntoViewByNodeTop: (top: number, offset?: number, animate?: boolean) => void;
40
+ scrollToY: (y: number, offset?: number, animate?: boolean) => void;
41
+ scrollToX: (x: number, offset?: number, animate?: boolean) => void;
42
+ scrollIntoViewByIndex: (index: number, offset?: number, animate?: boolean) => void;
43
+ scrollIntoViewByView: (view: unknown, offset?: number, animate?: boolean) => void;
44
+ setSpecialEffects: (args: Record<string, any>) => void;
45
+ setListSpecialEffects: (args: Record<string, any>) => void;
46
+ updateCache: () => void;
47
+ getVersion: () => string | undefined;
48
+ }
49
+
50
+ export interface HlwPagingInstance<T = any> extends HlwPagingRef<T> {}
@@ -14,7 +14,7 @@
14
14
  import { computed } from "vue";
15
15
 
16
16
  interface Props {
17
- type?: "primary" | "success" | "warning" | "danger" | "info";
17
+ type?: "primary" | "success" | "warning" | "danger" | "error" | "info";
18
18
  plain?: boolean;
19
19
  closable?: boolean;
20
20
  size?: "small" | "medium";
@@ -44,10 +44,11 @@ const customStyle = computed(() => {
44
44
  <style lang="scss" scoped>
45
45
  $colors: (
46
46
  primary: var(--primary-color, #3b82f6),
47
- success: #10b981,
48
- warning: #f59e0b,
49
- danger: #ef4444,
50
- info: #64748b,
47
+ success: var(--primary-success, #10b981),
48
+ warning: var(--primary-warning, #f59e0b),
49
+ danger: var(--primary-error, #ef4444),
50
+ error: var(--primary-error, #ef4444),
51
+ info: var(--primary-info, #64748b),
51
52
  );
52
53
 
53
54
  .hlw-tag {
@@ -30,4 +30,10 @@ export function useThemePageStyle() {
30
30
  export type { FontScale, FontPreset } from "./font";
31
31
  export { FONT_SCALE_KEY, FONT_PRESETS, getCurrentFontScale, getCurrentFontVars } from "./font";
32
32
  export type { ThemeColor } from "./palette";
33
- export { THEME_COLOR_KEY, DEFAULT_THEMES, getCurrentThemeColor, getCurrentThemeVars } from "./palette";
33
+ export {
34
+ THEME_COLOR_KEY,
35
+ THEME_SEMANTIC_COLORS,
36
+ DEFAULT_THEMES,
37
+ getCurrentThemeColor,
38
+ getCurrentThemeVars,
39
+ } from "./palette";
@@ -9,11 +9,18 @@ export interface ThemeColor {
9
9
 
10
10
  export const THEME_COLOR_KEY = "hlw_theme_color";
11
11
 
12
+ export const THEME_SEMANTIC_COLORS = {
13
+ success: "#10b981",
14
+ warning: "#f59e0b",
15
+ error: "#ef4444",
16
+ info: "#64748b",
17
+ } as const;
18
+
12
19
  export const DEFAULT_THEMES: ThemeColor[] = [
13
- { label: "默认蓝", value: "#3b82f6" },
14
- { label: "活力橙", value: "#f97316" },
15
20
  { label: "翡翠绿", value: "#10b981" },
16
- { label: "玫瑰红", value: "#f43f5e" },
21
+ { label: "活力橙", value: "#f97316" },
22
+ { label: "默认蓝", value: "#3b82f6" },
23
+ { label: "玫瑰粉", value: "#f43f5e" },
17
24
  { label: "紫罗兰", value: "#8b5cf6" },
18
25
  { label: "青石灰", value: "#64748b" },
19
26
  ];
@@ -32,5 +39,29 @@ export function getCurrentThemeVars(): Record<string, string> {
32
39
  "--primary-color": color,
33
40
  "--primary-light": hexToRgba(color, 0.12),
34
41
  "--primary-dark": darkenHex(color),
42
+ "--primary-success": THEME_SEMANTIC_COLORS.success,
43
+ "--primary-success-light": hexToRgba(THEME_SEMANTIC_COLORS.success, 0.12),
44
+ "--primary-success-dark": darkenHex(THEME_SEMANTIC_COLORS.success),
45
+ "--primary-warning": THEME_SEMANTIC_COLORS.warning,
46
+ "--primary-warning-light": hexToRgba(THEME_SEMANTIC_COLORS.warning, 0.12),
47
+ "--primary-warning-dark": darkenHex(THEME_SEMANTIC_COLORS.warning),
48
+ "--primary-error": THEME_SEMANTIC_COLORS.error,
49
+ "--primary-error-light": hexToRgba(THEME_SEMANTIC_COLORS.error, 0.12),
50
+ "--primary-error-dark": darkenHex(THEME_SEMANTIC_COLORS.error),
51
+ "--primary-info": THEME_SEMANTIC_COLORS.info,
52
+ "--primary-info-light": hexToRgba(THEME_SEMANTIC_COLORS.info, 0.12),
53
+ "--primary-info-dark": darkenHex(THEME_SEMANTIC_COLORS.info),
54
+ "--success-color": THEME_SEMANTIC_COLORS.success,
55
+ "--success-light": hexToRgba(THEME_SEMANTIC_COLORS.success, 0.12),
56
+ "--success-dark": darkenHex(THEME_SEMANTIC_COLORS.success),
57
+ "--warning-color": THEME_SEMANTIC_COLORS.warning,
58
+ "--warning-light": hexToRgba(THEME_SEMANTIC_COLORS.warning, 0.12),
59
+ "--warning-dark": darkenHex(THEME_SEMANTIC_COLORS.warning),
60
+ "--error-color": THEME_SEMANTIC_COLORS.error,
61
+ "--error-light": hexToRgba(THEME_SEMANTIC_COLORS.error, 0.12),
62
+ "--error-dark": darkenHex(THEME_SEMANTIC_COLORS.error),
63
+ "--info-color": THEME_SEMANTIC_COLORS.info,
64
+ "--info-light": hexToRgba(THEME_SEMANTIC_COLORS.info, 0.12),
65
+ "--info-dark": darkenHex(THEME_SEMANTIC_COLORS.info),
35
66
  };
36
67
  }
package/src/index.ts CHANGED
@@ -9,11 +9,13 @@
9
9
 
10
10
  export type { FontScale, FontPreset, ThemeColor } from "./composables/theme";
11
11
  export type { HlwMenuItem } from "./components/hlw-menu/types";
12
+ export type { HlwPagingRef, HlwPagingInstance } from "./components/hlw-paging/types";
12
13
  export {
13
14
  FONT_PRESETS,
14
15
  FONT_SCALE_KEY,
15
16
  DEFAULT_THEMES,
16
17
  THEME_COLOR_KEY,
18
+ THEME_SEMANTIC_COLORS,
17
19
  THEME_CHANGE_EVENT,
18
20
  getCurrentFontScale,
19
21
  getCurrentFontVars,
@@ -0,0 +1,8 @@
1
+ import "pinia";
2
+ import type { StateTree } from "pinia";
3
+
4
+ declare module "pinia" {
5
+ interface DefineStoreOptionsBase<S extends StateTree, Store> {
6
+ unistorage?: boolean;
7
+ }
8
+ }
@@ -38,7 +38,7 @@ export const useThemeStore = defineStore(
38
38
 
39
39
  /** 当前激活的主题信息 */
40
40
  const activeTheme = computed<ThemeColor>(() =>
41
- themes.find((t) => t.value === primaryColor.value)
41
+ themes.find((t: ThemeColor) => t.value === primaryColor.value)
42
42
  ?? { label: "自定义", value: primaryColor.value },
43
43
  );
44
44