feat(): 改为vite

This commit is contained in:
2026-03-16 17:58:57 +08:00
parent cd734d8524
commit 1ccbd4e872
74 changed files with 46574 additions and 2145 deletions
+10515
View File
File diff suppressed because it is too large Load Diff
+39
View File
@@ -0,0 +1,39 @@
{
"name": "@r-utils/uni-app",
"version": "1.0.0",
"private": true,
"description": "uni-app工具库",
"type": "module",
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./*": "./*"
},
"keywords": [
"uni-app"
],
"scripts": {
"build": "vite build",
"watch": "vite build --watch",
"lint": "eslint --ext .js,ts --fix src",
"release": "standard-version",
"commit": "cz",
"lint-staged": "lint-staged",
"test": "jest"
},
"dependencies": {
"@r-utils/common": "workspace:^",
"lodash": "^4.17.21"
},
"devDependencies": {
"@dcloudio/types": "^3.0.7",
"vue": "^3.3.0"
}
}
@@ -0,0 +1,461 @@
import { toPromise, showToast } from "@/uni-helper";
const systemInfo = uni.getSystemInfoSync();
const isAndroidApp =
systemInfo.uniPlatform === "app" && systemInfo.osName === "android";
// #ifdef APP-PLUS
const BluetoothAdapter = plus.android.importClass(
"android.bluetooth.BluetoothAdapter",
) as Android.BluetoothAdapter;
const UUID = plus.android.importClass("java.util.UUID") as Android.UUID;
const Context = plus.android.importClass("android.content.Context");
const Intent = plus.android.importClass("android.content.Intent");
const IntentFilter = plus.android.importClass("android.content.IntentFilter");
const InputStream = plus.android.importClass("java.io.InputStream");
const OutputStream = plus.android.importClass("java.io.OutputStream");
const Activity = plus.android.importClass("android.app.Activity");
const BluetoothSocket = plus.android.importClass(
"android.bluetooth.BluetoothSocket",
);
const BluetoothDevice = plus.android.importClass(
"android.bluetooth.BluetoothDevice",
) as Android.BluetoothDevice;
const invoke = plus.android.invoke;
// #endif
export interface Device extends UniNamespace.BluetoothDeviceInfo {
// 通知服务ID
notifyServiceId?: string;
// 通知特征值ID
notifyCharacterId?: string;
// 写入服务ID
writeServiceId?: string;
// 写入特征值ID
writeCharacterId?: string;
// 读取服务ID
readServiceId?: string;
// 读取特征值ID
readCharacterId?: string;
}
export class BluetoothError extends Error {
constructor(massage?: string) {
super(massage);
this.name = "BluetoothError";
}
}
export class BluetoothUtils {
static async safeInit(cb: () => Promise<unknown>) {
try {
console.log("uni.openBluetoothAdapter");
await uni.openBluetoothAdapter();
return await cb();
} catch (error) {
if (error instanceof BluetoothError) {
showToast(error.message, "error");
} else {
showToast("初始化蓝牙失败", "error");
}
throw error;
} finally {
console.log("uni.closeBluetoothAdapter");
await uni.closeBluetoothAdapter();
}
}
/**
* 获取所有设备
* @param cb
* @param time
* @returns
*/
static async getDevices(cb: (d: Device[]) => void, time: number) {
return BluetoothUtils.safeInit(() => BluetoothUtils._getDevices(cb, time));
}
private static async _getDevices(cb: (d: Device[]) => void, time = 3000) {
console.log("BluetoothUtils._getDevices()");
const foundDevices = <Device[]>[];
let btFindReceiver = null;
let activity = null;
let btAdapter: Android.BluetoothAdapter = <Android.BluetoothAdapter>{};
if (isAndroidApp) {
btAdapter = BluetoothAdapter.getDefaultAdapter();
}
try {
if (isAndroidApp) {
activity = plus.android.runtimeMainActivity() as Android.Activity;
console.log({ activity, btAdapter });
if (btAdapter.isDiscovering()) {
btAdapter.cancelDiscovery();
}
btFindReceiver = plus.android.implements(
"io.dcloud.android.content.BroadcastReceiver",
{
onReceive: function (
context: Android.Context,
intent: Android.Intent,
) {
// plus.android.importClass(context);
// plus.android.importClass(intent);
const action = intent.getAction();
console.log("onReceive");
if (BluetoothDevice.ACTION_FOUND === action) {
// 找到设备
const device =
intent.getParcelableExtra<Android.BluetoothDevice>(
BluetoothDevice.EXTRA_DEVICE,
);
const newDevice = {
name: device.getName(),
deviceId: device.getAddress(),
RSSI: -1,
advertisData: [],
advertisServiceUUIDs: [],
localName: "",
serviceData: [],
};
const repetition = foundDevices.some(
(v) => v.deviceId === newDevice.deviceId,
);
console.log({ newDevice });
console.log({ repetition });
if (
!repetition &&
newDevice.name &&
newDevice.name !== "未知设备"
) {
cb([newDevice]);
foundDevices.push(newDevice);
}
}
// if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED == action) {
// // 搜索完成
// }
},
},
);
const filter = plus.android.newObject(
"android.content.IntentFilter",
) as Android.IntentFilter;
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
activity.registerReceiver(btFindReceiver, filter);
btAdapter.startDiscovery();
} else {
const bluetoothAdapterState = await uni.getBluetoothAdapterState();
console.log(bluetoothAdapterState);
if (!bluetoothAdapterState.available) {
throw new BluetoothError("蓝牙适配器不可用");
}
if (bluetoothAdapterState.discovering) {
const res = await uni.stopBluetoothDevicesDiscovery();
console.log(res);
}
const sbddRes = await uni.startBluetoothDevicesDiscovery();
console.log("startBluetoothDevicesDiscovery", sbddRes);
// 蓝牙设备监听 plus.bluetooth.onBluetoothDeviceFound
uni.onBluetoothDeviceFound((result) => {
// console.log("uni.onBluetoothDeviceFound", result);
const newDevices = result.devices.filter((v) => {
// console.log(foundDevices, v);
return (
v.name &&
v.name !== "未知设备" &&
!foundDevices.some((v2) => v2.deviceId === v.deviceId)
);
});
if (newDevices.length > 0) {
cb(newDevices);
foundDevices.push(...newDevices);
}
});
}
return await new Promise((resolve) => {
setTimeout(async () => {
if (isAndroidApp) {
btAdapter.cancelDiscovery();
}
console.log({ foundDevices });
resolve(foundDevices);
}, time);
});
} catch (error) {
console.error(error);
if (error instanceof BluetoothError) {
showToast(error.message, "error");
} else {
showToast("获取蓝牙设备失败", "error");
}
return Promise.reject(error);
} finally {
if (isAndroidApp) {
if (activity) {
if (btFindReceiver != null) {
activity.unregisterReceiver(btFindReceiver);
}
activity = null;
}
} else {
await uni.stopBluetoothDevicesDiscovery();
}
}
}
/**
* 绑定蓝牙设备
* @param device
* @returns
*/
static async bindDevice(device: Device, cb: () => Promise<unknown>) {
return BluetoothUtils.safeInit(() =>
BluetoothUtils._bindDevice(device, cb),
);
}
private static async _bindDevice(device: Device, cb: () => Promise<unknown>) {
console.log("_bindDevice", device);
try {
let res = null;
if (isAndroidApp) {
res = await cb();
} else {
// 失败重连5次
for (var i = 1; i <= 5; i++) {
try {
await uni.createBLEConnection({
deviceId: device.deviceId,
});
break;
} catch (error) {
console.log(`蓝牙失败重连${i}`);
if (i == 5) {
throw error;
}
}
}
res = await new Promise((resolve) => {
setTimeout(async () => {
const services = await BluetoothUtils._getServices(device);
console.log("services", services);
await BluetoothUtils._setCharacteristics(device, services);
resolve(await cb());
// await cb()
}, 1000);
});
}
return res;
} catch (error) {
showToast("绑定蓝牙设备失败", "error");
throw error;
} finally {
if (!isAndroidApp) {
await uni.closeBLEConnection({
deviceId: device.deviceId,
});
}
}
}
/**
* 获取蓝牙设备所有服务
* @param device
* @returns
*/
private static async _getServices(device: Device) {
console.log("获取蓝牙设备所有服务");
// const res = await new Promise<UniNamespace.GetBLEDeviceServicesSuccess>(
// (resolve, reject) => {
// logger.info("uni.getBLEDeviceServices", device.deviceId);
// uni.getBLEDeviceServices({
// deviceId: device.deviceId,
// success: (res) => {
// console.log(res);
// logger.info("res: ", res);
// resolve(res);
// },
// fail: (err) => {
// console.error(err);
// logger.info("err: ", err);
// reject(err);
// },
// });
// }
// );
const res = await uni.getBLEDeviceServices({
deviceId: device.deviceId,
});
console.log(res);
return res.services;
}
/**
* 设置蓝牙设备特征值
*/
private static async _setCharacteristics(
device: Device,
services: UniNamespace.GetBLEDeviceServicesSuccessData[],
) {
console.log("_setCharacteristics", services);
// 获取蓝牙设备某个服务中所有特征值
// plus.bluetooth.getBLEDeviceCharacteristics
let notifyServiceId = "";
let writeServiceId = "";
let readServiceId = "";
let notifyCharacterId = "";
let writeCharacterId = "";
let readCharacterId = "";
for (const service of services) {
// const { notifyServiceId, writeServiceId, readServiceId } = device;
const done = [notifyServiceId, writeServiceId, readServiceId].every(
(v) => v !== "",
);
if (done) {
break;
}
console.log(
"获取蓝牙设备某个服务中所有特征值/uni.getBLEDeviceCharacteristics",
);
const res = await uni.getBLEDeviceCharacteristics({
deviceId: device.deviceId,
serviceId: service.uuid,
});
console.log(res);
for (const characteristic of res.characteristics) {
console.log(characteristic);
// const { notifyCharacterId, writeCharacterId, readCharacterId } = device;
const done = [
notifyCharacterId,
writeCharacterId,
readCharacterId,
].every((v) => v !== "");
if (done) {
break;
}
const { uuid, properties } = characteristic;
if (!notifyCharacterId) {
if (properties.notify) {
notifyCharacterId = uuid;
notifyServiceId = service.uuid;
}
}
if (!writeCharacterId) {
if (properties.write) {
writeCharacterId = uuid;
writeServiceId = service.uuid;
}
}
if (!readCharacterId) {
if (properties.read) {
readCharacterId = uuid;
readServiceId = service.uuid;
}
}
}
}
device.notifyServiceId = notifyServiceId;
device.notifyCharacterId = notifyCharacterId;
device.writeServiceId = writeServiceId;
device.writeCharacterId = writeCharacterId;
device.readServiceId = readServiceId;
device.readCharacterId = readCharacterId;
console.log(device);
}
static async getBLEMTU(device: Device) {
return BluetoothUtils.bindDevice(device, () =>
BluetoothUtils._getBLEMTU(device),
);
}
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);
// },
});
// });
}
static async sendData(device: Device, data: number[]) {
if (isAndroidApp) {
BluetoothUtils.sendDataAndroid(device, data);
} else {
const buf = new ArrayBuffer(data.length);
const dataView = new DataView(buf);
data.forEach((d, i) => {
dataView.setUint8(i, d);
});
return await new Promise((resolve, reject) => {
uni.writeBLECharacteristicValue({
deviceId: device.deviceId ?? "",
serviceId: device.writeServiceId ?? "",
characteristicId: device.writeCharacterId ?? "",
value: buf,
success: (res) => {
resolve(res);
},
fail: (err) => {
reject(err);
},
});
});
}
}
static async sendDataAndroid(device: Device, data: number[]) {
const btAdapter = BluetoothAdapter.getDefaultAdapter();
const deviceObj = invoke(btAdapter, "getRemoteDevice", device.deviceId);
const PRINTER_UUID = UUID.fromString(
"00001101-0000-1000-8000-00805F9B34FB",
);
const btSocket = invoke(
deviceObj,
"createRfcommSocketToServiceRecord",
PRINTER_UUID,
);
if (!invoke(btSocket, "isConnected")) {
console.log("检测到设备未连接,尝试连接....");
invoke(btSocket, "connect");
}
console.log("设备已连接");
const outputStream = invoke(btSocket, "getOutputStream");
invoke(outputStream, "write", data);
invoke(outputStream, "flush");
setTimeout(() => {
invoke(outputStream, "close");
invoke(btSocket, "close");
}, 10 * 1000);
}
}
+6
View File
@@ -0,0 +1,6 @@
export * from "./bluetooth-utils";
export * from "./nfc";
export * from "./printer";
export * from "./request";
export * from "./uni-helper";
export * from "./upload";
+206
View File
@@ -0,0 +1,206 @@
const systemInfo = uni.getSystemInfoSync();
const isAndroid =
systemInfo.uniPlatform === "app" && systemInfo.osName === "android";
let runtimeMainActivity = null;
let newObject = null;
let getAttribute = null;
let setAttribute = null;
let importClass = null;
let invoke = null;
let Intent = null;
let Activity = null;
let PendingIntent = null;
let IntentFilter = null;
let NfcAdapter = null;
let NdefRecord = null;
let NdefMessage = null;
let MifareClassic = null;
let Ndef = null;
let Tag = null;
let Parcelable = null;
let NfcV = null;
if (isAndroid) {
runtimeMainActivity = plus.android.runtimeMainActivity;
newObject = plus.android.newObject;
getAttribute = plus.android.getAttribute;
setAttribute = plus.android.setAttribute;
importClass = plus.android.importClass;
invoke = plus.android.invoke;
Intent = importClass("android.content.Intent");
Activity = importClass("android.app.Activity");
PendingIntent = importClass("android.app.PendingIntent");
IntentFilter = importClass("android.content.IntentFilter");
NfcAdapter = importClass("android.nfc.NfcAdapter");
NdefRecord = importClass("android.nfc.NdefRecord");
NdefMessage = importClass("android.nfc.NdefMessage");
MifareClassic = importClass("android.nfc.tech.MifareClassic");
Ndef = importClass("android.nfc.tech.Ndef");
Tag = importClass("android.nfc.Tag");
Parcelable = importClass("android.os.Parcelable");
NfcV = importClass("android.nfc.tech.NfcV");
}
export class AndroidNfcUtil {
main = null;
nfcAdapter = null;
pendingIntent = null;
intentFiltersArray = null;
techListsArray = null;
discoveredListenerList = [];
/**
* @type {Function}
*/
_discoveredHandler = null;
/**
* @type {Function}
*/
_resumeHandler = null;
constructor() {
this.main = runtimeMainActivity();
this.nfcAdapter = NfcAdapter.getDefaultAdapter(this.main);
this._discoveredHandler = this.discoveredHandler.bind(this);
this._resumeHandler = this.resumeHandler.bind(this);
}
/**
*
* @param {Function} cb
*/
addDiscoveredListener(cb) {
this.discoveredListenerList.push(cb);
}
/**
* 是否支持NFC
* @returns
*/
static isNfcSupported() {
const main = runtimeMainActivity();
const nfcAdapter = NfcAdapter.getDefaultAdapter(main);
return nfcAdapter != null;
}
/**
* 是否开启NFC
* @returns {boolean} true 已开启
*/
static async isNfcEnabled() {
const main = runtimeMainActivity();
const nfcAdapter = NfcAdapter.getDefaultAdapter(main);
return nfcAdapter && nfcAdapter.isEnabled();
}
/**
*
* @returns {Promise<number[]>}
*/
async startNfcScan() {
console.log("startNfcScan", this.nfcAdapter);
if (this.nfcAdapter == null) {
throw new Error("该设备不支持NFC");
} else if (!this.nfcAdapter.isEnabled()) {
throw new Error("未开启NFC");
} else {
return await this.init();
}
}
async stopNfcScan() {
console.log("stopNfcScan");
plus.globalEvent.removeEventListener("newintent", this._discoveredHandler);
plus.globalEvent.removeEventListener("resume", this._discoveredHandler);
this.nfcAdapter.disableForegroundDispatch(this.main);
}
/**
* 初始化ncf 并开启监听
*/
async init() {
console.log("init");
const ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
const tag = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
const tech = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
this.intentFiltersArray = [ndef, tag, tech];
this.techListsArray = [
["android.nfc.tech.Ndef"],
["android.nfc.tech.IsoDep"],
["android.nfc.tech.NfcA"],
["android.nfc.tech.NfcB"],
["android.nfc.tech.NfcF"],
["android.nfc.tech.Nfcf"],
["android.nfc.tech.Nfef"],
["android.nfc.tech.Ndef"],
["android.nfc.tech.NfcV"],
["android.nfc.tech.NdefFormatable"],
["android.nfc.tech.MifareClassi"],
["android.nfc.tech.MifareUltralight"],
];
const intent = new Intent(this.main, this.main.getClass());
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
this.pendingIntent = PendingIntent.getActivity(this.main, 0, intent, 0);
this.nfcAdapter.enableForegroundDispatch(
this.main,
this.pendingIntent,
this.intentFiltersArray,
this.techListsArray,
);
plus.globalEvent.addEventListener("newintent", this._discoveredHandler);
plus.globalEvent.addEventListener("resume", this._resumeHandler);
}
resumeHandler() {
console.log("resumeHandler");
this.nfcAdapter.enableForegroundDispatch(
this.main,
this.pendingIntent,
this.intentFiltersArray,
this.techListsArray,
);
}
discoveredHandler() {
console.log("discoveredHandler");
this.discoveredListenerList.forEach((cb) => {
cb(this.readId());
});
}
readId() {
console.log("readId");
const intent = this.main.getIntent();
const action = intent.getAction();
console.log("action: " + action);
// if (
// NfcAdapter.ACTION_NDEF_DISCOVERED == action ||
// NfcAdapter.ACTION_TAG_DISCOVERED == action ||
// NfcAdapter.ACTION_TECH_DISCOVERED == action
// ) {
console.log("intent.getAction()", intent.getAction());
const tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
console.log("tagFromIntent.getId", tagFromIntent.getId());
return tagFromIntent.getId();
// }
}
static async scan() {
const androidNfcUtil = new AndroidNfcUtil();
return await new Promise(async (resolve, reject) => {
await androidNfcUtil.startNfcScan();
androidNfcUtil.addDiscoveredListener((res) => {
androidNfcUtil.stopNfcScan();
resolve(res);
});
});
}
}
+38
View File
@@ -0,0 +1,38 @@
import { AndroidNfcUtil } from "./android";
import { WeixinNfcUtil } from "./weixin";
const systemInfo = uni.getSystemInfoSync();
const isAndroidApp =
systemInfo.uniPlatform === "app" && systemInfo.osName === "android";
const isAndroidWeixin =
systemInfo.uniPlatform === "mp-weixin" && systemInfo.osName === "android";
export async function nfcScan() {
if (isAndroidApp) {
return await AndroidNfcUtil.scan();
} else if (isAndroidWeixin) {
return await WeixinNfcUtil.scan();
} else {
console.warn("不支持当前平台");
}
}
export async function isNfcEnabled() {
if (isAndroidApp) {
return await AndroidNfcUtil.isNfcEnabled();
} else if (isAndroidWeixin) {
return await WeixinNfcUtil.isNfcEnabled();
} else {
console.warn("不支持当前平台");
}
}
export function getNFCUtil(){
if (isAndroidApp) {
return new AndroidNfcUtil();
} else if (isAndroidWeixin) {
return new WeixinNfcUtil();
} else {
console.warn("不支持当前平台");
}
}
+78
View File
@@ -0,0 +1,78 @@
export async function startNfcScan() {
const nfcAdapter = wx.getNFCAdapter();
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);
});
});
}
export class WeixinNfcUtil {
nfcAdapter = null;
discoveredListenerList = [];
/**
* @type {Function}
*/
_discoveredHandler = null;
constructor() {
this.nfcAdapter = wx.getNFCAdapter();
this._discoveredHandler = this.discoveredHandler.bind(this);
this.nfcAdapter.onDiscovered(this._discoveredHandler);
}
/**
* 是否开启NFC
* NOTE 微信无法判断是否开启
* @returns {boolean} true 已开启
*/
static async isNfcEnabled() {
try {
wx.getNFCAdapter();
return true;
} catch (e) {
console.error(e);
return false;
}
}
discoveredHandler(res) {
console.log("onDiscovered", res);
const arr = Array.from(new Int8Array(res.id));
this.discoveredListenerList.forEach((cb) => {
cb(arr);
});
}
/**
*
* @param {Function} cb
*/
addDiscoveredListener(cb) {
this.discoveredListenerList.push(cb);
}
async startNfcScan() {
return await this.nfcAdapter.startDiscovery();
}
async stopNfcScan() {
this.nfcAdapter.offDiscovered(this._discoveredHandler);
return await this.nfcAdapter.stopDiscovery();
}
static async scan() {
const weixinNfcUtil = new WeixinNfcUtil();
return await new Promise(async (resolve, reject) => {
await weixinNfcUtil.startNfcScan();
weixinNfcUtil.addDiscoveredListener((res) => {
weixinNfcUtil.stopNfcScan();
resolve(res);
});
});
}
}
+65
View File
@@ -0,0 +1,65 @@
import { BluetoothUtils, Device } from "@/bluetooth-utils";
import { wait } from "@r-utils/common";
type DeviceData = Required<Device>;
export class Printer {
device: DeviceData;
size = 0;
constructor(device: DeviceData, { size = 80 } = {}) {
this.device = device;
this.size = size;
}
async print(data: number[]) {
console.log(
data.length,
data.slice(0, 100),
data.slice(data.length - 100, data.length)
);
let res = null;
const systemInfo = uni.getSystemInfoSync();
if (systemInfo.uniPlatform === "app" && systemInfo.osName === "android") {
res = await BluetoothUtils.sendDataAndroid(this.device, data);
} else {
res = await BluetoothUtils.bindDevice(this.device, async () => {
await wait(100);
return await this._print(data);
});
}
return res;
}
async _print(data: number[]):Promise<any> {
const size = Math.min(data.length, this.size);
if (size === 0) {
return;
}
const buf = new ArrayBuffer(size);
const dataView = new DataView(buf);
for (let i = 0; i < size; i++) {
dataView.setUint8(i, data[i]);
}
await new Promise((resolve, reject) => {
uni.writeBLECharacteristicValue({
deviceId: this.device.deviceId,
serviceId: this.device.writeServiceId,
characteristicId: this.device.writeCharacterId,
value: buf,
success: (res) => {
resolve(res);
},
fail: (err) => {
reject(err);
},
});
});
await wait(30);
return await this._print(data.slice(size));
}
}
+183
View File
@@ -0,0 +1,183 @@
export type DataType = string | AnyObject | ArrayBuffer;
/** 请求配置 */
export interface Config<T extends DataType>
extends Partial<UniApp.RequestOptions> {
baseURL?: string;
data?: T;
[key: string]: any;
}
/** 响应 */
export interface Response<
T extends DataType,
D extends DataType,
C extends Config<D> = Config<D>
> extends UniApp.RequestSuccessCallbackResult {
data: T;
errMsg?: string;
config: C;
}
/** 成功拦截器 */
type FulfilledInterceptor<R, T> = (res: R) => T | Promise<T>;
/** 失败拦截器 */
type RejectedInterceptor = (error: any) => any;
/** 拦截器管理 */
class InterceptorManager<R> {
/** 拦截器列表 */
handlers: [FulfilledInterceptor<R, any>?, RejectedInterceptor?][] = [];
/** 添加拦截器 */
add<T = R>(
onFulfilled?: FulfilledInterceptor<R, T>,
onRejected?: RejectedInterceptor
): number {
this.handlers.push([onFulfilled, onRejected]);
return this.handlers.length - 1;
}
/** 移除拦截器 */
remove(id: number) {
return this.handlers.splice(id, 1);
}
forEach(
fn: (
onFulfilled?: FulfilledInterceptor<R, any>,
onRejected?: RejectedInterceptor
) => void
) {
this.handlers.forEach(([onFulfilled, onRejected]) => {
fn(onFulfilled, onRejected);
});
}
}
/**
* 请求类
*/
export class Request {
/** 请求配置 */
config: Config<any>;
/** 拦截器 */
interceptors: {
/** 请求拦截器 */
request: InterceptorManager<Config<any>>;
/** 响应拦截器 */
response: InterceptorManager<any>;
};
constructor(config: Config<any> = { url: "" }) {
this.config = config;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
/**
* 请求主方法
*/
async request<
RESPD extends DataType,
REQD extends DataType,
R = Response<RESPD, REQD>
>(config: Config<REQD>): Promise<R> {
// 合并方法配置 与 实例配置
let newConfig = Object.assign({}, this.config, config);
// 赋值默认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;
}
}
});
// 发送请求
// 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({
...newConfig,
url: newConfig.url,
}));
// 替换新请求的配置
responsePromise = {
...responsePromise,
config: newConfig,
};
// 执行响应拦截器
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;
}
}
});
return responsePromise;
}
/** GET 请求 */
get<RESPD extends DataType, REQD extends DataType, R = Response<RESPD, REQD>>(
url: string,
config?: Config<REQD>
): Promise<R> {
return this.request({ ...config, url, method: "GET" });
}
/** POST 请求 */
post<
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: "POST", data });
}
/** PUT 请求 */
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 });
}
}
export default Request;
+1
View File
@@ -0,0 +1 @@
export * from "./Request";
+245
View File
@@ -0,0 +1,245 @@
import { ComponentInternalInstance } from "vue";
/** 扫码 */
export async function scanCode() {
const res = await new Promise<UniApp.ScanCodeSuccessRes>(
(resolve, reject) => {
uni.scanCode({
success(res) {
resolve(res);
},
fail(res) {
reject(res);
},
});
},
);
console.log({ res });
return res.result;
}
/** 获取当前页面 */
export function getCurrentPage() {
const pages = getCurrentPages();
return pages[pages.length - 1];
}
/**
* 使用 TypeScript 工具类型实现自动类型推断的 toPromise
*
* 核心思路:
* 1. 使用类似 Parameters<T>[0] 的方式提取函数第一个参数类型
* 2. 从参数类型中提取 success 回调的参数类型
* 3. 使用条件类型和 infer 关键字实现类型推断
* 4. 虽然不能直接用 ReturnType(因为uni API返回void),但可以用类似思路提取回调参数类型
*/
// 方法1:直接使用 Parameters<T> 工具类型
// type ExtractFirstParameter<T> = Parameters<T>[0]; // 这样也可以,但需要T是函数类型
// 方法2:使用条件类型实现(更灵活,等价于 Parameters<T>[0]
type ExtractFirstParameter<T> = T extends (arg: infer P, ...args: unknown[]) => unknown ? P : never;
// 提取 success 回调的参数类型(类似 ReturnType 的思路,但提取回调参数)
type ExtractSuccessResult<T> = T extends { success?: (result: infer R) => void } ? R : never;
// 提取函数参数类型并排除回调函数(用于args参数)
type ExtractOptions<T> = T extends (options: infer P) => unknown
? Omit<P, "success" | "fail" | "complete">
: never;
// 类型测试示例(编译时验证)
// type TestDownloadOptions = ExtractOptions<typeof uni.downloadFile>; // UniNamespace.DownloadFileOption 去除回调
// type TestDownloadResult = ExtractSuccessResult<Parameters<typeof uni.downloadFile>[0]>; // UniNamespace.DownloadSuccessData
/**
* 将 uni-app 回调方法转换为 Promise(使用工具类型自动推断)
*
* 优势:
* - 无需手动指定泛型参数
* - 完全基于 TypeScript 工具类型实现
* - 支持所有 uni-app API,不需要为每个API单独写重载
* - 类型安全,有完整的智能提示
*
* @example
* // 自动推断为 Promise<UniNamespace.DownloadSuccessData>
* const downloadRes = await toPromise(uni.downloadFile, { url: 'https://example.com/file.jpg' });
* console.log(downloadRes.tempFilePath); // ✅ 类型安全,有智能提示
*
* // 自动推断为 Promise<UniNamespace.ScanCodeSuccessRes>
* const scanRes = await toPromise(uni.scanCode, {});
* console.log(scanRes.result); // ✅ 类型安全,有智能提示
*
* // 自动推断为 Promise<UniNamespace.UploadFileSuccessCallbackResult>
* const uploadRes = await toPromise(uni.uploadFile, {
* url: '/upload',
* filePath: 'temp://file.jpg',
* name: 'file'
* });
* console.log(uploadRes.statusCode); // ✅ 类型安全,有智能提示
*
* // 自动推断为 Promise<UniNamespace.RequestSuccessCallbackResult>
* const requestRes = await toPromise(uni.request, { url: '/api/data' });
* console.log(requestRes.data); // ✅ 类型安全,有智能提示
*/
export function toPromise<F extends (options: Record<string, unknown>) => void>(
uniFun: F,
args?: ExtractOptions<F>,
): Promise<ExtractSuccessResult<ExtractFirstParameter<F>>> {
type OptionsType = ExtractFirstParameter<F>;
type ResultType = ExtractSuccessResult<OptionsType>;
return new Promise<ResultType>((resolve, reject) => {
uniFun({
...args,
success(res: ResultType) {
resolve(res);
},
fail(err: unknown) {
reject(err);
},
} as OptionsType);
});
}
export function showToast(
title: string,
icon: "success" | "loading" | "error" | "none" = "none",
options?: UniNamespace.ShowToastOptions,
) {
uni.showToast({
...options,
title,
icon,
duration: options?.duration ?? 1000,
});
}
/**
* 通过URL生成canvas二维码
* @param canvasId
* @param url 图片URL
* @param x 定位于canvas左边宽度,单位px
* @param y 定位于canvas顶部高度,单位px
* @param width 二维码宽度,单位px
* @param height 二维码高度,单位px
* @param thisArg 页面上下文
* @returns 图像像素点数据
*/
export async function getCanvasImageData(
canvasId: string,
url: string,
x = 0,
y = 0,
width = 200,
height = 200,
thisArg: ComponentInternalInstance,
) {
const context = uni.createCanvasContext(canvasId, thisArg);
const imgRes = await toPromise(
uni.downloadFile,
{
url,
},
);
console.log(imgRes);
context.drawImage(imgRes.tempFilePath, x, y, width, height);
await new Promise((resolve) => {
context.draw(false, (res) => {
console.log(res);
resolve(res);
});
});
const imageData: UniNamespace.CanvasGetImageDataRes =
await uni.canvasGetImageData({
canvasId: canvasId,
x,
y,
width: width,
height: height,
});
return imageData.data;
}
export async function getCanvasImageDataSimplify(
canvasId: string,
url: string,
width = 200,
height = 200,
thisArg: ComponentInternalInstance,
) {
return await getCanvasImageData(canvasId, url, 0, 0, width, height, thisArg);
}
export async function getCanvasImageDataTransObj({
canvasId,
url,
x = 0,
y = 0,
width = 200,
height = 200,
thisArg,
}: {
canvasId: string;
url: string;
x: number;
y: number;
width: number;
height: number;
thisArg: ComponentInternalInstance;
}) {
return await getCanvasImageData(canvasId, url, x, y, width, height, thisArg);
}
export function rpxToPx(rpx: number | string) {
if (typeof rpx === "string") {
rpx = Number.parseInt(rpx);
}
const screenWidth = uni.getSystemInfoSync().screenWidth;
return (screenWidth * rpx) / 750;
}
export function pxToRpx(px: number | string) {
if (typeof px === "string") {
px = Number.parseInt(px);
}
const screenWidth = uni.getSystemInfoSync().screenWidth;
return (750 * px) / screenWidth;
}
/**
* 退出程序
*/
export function exitApp() {
// #ifdef APP-PLUS
if (plus.os.name?.toLowerCase() === "android") {
plus.runtime.quit();
} else {
const threadClass = plus.ios.importClass("NSThread");
const mainThread = plus.ios.invoke(threadClass, "mainThread");
plus.ios.invoke(mainThread, "exit");
}
// #endif
}
/**
* 检查是否有安卓权限
*/
export function checkAndroidPermission(permissionList: string[]) {
// #ifdef APP-PLUS
const ActivityCompat = plus.android.importClass(
"androidx.core.app.ActivityCompat",
) as Android.ActivityCompat;
const activity = plus.android.runtimeMainActivity();
return permissionList.every((v) => {
const p = ActivityCompat.checkSelfPermission(activity, v);
return p === 0;
});
// #endif
}
+78
View File
@@ -0,0 +1,78 @@
type UploadResponseData = {
originalFileName: string;
url: string;
};
type UploadResult = {
code: number;
msg: string;
data: UploadResponseData;
};
type UploadResponse = {
errno: number;
errmsg: string;
originalFileName: string;
url: string;
};
/**
* 上传文件
* @param options
* @returns
*/
export async function upload(
options: UniApp.UploadFileOption,
): Promise<UploadResult> {
console.log(options);
return new Promise<UniApp.UploadFileSuccessCallbackResult>(
(resolve, reject) => {
uni.uploadFile({
url: options.url,
filePath: options.filePath,
name: options.name ?? "ImageFile",
// header: {
// Authorization: `Bearer ${token}`,
// },
// formData: {
// shopId: userStore.shop?.shopId,
// },
success(res) {
console.log(res);
resolve(res);
},
fail(err) {
reject(err);
},
});
},
)
.then((res) => {
if (res.statusCode === 200) {
const data: UploadResponse = JSON.parse(res.data);
return {
code: data.errno,
msg:
data.errmsg != null && data.errmsg != "" ? data.errmsg : "上传成功",
data: {
originalFileName: data.originalFileName,
url: data.url,
},
} as UploadResult;
} else {
throw new Error(res.errMsg);
}
})
.catch((err) => {
console.error(err);
uni.showToast({
icon: "none",
title: err ?? "网络错误",
});
throw err;
});
}
+22
View File
@@ -0,0 +1,22 @@
{
"exclude": ["node_modules", "dist"],
"include": ["src/**/*.ts", "src/**/*.js"],
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "bundler",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["dom", "esnext"],
"types": ["@dcloudio/types"],
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
"allowJs": true
}
}
+69
View File
@@ -0,0 +1,69 @@
/// <reference types="@dcloudio/types" />
declare namespace Android {
class ActivityCompat implements PlusAndroidInstanceObject {
checkSelfPermission(context: Context, permission: string): number;
plusGetAttribute(name?: string | undefined): unknown;
plusSetAttribute(name?: string | undefined, value?: unknown): void;
}
class Build implements PlusAndroidClassObject {
readonly VERSION: {
SDK_INT: number;
ECLAIR_0_1: number;
};
plusGetAttribute(name?: string | undefined): unknown;
plusSetAttribute(name?: string | undefined, value?: unknown): void;
}
class BluetoothAdapter implements PlusAndroidInstanceObject {
readonly ACTION_DISCOVERY_FINISHED =
"android.bluetooth.device.extra.DEVICE";
startDiscovery(): boolean;
cancelDiscovery(): boolean;
isDiscovering(): boolean;
plusGetAttribute(name?: string | undefined): unknown;
plusSetAttribute(name?: string | undefined, value?: unknown): void;
getDefaultAdapter(): BluetoothAdapter;
getBondedDevices(): Set<BluetoothDevice>;
}
class BluetoothDevice implements PlusAndroidInstanceObject {
readonly ACTION_FOUND = "android.bluetooth.device.action.FOUND";
readonly EXTRA_DEVICE = "android.bluetooth.device.extra.DEVICE";
getName(): string;
getAddress(): string;
createRfcommSocketToServiceRecord(uuid: UUID): BluetoothSocket;
plusGetAttribute(name?: string | undefined): unknown;
plusSetAttribute(name?: string | undefined, value?: unknown): void;
}
interface Set<E> extends PlusAndroidClassObject {
iterator(): Iterator<E>;
}
interface BluetoothSocket extends PlusAndroidClassObject {
isConnected(): boolean;
}
interface UUID extends PlusAndroidClassObject {
fromString(name: string): UUID;
}
class Context implements PlusAndroidInstanceObject {
plusGetAttribute(name?: string | undefined): unknown;
plusSetAttribute(name?: string | undefined, value?: unknown): void;
}
class Intent implements PlusAndroidInstanceObject {
getParcelableExtra<T>(name: string): T;
getAction(): string;
plusGetAttribute(name?: string | undefined): unknown;
plusSetAttribute(name?: string | undefined, value?: unknown): void;
}
class BroadcastReceiver {}
class IntentFilter implements PlusAndroidInstanceObject {
addAction(action: string): void;
plusGetAttribute(name?: string | undefined): unknown;
plusSetAttribute(name?: string | undefined, value?: unknown): void;
}
class Activity implements PlusAndroidInstanceObject {
registerReceiver(receiver: BroadcastReceiver, filter: IntentFilter): Intent;
unregisterReceiver(receiver: BroadcastReceiver): void;
plusGetAttribute(name?: string | undefined): unknown;
plusSetAttribute(name?: string | undefined, value?: unknown): void;
}
}
+1074
View File
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
interface Config {
/** 环境 */
ENV?: string;
/** api 基础地址 */
API_BASE_URL?: string;
/** appId */
APP_ID?: string;
/** static 目录基础地址 */
STATIC_BASE_URL?: string;
/** 上传文件地址 */
UPLOAD_URL?: string;
/** 文件地址 */
FILE_BASE_URL?: string;
}
+4
View File
@@ -0,0 +1,4 @@
/// <reference types="vite/client" />
declare module "uview-ui";
declare module "uview-plus";
+5
View File
@@ -0,0 +1,5 @@
declare global {
function setInterval(handler: TimerHandler, timeout?: number): number;
}
export {};
+49
View File
@@ -0,0 +1,49 @@
import * as QQMapWX from "@jonny1994/qqmap-wx-jssdk";
export * from "@jonny1994/qqmap-wx-jssdk";
/**
* 行政区划列表
* @example
* cidx: [103, 118]
* fullname: "张家口市"
* id: "130700"
* location: {lat: 40.82444, lng: 114.88755}
* name: "张家口"
* pinyin: ["zhang", "jia", "kou"]
*/
export interface GetCityListSuccessResultResult {
/**
* 行政区划唯一标识
* @example "110000"
*/
id: number;
/**
* 简称,如“内蒙古”
*/
name?: string;
/**
* 全称,如“内蒙古自治区”
* @example "北京市"
*/
fullname: string;
/**
* 中心点坐标
* @example {lat: 39.90469, lng: 116.40717}
*/
location: QQMapWX.ResultLocation;
/**
* 行政区划拼音,每一下标为一个字的全拼,如:[“nei”,“meng”,“gu”]
*/
pinyin: string[];
/**
* 子级行政区划在下级数组中的下标位置
* @example [0, 15]
*/
cidx?: number[];
}
export interface GetCityListSuccessResult extends QQMapWX.CommonResult {
/**
* 结果数组,第0项,代表一级行政区划,第1项代表二级行政区划,以此类推;使用getchildren接口时,仅为指定父级行政区划的子级
*/
result: GetCityListSuccessResultResult[];
}
+124
View File
@@ -0,0 +1,124 @@
/// <reference types="@dcloudio/types" />
declare namespace UniNamespace {
type LocaldataItem = {
text: string;
value: string;
children?: LocaldataItem[];
};
type LocaldataNodeclickValue = {
text: string;
value: string;
parent_value?: string;
};
type LocaldataChangeValue = { text: string; value: string };
type Event<T> = { detail: T };
type InputEvent = Event<{ value: string }>;
type PickerEvent = Event<{ value: string }>;
type PickerViewEvent = Event<{ value: unknown[] }>;
type ScrollEvent = Event<{ scrollTop: number }>;
type SwiperEvent = Event<{ current: number }>;
type UniDataPickerEvent = Event<{ value: LocaldataChangeValue[] }>;
interface WriteBLECharacteristicValueOptions2 {
/**
* 蓝牙设备 id,参考 device 对象
*/
deviceId: string;
/**
* 蓝牙特征值对应服务的 uuid
*/
serviceId: string;
/**
* 蓝牙特征值的 uuid
*/
characteristicId: string;
/**
* 蓝牙设备特征值对应的二进制值
*/
value: ArrayBuffer;
/**
* 成功则返回本机蓝牙适配器状态
*/
success?: (result: StopBluetoothDevicesDiscoverySuccess) => void;
/**
* 接口调用失败的回调函数
*/
fail?: (result: unknown) => void;
/**
* 接口调用结束的回调函数(调用成功、失败都会执行)
*/
complete?: (result: unknown) => void;
}
interface BluetoothError {
code: number;
}
interface StopBluetoothDevicesDiscoveryOptions2 {
/**
* 成功则返回本机蓝牙适配器状态
*/
success?: (result: StopBluetoothDevicesDiscoverySuccess) => void;
/**
* 接口调用失败的回调函数
*/
fail?: (result: BluetoothError) => void;
/**
* 接口调用结束的回调函数(调用成功、失败都会执行)
*/
complete?: () => void;
}
}
// type DataType = string | AnyObject | ArrayBuffer;
interface Uni {
// request<T extends DataType, D extends DataType>(
// options: RequestOptionsGeneric<D>
// ): Promise<RequestSuccessCallbackResultGeneric<T>>;
request(
options: UniApp.RequestOptions,
): Promise<UniApp.RequestSuccessCallbackResult>;
writeBLECharacteristicValue(
options: UniNamespace.WriteBLECharacteristicValueOptions2,
): void;
stopBluetoothDevicesDiscovery(
options: UniNamespace.StopBluetoothDevicesDiscoveryOptions,
): void;
}
// interface RequestOptionsGeneric<T extends DataType>
// extends UniApp.RequestOptions {
// data?: T;
// }
// interface RequestSuccessCallbackResultGeneric<T extends DataType>
// extends UniApp.RequestSuccessCallbackResult {
// data: T;
// errMsg?: string;
// }
// interface PlusIoDirectoryEntry extends PlusIoFileEntry {
// file(
// succesCB?: (result: PlusIoFile) => void,
// errorCB?: (result: any) => void
// ): void;
// }
interface PlusIo {
resolveLocalFileSystemURL(
url?: string,
succesCB?: (result: PlusIoFileEntry) => void,
errorCB?: (result: any) => void,
): void;
}
// interface PlusIoFileEvent {
// target?: PlusIoFileReader;
// }
interface PlusIoFileReader {
onload?: (result: PlusIoFileEvent) => void;
}
+32
View File
@@ -0,0 +1,32 @@
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));
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
formats: ['es', 'cjs'],
fileName: (format) => `index.${format === 'es' ? 'mjs' : 'cjs'}`,
},
rollupOptions: {
// uni / plus / wx 是 uni-app 运行时注入的全局变量,无需 external
external: ['vue', 'lodash', /^@r-utils\/.*/],
},
sourcemap: true,
},
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
},
},
plugins: [
dts({
include: ['src'],
outDir: 'dist',
}),
],
});