@hlw-uni/mp-vue 1.2.28 → 2.0.2
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/dist/app.d.ts +33 -0
- package/dist/composables/_internal/unwrap.d.ts +15 -0
- package/dist/composables/ad/index.d.ts +101 -0
- package/dist/composables/color/index.d.ts +8 -0
- package/dist/composables/contact/index.d.ts +32 -0
- package/dist/composables/device/index.d.ts +129 -0
- package/dist/composables/format/index.d.ts +9 -0
- package/dist/composables/http/adapters/alist.d.ts +3 -0
- package/dist/composables/http/adapters/base.d.ts +19 -0
- package/dist/composables/http/adapters/cos.d.ts +3 -0
- package/dist/composables/http/adapters/index.d.ts +15 -0
- package/dist/composables/http/adapters/oss.d.ts +3 -0
- package/dist/composables/http/adapters/qiniu.d.ts +3 -0
- package/dist/composables/http/client.d.ts +66 -0
- package/dist/composables/http/index.d.ts +8 -0
- package/dist/composables/http/types.d.ts +51 -0
- package/dist/composables/index.d.ts +26 -0
- package/dist/composables/loading/index.d.ts +7 -0
- package/dist/composables/msg/index.d.ts +36 -0
- package/dist/composables/navigator/index.d.ts +47 -0
- package/dist/composables/page-meta/index.d.ts +18 -0
- package/dist/composables/refs/index.d.ts +8 -0
- package/dist/composables/share/index.d.ts +71 -0
- package/dist/composables/storage/index.d.ts +16 -0
- package/dist/composables/theme/font.d.ts +1 -1
- package/dist/composables/theme/index.d.ts +15 -2
- package/dist/composables/utils/index.d.ts +39 -0
- package/dist/composables/validate/index.d.ts +12 -0
- package/dist/directives/copy.d.ts +3 -0
- package/dist/directives/index.d.ts +1 -0
- package/dist/hlw.d.ts +14 -0
- package/dist/index.d.ts +10 -7
- package/dist/index.js +1733 -63
- package/dist/index.mjs +1732 -62
- package/package.json +5 -3
- package/src/app.ts +173 -0
- package/src/components/hlw-ad/index.vue +74 -30
- package/src/composables/_internal/unwrap.ts +19 -0
- package/src/composables/ad/index.ts +395 -0
- package/src/composables/color/index.ts +44 -0
- package/src/composables/contact/index.ts +92 -0
- package/src/composables/device/index.ts +168 -0
- package/src/composables/format/index.ts +48 -0
- package/src/composables/http/adapters/alist.ts +19 -0
- package/src/composables/http/adapters/base.ts +21 -0
- package/src/composables/http/adapters/cos.ts +23 -0
- package/src/composables/http/adapters/index.ts +31 -0
- package/src/composables/http/adapters/oss.ts +22 -0
- package/src/composables/http/adapters/qiniu.ts +19 -0
- package/src/composables/http/client.ts +237 -0
- package/src/composables/http/index.ts +8 -0
- package/src/composables/http/types.ts +57 -0
- package/src/composables/http/useRequest.ts +107 -0
- package/src/composables/index.ts +82 -0
- package/src/composables/loading/index.ts +23 -0
- package/src/composables/msg/index.ts +132 -0
- package/src/composables/navigator/index.ts +104 -0
- package/src/composables/page-meta/index.ts +49 -0
- package/src/composables/refs/index.ts +30 -0
- package/src/composables/share/index.ts +189 -0
- package/src/composables/storage/index.ts +76 -0
- package/src/composables/theme/font.ts +26 -5
- package/src/composables/theme/index.ts +26 -11
- package/src/composables/theme/palette.ts +1 -1
- package/src/composables/utils/index.ts +160 -0
- package/src/composables/validate/index.ts +58 -0
- package/src/directives/copy.ts +50 -0
- package/src/directives/index.ts +1 -0
- package/src/hlw.ts +37 -0
- package/src/index.ts +21 -20
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useLoading - 全局 Loading 状态
|
|
3
|
+
*/
|
|
4
|
+
export function useLoading() {
|
|
5
|
+
/**
|
|
6
|
+
* 显示全局加载提示。
|
|
7
|
+
*/
|
|
8
|
+
function showLoading(message = "加载中...") {
|
|
9
|
+
uni.showLoading({ title: message, mask: true });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 关闭全局加载提示。
|
|
14
|
+
*/
|
|
15
|
+
function hideLoading() {
|
|
16
|
+
uni.hideLoading();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
showLoading,
|
|
21
|
+
hideLoading,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useMsg - 消息提示 composable
|
|
3
|
+
*/
|
|
4
|
+
export type ToastIcon = "success" | "fail" | "exception" | "none";
|
|
5
|
+
export type ToastDuration = "short" | "long";
|
|
6
|
+
|
|
7
|
+
export interface ToastOptions {
|
|
8
|
+
message: string;
|
|
9
|
+
icon?: ToastIcon;
|
|
10
|
+
image?: string;
|
|
11
|
+
duration?: number;
|
|
12
|
+
mask?: boolean;
|
|
13
|
+
position?: "top" | "center" | "bottom";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ModalOptions {
|
|
17
|
+
title?: string;
|
|
18
|
+
content: string;
|
|
19
|
+
confirmText?: string;
|
|
20
|
+
cancelText?: string;
|
|
21
|
+
confirmColor?: string;
|
|
22
|
+
cancelColor?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface HlwMsg {
|
|
26
|
+
toast(opts: ToastOptions | string): void;
|
|
27
|
+
success(message: string): void;
|
|
28
|
+
error(message: string): void;
|
|
29
|
+
fail(message: string): void;
|
|
30
|
+
showLoading(message?: string): void;
|
|
31
|
+
hideLoading(): void;
|
|
32
|
+
confirm(opts: ModalOptions): Promise<boolean>;
|
|
33
|
+
modal(opts: ModalOptions): Promise<boolean>;
|
|
34
|
+
setLoadingBar(progress: number): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 统一的消息提示与弹窗能力。
|
|
39
|
+
*/
|
|
40
|
+
export function useMsg(): HlwMsg {
|
|
41
|
+
/**
|
|
42
|
+
* 显示普通 toast,支持字符串或完整配置。
|
|
43
|
+
*/
|
|
44
|
+
function toast(opts: ToastOptions | string) {
|
|
45
|
+
const normalized: ToastOptions = typeof opts === "string" ? { message: opts } : opts;
|
|
46
|
+
const { message, icon = "none", image, duration = 2000, mask = false, position = "center" } = normalized;
|
|
47
|
+
uni.showToast({ title: message, icon, image, duration, mask, position });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 显示成功提示。
|
|
52
|
+
*/
|
|
53
|
+
function success(message: string) {
|
|
54
|
+
uni.showToast({ title: message, icon: "success", duration: 2000 });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 显示失败提示。
|
|
59
|
+
*/
|
|
60
|
+
function error(message: string) {
|
|
61
|
+
uni.showToast({ title: message, icon: "fail", duration: 2000 });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 显示全局 loading。
|
|
66
|
+
*/
|
|
67
|
+
function showLoading(message = "加载中...") {
|
|
68
|
+
uni.showLoading({ title: message, mask: true });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 关闭全局 loading。
|
|
73
|
+
*/
|
|
74
|
+
function hideLoading() {
|
|
75
|
+
uni.hideLoading();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 显示确认弹窗,返回用户是否点击确认。
|
|
80
|
+
*/
|
|
81
|
+
function confirm(opts: ModalOptions): Promise<boolean> {
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
const {
|
|
84
|
+
title = "提示",
|
|
85
|
+
content,
|
|
86
|
+
confirmText = "确定",
|
|
87
|
+
cancelText = "取消",
|
|
88
|
+
confirmColor = "#3b82f6",
|
|
89
|
+
cancelColor = "#999999",
|
|
90
|
+
} = opts;
|
|
91
|
+
uni.showModal({
|
|
92
|
+
title,
|
|
93
|
+
content,
|
|
94
|
+
confirmText,
|
|
95
|
+
cancelText,
|
|
96
|
+
confirmColor,
|
|
97
|
+
cancelColor,
|
|
98
|
+
success: (res) => resolve(res.confirm),
|
|
99
|
+
fail: () => resolve(false),
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* modal 是 confirm 的语义别名。
|
|
106
|
+
*/
|
|
107
|
+
function modal(opts: ModalOptions): Promise<boolean> {
|
|
108
|
+
return confirm(opts);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 通过标题文本模拟简单进度条展示。
|
|
113
|
+
*/
|
|
114
|
+
function setLoadingBar(progress: number) {
|
|
115
|
+
const clamped = Math.max(0, Math.min(100, progress));
|
|
116
|
+
uni.setNavigationBarTitle({
|
|
117
|
+
title: `${"■".repeat(Math.round(clamped / 2))}${"□".repeat(50 - Math.round(clamped / 2))} ${clamped}%`,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
toast,
|
|
123
|
+
success,
|
|
124
|
+
error,
|
|
125
|
+
fail: error,
|
|
126
|
+
showLoading,
|
|
127
|
+
hideLoading,
|
|
128
|
+
confirm,
|
|
129
|
+
modal,
|
|
130
|
+
setLoadingBar,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 路由/跳转能力 —— 包装 uni-app 各类 jump API 为一个统一对象
|
|
3
|
+
*
|
|
4
|
+
* 使用:
|
|
5
|
+
* const { navigate } = useRouter();
|
|
6
|
+
* navigate(tool.jump_type, tool.jump_value);
|
|
7
|
+
*
|
|
8
|
+
* 未装 vue-router 也能用;命名风格对齐 Vue Router,熟悉的 push/replace/back
|
|
9
|
+
* 会在后续迭代中补齐。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** 已知的跳转类型;保留 `(string & {})` 允许任意自定义值向下兼容 */
|
|
13
|
+
export type NavigateType =
|
|
14
|
+
| "navigateTo"
|
|
15
|
+
| "redirectTo"
|
|
16
|
+
| "switchTab"
|
|
17
|
+
| "reLaunch"
|
|
18
|
+
| "webview"
|
|
19
|
+
| "miniprogram"
|
|
20
|
+
| (string & {});
|
|
21
|
+
|
|
22
|
+
export interface NavigateOptions {
|
|
23
|
+
/** 失败回调,收到 uni 返回的错误信息 */
|
|
24
|
+
onFail?: (errMsg: string) => void;
|
|
25
|
+
/** 是否禁用默认的 Toast 错误提示,默认 false */
|
|
26
|
+
silent?: boolean;
|
|
27
|
+
/** 仅 miniprogram:目标小程序的页面路径(如 pages/index/index?id=1),留空则打开首页 */
|
|
28
|
+
path?: string;
|
|
29
|
+
/** 仅 miniprogram:目标小程序版本,默认 release */
|
|
30
|
+
envVersion?: "develop" | "trial" | "release";
|
|
31
|
+
/** 仅 miniprogram:传给目标小程序的额外数据,对方从 onLaunch/onShow 读取 */
|
|
32
|
+
extraData?: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 根据 type + value 调用对应的 uni API,屏蔽参数差异。
|
|
37
|
+
*
|
|
38
|
+
* 对应关系:
|
|
39
|
+
* - `navigateTo`(默认) → uni.navigateTo
|
|
40
|
+
* - `redirectTo` → uni.redirectTo
|
|
41
|
+
* - `switchTab` → uni.switchTab(value 必须是 tabBar 页)
|
|
42
|
+
* - `reLaunch` → uni.reLaunch
|
|
43
|
+
* - `webview` → 默认 Toast 提示,上层需接入 web-view 承载页
|
|
44
|
+
* - `miniprogram` → uni.navigateToMiniProgram,value 格式 `appid:path`
|
|
45
|
+
*/
|
|
46
|
+
function doNavigate(type: NavigateType, value: string, options: NavigateOptions = {}): void {
|
|
47
|
+
const { onFail, silent = false } = options;
|
|
48
|
+
|
|
49
|
+
if (!value) {
|
|
50
|
+
if (!silent) uni.showToast({ title: "跳转目标未配置", icon: "none" });
|
|
51
|
+
onFail?.("跳转目标未配置");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const fail = (err?: { errMsg?: string }) => {
|
|
56
|
+
const msg = err?.errMsg || `无法跳转:${value}`;
|
|
57
|
+
if (!silent) uni.showToast({ title: msg, icon: "none" });
|
|
58
|
+
onFail?.(msg);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
switch (type) {
|
|
62
|
+
case "switchTab":
|
|
63
|
+
uni.switchTab({ url: value, fail });
|
|
64
|
+
break;
|
|
65
|
+
case "redirectTo":
|
|
66
|
+
uni.redirectTo({ url: value, fail });
|
|
67
|
+
break;
|
|
68
|
+
case "reLaunch":
|
|
69
|
+
uni.reLaunch({ url: value, fail });
|
|
70
|
+
break;
|
|
71
|
+
case "webview":
|
|
72
|
+
// H5 需要上层承载页接入;这里仅 Toast 告知
|
|
73
|
+
if (!silent) uni.showToast({ title: `H5:${value}`, icon: "none" });
|
|
74
|
+
break;
|
|
75
|
+
case "miniprogram": {
|
|
76
|
+
// @ts-ignore 部分平台 navigateToMiniProgram 定义可能缺失
|
|
77
|
+
uni.navigateToMiniProgram?.({
|
|
78
|
+
appId: value,
|
|
79
|
+
path: options.path || "",
|
|
80
|
+
envVersion: options.envVersion,
|
|
81
|
+
extraData: options.extraData,
|
|
82
|
+
fail,
|
|
83
|
+
});
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case "navigateTo":
|
|
87
|
+
default:
|
|
88
|
+
uni.navigateTo({ url: value, fail });
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 路由 composable:返回跳转方法集合。
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* const { navigate } = useRouter();
|
|
98
|
+
* navigate("switchTab", "/pages/index/index");
|
|
99
|
+
*/
|
|
100
|
+
export function useRouter() {
|
|
101
|
+
return {
|
|
102
|
+
navigate: doNavigate,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePageMeta - 页面元信息 composable
|
|
3
|
+
*/
|
|
4
|
+
export interface PageMeta {
|
|
5
|
+
title?: string;
|
|
6
|
+
navigationBarTitleText?: string;
|
|
7
|
+
navigationBarBackgroundColor?: string;
|
|
8
|
+
navigationBarTextStyle?: "white" | "black";
|
|
9
|
+
backgroundColor?: string;
|
|
10
|
+
enablePullDownRefresh?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 页面导航栏与背景元信息工具。
|
|
15
|
+
*/
|
|
16
|
+
export function usePageMeta() {
|
|
17
|
+
/**
|
|
18
|
+
* 设置当前页面标题。
|
|
19
|
+
*/
|
|
20
|
+
function setTitle(title: string) {
|
|
21
|
+
uni.setNavigationBarTitle({ title });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 批量设置页面标题、导航栏和背景样式。
|
|
26
|
+
*/
|
|
27
|
+
function setOptions(options: PageMeta) {
|
|
28
|
+
if (options.title || options.navigationBarTitleText) {
|
|
29
|
+
setTitle(options.title || options.navigationBarTitleText!);
|
|
30
|
+
}
|
|
31
|
+
if (options.navigationBarBackgroundColor || options.navigationBarTextStyle) {
|
|
32
|
+
uni.setNavigationBarColor({
|
|
33
|
+
frontColor: options.navigationBarTextStyle === "white" ? "#ffffff" : "#000000",
|
|
34
|
+
backgroundColor: options.navigationBarBackgroundColor ?? "#ffffff",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (options.backgroundColor) {
|
|
38
|
+
uni.setBackgroundColor({ backgroundColor: options.backgroundColor });
|
|
39
|
+
}
|
|
40
|
+
if (options.enablePullDownRefresh !== undefined) {
|
|
41
|
+
uni.setBackgroundTextStyle({ textStyle: "dark" });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
setTitle,
|
|
47
|
+
setOptions,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useRefs - 模板 Ref 批量管理
|
|
3
|
+
*
|
|
4
|
+
* 用于 v-for 场景下收集一组动态生成的子元素或子组件引用。
|
|
5
|
+
*/
|
|
6
|
+
import { ref, onBeforeUpdate, onUnmounted } from "vue";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 管理动态 ref 集合,适合 v-for 场景。
|
|
10
|
+
*/
|
|
11
|
+
export function useRefs() {
|
|
12
|
+
const refs = ref<Record<string, any>>({});
|
|
13
|
+
|
|
14
|
+
onBeforeUpdate(() => {
|
|
15
|
+
refs.value = {};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
onUnmounted(() => {
|
|
19
|
+
refs.value = {};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 按 key 生成 ref 回调并写入 refs 集合。
|
|
24
|
+
*/
|
|
25
|
+
const setRefs = (key: string) => (el: any) => {
|
|
26
|
+
if (el) refs.value[key] = el;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return { refs, setRefs };
|
|
30
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useShare - 小程序分享 composable
|
|
3
|
+
*
|
|
4
|
+
* 两层 API 并存:
|
|
5
|
+
* - useShare(config) —— SDK 层:直接注册 onShareAppMessage / onShareTimeline 钩子
|
|
6
|
+
* - useShareConfig(...) —— 业务层:lazy load 后端配置 + cache + fallback resolver
|
|
7
|
+
*/
|
|
8
|
+
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app';
|
|
9
|
+
import { ref } from 'vue';
|
|
10
|
+
import { unwrapPayload, type AdapterPayload } from '../_internal/unwrap';
|
|
11
|
+
|
|
12
|
+
export interface ShareAppMessageContent {
|
|
13
|
+
/** 分享标题 */
|
|
14
|
+
title?: string;
|
|
15
|
+
/** 分享路径,必须是以 / 开头的完整路径 */
|
|
16
|
+
path?: string;
|
|
17
|
+
/** 自定义图片路径 */
|
|
18
|
+
imageUrl?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ShareTimelineContent {
|
|
22
|
+
/** 朋友圈分享标题 */
|
|
23
|
+
title?: string;
|
|
24
|
+
/** 页面携带的 query 参数 */
|
|
25
|
+
query?: string;
|
|
26
|
+
/** 自定义图片路径 */
|
|
27
|
+
imageUrl?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ShareConfig extends ShareAppMessageContent {
|
|
31
|
+
/** 朋友圈专属配置,不填则复用朋友分享配置 */
|
|
32
|
+
timeline?: ShareTimelineContent;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 分享来源。
|
|
37
|
+
* - `button` 页面内转发按钮
|
|
38
|
+
* - `menu` 右上角菜单
|
|
39
|
+
*/
|
|
40
|
+
export type ShareFrom = 'button' | 'menu';
|
|
41
|
+
|
|
42
|
+
export type ShareConfigResolver = (from: ShareFrom) => ShareConfig;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 注册小程序分享钩子。
|
|
46
|
+
*/
|
|
47
|
+
export function useShare(config: ShareConfig | ShareConfigResolver) {
|
|
48
|
+
/**
|
|
49
|
+
* 根据分享来源解析最终分享配置。
|
|
50
|
+
*/
|
|
51
|
+
const resolve = (from: ShareFrom): ShareConfig =>
|
|
52
|
+
typeof config === 'function' ? config(from) : config;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 注册分享给朋友的回调。
|
|
56
|
+
*/
|
|
57
|
+
onShareAppMessage((options: { from?: string } | undefined) => {
|
|
58
|
+
const resolved = resolve((options?.from as ShareFrom) ?? 'menu');
|
|
59
|
+
const payload: ShareAppMessageContent = {};
|
|
60
|
+
if (resolved.title !== undefined) payload.title = resolved.title;
|
|
61
|
+
if (resolved.path !== undefined) payload.path = resolved.path;
|
|
62
|
+
if (resolved.imageUrl !== undefined) payload.imageUrl = resolved.imageUrl;
|
|
63
|
+
return payload;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 注册分享到朋友圈的回调。
|
|
68
|
+
*/
|
|
69
|
+
onShareTimeline(() => {
|
|
70
|
+
const resolved = resolve('menu');
|
|
71
|
+
const timeline = resolved.timeline ?? {};
|
|
72
|
+
const payload: ShareTimelineContent = {};
|
|
73
|
+
const title = timeline.title ?? resolved.title;
|
|
74
|
+
const imageUrl = timeline.imageUrl ?? resolved.imageUrl;
|
|
75
|
+
if (title !== undefined) payload.title = title;
|
|
76
|
+
if (timeline.query !== undefined) payload.query = timeline.query;
|
|
77
|
+
if (imageUrl !== undefined) payload.imageUrl = imageUrl;
|
|
78
|
+
return payload;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* ============================================================
|
|
83
|
+
* useShareConfig —— 业务层:lazy load 后端聚合配置 + cache + fallback resolver
|
|
84
|
+
*
|
|
85
|
+
* 后端按页面 key 聚合返回各页 title / image,前端拉一次缓存全局共用。
|
|
86
|
+
* 返回的 resolver 同时喂给 onShareAppMessage 和 onShareTimeline。
|
|
87
|
+
*
|
|
88
|
+
* 使用方式:
|
|
89
|
+
*
|
|
90
|
+
* 1. App.vue / bootstrap 注入回调:
|
|
91
|
+
*
|
|
92
|
+
* setConfigShare({
|
|
93
|
+
* getConfig: async () => {
|
|
94
|
+
* const res = await getShareConfig();
|
|
95
|
+
* return res.code === 1 ? res.data : null;
|
|
96
|
+
* },
|
|
97
|
+
* });
|
|
98
|
+
*
|
|
99
|
+
* 2. 任意页面 setup:
|
|
100
|
+
*
|
|
101
|
+
* const share = useShareConfig("index", {
|
|
102
|
+
* title: "兜底标题",
|
|
103
|
+
* path: "/pages/index/index",
|
|
104
|
+
* });
|
|
105
|
+
* onShareAppMessage(share);
|
|
106
|
+
* onShareTimeline(share);
|
|
107
|
+
* ============================================================ */
|
|
108
|
+
|
|
109
|
+
/** 单页分享配置 */
|
|
110
|
+
export interface PageShareItem {
|
|
111
|
+
title: string;
|
|
112
|
+
image: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** 后端返回的页面 key → 配置映射 */
|
|
116
|
+
export type ShareConfigMap = Record<string, PageShareItem>;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Adapter 注入接口 —— getConfig 支持「已解包」或「ThinkAdmin envelope」两种返回值。
|
|
120
|
+
* 业务方可以直接传 envelope-returning 接口:setConfigShare({ getConfig: getShareConfig })
|
|
121
|
+
*/
|
|
122
|
+
export interface ShareConfigAdapter {
|
|
123
|
+
getConfig: () => Promise<AdapterPayload<ShareConfigMap>>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** 页面声明的兜底文案 / 跳转路径 */
|
|
127
|
+
export interface PageShareFallback {
|
|
128
|
+
/** 后端 title 为空时用 */
|
|
129
|
+
title: string;
|
|
130
|
+
/** 分享跳转路径(必须 / 开头);path 不走后端,由页面自定义 */
|
|
131
|
+
path: string;
|
|
132
|
+
/** 后端 image 为空时用;通常留空让微信自动截图 */
|
|
133
|
+
image?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** 分享 payload —— 同时喂给 onShareAppMessage / onShareTimeline */
|
|
137
|
+
export interface SharePayload {
|
|
138
|
+
title: string;
|
|
139
|
+
path: string;
|
|
140
|
+
imageUrl?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let shareAdapter: ShareConfigAdapter | null = null;
|
|
144
|
+
const shareCache = ref<ShareConfigMap | null>(null);
|
|
145
|
+
let sharePending: Promise<void> | null = null;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 注入业务回调(应用启动时调用一次;不调用则始终用 fallback)。
|
|
149
|
+
*/
|
|
150
|
+
export function setConfigShare(a: ShareConfigAdapter): void {
|
|
151
|
+
shareAdapter = a;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function loadShareConfig(): Promise<void> {
|
|
155
|
+
if (shareCache.value) return Promise.resolve();
|
|
156
|
+
if (sharePending) return sharePending;
|
|
157
|
+
if (!shareAdapter?.getConfig) {
|
|
158
|
+
console.warn("[useShareConfig] adapter.getConfig 未注入;先调用 setConfigShare()");
|
|
159
|
+
return Promise.resolve();
|
|
160
|
+
}
|
|
161
|
+
sharePending = shareAdapter.getConfig()
|
|
162
|
+
.then((raw) => {
|
|
163
|
+
const cfg = unwrapPayload(raw);
|
|
164
|
+
if (cfg) shareCache.value = cfg;
|
|
165
|
+
})
|
|
166
|
+
.catch((e) => {
|
|
167
|
+
console.warn("[useShareConfig] load failed", e);
|
|
168
|
+
})
|
|
169
|
+
.finally(() => {
|
|
170
|
+
sharePending = null;
|
|
171
|
+
});
|
|
172
|
+
return sharePending;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 业务级分享配置 helper —— 返回 resolver,可直接喂 onShareAppMessage / onShareTimeline。
|
|
177
|
+
* 后端配置异步加载,加载完成前先用 fallback、加载完后自动切到后端值。
|
|
178
|
+
*/
|
|
179
|
+
export function useShareConfig(pageKey: string, fallback: PageShareFallback) {
|
|
180
|
+
void loadShareConfig();
|
|
181
|
+
return (): SharePayload => {
|
|
182
|
+
const remote = shareCache.value?.[pageKey];
|
|
183
|
+
const title = remote?.title || fallback.title;
|
|
184
|
+
const image = remote?.image || fallback.image;
|
|
185
|
+
const payload: SharePayload = { title, path: fallback.path };
|
|
186
|
+
if (image) payload.imageUrl = image;
|
|
187
|
+
return payload;
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useStorage - 本地存储 composable
|
|
3
|
+
*/
|
|
4
|
+
export interface StorageInstance {
|
|
5
|
+
get: <T = unknown>(key: string) => T | null;
|
|
6
|
+
set: <T>(key: string, value: T) => boolean;
|
|
7
|
+
remove: (key: string) => boolean;
|
|
8
|
+
clear: () => boolean;
|
|
9
|
+
info: () => UniApp.GetStorageInfoSuccess | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 本地存储读写工具。
|
|
14
|
+
*/
|
|
15
|
+
export function useStorage(): StorageInstance {
|
|
16
|
+
/**
|
|
17
|
+
* 读取指定 key 的缓存值。
|
|
18
|
+
*/
|
|
19
|
+
function get<T = unknown>(key: string): T | null {
|
|
20
|
+
try {
|
|
21
|
+
const value = uni.getStorageSync(key);
|
|
22
|
+
return value ?? null;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 写入指定 key 的缓存值。
|
|
30
|
+
*/
|
|
31
|
+
function set<T>(key: string, value: T): boolean {
|
|
32
|
+
try {
|
|
33
|
+
uni.setStorageSync(key, value);
|
|
34
|
+
return true;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 删除指定 key 的缓存值。
|
|
42
|
+
*/
|
|
43
|
+
function remove(key: string): boolean {
|
|
44
|
+
try {
|
|
45
|
+
uni.removeStorageSync(key);
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 清空全部本地缓存。
|
|
54
|
+
*/
|
|
55
|
+
function clear(): boolean {
|
|
56
|
+
try {
|
|
57
|
+
uni.clearStorageSync();
|
|
58
|
+
return true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 获取当前缓存使用信息。
|
|
66
|
+
*/
|
|
67
|
+
function info(): UniApp.GetStorageInfoSuccess | null {
|
|
68
|
+
try {
|
|
69
|
+
return uni.getStorageInfoSync();
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { get, set, remove, clear, info };
|
|
76
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type FontScale = "small" | "normal" | "large" | "xlarge";
|
|
1
|
+
export type FontScale = "small" | "compact" | "normal" | "medium" | "large" | "xlarge" | "xxlarge";
|
|
2
2
|
|
|
3
3
|
export interface FontPreset {
|
|
4
4
|
label: string;
|
|
@@ -9,12 +9,19 @@ export const FONT_SCALE_KEY = "hlw_font_scale";
|
|
|
9
9
|
|
|
10
10
|
export const FONT_PRESETS: Record<FontScale, FontPreset> = {
|
|
11
11
|
small: {
|
|
12
|
-
label: "
|
|
12
|
+
label: "较小",
|
|
13
13
|
vars: {
|
|
14
14
|
"--font-xs": "16rpx", "--font-sm": "20rpx", "--font-base": "24rpx",
|
|
15
15
|
"--font-md": "28rpx", "--font-lg": "32rpx", "--font-xl": "36rpx",
|
|
16
16
|
},
|
|
17
17
|
},
|
|
18
|
+
compact: {
|
|
19
|
+
label: "略小",
|
|
20
|
+
vars: {
|
|
21
|
+
"--font-xs": "18rpx", "--font-sm": "22rpx", "--font-base": "26rpx",
|
|
22
|
+
"--font-md": "30rpx", "--font-lg": "34rpx", "--font-xl": "38rpx",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
18
25
|
normal: {
|
|
19
26
|
label: "标准",
|
|
20
27
|
vars: {
|
|
@@ -22,26 +29,40 @@ export const FONT_PRESETS: Record<FontScale, FontPreset> = {
|
|
|
22
29
|
"--font-md": "32rpx", "--font-lg": "36rpx", "--font-xl": "40rpx",
|
|
23
30
|
},
|
|
24
31
|
},
|
|
32
|
+
medium: {
|
|
33
|
+
label: "适中",
|
|
34
|
+
vars: {
|
|
35
|
+
"--font-xs": "22rpx", "--font-sm": "28rpx", "--font-base": "32rpx",
|
|
36
|
+
"--font-md": "36rpx", "--font-lg": "42rpx", "--font-xl": "46rpx",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
25
39
|
large: {
|
|
26
|
-
label: "
|
|
40
|
+
label: "较大",
|
|
27
41
|
vars: {
|
|
28
42
|
"--font-xs": "24rpx", "--font-sm": "30rpx", "--font-base": "34rpx",
|
|
29
43
|
"--font-md": "40rpx", "--font-lg": "46rpx", "--font-xl": "52rpx",
|
|
30
44
|
},
|
|
31
45
|
},
|
|
32
46
|
xlarge: {
|
|
33
|
-
label: "
|
|
47
|
+
label: "超大",
|
|
34
48
|
vars: {
|
|
35
49
|
"--font-xs": "28rpx", "--font-sm": "36rpx", "--font-base": "42rpx",
|
|
36
50
|
"--font-md": "48rpx", "--font-lg": "56rpx", "--font-xl": "64rpx",
|
|
37
51
|
},
|
|
38
52
|
},
|
|
53
|
+
xxlarge: {
|
|
54
|
+
label: "特大",
|
|
55
|
+
vars: {
|
|
56
|
+
"--font-xs": "32rpx", "--font-sm": "42rpx", "--font-base": "48rpx",
|
|
57
|
+
"--font-md": "56rpx", "--font-lg": "64rpx", "--font-xl": "72rpx",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
39
60
|
};
|
|
40
61
|
|
|
41
62
|
export function getCurrentFontScale(): FontScale {
|
|
42
63
|
try {
|
|
43
64
|
const v = uni.getStorageSync(FONT_SCALE_KEY);
|
|
44
|
-
if (v === "small" || v === "large" || v === "xlarge") return v;
|
|
65
|
+
if (v === "small" || v === "compact" || v === "medium" || v === "large" || v === "xlarge" || v === "xxlarge") return v;
|
|
45
66
|
} catch {}
|
|
46
67
|
return "normal";
|
|
47
68
|
}
|