@hlw-uni/mp-vue 1.2.26 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +33 -0
- package/dist/composables/ad/index.d.ts +134 -0
- package/dist/composables/color/index.d.ts +8 -0
- package/dist/composables/contact/index.d.ts +28 -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 +67 -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 +1722 -63
- package/dist/index.mjs +1721 -62
- package/package.json +5 -3
- package/src/app.ts +173 -0
- package/src/components/hlw-ad/index.vue +74 -30
- package/src/composables/ad/index.ts +386 -0
- package/src/composables/color/index.ts +44 -0
- package/src/composables/contact/index.ts +88 -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 +185 -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,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useFormat — 格式化工具 composable
|
|
3
|
+
*/
|
|
4
|
+
export function useFormat() {
|
|
5
|
+
/**
|
|
6
|
+
* 按指定模板格式化日期时间。
|
|
7
|
+
*/
|
|
8
|
+
function date(date: Date | number | string, format = "YYYY-MM-DD HH:mm:ss"): string {
|
|
9
|
+
const d = new Date(date);
|
|
10
|
+
if (isNaN(d.getTime())) return "";
|
|
11
|
+
|
|
12
|
+
const year = d.getFullYear();
|
|
13
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
14
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
15
|
+
const hours = String(d.getHours()).padStart(2, "0");
|
|
16
|
+
const minutes = String(d.getMinutes()).padStart(2, "0");
|
|
17
|
+
const seconds = String(d.getSeconds()).padStart(2, "0");
|
|
18
|
+
|
|
19
|
+
return format.replace("YYYY", String(year)).replace("MM", month).replace("DD", day).replace("HH", hours).replace("mm", minutes).replace("ss", seconds);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 将字节数格式化为更易读的文件大小。
|
|
24
|
+
*/
|
|
25
|
+
function fileSize(bytes: number): string {
|
|
26
|
+
if (bytes === 0) return "0 B";
|
|
27
|
+
const k = 1024;
|
|
28
|
+
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
|
29
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
30
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 对手机号中间四位进行脱敏。
|
|
35
|
+
*/
|
|
36
|
+
function phone(value: string): string {
|
|
37
|
+
return value.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 按指定精度与千分位格式输出金额。
|
|
42
|
+
*/
|
|
43
|
+
function money(amount: number, decimals = 2, decPoint = ".", thousandsSep = ","): string {
|
|
44
|
+
return amount.toFixed(decimals).replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSep);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { date, fileSize, phone, money };
|
|
48
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alist 上传适配器。
|
|
3
|
+
*/
|
|
4
|
+
import type { UploadAdapter } from "./base";
|
|
5
|
+
|
|
6
|
+
export const alistAdapter: UploadAdapter = {
|
|
7
|
+
name: "alist",
|
|
8
|
+
/**
|
|
9
|
+
* 生成 Alist 所需的表单字段。
|
|
10
|
+
*/
|
|
11
|
+
buildFormData(ctx) {
|
|
12
|
+
const c = ctx.credentials ?? {};
|
|
13
|
+
return {
|
|
14
|
+
"file-path": c["file-path"] ?? ctx.fileName,
|
|
15
|
+
authorization: c["token"] ?? "",
|
|
16
|
+
...(ctx.extraData ?? {}),
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 云存储上传适配器 - 上传上下文
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** 上传上下文。 */
|
|
6
|
+
export interface UploadContext {
|
|
7
|
+
filePath: string;
|
|
8
|
+
fileName: string;
|
|
9
|
+
/** 云存储密钥凭证 */
|
|
10
|
+
credentials?: Record<string, string>;
|
|
11
|
+
/** 额外表单数据 */
|
|
12
|
+
extraData?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** 云存储适配器接口。 */
|
|
16
|
+
export interface UploadAdapter {
|
|
17
|
+
/** 适配器名称 */
|
|
18
|
+
name: string;
|
|
19
|
+
/** 根据上下文构建表单数据 */
|
|
20
|
+
buildFormData(ctx: UploadContext): Record<string, string | number>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 腾讯云 COS 上传适配器。
|
|
3
|
+
*/
|
|
4
|
+
import type { UploadAdapter } from "./base";
|
|
5
|
+
|
|
6
|
+
export const cosAdapter: UploadAdapter = {
|
|
7
|
+
name: "cos",
|
|
8
|
+
/**
|
|
9
|
+
* 生成 COS 直传所需的表单字段。
|
|
10
|
+
*/
|
|
11
|
+
buildFormData(ctx) {
|
|
12
|
+
const c = ctx.credentials ?? {};
|
|
13
|
+
return {
|
|
14
|
+
"q-ak": c["ak"] ?? "",
|
|
15
|
+
policy: c["policy"] ?? "",
|
|
16
|
+
"q-key-time": c["key-time"] ?? "",
|
|
17
|
+
"q-signature": c["signature"] ?? "",
|
|
18
|
+
"Content-Disposition": `inline;filename=${encodeURIComponent(ctx.fileName)}`,
|
|
19
|
+
success_action_status: 200,
|
|
20
|
+
...(ctx.extraData ?? {}),
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 云存储上传适配器统一导出
|
|
3
|
+
*/
|
|
4
|
+
export { cosAdapter } from "./cos";
|
|
5
|
+
export { ossAdapter } from "./oss";
|
|
6
|
+
export { qiniuAdapter } from "./qiniu";
|
|
7
|
+
export { alistAdapter } from "./alist";
|
|
8
|
+
export type { UploadAdapter, UploadContext } from "./base";
|
|
9
|
+
|
|
10
|
+
import { cosAdapter } from "./cos";
|
|
11
|
+
import { ossAdapter } from "./oss";
|
|
12
|
+
import { qiniuAdapter } from "./qiniu";
|
|
13
|
+
import { alistAdapter } from "./alist";
|
|
14
|
+
import type { UploadAdapter } from "./base";
|
|
15
|
+
|
|
16
|
+
/** 所有已注册的上传适配器。 */
|
|
17
|
+
export const adapters: Record<string, UploadAdapter> = {
|
|
18
|
+
cos: cosAdapter,
|
|
19
|
+
oss: ossAdapter,
|
|
20
|
+
qiniu: qiniuAdapter,
|
|
21
|
+
alist: alistAdapter,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 按名称获取上传适配器,不存在时抛错。
|
|
26
|
+
*/
|
|
27
|
+
export function getAdapter(name: string): UploadAdapter {
|
|
28
|
+
const adapter = adapters[name];
|
|
29
|
+
if (!adapter) throw new Error(`[hlw] Unknown upload adapter: ${name}`);
|
|
30
|
+
return adapter;
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 阿里云 OSS 上传适配器。
|
|
3
|
+
*/
|
|
4
|
+
import type { UploadAdapter } from "./base";
|
|
5
|
+
|
|
6
|
+
export const ossAdapter: UploadAdapter = {
|
|
7
|
+
name: "oss",
|
|
8
|
+
/**
|
|
9
|
+
* 生成 OSS 直传所需的表单字段。
|
|
10
|
+
*/
|
|
11
|
+
buildFormData(ctx) {
|
|
12
|
+
const c = ctx.credentials ?? {};
|
|
13
|
+
return {
|
|
14
|
+
policy: c["policy"] ?? "",
|
|
15
|
+
signature: c["signature"] ?? "",
|
|
16
|
+
OSSAccessKeyId: c["accessKeyId"] ?? "",
|
|
17
|
+
success_action_status: 200,
|
|
18
|
+
"Content-Disposition": `inline;filename=${encodeURIComponent(ctx.fileName)}`,
|
|
19
|
+
...(ctx.extraData ?? {}),
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 七牛云上传适配器。
|
|
3
|
+
*/
|
|
4
|
+
import type { UploadAdapter } from "./base";
|
|
5
|
+
|
|
6
|
+
export const qiniuAdapter: UploadAdapter = {
|
|
7
|
+
name: "qiniu",
|
|
8
|
+
/**
|
|
9
|
+
* 生成七牛直传所需的表单字段。
|
|
10
|
+
*/
|
|
11
|
+
buildFormData(ctx) {
|
|
12
|
+
const c = ctx.credentials ?? {};
|
|
13
|
+
return {
|
|
14
|
+
token: c["token"] ?? "",
|
|
15
|
+
key: c["key"] ?? ctx.fileName,
|
|
16
|
+
...(ctx.extraData ?? {}),
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HttpClient - Axios 风格的 HTTP 客户端
|
|
3
|
+
* 支持请求/响应拦截器、组件内请求 composable、上传策略模式。
|
|
4
|
+
*/
|
|
5
|
+
import { ref } from 'vue';
|
|
6
|
+
import type {
|
|
7
|
+
ApiResponse,
|
|
8
|
+
RequestConfig,
|
|
9
|
+
RequestInterceptor,
|
|
10
|
+
ResponseInterceptor,
|
|
11
|
+
ErrorInterceptor,
|
|
12
|
+
UploadConfig,
|
|
13
|
+
UploadResult,
|
|
14
|
+
} from './types';
|
|
15
|
+
import { getAdapter } from './adapters';
|
|
16
|
+
|
|
17
|
+
/** 组件内请求返回的状态对象。 */
|
|
18
|
+
export interface UseRequestReturn<T = unknown> {
|
|
19
|
+
loading: ReturnType<typeof ref<boolean>>;
|
|
20
|
+
data: ReturnType<typeof ref<T | null>>;
|
|
21
|
+
error: ReturnType<typeof ref<Error | null>>;
|
|
22
|
+
run: (config: RequestConfig) => Promise<ApiResponse<T>>;
|
|
23
|
+
get: (url: string, data?: unknown) => Promise<ApiResponse<T>>;
|
|
24
|
+
post: (url: string, data?: unknown) => Promise<ApiResponse<T>>;
|
|
25
|
+
put: (url: string, data?: unknown) => Promise<ApiResponse<T>>;
|
|
26
|
+
del: (url: string, data?: unknown) => Promise<ApiResponse<T>>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class HttpClient {
|
|
30
|
+
private _reqInterceptors: RequestInterceptor[] = [];
|
|
31
|
+
private _resInterceptors: ResponseInterceptor[] = [];
|
|
32
|
+
private _errInterceptors: ErrorInterceptor[] = [];
|
|
33
|
+
private _baseURL: string;
|
|
34
|
+
private _defaultHeaders: Record<string, string>;
|
|
35
|
+
private _noCache: boolean;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 创建 HttpClient 实例并初始化默认配置。
|
|
39
|
+
*/
|
|
40
|
+
constructor(options: { baseURL?: string; headers?: Record<string, string>; noCache?: boolean } = {}) {
|
|
41
|
+
this._baseURL = options.baseURL ?? '';
|
|
42
|
+
this._noCache = options.noCache ?? true;
|
|
43
|
+
this._defaultHeaders = {
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
...options.headers,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 运行时设置 baseURL。 */
|
|
50
|
+
setBaseURL(url: string): void {
|
|
51
|
+
this._baseURL = url;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 注册请求拦截器,并返回注销函数。 */
|
|
55
|
+
onRequest(fn: RequestInterceptor): () => void {
|
|
56
|
+
this._reqInterceptors.push(fn);
|
|
57
|
+
return () => {
|
|
58
|
+
const idx = this._reqInterceptors.indexOf(fn);
|
|
59
|
+
if (idx > -1) this._reqInterceptors.splice(idx, 1);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 注册响应拦截器,并返回注销函数。 */
|
|
64
|
+
onResponse<T = unknown>(fn: ResponseInterceptor<T>): () => void {
|
|
65
|
+
this._resInterceptors.push(fn as ResponseInterceptor);
|
|
66
|
+
return () => {
|
|
67
|
+
const idx = this._resInterceptors.indexOf(fn as ResponseInterceptor);
|
|
68
|
+
if (idx > -1) this._resInterceptors.splice(idx, 1);
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 注册错误拦截器,并返回注销函数。 */
|
|
73
|
+
onError(fn: ErrorInterceptor): () => void {
|
|
74
|
+
this._errInterceptors.push(fn);
|
|
75
|
+
return () => {
|
|
76
|
+
const idx = this._errInterceptors.indexOf(fn);
|
|
77
|
+
if (idx > -1) this._errInterceptors.splice(idx, 1);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 执行一次全局请求,自动串联请求与响应拦截器。
|
|
83
|
+
*/
|
|
84
|
+
async request<T = unknown>(config: RequestConfig): Promise<ApiResponse<T>> {
|
|
85
|
+
let cfg: RequestConfig = {
|
|
86
|
+
method: 'GET',
|
|
87
|
+
...config,
|
|
88
|
+
headers: { ...this._defaultHeaders, ...config.headers },
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
for (const fn of this._reqInterceptors) {
|
|
92
|
+
cfg = await fn(cfg);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const fullUrl = this._buildUrl(cfg.url);
|
|
97
|
+
const res = await this._doRequest<T>(fullUrl, cfg);
|
|
98
|
+
|
|
99
|
+
for (const fn of this._resInterceptors) {
|
|
100
|
+
const modified = await fn(res as ApiResponse<unknown>);
|
|
101
|
+
if (modified !== undefined) return modified as ApiResponse<T>;
|
|
102
|
+
}
|
|
103
|
+
return res;
|
|
104
|
+
} catch (e) {
|
|
105
|
+
const err = e as Error;
|
|
106
|
+
await this._applyErrorInterceptors(err);
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 创建组件内可复用的请求状态对象。
|
|
113
|
+
*/
|
|
114
|
+
useRequest<T = unknown>(): UseRequestReturn<T> {
|
|
115
|
+
const loading = ref(false);
|
|
116
|
+
const data = ref<T | null>(null);
|
|
117
|
+
const error = ref<Error | null>(null);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 执行一次请求并维护 loading、data、error 状态。
|
|
121
|
+
*/
|
|
122
|
+
const run = async (cfg: RequestConfig): Promise<ApiResponse<T>> => {
|
|
123
|
+
loading.value = true;
|
|
124
|
+
error.value = null;
|
|
125
|
+
try {
|
|
126
|
+
const res = await this.request<T>(cfg);
|
|
127
|
+
data.value = res.data as T;
|
|
128
|
+
return res;
|
|
129
|
+
} catch (e) {
|
|
130
|
+
error.value = e as Error;
|
|
131
|
+
throw e;
|
|
132
|
+
} finally {
|
|
133
|
+
loading.value = false;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/** 发起 GET 请求。 */
|
|
138
|
+
const get = (url: string, d?: unknown) => run({ url, method: 'GET', data: d });
|
|
139
|
+
/** 发起 POST 请求。 */
|
|
140
|
+
const post = (url: string, d?: unknown) => run({ url, method: 'POST', data: d });
|
|
141
|
+
/** 发起 PUT 请求。 */
|
|
142
|
+
const put = (url: string, d?: unknown) => run({ url, method: 'PUT', data: d });
|
|
143
|
+
/** 发起 DELETE 请求。 */
|
|
144
|
+
const del = (url: string, d?: unknown) => run({ url, method: 'DELETE', data: d });
|
|
145
|
+
|
|
146
|
+
return { loading, data, error, run, get, post, put, del };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 根据上传类型选择适配器并执行文件上传。
|
|
151
|
+
*/
|
|
152
|
+
upload(config: UploadConfig): Promise<UploadResult> {
|
|
153
|
+
const fileName = config.fileName ?? config.filePath.split('/').pop() ?? 'file';
|
|
154
|
+
|
|
155
|
+
let server = config.server;
|
|
156
|
+
if (config.type === 'local' && config.url) server = config.url;
|
|
157
|
+
|
|
158
|
+
const formData = config.type === 'local'
|
|
159
|
+
? (config.credentials ?? {})
|
|
160
|
+
: getAdapter(config.type).buildFormData({
|
|
161
|
+
filePath: config.filePath,
|
|
162
|
+
fileName,
|
|
163
|
+
credentials: config.credentials,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
uni.uploadFile({
|
|
168
|
+
url: server,
|
|
169
|
+
filePath: config.filePath,
|
|
170
|
+
name: 'file',
|
|
171
|
+
formData: formData as unknown as UniNamespace.UploadFileOption['formData'],
|
|
172
|
+
header: config.header as Record<string, string>,
|
|
173
|
+
success: (res) => {
|
|
174
|
+
if (res.statusCode === 200) {
|
|
175
|
+
try {
|
|
176
|
+
const body = JSON.parse(res.data);
|
|
177
|
+
resolve({ code: body.code ?? 1, msg: body.message ?? '上传成功', data: body.data ?? '' });
|
|
178
|
+
} catch {
|
|
179
|
+
resolve({ code: 1, msg: '上传成功', data: res.data });
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
reject(new Error('上传失败'));
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
fail: (err) => reject(new Error(err.errMsg || '上传失败')),
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 拼接 baseURL,并在开启防缓存时追加时间戳参数。
|
|
192
|
+
*/
|
|
193
|
+
private _buildUrl(url: string): string {
|
|
194
|
+
if (/^https?:\/\//.test(url)) return url;
|
|
195
|
+
const full = `${this._baseURL}${url}`;
|
|
196
|
+
if (!this._noCache) return full;
|
|
197
|
+
const sep = full.includes('?') ? '&' : '?';
|
|
198
|
+
return `${full}${sep}_t=${Date.now()}`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 调用 uni.request 发起底层网络请求。
|
|
203
|
+
*/
|
|
204
|
+
private async _doRequest<T>(url: string, cfg: RequestConfig): Promise<ApiResponse<T>> {
|
|
205
|
+
return new Promise((resolve, reject) => {
|
|
206
|
+
uni.request({
|
|
207
|
+
url,
|
|
208
|
+
method: cfg.method,
|
|
209
|
+
data: cfg.data as UniNamespace.RequestOptions['data'],
|
|
210
|
+
header: cfg.headers as Record<string, string>,
|
|
211
|
+
success: (res) => {
|
|
212
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
213
|
+
resolve(res.data as ApiResponse<T>);
|
|
214
|
+
} else {
|
|
215
|
+
const msg = (res.data as Record<string, unknown>)?.info ?? `请求失败: ${res.statusCode}`;
|
|
216
|
+
reject(new Error(String(msg)));
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
fail: (err) => reject(new Error(err.errMsg || '网络请求失败')),
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 顺序执行已注册的错误拦截器。
|
|
226
|
+
*/
|
|
227
|
+
private async _applyErrorInterceptors(err: Error): Promise<void> {
|
|
228
|
+
for (const fn of this._errInterceptors) {
|
|
229
|
+
await fn(err);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 全局 HTTP 实例。
|
|
236
|
+
*/
|
|
237
|
+
export const http = new HttpClient();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP 模块统一导出
|
|
3
|
+
*/
|
|
4
|
+
export { http, HttpClient } from "./client";
|
|
5
|
+
export { useRequest, useUpload } from "./useRequest";
|
|
6
|
+
export type { ApiResponse, PageResult, RequestConfig, RequestInterceptor, ResponseInterceptor, ErrorInterceptor, UploadConfig, UploadResult } from "./types";
|
|
7
|
+
export type { UseRequestReturn } from "./client";
|
|
8
|
+
export * from "./adapters";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP 类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** API 统一响应格式 */
|
|
6
|
+
export interface ApiResponse<T = unknown> {
|
|
7
|
+
code: number;
|
|
8
|
+
data: T;
|
|
9
|
+
info: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** 分页数据 */
|
|
13
|
+
export interface PageResult<T = unknown> {
|
|
14
|
+
list: T[];
|
|
15
|
+
total: number;
|
|
16
|
+
page: number;
|
|
17
|
+
pageSize: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 请求配置 */
|
|
21
|
+
export interface RequestConfig {
|
|
22
|
+
url: string;
|
|
23
|
+
method?: "GET" | "POST" | "PUT" | "DELETE";
|
|
24
|
+
data?: unknown;
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
timeout?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 拦截器类型 */
|
|
30
|
+
export type RequestInterceptor = (config: RequestConfig) => RequestConfig | Promise<RequestConfig>;
|
|
31
|
+
export type ResponseInterceptor<T = unknown> = (res: ApiResponse<T>) => ApiResponse<T> | void | Promise<ApiResponse<T> | void>;
|
|
32
|
+
export type ErrorInterceptor = (err: Error) => void | Error | Promise<void | Error>;
|
|
33
|
+
|
|
34
|
+
/** 上传配置 */
|
|
35
|
+
export interface UploadConfig {
|
|
36
|
+
/** 上传接口地址 */
|
|
37
|
+
server: string;
|
|
38
|
+
/** 文件路径 */
|
|
39
|
+
filePath: string;
|
|
40
|
+
/** 文件名,可选,默认取 filePath */
|
|
41
|
+
fileName?: string;
|
|
42
|
+
/** 上传类型,支持 cos | oss | qiniu | alist | local */
|
|
43
|
+
type: string;
|
|
44
|
+
/** 云存储密钥凭证 */
|
|
45
|
+
credentials?: Record<string, string>;
|
|
46
|
+
/** 自定义请求头 */
|
|
47
|
+
header?: Record<string, string>;
|
|
48
|
+
/** 上传类型为 local 时,作为最终 server */
|
|
49
|
+
url?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** 上传结果 */
|
|
53
|
+
export interface UploadResult {
|
|
54
|
+
code: number;
|
|
55
|
+
msg: string;
|
|
56
|
+
data: string;
|
|
57
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useRequest - 组件内请求 composable
|
|
3
|
+
*/
|
|
4
|
+
import { http } from './client';
|
|
5
|
+
import type { RequestConfig, ApiResponse } from './types';
|
|
6
|
+
import { ref } from 'vue';
|
|
7
|
+
|
|
8
|
+
export interface UseRequestOptions<T = unknown> {
|
|
9
|
+
/** 初始数据 */
|
|
10
|
+
initialData?: T | null;
|
|
11
|
+
/** 手动触发,不自动请求 */
|
|
12
|
+
manual?: boolean;
|
|
13
|
+
/** 成功回调 */
|
|
14
|
+
onSuccess?: (data: T, res: ApiResponse<T>) => void;
|
|
15
|
+
/** 失败回调 */
|
|
16
|
+
onError?: (err: Error) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 创建带有 loading、data、error 状态的请求方法集。
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const { loading, data, error, run } = useRequest<User>();
|
|
24
|
+
* run({ url: '/user/info', method: 'GET' });
|
|
25
|
+
*/
|
|
26
|
+
export function useRequest<T = unknown>(options: UseRequestOptions<T> = {}) {
|
|
27
|
+
const { initialData = null, manual = false, onSuccess, onError } = options;
|
|
28
|
+
|
|
29
|
+
const loading = ref(false);
|
|
30
|
+
const data = ref<T | null>(initialData);
|
|
31
|
+
const error = ref<Error | null>(null);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 执行一次自定义请求配置。
|
|
35
|
+
*/
|
|
36
|
+
async function run(config: RequestConfig): Promise<ApiResponse<T>> {
|
|
37
|
+
loading.value = true;
|
|
38
|
+
error.value = null;
|
|
39
|
+
try {
|
|
40
|
+
const res = await http.request<T>(config);
|
|
41
|
+
data.value = res.data as T;
|
|
42
|
+
onSuccess?.(res.data as T, res);
|
|
43
|
+
return res;
|
|
44
|
+
} catch (e) {
|
|
45
|
+
error.value = e as Error;
|
|
46
|
+
onError?.(e as Error);
|
|
47
|
+
throw e;
|
|
48
|
+
} finally {
|
|
49
|
+
loading.value = false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 发起 GET 请求。
|
|
55
|
+
*/
|
|
56
|
+
function get(url: string, data?: unknown) {
|
|
57
|
+
return run({ url, method: 'GET', data });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 发起 POST 请求。
|
|
62
|
+
*/
|
|
63
|
+
function post(url: string, data?: unknown) {
|
|
64
|
+
return run({ url, method: 'POST', data });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 发起 PUT 请求。
|
|
69
|
+
*/
|
|
70
|
+
function put(url: string, data?: unknown) {
|
|
71
|
+
return run({ url, method: 'PUT', data });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 发起 DELETE 请求。
|
|
76
|
+
*/
|
|
77
|
+
function del(url: string, data?: unknown) {
|
|
78
|
+
return run({ url, method: 'DELETE', data });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!manual) {
|
|
82
|
+
// 空配置时保持手动触发,避免误请求。
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { loading, data, error, run, get, post, put, del };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 上传文件状态管理 composable。
|
|
90
|
+
*/
|
|
91
|
+
export function useUpload() {
|
|
92
|
+
const uploading = ref(false);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 调用全局 http.upload 执行文件上传。
|
|
96
|
+
*/
|
|
97
|
+
async function upload(options: Parameters<typeof http.upload>[0]) {
|
|
98
|
+
uploading.value = true;
|
|
99
|
+
try {
|
|
100
|
+
return await http.upload(options);
|
|
101
|
+
} finally {
|
|
102
|
+
uploading.value = false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { uploading, upload };
|
|
107
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composables 统一导出
|
|
3
|
+
*/
|
|
4
|
+
export * from "./http";
|
|
5
|
+
export { useLoading } from "./loading";
|
|
6
|
+
export { useMsg, type HlwMsg, type ToastOptions, type ModalOptions, type ToastIcon } from "./msg";
|
|
7
|
+
export { useDevice, deviceToQuery, clearDeviceCache, type DeviceInfo } from "./device";
|
|
8
|
+
export { useRefs } from "./refs";
|
|
9
|
+
export { usePageMeta } from "./page-meta";
|
|
10
|
+
export { useStorage, type StorageInstance } from "./storage";
|
|
11
|
+
export { useValidate } from "./validate";
|
|
12
|
+
export { useFormat } from "./format";
|
|
13
|
+
export {
|
|
14
|
+
useAd,
|
|
15
|
+
configAd,
|
|
16
|
+
destroyAds,
|
|
17
|
+
confirmReward,
|
|
18
|
+
type AdType,
|
|
19
|
+
type AdConfig,
|
|
20
|
+
type AdError,
|
|
21
|
+
type AdAdapter,
|
|
22
|
+
type AdCloseResult,
|
|
23
|
+
type AdClaimResult,
|
|
24
|
+
type AdClaimFn,
|
|
25
|
+
} from "./ad";
|
|
26
|
+
export {
|
|
27
|
+
useShare,
|
|
28
|
+
useShareConfig,
|
|
29
|
+
configShare,
|
|
30
|
+
type ShareConfig,
|
|
31
|
+
type ShareConfigResolver,
|
|
32
|
+
type ShareFrom,
|
|
33
|
+
type ShareAppMessageContent,
|
|
34
|
+
type ShareTimelineContent,
|
|
35
|
+
type ShareConfigMap,
|
|
36
|
+
type ShareConfigAdapter,
|
|
37
|
+
type PageShareItem,
|
|
38
|
+
type PageShareFallback,
|
|
39
|
+
type SharePayload,
|
|
40
|
+
} from "./share";
|
|
41
|
+
export {
|
|
42
|
+
useContact,
|
|
43
|
+
configContact,
|
|
44
|
+
type ContactConfig,
|
|
45
|
+
type ContactAdapter,
|
|
46
|
+
type ContactBindProps,
|
|
47
|
+
} from "./contact";
|
|
48
|
+
export { useUtils, type DownloadFileOptions, type DownloadFileResult, type TapEvent } from "./utils";
|
|
49
|
+
export { useColor } from "./color";
|
|
50
|
+
export { useRouter, type NavigateType, type NavigateOptions } from "./navigator";
|
|
51
|
+
|
|
52
|
+
// Theme(mp-vue 自带)
|
|
53
|
+
export type { FontScale, FontPreset } from "./theme";
|
|
54
|
+
export {
|
|
55
|
+
FONT_SCALE_KEY,
|
|
56
|
+
FONT_PRESETS,
|
|
57
|
+
getCurrentFontScale,
|
|
58
|
+
getCurrentFontVars,
|
|
59
|
+
THEME_CHANGE_EVENT,
|
|
60
|
+
buildThemeStyle,
|
|
61
|
+
useThemePageStyle,
|
|
62
|
+
} from "./theme";
|
|
63
|
+
export type { ThemeColor } from "./theme";
|
|
64
|
+
export {
|
|
65
|
+
THEME_COLOR_KEY,
|
|
66
|
+
THEME_SEMANTIC_COLORS,
|
|
67
|
+
DEFAULT_THEMES,
|
|
68
|
+
getCurrentThemeColor,
|
|
69
|
+
getCurrentThemeVars,
|
|
70
|
+
} from "./theme";
|
|
71
|
+
export type { Appearance, AppearanceMode, AppearancePreset } from "./theme";
|
|
72
|
+
export {
|
|
73
|
+
APPEARANCE_KEY,
|
|
74
|
+
APPEARANCE_PRESETS,
|
|
75
|
+
APPEARANCE_VAR_MAP,
|
|
76
|
+
getCurrentAppearance,
|
|
77
|
+
getCurrentAppearanceMode,
|
|
78
|
+
getCurrentAppearanceVars,
|
|
79
|
+
resolveAppearance,
|
|
80
|
+
} from "./theme";
|
|
81
|
+
export type { TypographyRole } from "./theme";
|
|
82
|
+
export { TYPOGRAPHY_ROLES, getCurrentTypographyVars } from "./theme";
|