feat(all): 新增工具

This commit is contained in:
2026-05-28 11:30:35 +08:00
parent 5dbedcaf0f
commit 3a55edfafc
85 changed files with 2902 additions and 2591 deletions
+12
View File
@@ -0,0 +1,12 @@
# @r-utils/uni-app
## 1.4.0
### Minor Changes
- 添加工具
### Patch Changes
- Updated dependencies
- @r-utils/common@1.4.0
+93
View File
@@ -0,0 +1,93 @@
# @r-utils/uni-app
仅用于 uni-app 项目的工具包,封装请求、上传、NFC、蓝牙、打印和常用 uni API 辅助方法。
## 适用范围
- 适用于 uni-app 项目(含 uni 运行时能力)。
- 适用于需要复用请求、上传、蓝牙、NFC、打印等能力的业务项目。
## 不适用范围
- 不适用于普通 Web Vue2/Vue3 项目。
- 不适用于没有 `uni` 运行时的纯 Node.js 环境。
## 安装
```bash
pnpm add @r-utils/uni-app
```
## 导入方式
### 推荐:根入口导入
大多数场景推荐从根入口导入,路径简单,使用心智负担更低。
```ts
import { Request, showToast, toPromise, upload, Printer } from "@r-utils/uni-app";
```
### 兼容:子路径导入
如果你希望模块边界更清晰,也可以使用子路径导入。两种方式都支持。
```ts
import { Request } from "@r-utils/uni-app/request";
import { showToast, toPromise } from "@r-utils/uni-app/uni-helper";
import { upload } from "@r-utils/uni-app/upload";
import { Printer } from "@r-utils/uni-app/printer";
import { nfcScan } from "@r-utils/uni-app/nfc";
import { BluetoothUtils } from "@r-utils/uni-app/bluetooth-utils";
```
## 导出模块
| 子路径 | 说明 |
| --- | --- |
| `@r-utils/uni-app/request` | 类 axios 的 `uni.request` 封装 |
| `@r-utils/uni-app/uni-helper` | 常用 uni API 辅助函数 |
| `@r-utils/uni-app/upload` | 上传相关工具 |
| `@r-utils/uni-app/printer` | 打印机相关工具 |
| `@r-utils/uni-app/nfc` | NFC 相关工具 |
| `@r-utils/uni-app/bluetooth-utils` | 蓝牙相关工具 |
## 使用示例
### 请求封装
```ts
import { Request } from "@r-utils/uni-app";
const request = new Request({
baseURL: "https://api.example.com",
});
const res = await request.get("/users", {
data: { pageNum: 1, pageSize: 10 },
});
```
### 使用 showToast
```ts
import { showToast } from "@r-utils/uni-app";
showToast("保存成功", "success");
```
### 子路径导入请求模块
```ts
import { Request } from "@r-utils/uni-app/request";
const request = new Request({
baseURL: "https://api.example.com",
});
```
## 注意事项
- 推荐优先使用根入口导入;如果需要更精确的模块边界,也可以使用子路径导入。
- 本包依赖 uni-app 运行时能力,需在真实 uni-app 环境中验证平台相关功能。
- NFC、蓝牙、打印功能通常依赖具体平台和设备能力,建议按目标端单独测试。
+36 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@r-utils/uni-app",
"version": "1.3.0",
"version": "1.4.0",
"private": false,
"description": "uni-app工具库",
"type": "module",
@@ -8,13 +8,45 @@
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"sideEffects": false,
"files": [
"dist"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./*": "./*"
"./bluetooth-utils": {
"types": "./dist/bluetooth-utils/index.d.ts",
"import": "./dist/bluetooth-utils/index.mjs",
"require": "./dist/bluetooth-utils/index.cjs"
},
"./nfc": {
"types": "./dist/nfc/index.d.ts",
"import": "./dist/nfc/index.mjs",
"require": "./dist/nfc/index.cjs"
},
"./printer": {
"types": "./dist/printer/index.d.ts",
"import": "./dist/printer/index.mjs",
"require": "./dist/printer/index.cjs"
},
"./request": {
"types": "./dist/request/index.d.ts",
"import": "./dist/request/index.mjs",
"require": "./dist/request/index.cjs"
},
"./uni-helper": {
"types": "./dist/uni-helper/index.d.ts",
"import": "./dist/uni-helper/index.mjs",
"require": "./dist/uni-helper/index.cjs"
},
"./upload": {
"types": "./dist/upload/index.d.ts",
"import": "./dist/upload/index.mjs",
"require": "./dist/upload/index.cjs"
}
},
"keywords": [
"uni-app"
@@ -43,8 +75,9 @@
"scripts": {
"build": "vite build",
"watch": "vite build --watch",
"typecheck": "tsc -p tsconfig.json --noEmit",
"lint": "eslint --ext .js,ts --fix src",
"release": "standard-version",
"format": "prettier --write src",
"commit": "cz",
"lint-staged": "lint-staged",
"test": "vitest run"
@@ -1,4 +1,5 @@
import { toPromise, showToast } from "@/uni-helper";
/* eslint-disable no-useless-assignment, @typescript-eslint/no-unused-vars */
import { showToast } from "@/uni-helper";
const systemInfo = uni.getSystemInfoSync();
const isAndroidApp =
@@ -237,7 +238,7 @@ export class BluetoothUtils {
res = await cb();
} else {
// 失败重连5次
for (var i = 1; i <= 5; i++) {
for (let i = 1; i <= 5; i++) {
try {
await uni.createBLEConnection({
deviceId: device.deviceId,
@@ -397,12 +398,12 @@ export class BluetoothUtils {
private static async _getBLEMTU(device: Device) {
console.log("_getBLEMTU", device);
// return new Promise<number>(async (resolve, reject) => {
return uni.getBLEMTU({
deviceId: device.deviceId,
// success: (res) => {
// resolve(res.mtu);
// },
});
return uni.getBLEMTU({
deviceId: device.deviceId,
// success: (res) => {
// resolve(res.mtu);
// },
});
// });
}
+11 -6
View File
@@ -1,3 +1,4 @@
/* eslint-disable no-useless-assignment, @typescript-eslint/no-unused-vars */
const systemInfo = uni.getSystemInfoSync();
const isAndroid =
systemInfo.uniPlatform === "app" && systemInfo.osName === "android";
@@ -195,12 +196,16 @@ export class AndroidNfcUtil {
static async scan() {
const androidNfcUtil = new AndroidNfcUtil();
return await new Promise(async (resolve, reject) => {
await androidNfcUtil.startNfcScan();
androidNfcUtil.addDiscoveredListener((res) => {
androidNfcUtil.stopNfcScan();
resolve(res);
});
return await new Promise((resolve, reject) => {
androidNfcUtil
.startNfcScan()
.then(() => {
androidNfcUtil.addDiscoveredListener((res) => {
androidNfcUtil.stopNfcScan();
resolve(res);
});
})
.catch(reject);
});
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ export async function isNfcEnabled() {
}
}
export function getNFCUtil(){
export function getNFCUtil() {
if (isAndroidApp) {
return new AndroidNfcUtil();
} else if (isAndroidWeixin) {
+19 -11
View File
@@ -3,11 +3,15 @@ export async function startNfcScan() {
await nfcAdapter.startDiscovery();
return new Promise((resolve, reject) => {
nfcAdapter.onDiscovered((res) => {
console.log("onDiscovered", res);
const arr = Array.from(new Int8Array(res.id));
resolve(arr);
});
try {
nfcAdapter.onDiscovered((res) => {
console.log("onDiscovered", res);
const arr = Array.from(new Int8Array(res.id));
resolve(arr);
});
} catch (e) {
reject(e);
}
});
}
@@ -67,12 +71,16 @@ export class WeixinNfcUtil {
static async scan() {
const weixinNfcUtil = new WeixinNfcUtil();
return await new Promise(async (resolve, reject) => {
await weixinNfcUtil.startNfcScan();
weixinNfcUtil.addDiscoveredListener((res) => {
weixinNfcUtil.stopNfcScan();
resolve(res);
});
return await new Promise((resolve, reject) => {
weixinNfcUtil
.startNfcScan()
.then(() => {
weixinNfcUtil.addDiscoveredListener((res) => {
weixinNfcUtil.stopNfcScan();
resolve(res);
});
})
.catch(reject);
});
}
}
+4 -5
View File
@@ -1,6 +1,5 @@
import { BluetoothUtils, Device } from "@/bluetooth-utils";
import { wait } from "@r-utils/common";
import { BluetoothUtils, Device } from "@/bluetooth-utils";
type DeviceData = Required<Device>;
@@ -17,9 +16,9 @@ export class Printer {
console.log(
data.length,
data.slice(0, 100),
data.slice(data.length - 100, data.length)
data.slice(data.length - 100, data.length),
);
let res = null;
let res;
const systemInfo = uni.getSystemInfoSync();
if (systemInfo.uniPlatform === "app" && systemInfo.osName === "android") {
res = await BluetoothUtils.sendDataAndroid(this.device, data);
@@ -33,7 +32,7 @@ export class Printer {
return res;
}
async _print(data: number[]):Promise<any> {
async _print(data: number[]): Promise<void> {
const size = Math.min(data.length, this.size);
if (size === 0) {
return;
+99 -72
View File
@@ -1,37 +1,57 @@
export type DataType = string | AnyObject | ArrayBuffer;
/** 请求配置 */
export interface Config<T extends DataType>
extends Partial<UniApp.RequestOptions> {
export interface Config<
T extends DataType,
> extends Partial<UniApp.RequestOptions> {
baseURL?: string;
data?: T;
[key: string]: any;
[key: string]: unknown;
}
/** 响应 */
export interface Response<
T extends DataType,
D extends DataType,
C extends Config<D> = Config<D>
> extends UniApp.RequestSuccessCallbackResult {
C extends Config<D> = Config<D>,
>
extends UniApp.RequestSuccessCallbackResult {
data: T;
errMsg?: string;
config: C;
}
/** 判断是否为绝对地址 */
const isAbsoluteUrl = (url: string): boolean =>
/^(?:[a-z][a-z\d+\-.]*:)?\/\//i.test(url);
/** 拼接基础地址 */
const joinBaseURL = (baseURL: string, url: string): string => {
if (!baseURL || isAbsoluteUrl(url)) {
return url;
}
const normalizedBaseURL = baseURL.endsWith("/")
? baseURL.slice(0, -1)
: baseURL;
const normalizedUrl = url.startsWith("/") ? url : `/${url}`;
return `${normalizedBaseURL}${normalizedUrl}`;
};
/** 成功拦截器 */
type FulfilledInterceptor<R, T> = (res: R) => T | Promise<T>;
/** 失败拦截器 */
type RejectedInterceptor = (error: any) => any;
type RejectedInterceptor = (error: unknown) => unknown | Promise<unknown>;
/** 拦截器管理 */
class InterceptorManager<R> {
/** 拦截器列表 */
handlers: [FulfilledInterceptor<R, any>?, RejectedInterceptor?][] = [];
handlers: [FulfilledInterceptor<R, unknown>?, RejectedInterceptor?][] = [];
/** 添加拦截器 */
add<T = R>(
onFulfilled?: FulfilledInterceptor<R, T>,
onRejected?: RejectedInterceptor
onRejected?: RejectedInterceptor,
): number {
this.handlers.push([onFulfilled, onRejected]);
return this.handlers.length - 1;
@@ -44,9 +64,9 @@ class InterceptorManager<R> {
forEach(
fn: (
onFulfilled?: FulfilledInterceptor<R, any>,
onRejected?: RejectedInterceptor
) => void
onFulfilled?: FulfilledInterceptor<R, unknown>,
onRejected?: RejectedInterceptor,
) => void,
) {
this.handlers.forEach(([onFulfilled, onRejected]) => {
fn(onFulfilled, onRejected);
@@ -59,16 +79,16 @@ class InterceptorManager<R> {
*/
export class Request {
/** 请求配置 */
config: Config<any>;
config: Config<DataType>;
/** 拦截器 */
interceptors: {
/** 请求拦截器 */
request: InterceptorManager<Config<any>>;
request: InterceptorManager<Config<DataType>>;
/** 响应拦截器 */
response: InterceptorManager<any>;
response: InterceptorManager<unknown>;
};
constructor(config: Config<any> = { url: "" }) {
constructor(config: Config<DataType> = { url: "" }) {
this.config = config;
this.interceptors = {
request: new InterceptorManager(),
@@ -76,79 +96,86 @@ export class Request {
};
}
/** 依次执行请求拦截器 */
private async runRequestInterceptors<REQD extends DataType>(
config: Config<REQD>,
): Promise<Config<REQD>> {
let currentConfig = config;
for (const [onFulfilled, onRejected] of this.interceptors.request
.handlers) {
try {
if (onFulfilled) {
currentConfig = (await onFulfilled(currentConfig)) as Config<REQD>;
}
} catch (error) {
if (onRejected) {
currentConfig = (await onRejected(error)) as Config<REQD>;
} else {
throw error;
}
}
}
return currentConfig;
}
/** 依次执行响应拦截器 */
private async runResponseInterceptors<R>(response: R): Promise<R> {
let currentResponse = response;
for (const [onFulfilled, onRejected] of this.interceptors.response
.handlers) {
try {
if (onFulfilled) {
currentResponse = (await onFulfilled(currentResponse)) as R;
}
} catch (error) {
if (onRejected) {
currentResponse = (await onRejected(error)) as R;
} else {
throw error;
}
}
}
return currentResponse;
}
/**
* 请求主方法
*/
async request<
RESPD extends DataType,
REQD extends DataType,
R = Response<RESPD, REQD>
R = Response<RESPD, REQD>,
>(config: Config<REQD>): Promise<R> {
// 合并方法配置 与 实例配置
let newConfig = Object.assign({}, this.config, config);
let newConfig = Object.assign({}, this.config, config) as Config<REQD>;
// 赋值默认URL
newConfig.url = newConfig.url ?? "";
// 如果设置了 baseURL 并且 url 不是绝对路径,则拼接 baseURL
if (newConfig.baseURL && !/https?\/\//.test(newConfig.url)) {
newConfig.url = newConfig.baseURL + newConfig.url;
}
// 执行请求拦截器
this.interceptors.request.forEach((onFulfilled, onRejected) => {
try {
if (onFulfilled) {
newConfig = onFulfilled(newConfig);
}
} catch (error) {
if (onRejected) {
throw onRejected(error);
} else {
throw error;
}
}
});
newConfig = await this.runRequestInterceptors(newConfig);
// 如果设置了 baseURL 并且 url 不是绝对路径,则拼接 baseURL
newConfig.url = joinBaseURL(newConfig.baseURL ?? "", newConfig.url ?? "");
// 发送请求
// let responsePromise = await new Promise<Response<RESPD>>((resolve, reject) => {
// uni.request({
// ...newConfig,
// success(result) {
// resolve(result);
// },
// fail(result) {
// reject(result);
// },
// });
// });
let responsePromise = await (<Promise<R>>uni.request({
let responsePromise = (await (uni.request({
...newConfig,
url: newConfig.url,
}));
}) as Promise<R>)) as R;
// 替换新请求的配置
responsePromise = {
...responsePromise,
config: newConfig,
};
config: newConfig as Config<REQD>,
} as R;
// 执行响应拦截器
this.interceptors.response.forEach((onFulfilled, onRejected) => {
// this.interceptors?.response.handlers.forEach((handler) => {
// const [onFulfilled, onRejected] = handler;
// responsePromise = responsePromise.then(onFulfilled, onRejected);
try {
if (typeof onFulfilled !== "undefined") {
responsePromise = onFulfilled(responsePromise);
}
} catch (error) {
if (typeof onRejected !== "undefined") {
onRejected(error);
} else {
throw error;
}
}
});
responsePromise = await this.runResponseInterceptors(responsePromise);
return responsePromise;
}
@@ -156,7 +183,7 @@ export class Request {
/** GET 请求 */
get<RESPD extends DataType, REQD extends DataType, R = Response<RESPD, REQD>>(
url: string,
config?: Config<REQD>
config?: Config<REQD>,
): Promise<R> {
return this.request({ ...config, url, method: "GET" });
}
@@ -165,17 +192,17 @@ export class Request {
post<
RESPD extends DataType,
REQD extends DataType,
R = Response<RESPD, REQD>
R = Response<RESPD, REQD>,
>(url: string, data?: REQD, config?: Config<REQD>): Promise<R> {
return this.request({ ...config, url, method: "POST", data });
}
/** PUT 请求 */
put<
RESPD extends DataType,
REQD extends DataType,
R = Response<RESPD, REQD>
>(url: string, data?: REQD, config?: Config<REQD>): Promise<R> {
put<RESPD extends DataType, REQD extends DataType, R = Response<RESPD, REQD>>(
url: string,
data?: REQD,
config?: Config<REQD>,
): Promise<R> {
return this.request({ ...config, url, method: "PUT", data });
}
}
@@ -1,4 +1,3 @@
type UploadResponseData = {
originalFileName: string;
url: string;
+1 -1
View File
@@ -7,6 +7,6 @@
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
}
}
}
-1074
View File
File diff suppressed because it is too large Load Diff
+45
View File
@@ -0,0 +1,45 @@
export class AndroidNfcUtil {
/**
* 是否支持NFC
* @returns
*/
static isNfcSupported(): boolean;
/**
* 是否开启NFC
* @returns {boolean} true 已开启
*/
static isNfcEnabled(): Promise<boolean>;
static scan(): Promise<number[]>;
main: PlusAndroidInstanceObject | null;
nfcAdapter: PlusAndroidInstanceObject | null;
pendingIntent: PlusAndroidInstanceObject | null;
intentFiltersArray: PlusAndroidInstanceObject[] | null;
techListsArray: string[][] | null;
discoveredListenerList: Array<(id: number[]) => void>;
/**
* 内部发现处理器
*/
_discoveredHandler: (() => void) | null;
/**
* 内部恢复处理器
*/
_resumeHandler: (() => void) | null;
/**
* 添加NFC发现监听器
* @param cb 回调函数,接收读取到的NFC ID
*/
addDiscoveredListener(cb: (id: number[]) => void): void;
/**
* 开始NFC扫描
* @returns {Promise<number[]>}
*/
startNfcScan(): Promise<number[]>;
stopNfcScan(): Promise<void>;
/**
* 初始化NFC并开启监听
*/
init(): Promise<void>;
resumeHandler(): void;
discoveredHandler(): void;
readId(): number[];
}
+6
View File
@@ -0,0 +1,6 @@
import { AndroidNfcUtil } from "./android";
import { WeixinNfcUtil } from "./weixin";
export function nfcScan(): Promise<number[] | undefined>;
export function isNfcEnabled(): Promise<boolean | undefined>;
export function getNFCUtil(): AndroidNfcUtil | WeixinNfcUtil | undefined;
+27
View File
@@ -0,0 +1,27 @@
export function startNfcScan(): Promise<number[]>;
export class WeixinNfcUtil {
/**
* 是否开启NFC
* NOTE 微信无法判断是否开启
* @returns {boolean} true 已开启
*/
static isNfcEnabled(): Promise<boolean>;
static scan(): Promise<number[]>;
nfcAdapter: WechatMiniprogram.NFCAdapter | null;
discoveredListenerList: Array<(id: number[]) => void>;
/**
* 内部发现处理器
*/
_discoveredHandler:
| ((res: WechatMiniprogram.OnDiscoveredCallbackResult) => void)
| null;
discoveredHandler(res: WechatMiniprogram.OnDiscoveredCallbackResult): void;
/**
* 添加NFC发现监听器
* @param cb 回调函数,接收读取到的NFC ID
*/
addDiscoveredListener(cb: (id: number[]) => void): void;
startNfcScan(): Promise<WechatMiniprogram.NFCError>;
stopNfcScan(): Promise<WechatMiniprogram.NFCError>;
}
+1
View File
@@ -1,4 +1,5 @@
import * as QQMapWX from "@jonny1994/qqmap-wx-jssdk";
export * from "@jonny1994/qqmap-wx-jssdk";
/**
* 行政区划列表
+2 -2
View File
@@ -1,7 +1,7 @@
/// <reference types="@dcloudio/types" />
declare namespace UniNamespace {
type LocaldataItem<T=string> = {
type LocaldataItem<T = string> = {
text: string;
value: T;
children?: LocaldataItem<T>[];
@@ -111,7 +111,7 @@ interface PlusIo {
resolveLocalFileSystemURL(
url?: string,
succesCB?: (result: PlusIoFileEntry) => void,
errorCB?: (result: any) => void,
errorCB?: (result: PlusIoDirectoryEntry) => void,
): void;
}
+28 -13
View File
@@ -1,32 +1,47 @@
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { defineConfig } from "vite";
import dts from "vite-plugin-dts";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const __dirname = fileURLToPath(new URL(".", import.meta.url));
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
formats: ['es', 'cjs'],
fileName: (format) => `index.${format === 'es' ? 'mjs' : 'cjs'}`,
entry: {
index: resolve(__dirname, "src/index.ts"),
"bluetooth-utils/index": resolve(
__dirname,
"src/bluetooth-utils/index.ts",
),
"nfc/index": resolve(__dirname, "src/nfc/index.js"),
"printer/index": resolve(__dirname, "src/printer/index.ts"),
"request/index": resolve(__dirname, "src/request/index.ts"),
"uni-helper/index": resolve(__dirname, "src/uni-helper/index.ts"),
"upload/index": resolve(__dirname, "src/upload/index.ts"),
},
formats: ["es", "cjs"],
fileName: (format, entryName) =>
`${entryName}.${format === "es" ? "mjs" : "cjs"}`,
},
rollupOptions: {
// uni / plus / wx 是 uni-app 运行时注入的全局变量,无需 external
external: ['vue', 'lodash', /^@r-utils\/.*/],
external: ["vue", "lodash", /^@r-utils\/.*/],
output: {
chunkFileNames: "chunks/[name]-[hash].js",
assetFileNames: "assets/[name]-[hash][extname]",
},
},
sourcemap: true,
},
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
"@": resolve(__dirname, "src"),
},
},
plugins: [
dts({
include: ['src', 'types'],
outDir: 'dist',
include: ["src", "types"],
outDir: "dist",
}),
],
});