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
+7
View File
@@ -0,0 +1,7 @@
# @r-utils/common
## 1.4.0
### Minor Changes
- 添加工具
+184
View File
@@ -0,0 +1,184 @@
# @r-utils/common
与框架无关的通用 JS/TS 工具库,适用于任意前端项目,也可在具备对应运行时 API 的环境中使用。
## 特性
- 不依赖 Vue、uni-app 或 uview-plus。
- 支持 ESM / CJS 双格式产物。
- 支持根入口导入和子路径按需导入。
- 标记 `sideEffects: false`,方便业务打包器进行 tree-shaking。
## 安装
```bash
pnpm add @r-utils/common
```
## 导入方式
### 推荐:根入口导入
大多数场景推荐从根入口导入,使用心智负担更低,现代打包器仍可结合 ESM 和 `sideEffects: false` 做 tree-shaking。
```ts
import {
Permission,
wait,
Countdown,
TimeoutTimer,
getValueOfRule,
} from "@r-utils/common";
```
### 兼容:子路径按需导入
如果你希望导入路径更精确,也可以使用子路径导入。两种方式都支持,按团队习惯选择即可。
```ts
import { Permission } from "@r-utils/common/permission";
import { wait } from "@r-utils/common/time";
import { Countdown, TimeoutTimer } from "@r-utils/common/timer";
import { getValueOfRule } from "@r-utils/common/input-rule";
```
## 导出模块
| 子路径 | 说明 |
| --- | --- |
| `@r-utils/common/permission` | 权限字符串格式校验 |
| `@r-utils/common/time` | 时间相关工具 |
| `@r-utils/common/timer` | 倒计时、超时计时器 |
| `@r-utils/common/input-rule` | 输入值规则处理 |
| `@r-utils/common/knock-test` | 连续敲击/点击触发器 |
| `@r-utils/common/ui` | 与框架无关的 UI 计算工具 |
| `@r-utils/common/printer` | ESC/TSC 打印相关工具 |
## 使用示例
### 权限字符串校验
```ts
import { Permission } from "@r-utils/common";
const permission = new Permission([], 3, ":");
permission.isValid("user:create:button"); // true
permission.isValid("user:create"); // false
```
### 等待指定时间
```ts
import { wait } from "@r-utils/common";
async function submit() {
await wait(300);
console.log("继续执行");
}
```
### 倒计时
```ts
import { Countdown } from "@r-utils/common";
const countdown = new Countdown(10 * 1000);
countdown.addStepEventListener((time) => {
console.log("剩余时间:", time);
});
countdown.addCountdownEventListener(() => {
console.log("倒计时结束");
});
countdown.start();
```
### 简单数值倒计时
```ts
import { TimeoutTimer } from "@r-utils/common";
const timer = new TimeoutTimer(5000, 0, 1000);
timer.addStepEventListener((time) => {
console.log("剩余毫秒:", time);
});
timer.addCountdownEventListener(() => {
console.log("完成");
});
timer.start();
```
### 输入值规则处理
```ts
import { getValueOfRule } from "@r-utils/common";
const value = getValueOfRule("12.345", {
min: 0,
max: 99999.99,
required: true,
digits: 2,
keepDecimal: true,
});
console.log(value); // "12.35"
```
### 禁止正负号并转成数字
```ts
import { getValueOfRule } from "@r-utils/common";
const value = getValueOfRule("-12", {
min: 0,
required: true,
noSign: true,
number: true,
});
console.log(value); // 0
```
### 连续敲击触发回调
```ts
import { KnockTest } from "@r-utils/common";
const knockTest = new KnockTest({
maxWaitTime: 5000,
operations: [
{ times: 3, duration: 1000, delay: 500 },
{ times: 2, duration: 1000, delay: 500 },
],
});
knockTest.addCallback(() => {
console.log("触发隐藏功能");
});
button.addEventListener("click", () => {
knockTest.knock();
});
```
### 缓动滚动计算
```ts
import { slowlyScroll } from "@r-utils/common";
await slowlyScroll(0, 300, 500, (value) => {
window.scrollTo(0, value);
});
```
## 注意事项
- `Countdown``TimeoutTimer``KnockTest` 内部使用 `window.setInterval` / `window.setTimeout`,更适合浏览器或类浏览器环境。
- 推荐优先使用根入口导入;如果需要更精确的模块边界,也可以使用子路径导入。
- 打印相关模块通常依赖具体设备和业务场景,建议在真实设备环境中验证。
+40 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@r-utils/common",
"version": "1.3.0",
"version": "1.4.0",
"private": false,
"description": "js通用工具库",
"type": "module",
@@ -8,13 +8,50 @@
"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"
},
"./*": "./*"
"./input-rule": {
"types": "./dist/input-rule/index.d.ts",
"import": "./dist/input-rule/index.mjs",
"require": "./dist/input-rule/index.cjs"
},
"./knock-test": {
"types": "./dist/knock-test/index.d.ts",
"import": "./dist/knock-test/index.mjs",
"require": "./dist/knock-test/index.cjs"
},
"./permission": {
"types": "./dist/permission/index.d.ts",
"import": "./dist/permission/index.mjs",
"require": "./dist/permission/index.cjs"
},
"./printer": {
"types": "./dist/printer/index.d.ts",
"import": "./dist/printer/index.mjs",
"require": "./dist/printer/index.cjs"
},
"./time": {
"types": "./dist/time/index.d.ts",
"import": "./dist/time/index.mjs",
"require": "./dist/time/index.cjs"
},
"./timer": {
"types": "./dist/timer/index.d.ts",
"import": "./dist/timer/index.mjs",
"require": "./dist/timer/index.cjs"
},
"./ui": {
"types": "./dist/ui/index.d.ts",
"import": "./dist/ui/index.mjs",
"require": "./dist/ui/index.cjs"
}
},
"keywords": [
"utils",
@@ -44,9 +81,9 @@
"scripts": {
"build": "vite build",
"watch": "vite build --watch",
"typecheck": "tsc -p tsconfig.json --noEmit",
"lint": "eslint --ext .js,ts --fix src",
"format": "prettier --write src",
"release": "standard-version",
"commit": "cz",
"lint-staged": "lint-staged",
"test": "vitest run"
@@ -59,7 +59,10 @@ export function getValueOfRule(
/**
* -
*/
function getTextTargetValue(innerValue: InputValue, options: InputOptions): InputValue {
function getTextTargetValue(
innerValue: InputValue,
options: InputOptions,
): InputValue {
const { required, init, pattern, number } = options;
innerValue ??= "";
@@ -143,7 +146,10 @@ function applyPrecision(num: number, options: InputOptions): number {
/**
* -
*/
function getNumberTargetValue(innerValue: InputValue, options: InputOptions): InputValue {
function getNumberTargetValue(
innerValue: InputValue,
options: InputOptions,
): InputValue {
const { number, keepDecimal, noSign, digits } = options;
const strValue = String(innerValue ?? "");
+14 -7
View File
@@ -127,7 +127,7 @@ export class JpPrinter {
addText(content) {
content = String(content);
let code = [];
let code;
if (isAndroidApp) {
code = plus.android.invoke(content, "getBytes", "gbk");
} else {
@@ -254,8 +254,8 @@ export class JpPrinter {
let code = new TextEncoder("gb18030", {
NONSTANDARD_allowLegacyEncoding: true,
}).encode(content);
const pL = parseInt((code.length + 3) % 256);
const pH = parseInt((code.length + 3) / 256);
// const pL = parseInt((code.length + 3) % 256);
// const pH = parseInt((code.length + 3) / 256);
this.data.push(29, 40, 107, 3, 0, 49, 69, ...code);
return this;
}
@@ -740,6 +740,7 @@ export class JpPrinter {
let ch = 0;
text.split("").forEach((c) => {
// 是否是汉字,汉字两倍宽
// eslint-disable-next-line no-control-regex
const isChinese = /[^\x00-\xff]/.test(c);
if (isChinese) {
ch += 2;
@@ -984,10 +985,12 @@ export class JpPrinter {
return this;
}
setBarcodeContent(t, content) {
setBarcodeContent(t) {
let ty = 73;
this.data.push(29);
this.data.push(107);
const bar = JpPrinter.bar;
switch (t) {
case bar[0]:
ty = 65;
@@ -1031,12 +1034,16 @@ export class JpPrinter {
}
export class Query {
constructor() {
this.queryStatus = new Query();
}
/**
* 查询打印机实时状态
* @param {*} n
* @param {*} device
*/
getRealtimeStatusTransmission(n, device) {
getRealtimeStatusTransmission(n) {
/*
n = 1:传送打印机状态
n = 2:传送脱机状态
@@ -1048,7 +1055,7 @@ export class Query {
dateView.setUint8(0, 16);
dateView.setUint8(1, 4);
dateView.setUint8(2, n);
queryStatus.query(buf);
this.queryStatus.query(buf);
}
addGeneratePlus(n, m, t, device) {
@@ -1059,7 +1066,7 @@ export class Query {
dateView.setUint8(2, n);
dateView.setUint8(3, m);
dateView.setUint8(4, t);
queryStatus.query(buf, device);
this.queryStatus.query(buf, device);
}
query(buf, device) {
+1
View File
@@ -195,6 +195,7 @@ export class TSCPlus extends TSC {
let ch = 0;
text.split("").forEach((c) => {
// 是否是汉字,汉字两倍宽
// eslint-disable-next-line no-control-regex
const isChinese = /[^\x00-\xff]/.test(c);
if (isChinese) {
ch += 2;
+1 -1
View File
@@ -417,7 +417,7 @@ export class TSC {
for (x = 0; x < w; x++) {
var color = bitmapData[(y * w + x) * 4 + 1];
if (color <= 128) {
bits[parseInt(y * pitch + x / 8)] |= 0x80 >> x % 8;
bits[parseInt(y * pitch + x / 8)] |= 0x80 >> (x % 8);
}
}
}
+40 -10
View File
@@ -13,7 +13,9 @@ describe("getValueOfRule - 文本模式 (text: true)", () => {
});
test("required + init:非数字文本回退到 init", () => {
expect(getValueOfRule("abc", { ...textOpts, required: true, init: "默认" })).toBe("默认");
expect(
getValueOfRule("abc", { ...textOpts, required: true, init: "默认" }),
).toBe("默认");
});
test("required 无 init:非数字文本保持原值", () => {
@@ -29,7 +31,9 @@ describe("getValueOfRule - 文本模式 (text: true)", () => {
});
test("pattern 匹配成功返回原值", () => {
expect(getValueOfRule("123", { ...textOpts, pattern: "^\\d+$" })).toBe("123");
expect(getValueOfRule("123", { ...textOpts, pattern: "^\\d+$" })).toBe(
"123",
);
});
});
@@ -107,7 +111,9 @@ describe("getValueOfRule - digits 小数位数", () => {
});
test("keepDecimal + number 返回数字类型", () => {
expect(getValueOfRule("3.10", { digits: 2, keepDecimal: true, number: true })).toBe(3.1);
expect(
getValueOfRule("3.10", { digits: 2, keepDecimal: true, number: true }),
).toBe(3.1);
});
});
@@ -127,11 +133,15 @@ describe("getValueOfRule - integer 整数模式", () => {
describe("getValueOfRule - noSign 不允许正负号", () => {
test("带正号的输入被视为非法", () => {
expect(getValueOfRule("+5", { noSign: true, required: true, init: 0 })).toBe("0");
expect(
getValueOfRule("+5", { noSign: true, required: true, init: 0 }),
).toBe("0");
});
test("带负号的输入被视为非法", () => {
expect(getValueOfRule("-5", { noSign: true, required: true, init: 0 })).toBe("0");
expect(
getValueOfRule("-5", { noSign: true, required: true, init: 0 }),
).toBe("0");
});
test("无符号数字正常通过", () => {
@@ -142,25 +152,45 @@ describe("getValueOfRule - noSign 不允许正负号", () => {
describe("getValueOfRule - callback", () => {
test("callback 接收最终处理后的值", () => {
let received: unknown;
getValueOfRule("42", { number: true }, (v) => { received = v; });
getValueOfRule("42", { number: true }, (v) => {
received = v;
});
expect(received).toBe(42);
});
});
describe("getValueOfRule - 综合场景", () => {
test("限制输入 min:0, max:99999.99, required, digits:2", () => {
expect(getValueOfRule("100.456", { min: 0, max: 99999.99, required: true, digits: 2 })).toBe("100.46");
expect(
getValueOfRule("100.456", {
min: 0,
max: 99999.99,
required: true,
digits: 2,
}),
).toBe("100.46");
});
test("超出 max 时截断到 max", () => {
expect(getValueOfRule("100000", { min: 0, max: 99999.99, required: true, digits: 2 })).toBe("99999.99");
expect(
getValueOfRule("100000", {
min: 0,
max: 99999.99,
required: true,
digits: 2,
}),
).toBe("99999.99");
});
test("空输入 required 回退到 min", () => {
expect(getValueOfRule("", { min: 0, max: 99999.99, required: true, digits: 2 })).toBe("0");
expect(
getValueOfRule("", { min: 0, max: 99999.99, required: true, digits: 2 }),
).toBe("0");
});
test("负数输入限制到 min:0", () => {
expect(getValueOfRule("-5", { min: 0, max: 100, required: true })).toBe("0");
expect(getValueOfRule("-5", { min: 0, max: 100, required: true })).toBe(
"0",
);
});
});
+2 -2
View File
@@ -1,11 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"exclude": ["node_modules", "dist"],
"include": ["src/**/*.ts"],
"include": ["src/**/*.ts", "types/**/*.d.ts"],
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
}
}
}
+338
View File
@@ -0,0 +1,338 @@
export function row2col(arr: any): any;
/**
* NOTE 打印机普通文本最大宽度32个数字,16个汉字超出换行
*
*/
export class JpPrinter {
static bar: string[];
/**
* 获取文本宽度点数
* @param {string} text
* @returns {number}
*/
static getTextDots(text: string): number;
/**
* 根据最大点数获取文本
* @param {string} text
* @param {number} maxDots
* @return {string}
*/
static getMaxText(text?: string, maxDots?: number): string;
/**
* 分割字符
* "123456789" => ["1234", "5678", "9"]
* @param {string} str
* @param {number} maxDots
* @return {string[]}
*/
static splitString(str: string, maxDots: number): string[];
static createDefaultPrinter(): JpPrinter;
static getQuery(): Query;
constructor(width?: number, dpi?: number);
name: string;
data: any[];
align: string;
bold: boolean;
lineSpacing: number;
fontSize: number;
/** 有效打印宽度 */
width: number;
dpi: number;
_layout: null;
storeLayout(): this;
restoreLayout(): this;
getXDots(): number;
/** 初始化打印机 */
init(): this;
/**
* 添加文本内容
* @param {string} content
*/
addText(content: string): this;
/**
* GS ! 选择字符大小
* @param {number} n 放大倍数
* */
setFontSize(n?: number): this;
/**
* ESC E 选择/取消加粗模式
* @param {boolean} bold
* */
setBold(bold: boolean): this;
/**
* ESC – 选择/取消下划线模式
* 根据 n 的值选择或取消下划线模式:
* • 下划线可加在所有字符下(包括右间距),但不包括 HT 设置的空格。
* • 下划线不能作用在顺时针旋转 90°和反色的字符下。
* • 当取消下划线模式时,后面的字符不加下划线,下划线的宽度不改变。默认宽度是一点宽。
* • 改变字符大小不影响当前下划线宽度。
* • 下划线选择取消也可以由 ESC !来设置,最后执行的命令有效。
* • 该命令不影响汉字字符的设定。
* @param {number} n
* 0, 48 取消下划线模式
* 1, 49 选择下划线模式(1 点宽)
* 2, 50 选择下划线模式(2 点宽)
* @returns
*/
setUnderline(n?: number): this;
/**
* FS - 选择/取消汉字下划线模式
* 根据 n 的值,选择或取消汉字的下划线
* • 打印机能对所有字符加下划线,包括左右间距。但不能对由 HT 命令(横向跳格)引起的空格加下划线,也不对顺时针旋转 90 度的字符加下划线。
* • 消下划线模式后,不再执行下划线打印,但原先设置的下划线线宽不会改变。默认下划线线宽为 1 点。
* • 即使改变字符大小,设定的下划线线宽也不会改变。
* • 用 FS !也可选择或取消下划线模式,最后一条命令有效。
* @param {*} n
* 0, 48 取消汉字下划线
* 1, 49 选择汉字下划线(1 点宽)
* 2, 50 选择汉字下划线(2 点宽
* @returns
*/
setUnderlineChinese(n?: any): this;
/**
* 设置二维码大小
* @param {*} n
* @see {JpPrinter#setQRCodeSize}
*/
setSelectSizeOfModuleForQRCode(n: any): this;
/**
* 设置 QRCode 模块大小为 n dot
* @param {number} n
* @returns
*/
setQRCodeSize(n: number): this;
/**
* 选择 QRCode 纠错等级
* @see {JpPrinter#setQRCodeErrorCorrectionLevel}
*/
setSelectErrorCorrectionLevelForQRCode(n: any): this;
/**
* 选择 QRCode 纠错等级
* n 功能 纠错能力
* 48 选择纠错等级 L 7
* 49 选择纠错等级 M 15
* 50 选择纠错等级 Q 25
* 51 选择纠错等级 H 30
* @see
*/
setQRCodeErrorCorrectionLevel(n: any): this;
/** 设置二维码内容 */
setStoreQRCodeData(content: any): this;
/**
* 打印二维码
* @deprecated
* @see {JpPrinter#printQRCode}
* */
setPrintQRCode(): this;
/** 打印二维码 */
printQRCode(): this;
addQrCodeByUrl(url: any, width: any, height: any): this;
addImageByUrl(url: any, width: any, height: any): this;
addQrCodeByText(contents: any, width: any, height: any): this;
/**
* HT 水平定位
* 移动打印位置到下一个水平定位点的位置。
* @deprecated
* @see {JpPrinter#addHorTab}
*/
setHorTab(): this;
/**
* HT 水平定位
* 移动打印位置到下一个水平定位点的位置。
*/
addHorTab(): this;
/**
* ESC $ 设置绝对打印位置
* @param {number} where
* @returns
*/
setAbsolutePrintPosition(where: number): this;
/**
* ESC \ 设置相对横向打印位置
* 安卓无法传入超过127的数
*
* @param {number} where
* @returns
*/
setRelativePrintPosition(where: number): this;
/**
* ESC a 选择对齐方式
* @param {number} n
* 0, 48 左对齐
* 1, 49 中间对齐
* 2, 50 右对齐
*/
setSelectJustification(n: number): this;
/**
* 设置对齐方式
* @param {string} align
* "l" 左对齐
* "m" 中间对齐
* "r" 右对齐
* @returns
*/
setAlign(align: string): this;
/**
* ESC D 设置横向跳格位
* @param {number} n
* @deprecated
* @see {}
*/
space(...nk: any[]): this;
/**
* ESC D 设置横向跳格位
* @param {number} n
*/
addSpace(...nk: any[]): this;
/**
* GS L 设置左边距
* @param {number} n
* @returns
*/
setLeftMargin(n: number): this;
textMarginRight(n: any): this;
/**
* ESC 3 设置行间距
* @deprecated
* @see {JpPrinter#setLineSpacing}
* */
rowSpace(n: any): this;
/**
* ESC 3 设置行间距
* */
setLineSpacing(n: any): this;
/**
* GS W 设置打印区域宽度
* @param {number} width
* @returns
*/
setPrintingAreaWidth(width: number): this;
setSound(n: any, t: any): this;
setBitmap(res: any): this;
/**
* 添加位图 GS v 0
* */
addBitmap(m: any, xL: any, xH: any, yL: any, yH: any, ...data: any[]): this;
/**
* 添加位图
* @param {Uint8ClampedArray} imgData
* @param {number} width
* @param {number} height
*/
addBitmapHelper(imgData: Uint8ClampedArray, width: number, height: number): this;
/**
* 打印并换行
* */
addLF(): this;
/**
* ESC J 打印并走纸 n 个单位
* @param {number} n
* @see {JpPrinter#addPrintAndFeed}
*/
setPrintAndFeed(n: number): this;
/**
* ESC J 打印并走纸 n 个单位
* @param {number} n
*/
addPrintAndFeed(n: number): this;
/**
* ESC d 打印并走纸 n 行
* @param {number} n
* @see {JpPrinter#addPrintAndFeedRow}
*/
setPrintAndFeedRow(n: number): this;
/**
* ESC d 打印并走纸 n 行
* @param {number} n
* @returns
*/
addPrintAndFeedRow(n: number): this;
/**
* 添加 n 行
* @param {number} n
* @see {JpPrinter#addPrintAndFeedRow}
*/
addRow(n: number): this;
/**
* 添加打印指令数据
* @param {number[]} data
* @returns
*/
addData(...data: number[]): this;
/**
* 获取打印指令数据
*/
getData(): any[];
/*********************** 自增 ***********************/
/**
* 计算文字位置
* @param {*} value
* @param {*} width
* @deprecated
* @see {JpPrinter#getTextDots}
*/
siteText(value: any, width: any): number;
/**
* 计算数字位置
* @param {*} value
* @param {*} width
* @deprecated
* @see {JpPrinter#getTextDots}
*/
siteNumber(value: any, width: any): number;
/**
* 左对齐文字
* @deprecated
*/
alignLeft(name: any, text: any, arr: any): this;
/**
* @deprecated
* @see {JpPrinter.getTextDots}
*/
getTextDots(text: any): number;
/**
* @deprecated
* @see {JpPrinter.getMaxText}
*/
getMaxText(text?: string, maxDots?: number): string;
/**
* 添加两端对齐的文字
*/
addTextJustifyAlign(text1: any, text2: any): this;
/**
* 添加分割线
*/
addDivider(): this;
/**
* 添加表格
* @param {Object} table
* @param {Array} table.header
* @param {Array} table.data
* @param {number[]} [table.columns] 每列百分比
*/
addTable({ header, data, columns }: {
header: any[];
data: any[];
columns?: number[] | undefined;
}): this;
/**
* 分割符
* @deprecated
* @see {JpPrinter#addDivider}
*/
separator(width: any): this;
setBarcodeWidth(width: any): this;
setBarcodeHeight(height: any): this;
setBarcodeContent(t: any): this;
}
export class Query {
queryStatus: Query;
/**
* 查询打印机实时状态
* @param {*} n
* @param {*} device
*/
getRealtimeStatusTransmission(n: any): void;
addGeneratePlus(n: any, m: any, t: any, device: any): void;
query(buf: any, device: any): void;
}
+63
View File
@@ -0,0 +1,63 @@
export class TSCPlus extends TSC {
static MILLIMETERS_PER_INCH: number;
static getLetterWidthDot(font: any): number;
static getFontLineHeightDot(font: any, lineHeight: any): any;
/**
* 获取文本宽度点数
* @param {string} text
* @returns {number}
*/
static getTextDots(text: string, letterDots?: number): number;
/**
* 根据最大点数获取文本
* @param {string} text
* @param {number} maxDots
* @return {string}
*/
static getMaxText(text?: string, maxDots?: number): string;
/**
* 分割字符串
* "123456789" => ["1234", "5678", "9"]
* @param {string} str
* @param {number} maxDots
* @return {string[]}
*/
static splitString(str: string, maxDots: number): string[];
font: string;
lineHeight: string;
dpi: number;
nextX: number;
nextY: number;
widthDot: number;
heightDot: number;
paddingTopDot: number;
paddingLeftDot: number;
paddingRightDot: number;
paddingBottomDot: number;
/**
* 设置内边距
* @param {number} paddingTopDot
* @param {number} paddingRightDot
* @param {number} paddingBottomDot
* @param {number} paddingLeftDot
*/
setPadding(paddingTopDot: number, paddingRightDot: number, paddingBottomDot: number, paddingLeftDot: number): void;
mmToDot(mm: any): number;
setDpi(dpi: any): void;
setSize(w: any, h: any): TSC;
setSizeMM(w: any, h: any): TSC;
setSizeDot(w: any, h: any): TSC;
/**
* 设置字体
* @param {string} font
*/
setFont(font: string): void;
/**
* 设置行高
* @param {string|number} lineHeight
*/
setLineHeight(lineHeight: string | number): void;
addTextLn(text: any, options?: {}): this;
addAddBarCode(content: any, options?: {}): this;
}
import { TSC } from "./tsc.js";
+369
View File
@@ -0,0 +1,369 @@
export class TSC {
static LINE_BREAK: string;
constructor(data?: any[]);
data: any[];
addData(...data: any[]): void;
addDataArray(dataArray: any): void;
addCode(code: any): this;
getData: () => any;
/**
* 该指令用于设定卷标纸的宽度和长度
* @param {number|string} w 标签宽度 单位英寸inch
* @param {number|string} h 标签高度 单位英寸inch
* @returns {TSC}
*/
setSize(w: number | string, h: number | string): TSC;
/**
* 该指令用于设定卷标纸的宽度和长度
* @param {number|string} w 标签宽度 单位毫米mm
* @param {number|string} h 标签高度 单位毫米mm
* @returns {TSC}
*/
setSizeMM(w: number | string, h: number | string): TSC;
/**
* 该指令用于定义两张卷标纸间的垂直间距距离
* @param {number|string} m 两标签纸中间的垂直距离 单位英寸inch
* @param {number|string} n 垂直间距偏移 单位英寸inch
* @returns {TSC}
*/
setGap(m?: number | string, n?: number | string): TSC;
/**
* 该指令用于定义两张卷标纸间的垂直间距距离
* @param {number|string} m 两标签纸中间的垂直距离 单位毫米mm
* @param {number|string} n 垂直间距偏移 单位毫米mm
* @returns {TSC}
*/
setGapMM(m?: number | string, n?: number | string): TSC;
/**
* 该指令用于设定黑标高度及定义标签印完后标签额外送出的长度
* @param {number|string} m 黑标高度 单位英寸inch
* @param {number|string} n 额外送出纸张长度 单位英寸inch
* @returns {TSC}
*/
setBLine(m?: number | string, n?: number | string): TSC;
/**
* 该指令用于控制在剥离模式时(peel-off mode)每张卷标停止的位置,
* 在打印下一张时打印机会将原先多推出或少推出的部分以回拉方式补偿回来。
* 该指令仅适用于剥离模式。
* @param {number|string} m 纸张停止的距离 单位英寸inch
* @returns {TSC}
*/
setOffset(offset?: number): TSC;
/**
* 该指令用于控制打印速度
* @param {number|string} speed 打印速度
* @returns {TSC}
*/
setSpeed(speed?: number | string): TSC;
/**
* 该指令用于控制打印时的浓度
* @param {number|string} density 打印浓度
* @returns {TSC}
*/
setDensity(density?: number | string): TSC;
/**
* 该指令用于定义打印时出纸和打印字体的方向
* @param {number|string} direction
* @returns {TSC}
*/
setDirection(direction?: number | string): TSC;
/**
* 该指令用于定义卷标的参考坐标原点。坐标原点位置和打印方向有关
* @param {number|string} x 水平方向的坐标位置,单位dot
* @param {number|string} y 垂直方向的坐标位置,单位dot
* @returns {TSC}
*/
setReference(x?: number | string, y?: number | string): TSC;
/**
* 该指令表示标签打印偏移量多少设置
* @param {number|string} n 打印偏移量
* @returns {TSC}
*/
setShift(n?: number | string): TSC;
/**
* 该指令用于选择对应的国际字符集
* 001:USA
* 002:French
* 003:Latin America
* 034:Spanish
* 039:Italian
* 044:United Kingdom
* 046:Swedish
* 047:Norwegian
* 049:German
* @param {number|string} country 字符集
* @returns {TSC}
*/
setCountry(country?: number | string): TSC;
/**
* 该指令用于选择对应的国际代码页
* 8-bit codepage 字符集代表
* 437:United States
* 850:Multilingual
* 852:Slavic
* 860:Portuguese
* 863:Canadian/French
* 865:Nordic
*
* Windows code page
* 1250:Central Europe
* 1252:Latin I
* 1253:Greek
* 1254:Turkish
*
* 以下代码页仅限于12×24 dot 英数字体
* WestEurope:WestEurope
* Greek:Greek
* Hebrew:Hebrew
* EastEurope:EastEurope
* Iran:Iran
* IranII:IranII
* Latvian:Latvian
* Arabic:Arabic
* Vietnam:Vietnam
* Uygur:Uygur
* Thai:Thai
* 1252:Latin I
* 1257:WPC1257
* 1251:WPC1251
* 866:Cyrillic
* 858:PC858
* 747:PC747
* 864:PC864
* 1001:PC1001
* @param {number|string} n 代码页
* @returns {TSC}
*/
setCodepage(n?: number | string): TSC;
/**
* 该指令用于清除图像缓冲区(image buffer)的数据
* 注:此项指令必须置于 SIZE 指令之后
* @param {number|string} n 代码页
* @returns {TSC}
*/
setCls(): TSC;
/**
* 该指令用于将标签纸向前推送指定的长度
* 打印机分辨率200 DPI:1 mm = 8 dots
* 打印机分辨率300 DPI:1 mm = 12 dots
* @param {number|string} n 1≤n≤9999,单位dot
* @returns {TSC}
*/
setFeed(n: number | string): TSC;
/**
* 该指令用于将标签纸向前推送指定的长度
* 打印机分辨率200 DPI:1 mm = 8 dots
* 打印机分辨率300 DPI:1 mm = 12 dots
* @param {number|string} n 1≤n≤9999,单位dot
* @returns {TSC}
*/
setBackFeed(n: number | string): TSC;
/**
* 该指令用于将标签纸向前推送指定的长度
* 打印机分辨率200 DPI:1 mm = 8 dots
* 打印机分辨率300 DPI:1 mm = 12 dots
* @param {number|string} n 1≤n≤9999,单位dot
* @returns {TSC}
*/
setBackUp(n: number | string): TSC;
/**
* 该指令用于控制打印机进一张标签纸
* @returns {TSC}
*/
setFromFeed(): TSC;
/**
* 在使用含有间隙或黑标的标签纸时,
* 若不能确定第一张标签纸是否在正确打印位置时,
* 此指令可将标签纸向前推送至下一张标签纸的起点开始打印。
* 标签尺寸和间隙需要在本条指令前设置
* 注:使用该指令时,纸张高度大于或等于30 mm
* @returns {TSC}
*/
setHome(): TSC;
/**
* 该指令用于打印出存储于影像缓冲区内的数据
* @param {number|string} m 指定打印的份数(set
* @param {number|string} n 每张标签需重复打印的张数
* @returns {TSC}
*/
setPrint(m?: number | string, n?: number | string): TSC;
/**
* 该指令用于控制蜂鸣器的频率,可设定10阶的声音,
* 频率,可设定10阶的声音,每阶声音的长短由第二个参数控制
* @param {number|string} m 指定打印的份数(set
* @param {number|string} n 每张标签需重复打印的张数
* @returns {TSC}
*/
setSound(level?: number, interval?: number): TSC;
/**
* 该指令用于设定打印机进纸时,若经过所设定的长度仍无法侦测到垂直间距,
* 则打印机在连续纸模式工作。
* @param {number|string} limit 英制系统(inch
* @returns {TSC}
*/
setLimitFeed(limit?: number | string): TSC;
/**
* 不经自测动作,直接打印自检页信息。
* @returns {TSC}
*/
setSelfTest(): TSC;
/**
* 该指令用于在标签上画线
* @param {number|string} x 线条左上角X坐标,单位dot
* @param {number|string} y 线条左上角Y坐标,单位dot
* @param {number|string} width 线宽,单位dot
* @param {number|string} height 线高,单位dot
* @returns {TSC}
*/
setBar(x: number | string, y: number | string, width: number | string, height: number | string): TSC;
/**
* 该指令用于在标签上画线
* @param {number|string} x 线条左上角X坐标,单位dot
* @param {number|string} y 线条左上角Y坐标,单位dot
* @param {number|string} codeType 线宽,单位dot
* @param {number|string} height 条形码高度,以点(dot)表示
* @param {number|string} readable 0 表示人眼不可识,1表示人眼可识
* @param {number|string} rotation 条形码旋转角度,顺时针方向
* @param {number|string} narrow 窄bar宽度,以点(dot)表示
* @param {number|string} wide 宽bar宽度,以点(dot)表示
* @param {number|string} content 内容
* @returns {TSC}
*/
setBarCode(x: number | string, y: number | string, codeType: number | string | undefined, height: number | string | undefined, readable: number | string | undefined, rotation: number | string | undefined, narrow: number | string | undefined, wide: number | string | undefined, content: number | string): TSC;
/**
* 该指令用于在卷标上绘制矩形方框
* @param {number|string} x1 线条左上角X坐标,单位dot
* @param {number|string} y1 线条左上角Y坐标,单位dot
* @param {number|string} x2 方框右下角X坐标,单位dot
* @param {number|string} y2 方框右下角Y坐标,单位dot
* @param {number|string} thickness 方框线宽,单位dot
* @returns {TSC}
*/
setBox(x1: number | string, y1: number | string, x2: number | string, y2: number | string, thickness: number | string): TSC;
/**
* 该指令用于在卷标上绘制位图(非BMP格式图档)
* @param {number|string} x 线条左上角X坐标,单位dot
* @param {number|string} y 线条左上角Y坐标,单位dot
* @param {number|string} width 方框右下角X坐标,单位dot
* @param {number|string} height 方框右下角Y坐标,单位dot
* @param {number|string} mode 方框线宽,单位dot
* @param {number|string} bitmapData 方框线宽,单位dot
* @returns {TSC}
*/
setBitmap(x: number | string, y: number | string, width: number | string, height: number | string, mode: number | string, bitmapData: number | string): TSC;
/**
* 该指令用于清除影像缓冲区部分区域的数据
* @param {number|string} x 清除区域的左上角X座标,单位dot
* @param {number|string} y 清除区域的左上角Y座标,单位dot
* @param {number|string} width 清除区域宽度,单位dot
* @param {number|string} height 清除区域高度,单位dot
* @returns {TSC}
*/
setErase(x: number | string, y: number | string, width: number | string, height: number | string): TSC;
/**
* 将指定的区域反相打印
* @param {number|string} x 反相区域的左上角X座标,单位dot
* @param {number|string} y 反相区域的左上角Y座标,单位dot
* @param {number|string} width 反相区域宽度,单位dot
* @param {number|string} height 反相区域高度,单位dot
* @returns {TSC}
*/
setReverse(x: number | string, y: number | string, width: number | string, height: number | string): TSC;
/**
* 该指令用于打印字符串
* @param {number|string} x 文字X方向起始点坐标
* @param {number|string} y 文字Y方向起始点坐标
* @param {number|string} font 字体名称
* @param {number|string} sx X 方向放大倍率1-10
* @param {number|string} sy Y 方向放大倍率1-10
* @param {number|string} content 内容
* @returns {TSC}
*/
setText(x: number | string, y: number | string, font: number | string, rotation: any, sx: number | string, sy: number | string, content: number | string): TSC;
/**
* 该指令用来打印二维码
* @param {number|string} x 二维码水平方向起始点坐标
* @param {number|string} y 反相区域的左上角Y座标,单位dot
* @param {number|string} level 选择QRCODE纠错等级
* @param {number|string} width 二维码宽度1-10
* @param {number|string} mode 手动/自动编码
* @param {number|string} rotation 旋转角度(顺时针方向)
* @param {number|string} content 内容
* @returns {TSC}
*/
setQrcode(x: number | string, y: number | string, level: number | string, width: number | string, mode: number | string, rotation: number | string, content: number | string): TSC;
/**
* 该指令用来起动Key1 的预设功能
* 预设为进纸功能
* @param {"ON"|"OFF"} k 开启按键/关闭按键
* @returns {TSC}
*/
setKey1(k: "ON" | "OFF"): TSC;
/**
* 该指令用来起动Key2 的预设功能
* 预设为暂停功能
* @param {"ON"|"OFF"} k 开启按键/关闭按键
* @returns {TSC}
*/
setKey2(k: "ON" | "OFF"): TSC;
/**
* 该指令用来启动/关闭剥离模式,默认值为关闭
* @param {"ON"|"OFF"} k 开启按键/关闭按键
* @returns {TSC}
*/
setPeel(k: "ON" | "OFF"): TSC;
/**
* 此命令是用来启用/禁用撕纸位置走到撕纸处,此设置关掉电源后将保存在打印机内
* @param {"ON"|"OFF"} k 开启按键/关闭按键
* @returns {TSC}
*/
setTear(k: "ON" | "OFF"): TSC;
/**
* 此命令是用来启用/禁用撕纸位置走到撕纸处,此设置关掉电源后将保存在打印机内
* @param {"ON"|"OFF"} k 开启按键/关闭按键
* @returns {TSC}
*/
setStripper(k: "ON" | "OFF"): TSC;
/**
* 此设置用于启用/禁用打印头合盖传感器。如果禁用合盖传感器,打印机头被打开时,将不会传回错误信息。
* 此设置将保存在打印机内存。
* @param {"ON"|"OFF"} k 开启按键/关闭按键
* @returns {TSC}
*/
setHead(k: "ON" | "OFF"): TSC;
/**
* 此设置用于启用/禁用打印头合盖传感器。如果禁用合盖传感器,打印机头被打开时,将不会传回错误信息。
* 此设置将保存在打印机内存。
* @param {"ON"|"OFF"|"AUTO"|string|number} k 开启按键/关闭按键
* @returns {TSC}
*/
setHead2(k: "ON" | "OFF" | "AUTO" | string | number): TSC;
/**
* 此命令将禁用/启用标签机在无纸或开盖错误发生后,
* 上纸或合盖后重新打印一次标签内容
* @param {"ON"|"OFF"} k 开启按键/关闭按键
* @returns {TSC}
*/
setReprint(k: "ON" | "OFF"): TSC;
/**
* 设定开启/关闭碳带感应器,即切换热转式/热感印式打印。通常打印机于开启电源时,
* 碳带感应器即会自动检测打印机是否已装上碳带,并藉此决定使用热感式或热转式打印。
* 此项设定并不会存于打印机中。此方法仅适用于热转式机器。
* @param {"ON"|"OFF"} k 开启按键/关闭按键
* @returns {TSC}
*/
setRibbon(k: "ON" | "OFF"): TSC;
/**
* 此命令用于设置切刀状态,关闭打印机电源后,该设置将会被存储在打印机内存中。
* @param {"OFF"|"BATCH"|string|number} k 开启按键/关闭按键
* @returns {TSC}
*/
setCutter(k: "OFF" | "BATCH" | string | number): TSC;
/**
* 此指令用于设置打印机自动返回状态
* @param {"ON"|"OFF"|"BATCH"} k 开启按键/关闭按键
* @param {string} content
* @returns {TSC}
*/
setResponse(k: "ON" | "OFF" | "BATCH", content: string): TSC;
}
+33 -5
View File
@@ -5,15 +5,43 @@ import dts from "vite-plugin-dts";
const __dirname = fileURLToPath(new URL(".", import.meta.url));
const sharedOutput = {
preserveModules: true,
preserveModulesRoot: resolve(__dirname, "src"),
assetFileNames: "assets/[name]-[hash][extname]",
exports: "named",
} as const;
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"),
"input-rule/index": resolve(__dirname, "src/input-rule/index.ts"),
"knock-test/index": resolve(__dirname, "src/knock-test/index.ts"),
"permission/index": resolve(__dirname, "src/permission/index.ts"),
"printer/index": resolve(__dirname, "src/printer/index.ts"),
"time/index": resolve(__dirname, "src/time/index.ts"),
"timer/index": resolve(__dirname, "src/timer/index.ts"),
"ui/index": resolve(__dirname, "src/ui/index.ts"),
},
},
rollupOptions: {
rolldownOptions: {
external: ["dayjs", "lodash-es", "text-encoding", "tslib"],
output: [
{
...sharedOutput,
format: "es",
entryFileNames: "[name].mjs",
chunkFileNames: "chunks/[name]-[hash].mjs",
},
{
...sharedOutput,
format: "cjs",
entryFileNames: "[name].cjs",
chunkFileNames: "chunks/[name]-[hash].cjs",
},
],
},
sourcemap: true,
},
@@ -24,7 +52,7 @@ export default defineConfig({
},
plugins: [
dts({
include: ["src"],
include: ["src", "types"],
outDir: "dist",
}),
],
+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",
}),
],
});
+7
View File
@@ -0,0 +1,7 @@
# @r-utils/uview-plus
## 1.3.0
### Minor Changes
- 添加工具
+56 -79
View File
@@ -1,6 +1,16 @@
# @r-utils/uview-plus
基于 [uview-plus](https://uview-plus.jiangruyi.com/) 的 Vue3 组合式 API 工具 Hooks,适用于 uni-app 项目
在项目已使用 [uview-plus](https://uview-plus.jiangruyi.com/) 的前提下,提供适用于 uview-plus 组件的组合式 API Hooks。
## 适用范围
- 适用于已接入 `uview-plus` 的项目。
- 典型场景是 uni-app + Vue3 + uview-plus。
## 不适用范围
- 未使用 `uview-plus` 的项目不建议使用。
- 不适用于 Vue2 项目。
## 安装
@@ -8,102 +18,69 @@
pnpm add @r-utils/uview-plus
```
## 使用
## 导入方式
### 推荐:根入口导入
大多数场景推荐从根入口导入,路径简单,使用心智负担更低。
```ts
import { usePickerSingle, usePicker, useCalendar } from '@r-utils/uview-plus'
import {
usePickerSingle,
usePicker,
useDateTimePicker,
useCalendar,
} from "@r-utils/uview-plus";
```
## API
### 兼容:子路径导入
### `usePickerSingle(options)`
单列 Picker 封装。
| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `value` | `unknown \| Ref<unknown>` | `null` | 选中的值 |
| `show` | `boolean \| Ref<boolean>` | `false` | 是否显示 |
| `indexes` | `Array<number \| null> \| Ref<...>` | `[null]` | 选中的索引 |
| `list` | `PickerColumns[0] \| Ref<...>` | `[]` | 列数据 |
| `textName` | `string` | `'text'` | 显示字段名 |
| `valueName` | `string` | `'value'` | 值字段名 |
| `placeholder` | `string` | `'请选择'` | 占位文本 |
**返回值:**
如果你希望模块边界更清晰,也可以使用子路径导入。两种方式都支持。
```ts
{
value, show, indexes, columns, text, defaultIndex,
showPicker, hidePicker, handleConfirm, handleClose
}
import { usePickerSingle } from "@r-utils/uview-plus/picker-single";
import { usePicker } from "@r-utils/uview-plus/picker";
import { useDateTimePicker } from "@r-utils/uview-plus/datetime-picker";
import { useCalendar } from "@r-utils/uview-plus/calendar";
```
---
## 导出模块
### `usePicker(options)`
| 子路径 | 说明 |
| --- | --- |
| `@r-utils/uview-plus/picker-single` | 单列 picker Hook |
| `@r-utils/uview-plus/picker` | 多列 picker Hook |
| `@r-utils/uview-plus/datetime-picker` | 时间选择器 Hook |
| `@r-utils/uview-plus/calendar` | 日历选择器 Hook |
多列 Picker 封装。
## 使用示例
| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `value` | `unknown[] \| Ref<unknown[]>` | `[]` | 选中的值数组 |
| `show` | `boolean \| Ref<boolean>` | `false` | 是否显示 |
| `indexes` | `Array<number \| null> \| Ref<...>` | `[]` | 选中的索引数组 |
| `columns` | `PickerColumns \| Ref<PickerColumns>` | `[]` | 列数据 |
| `textName` | `string` | `'text'` | 显示字段名 |
| `valueName` | `string` | `'value'` | 值字段名 |
| `placeholder` | `string` | `'请选择'` | 占位文本 |
| `separator` | `string` | `' '` | 多列值拼接分隔符 |
**返回值:**
### 单列选择器
```ts
{
value, show, indexes, columns, text, defaultIndex,
showPicker, hidePicker, handleConfirm, handleClose
}
import { usePickerSingle } from "@r-utils/uview-plus";
const picker = usePickerSingle({
list: [
{ text: "启用", value: 1 },
{ text: "禁用", value: 0 },
],
});
picker.showPicker();
```
---
### `useCalendar(options)`
日历选择封装。
| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `value` | `string \| string[] \| Ref<...>` | `null` | 选中的日期 |
| `show` | `boolean \| Ref<boolean>` | `false` | 是否显示 |
| `mode` | `'single' \| 'multiple' \| 'range' \| Ref<...>` | `'single'` | 日历模式 |
| `placeholder` | `string` | `'请选择'` | 占位文本 |
**返回值:**
### 子路径导入
```ts
{
value, show, text,
showCalendar, hideCalendar, handleConfirm, handleClose
}
import { usePicker } from "@r-utils/uview-plus/picker";
const picker = usePicker({
columns: [[{ text: "浙江", value: "zhejiang" }]],
});
```
## 类型声明
## 注意事项
包内置了 `UViewPlus` namespace 类型声明,无需额外引入。
```ts
declare namespace UViewPlus {
type PickerColumns = any[][];
type PickerValue<T extends PickerColumns = PickerColumns> = T[number][number][];
type PickerConfirmEvent<T extends PickerColumns = PickerColumns> = {
indexs: number[];
value: PickerValue<T>;
values: T;
};
type CalendarConfirmEvent = string[];
}
```
## 许可证
ISC
- 推荐优先使用根入口导入;如果需要更精确的模块边界,也可以使用子路径导入。
- 本包依赖 Vue3 和 uview-plus,请确保业务项目已安装并正确配置。
+29 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@r-utils/uview-plus",
"version": "1.2.1",
"version": "1.3.0",
"private": false,
"description": "uview-plus 组合式 API Hooks",
"type": "module",
@@ -8,13 +8,35 @@
"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"
},
"./*": "./*"
"./calendar": {
"types": "./dist/calendar/index.d.ts",
"import": "./dist/calendar/index.mjs",
"require": "./dist/calendar/index.cjs"
},
"./datetime-picker": {
"types": "./dist/datetime-picker/index.d.ts",
"import": "./dist/datetime-picker/index.mjs",
"require": "./dist/datetime-picker/index.cjs"
},
"./picker-single": {
"types": "./dist/picker-single/index.d.ts",
"import": "./dist/picker-single/index.mjs",
"require": "./dist/picker-single/index.cjs"
},
"./picker": {
"types": "./dist/picker/index.d.ts",
"import": "./dist/picker/index.mjs",
"require": "./dist/picker/index.cjs"
}
},
"keywords": [
"vue3",
@@ -46,11 +68,12 @@
"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": "jest"
"test": "vitest run"
},
"peerDependencies": {
"uview-plus": ">=3.0.0",
@@ -62,9 +85,11 @@
}
},
"dependencies": {
"@jonny1994/qqmap-wx-jssdk": "^1.4.0",
"lodash-es": "catalog:"
},
"devDependencies": {
"@dcloudio/types": "^3.4.14",
"@types/lodash-es": "catalog:",
"vite": "catalog:",
"vite-plugin-dts": "catalog:",
+4 -1
View File
@@ -17,7 +17,10 @@ describe("useCalendar", () => {
test("单选模式:text 显示 value,无值时显示 placeholder", () => {
const scope = effectScope();
scope.run(() => {
const { value, text } = useCalendar({ mode: "single", placeholder: "请选日期" });
const { value, text } = useCalendar({
mode: "single",
placeholder: "请选日期",
});
expect(text.value).toBe("请选日期");
value.value = "2024-01-15";
expect(text.value).toBe("2024-01-15");
@@ -50,7 +50,9 @@ describe("useDateTimePicker", () => {
test("handleConfirm 关闭弹窗(不更新 value", () => {
const scope = effectScope();
scope.run(() => {
const { value, show, handleConfirm } = useDateTimePicker({ value: "2024-01-01" });
const { value, show, handleConfirm } = useDateTimePicker({
value: "2024-01-01",
});
show.value = true;
const event = { indexs: [], value: [], values: [] } as any;
handleConfirm(event);
+10 -2
View File
@@ -60,7 +60,11 @@ describe("usePicker", () => {
test("indexes 含 null 时 text 显示 placeholder", async () => {
const scope = effectScope();
await scope.run(async () => {
const { text } = usePicker({ indexes: [null, 1], columns, placeholder: "请选择地区" });
const { text } = usePicker({
indexes: [null, 1],
columns,
placeholder: "请选择地区",
});
await nextTick();
expect(text.value).toBe("请选择地区");
});
@@ -71,7 +75,11 @@ describe("usePicker", () => {
const scope = effectScope();
await scope.run(async () => {
const { indexes, value, handleConfirm } = usePicker({ columns });
handleConfirm({ indexs: [1, 0], value: ["pb", "ca"], values: columns } as any);
handleConfirm({
indexs: [1, 0],
value: ["pb", "ca"],
values: columns,
} as any);
expect(indexes.value).toEqual([1, 0]);
await nextTick();
expect(value.value).toEqual(["pb", "ca"]);
+1 -1
View File
@@ -6,6 +6,6 @@
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
}
}
}
+50
View File
@@ -0,0 +1,50 @@
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[];
}
+2 -3
View File
@@ -1,10 +1,9 @@
/// <reference types="uview-plus/types" />
declare namespace UViewPlus {
type PickerColumns = never[][];
type PickerValue<T extends PickerColumns = PickerColumns> = T[number][number][];
type PickerValue<T extends PickerColumns = PickerColumns> =
T[number][number][];
type PickerConfirmEvent<T extends PickerColumns = PickerColumns> = {
indexs: number[];
+29 -12
View File
@@ -1,31 +1,48 @@
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"),
"calendar/index": resolve(__dirname, "src/calendar/index.ts"),
"datetime-picker/index": resolve(
__dirname,
"src/datetime-picker/index.ts",
),
"picker-single/index": resolve(
__dirname,
"src/picker-single/index.ts",
),
"picker/index": resolve(__dirname, "src/picker/index.ts"),
},
formats: ["es", "cjs"],
fileName: (format, entryName) =>
`${entryName}.${format === "es" ? "mjs" : "cjs"}`,
},
rollupOptions: {
external: ['vue', 'lodash-es'],
external: ["vue", "lodash-es"],
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",
}),
],
});
+7
View File
@@ -0,0 +1,7 @@
# @r-utils/vue2
## 1.4.0
### Minor Changes
- 添加工具
+76
View File
@@ -0,0 +1,76 @@
# @r-utils/vue2
仅用于 Vue2 项目的工具包。
## 适用范围
- 适用于 Vue2 项目。
- 适用于需要根据页面可见性触发组件 `onShow` / `onHide` 的场景。
## 不适用范围
- 不适用于 Vue3 项目。
- 如果是 uni-app 专用能力,建议优先使用 `@r-utils/uni-app`
## 安装
```bash
pnpm add @r-utils/vue2
```
## 导入方式
### 推荐:根入口导入
大多数场景推荐从根入口导入,路径简单,使用心智负担更低。
```ts
import Vue from "vue";
import { VisibilityPlugin } from "@r-utils/vue2";
Vue.use(VisibilityPlugin);
```
也可以使用默认导出:
```ts
import Vue from "vue";
import VisibilityPlugin from "@r-utils/vue2";
Vue.use(VisibilityPlugin);
```
### 兼容:子路径导入
如果你希望模块边界更清晰,也可以使用子路径导入。两种方式都支持。
```ts
import Vue from "vue";
import VisibilityPlugin from "@r-utils/vue2/plugins/visibility";
Vue.use(VisibilityPlugin);
```
## 当前能力
| 子路径 | 说明 |
| --- | --- |
| `@r-utils/vue2/plugins/visibility` | 基于页面可见性变更触发 `onShow` / `onHide` |
## 使用示例
```ts
export default {
onShow() {
console.log("页面显示");
},
onHide() {
console.log("页面隐藏");
},
};
```
## 注意事项
- 推荐优先使用根入口导入;如果需要更精确的模块边界,也可以使用子路径导入。
- 本包依赖 Vue2,请确保业务项目已安装兼容版本的 `vue`
+11 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@r-utils/vue2",
"version": "1.3.0",
"version": "1.4.0",
"private": false,
"description": "Vue2工具库",
"type": "module",
@@ -8,13 +8,20 @@
"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"
},
"./*": "./*"
"./plugins/visibility": {
"types": "./dist/plugins/visibility/index.d.ts",
"import": "./dist/plugins/visibility/index.mjs",
"require": "./dist/plugins/visibility/index.cjs"
}
},
"keywords": [
"vue2"
@@ -43,8 +50,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"
+22
View File
@@ -0,0 +1,22 @@
import type Vue from "vue";
import type { PluginObject, VueConstructor } from "vue";
declare module "vue/types/options" {
interface ComponentOptions<V extends Vue> {
/** 页面变为可见时触发的生命周期钩子 */
onShow?: (this: V) => void;
/** 页面变为隐藏时触发的生命周期钩子 */
onHide?: (this: V) => void;
}
}
/** 安装页面可见性生命周期插件 */
export declare function install(Vue: VueConstructor): void;
/** 页面可见性生命周期插件 */
declare const VisibilityPlugin: PluginObject<never> & {
/** 安装页面可见性生命周期插件 */
install: typeof install;
};
export default VisibilityPlugin;
@@ -1,3 +1,7 @@
/**
* 安装页面可见性生命周期插件
* @param {import("vue").VueConstructor} Vue Vue2 构造器
*/
function install(Vue) {
Vue.mixin({
created() {
@@ -8,14 +12,14 @@ function install(Vue) {
this.visibilitychangeCallback();
document.addEventListener(
"visibilitychange",
this.visibilitychangeCallback
this.visibilitychangeCallback,
);
}
},
destroyed() {
document.removeEventListener(
"visibilitychange",
this.visibilitychangeCallback
this.visibilitychangeCallback,
);
},
methods: {
+1 -1
View File
@@ -6,6 +6,6 @@
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
}
}
}
+24 -12
View File
@@ -1,31 +1,43 @@
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"),
"plugins/visibility/index": resolve(
__dirname,
"src/plugins/visibility/index.js",
),
},
formats: ["es", "cjs"],
fileName: (format, entryName) =>
`${entryName}.${format === "es" ? "mjs" : "cjs"}`,
},
rollupOptions: {
external: ['vue', 'lodash'],
external: ["vue", "lodash"],
output: {
chunkFileNames: "chunks/[name]-[hash].js",
assetFileNames: "assets/[name]-[hash][extname]",
exports: "named",
},
},
sourcemap: true,
},
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
"@": resolve(__dirname, "src"),
},
},
plugins: [
dts({
include: ['src'],
outDir: 'dist',
include: ["src"],
outDir: "dist",
}),
],
});
+12
View File
@@ -0,0 +1,12 @@
# @r-utils/vue3
## 1.4.0
### Minor Changes
- 添加工具
### Patch Changes
- Updated dependencies
- @r-utils/common@1.4.0
+92
View File
@@ -0,0 +1,92 @@
# @r-utils/vue3
仅用于 Vue3 项目的工具包,提供 class 处理、组件事件派发和常用组合式 Hooks。
## 适用范围
- 适用于 Vue3 项目。
- 适用于需要复用 Vue3 class 处理、列表加载、表单输入值处理等能力的项目。
## 不适用范围
- 不适用于 Vue2 项目。
- 如果是 uni-app 专用能力,建议优先使用 `@r-utils/uni-app`
## 安装
```bash
pnpm add @r-utils/vue3
```
## 导入方式
### 推荐:根入口导入
大多数场景推荐从根入口导入,路径简单,使用心智负担更低。
```ts
import {
mergeClass,
dispatch,
useLoadMore,
useTimer,
useValueOfRule,
} from "@r-utils/vue3";
```
### 兼容:子路径导入
如果你希望模块边界更清晰,也可以使用子路径导入。两种方式都支持。
```ts
import { mergeClass } from "@r-utils/vue3/vue-helper";
import { useLoadMore } from "@r-utils/vue3/hooks/list";
import { useTimer, useValueOfRule } from "@r-utils/vue3/hooks/utils";
```
## 导出模块
| 子路径 | 说明 |
| --- | --- |
| `@r-utils/vue3/vue-helper` | Vue class 合并、转换和祖先组件事件派发 |
| `@r-utils/vue3/hooks/list` | 列表分页加载 Hook |
| `@r-utils/vue3/hooks/utils` | 常用组合式工具 Hook |
## 使用示例
### 合并 class
```ts
import { mergeClass } from "@r-utils/vue3";
const cls = mergeClass("btn", ["btn-primary"], { active: true });
```
### 列表加载
```ts
import { useLoadMore } from "@r-utils/vue3";
const listState = useLoadMore(async (pageNum, pageSize) => {
return {
total: 100,
list: await fetchList(pageNum, pageSize),
};
});
await listState.loadMore();
```
### 子路径导入 Hook
```ts
import { useTimer } from "@r-utils/vue3/hooks/utils";
const timer = useTimer(60);
timer.start();
```
## 注意事项
- 推荐优先使用根入口导入;如果需要更精确的模块边界,也可以使用子路径导入。
- 本包依赖 Vue3,请确保业务项目已安装兼容版本的 `vue`
+21 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@r-utils/vue3",
"version": "1.3.0",
"version": "1.4.0",
"private": false,
"description": "Vue3 工具",
"type": "module",
@@ -8,13 +8,30 @@
"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"
},
"./*": "./*"
"./hooks/list": {
"types": "./dist/hooks/list/index.d.ts",
"import": "./dist/hooks/list/index.mjs",
"require": "./dist/hooks/list/index.cjs"
},
"./hooks/utils": {
"types": "./dist/hooks/utils/index.d.ts",
"import": "./dist/hooks/utils/index.mjs",
"require": "./dist/hooks/utils/index.cjs"
},
"./vue-helper": {
"types": "./dist/vue-helper/index.d.ts",
"import": "./dist/vue-helper/index.mjs",
"require": "./dist/vue-helper/index.cjs"
}
},
"keywords": [
"vue3"
@@ -42,8 +59,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"
@@ -9,7 +9,7 @@ import type { Ref } from "vue";
*/
export function useTimer(initTime = 0) {
const time = ref(initTime);
const timeInterval = ref();
const timeInterval = ref<ReturnType<typeof setInterval>>();
const start = () => {
timeInterval.value = setInterval(() => {
+3 -1
View File
@@ -1 +1,3 @@
export * from "./vue-helper"
export * from "./vue-helper";
export * from "./hooks/list";
export * from "./hooks/utils";
@@ -5,10 +5,10 @@ type CustomClass = string | Array<string> | CustomClassObj;
type DistCustomClass = Record<string, true>;
export function createCustomClassObj(customClass: string): DistCustomClass;
export function createCustomClassObj(
customClass: Array<string>
customClass: Array<string>,
): DistCustomClass;
export function createCustomClassObj(
customClass: string | Array<string>
customClass: string | Array<string>,
): DistCustomClass {
let customClassObj = <DistCustomClass>{};
if (typeof customClass === "string") {
@@ -26,7 +26,7 @@ export function createCustomClassObj(
}
} else {
throw new TypeError(
`customClass只能是字符串或数组类型,customClass: ${customClass}`
`customClass只能是字符串或数组类型,customClass: ${customClass}`,
);
}
@@ -57,7 +57,7 @@ export function convertCustomClass(sourceClass: CustomClass): CustomClassObj {
customClassObj = sourceClass;
} else {
throw new TypeError(
`sourceClass不是有效的vue classsourceClass: ${sourceClass}`
`sourceClass不是有效的vue classsourceClass: ${sourceClass}`,
);
}
@@ -72,7 +72,7 @@ export function convertCustomClass(sourceClass: CustomClass): CustomClassObj {
export function mergeClass(...customClass: CustomClass[]) {
return customClass
.map((cc) => convertCustomClass(cc))
.reduce((a, b) => Object.assign(a, b),{});
.reduce((a, b) => Object.assign(a, b), {});
}
/**
@@ -87,7 +87,7 @@ export function dispatch(
thisArg: ComponentPublicInstance,
componentName: ComponentPublicInstance,
eventName: string,
params: unknown
params: unknown,
): void {
let parent = thisArg.$parent || thisArg.$root;
if (parent == null) {
+1 -1
View File
@@ -6,6 +6,6 @@
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
}
}
}
+1
View File
@@ -8,6 +8,7 @@
*/
import * as QQMapWX from "@jonny1994/qqmap-wx-jssdk";
export * from "@jonny1994/qqmap-wx-jssdk";
/**
+22 -12
View File
@@ -1,31 +1,41 @@
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"),
"hooks/list/index": resolve(__dirname, "src/hooks/list/index.ts"),
"hooks/utils/index": resolve(__dirname, "src/hooks/utils/index.ts"),
"vue-helper/index": resolve(__dirname, "src/vue-helper/index.ts"),
},
formats: ["es", "cjs"],
fileName: (format, entryName) =>
`${entryName}.${format === "es" ? "mjs" : "cjs"}`,
},
rollupOptions: {
external: ['vue', 'lodash'],
external: ["vue", "lodash"],
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'],
outDir: 'dist',
include: ["src"],
outDir: "dist",
}),
],
});