feat(): 改为vite
This commit is contained in:
parent
cd734d8524
commit
1ccbd4e872
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(pnpm --filter @r-utils/vue3 add vue)",
|
||||
"Bash(grep -A 5 '\"\"dependencies\"\"' /d/Projects/r-util-js/packages/*/package.json)",
|
||||
"Bash(grep -r \"console.log\" /d/Projects/r-util-js/packages/*/webpack.config.js)",
|
||||
"Bash(grep -c \"__dirname\" /d/Projects/r-util-js/packages/*/webpack.config.js)",
|
||||
"Bash(grep -r \"private.*true\" /d/Projects/r-util-js/packages/*/package.json)",
|
||||
"Bash(grep \"name\" /d/Projects/r-util-js/packages/*/package.json)",
|
||||
"Bash(git -C /d/Projects/r-util-js status --short)",
|
||||
"Bash(pnpm install)",
|
||||
"Bash(pnpm build)",
|
||||
"Bash(pnpm test)",
|
||||
"Bash(printf 'npm run lint-staged\\\\nnpm test\\\\n')",
|
||||
"Bash(printf 'npx commitlint --edit \"\"$1\"\"\\\\n')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx commitlint --edit "$1"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,2 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm run lint-staged
|
||||
npm test
|
||||
|
|
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
# https://prettier.io/docs/en/ignore.html
|
||||
node_modules/
|
||||
|
||||
.vscode/
|
||||
.idea/
|
||||
.husky/
|
||||
Dockerfile
|
||||
.hbuilderx/
|
||||
.vite/
|
||||
|
||||
/dist/
|
||||
/public/*
|
||||
!/public/index.html
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## 项目概述
|
||||
|
||||
这是一个前端工具库 monorepo,使用 **pnpm workspace** 管理,包含以下子包:
|
||||
|
||||
| 包名 | 路径 | 描述 |
|
||||
|------|------|------|
|
||||
| `@r-utils/common` | `packages/common` | 通用 JS/TS 工具库(不依赖框架) |
|
||||
| `@r-utils/vue3` | `packages/vue3` | Vue3 专用工具 |
|
||||
| `@r-utils/uni-app` | `packages/uni-app` | uni-app 专用工具 |
|
||||
| `@r-utils/vue2` | `packages/vue2` | Vue2 专用工具 |
|
||||
|
||||
## 常用命令
|
||||
|
||||
### 构建
|
||||
|
||||
```bash
|
||||
# 构建所有包
|
||||
pnpm build
|
||||
|
||||
# 构建单个包(在包目录内)
|
||||
cd packages/common && pnpm build
|
||||
```
|
||||
|
||||
> `@r-utils/common` 同时有 `webpack.config.js` 和 `rollup.config.js`,当前 `build` 脚本使用 webpack;rollup 配置备用。
|
||||
|
||||
### 测试
|
||||
|
||||
```bash
|
||||
# 在根目录运行所有测试
|
||||
pnpm test
|
||||
|
||||
# 运行单个测试文件
|
||||
npx jest packages/common/test/permission.test.ts
|
||||
```
|
||||
|
||||
测试文件位于各包的 `test/` 目录下,使用 Jest + Babel。
|
||||
|
||||
### 代码检查与格式化
|
||||
|
||||
```bash
|
||||
# Lint(从根目录,仅检查 src 目录)
|
||||
pnpm lint
|
||||
|
||||
# 格式化
|
||||
pnpm format
|
||||
```
|
||||
|
||||
ESLint 使用 v9 flat config(`eslint.config.js`),已整合 TypeScript 和 Prettier 规则。
|
||||
|
||||
### Git 提交
|
||||
|
||||
```bash
|
||||
# 使用 commitizen 提交(遵循 conventional commits 规范)
|
||||
pnpm commit
|
||||
```
|
||||
|
||||
**注意**:提交时 husky pre-commit 钩子会自动运行 lint-staged 和 jest 测试,commit-msg 钩子会用 commitlint 校验提交信息格式。
|
||||
|
||||
## 架构说明
|
||||
|
||||
### Monorepo 结构
|
||||
|
||||
- 根 `package.json` 定义 devDependencies(构建工具、ESLint、Jest 等),各子包在自身 `package.json` 中声明运行时依赖
|
||||
- `@r-utils/uni-app` 通过 `workspace:^` 依赖 `@r-utils/common`;其他包目前通过 `file:` 本地路径引用
|
||||
- 每个包均输出 ES 模块格式,入口为 `dist/index.js`,类型声明在 `dist/index.d.ts`
|
||||
|
||||
### 构建工具
|
||||
|
||||
- 各包使用 **webpack 5** + **ts-loader** 进行构建,输出 ES module(`experiments.outputModule: true`)
|
||||
- TypeScript 配置:`target: esnext`、`strict: true`、路径别名 `@` 指向 `src/`
|
||||
|
||||
### `@r-utils/common` 模块结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── permission/ Permission 类:权限字符串格式校验(如 "module:action:resource")
|
||||
├── timer/ Countdown(倒计时)、TimeoutTimer
|
||||
├── printer/ ESC 打印工具,PrintUtil 继承自 JpPrinter(esc.js)
|
||||
├── knock-test/ KnockTest:按顺序敲击/点击触发回调的状态机
|
||||
└── time/ 时间工具函数
|
||||
```
|
||||
|
||||
### `@r-utils/uni-app` 主要模块
|
||||
|
||||
- `request/Request.ts`:类 axios 的 uni.request 封装,支持请求/响应拦截器
|
||||
- `uni-helper.ts`:uni-app 常用工具(scanCode、toPromise、rpxToPx、pathToBase64、Canvas 工具等)
|
||||
- `bluetooth-utils/`、`nfc/`、`printer/`:蓝牙、NFC、打印机功能
|
||||
|
||||
### `@r-utils/vue3`
|
||||
|
||||
- `vue-helper.ts`:Vue class 处理工具(`mergeClass`、`createCustomClassObj`)、`dispatch` 祖先组件事件
|
||||
|
||||
### `@r-utils/vue2`
|
||||
|
||||
- `plugins/visibility.js`:Vue2 mixin 插件,为组件提供 `onShow`/`onHide` 生命周期钩子
|
||||
36
README.en.md
36
README.en.md
|
|
@ -1,36 +0,0 @@
|
|||
# r-util-js
|
||||
|
||||
#### Description
|
||||
js工具
|
||||
|
||||
#### Software Architecture
|
||||
Software architecture description
|
||||
|
||||
#### Installation
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Instructions
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Contribution
|
||||
|
||||
1. Fork the repository
|
||||
2. Create Feat_xxx branch
|
||||
3. Commit your code
|
||||
4. Create Pull Request
|
||||
|
||||
|
||||
#### Gitee Feature
|
||||
|
||||
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
||||
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
||||
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
||||
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
||||
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
172
README.md
172
README.md
|
|
@ -1,37 +1,159 @@
|
|||
# r-util-js
|
||||
# r-utils
|
||||
|
||||
#### 介绍
|
||||
js工具
|
||||
前端工具库 monorepo,使用 pnpm workspace 管理,Vite 构建。
|
||||
|
||||
#### 软件架构
|
||||
软件架构说明
|
||||
## 包列表
|
||||
|
||||
| 包名 | 路径 | 描述 |
|
||||
| ---- | ---- | ---- |
|
||||
| `@r-utils/common` | `packages/common` | 通用 JS/TS 工具(权限、倒计时、打印、时间等) |
|
||||
| `@r-utils/vue3` | `packages/vue3` | Vue3 工具(class 处理、dispatch 等) |
|
||||
| `@r-utils/uni-app` | `packages/uni-app` | uni-app 工具(请求封装、蓝牙、NFC、打印等) |
|
||||
| `@r-utils/vue2` | `packages/vue2` | Vue2 工具(visibility 插件) |
|
||||
|
||||
#### 安装教程
|
||||
## 环境要求
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
- Node.js >= 18.12.0
|
||||
- pnpm >= 8.15.6
|
||||
|
||||
#### 使用说明
|
||||
## 安装依赖
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
#### 参与贡献
|
||||
## 构建
|
||||
|
||||
1. Fork 本仓库
|
||||
2. 新建 Feat_xxx 分支
|
||||
3. 提交代码
|
||||
4. 新建 Pull Request
|
||||
### 构建所有包
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
#### 特技
|
||||
### 构建单个包
|
||||
|
||||
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
||||
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
||||
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
||||
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
||||
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
```bash
|
||||
cd packages/common
|
||||
pnpm build
|
||||
```
|
||||
|
||||
每个包使用 **Vite lib 模式**构建,输出到 `dist/` 目录:
|
||||
|
||||
```text
|
||||
dist/
|
||||
├── index.mjs # ESM 格式
|
||||
├── index.cjs # CJS 格式
|
||||
├── index.d.ts # TypeScript 类型声明
|
||||
├── index.mjs.map # Source Map
|
||||
└── index.cjs.map
|
||||
```
|
||||
|
||||
### 监听模式
|
||||
|
||||
```bash
|
||||
cd packages/common
|
||||
pnpm watch
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
### ESM 引入(推荐)
|
||||
|
||||
支持 tree-shaking,打包工具会自动按需引入用到的部分。
|
||||
|
||||
```ts
|
||||
// 完整引入
|
||||
import { Permission, Countdown, KnockTest } from '@r-utils/common';
|
||||
|
||||
// 按需引入(bundler 自动 tree-shaking,只打包 Permission)
|
||||
import { Permission } from '@r-utils/common';
|
||||
|
||||
// Vue3 工具
|
||||
import { mergeClass, dispatch } from '@r-utils/vue3';
|
||||
|
||||
// uni-app 工具
|
||||
import { Request } from '@r-utils/uni-app';
|
||||
|
||||
// Vue2 插件
|
||||
import { VisibilityPlugin } from '@r-utils/vue2';
|
||||
Vue.use(VisibilityPlugin);
|
||||
```
|
||||
|
||||
### CJS 引入
|
||||
|
||||
```js
|
||||
const { Permission } = require('@r-utils/common');
|
||||
```
|
||||
|
||||
### package.json exports
|
||||
|
||||
每个包均配置了标准的 `exports` 字段,打包工具会自动选择正确的格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.cjs"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 开发
|
||||
|
||||
### 测试
|
||||
|
||||
```bash
|
||||
# 运行所有测试
|
||||
pnpm test
|
||||
|
||||
# 运行单个测试文件
|
||||
npx jest packages/common/test/permission.test.ts
|
||||
```
|
||||
|
||||
### 代码检查与格式化
|
||||
|
||||
```bash
|
||||
pnpm lint
|
||||
pnpm format
|
||||
```
|
||||
|
||||
### Git 提交
|
||||
|
||||
```bash
|
||||
# 使用 commitizen(遵循 conventional commits)
|
||||
pnpm commit
|
||||
```
|
||||
|
||||
提交时 husky 会自动运行 lint-staged 和 jest 测试。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```text
|
||||
r-util-js/
|
||||
├── packages/
|
||||
│ ├── common/ # @r-utils/common
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── permission/ # Permission 权限校验
|
||||
│ │ │ ├── timer/ # Countdown、TimeoutTimer
|
||||
│ │ │ ├── printer/ # ESC/TSC 打印指令
|
||||
│ │ │ ├── knock-test/ # KnockTest 敲击触发
|
||||
│ │ │ └── time/ # 时间工具
|
||||
│ │ ├── vite.config.ts
|
||||
│ │ └── tsconfig.json
|
||||
│ ├── vue3/ # @r-utils/vue3
|
||||
│ ├── uni-app/ # @r-utils/uni-app
|
||||
│ └── vue2/ # @r-utils/vue2
|
||||
├── eslint.config.js # ESLint v9 flat config
|
||||
├── prettier.config.js
|
||||
└── pnpm-workspace.yaml
|
||||
```
|
||||
|
||||
## 参与贡献
|
||||
|
||||
1. Fork 本仓库
|
||||
2. 新建 `feat/xxx` 分支
|
||||
3. 提交代码
|
||||
4. 新建 Pull Request
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
import js from "@eslint/js";
|
||||
import ts from "typescript-eslint";
|
||||
import prettier from "eslint-config-prettier";
|
||||
import globals from "globals";
|
||||
|
||||
export default [
|
||||
// 基础配置
|
||||
{
|
||||
files: ["**/*.{js,mjs,cjs,ts,vue}"],
|
||||
ignores: [
|
||||
"**/node_modules/*",
|
||||
"**/dist/*",
|
||||
"build/",
|
||||
"public/",
|
||||
"docs/",
|
||||
"bin/",
|
||||
"pnpm-lock.yaml",
|
||||
".vscode/",
|
||||
".idea/",
|
||||
".husky/",
|
||||
"Dockerfile",
|
||||
".hbuilderx/",
|
||||
".vite/",
|
||||
"uni_modules/",
|
||||
"libs/",
|
||||
"static/",
|
||||
"patches/",
|
||||
"wxcomponents/",
|
||||
"lib/",
|
||||
],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.browser,
|
||||
},
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
impliedStrict: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
},
|
||||
},
|
||||
|
||||
// 应用推荐规则
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
prettier,
|
||||
|
||||
// 测试文件覆盖配置
|
||||
{
|
||||
files: ["**/__tests__/*.{j,t}s?(x)", "**/tests/unit/**/*.spec.{j,t}s?(x)"],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.mocha,
|
||||
...globals.jest,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
File diff suppressed because it is too large
Load Diff
84
package.json
84
package.json
|
|
@ -1,49 +1,79 @@
|
|||
{
|
||||
"name": "r-util-js",
|
||||
"name": "@r-utils/root",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"description": "前端工具库",
|
||||
"keywords": [
|
||||
"vue",
|
||||
"js",
|
||||
"utils",
|
||||
"tools"
|
||||
],
|
||||
"author": {
|
||||
"name": "CodiceFabbrica",
|
||||
"email": "randy1924@163.com",
|
||||
"url": "https://gitee.com/codice_fabbrica"
|
||||
},
|
||||
"homepage": "https://gitee.com/codice_fabbrica/r-util-js",
|
||||
"repository": {
|
||||
"url": "https://gitee.com/codice_fabbrica/r-util-js.git",
|
||||
"type": "git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://gitee.com/codice_fabbrica/r-util-js/issues"
|
||||
},
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
},
|
||||
"packageManager": "pnpm@8.15.6",
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"watch": "webpack watch --mode development",
|
||||
"start": "webpack --mode development && node ./dist/index.js",
|
||||
"build": "pnpm -r run build",
|
||||
"prepare": "husky install",
|
||||
"lint": "eslint --ext .js,ts --fix src",
|
||||
"format": "prettier --write src",
|
||||
"release": "standard-version",
|
||||
"commit": "cz",
|
||||
"lint-staged": "lint-staged",
|
||||
"test": "jest"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21",
|
||||
"webpack": "^5.80.0",
|
||||
"webpack-cli": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.8.0",
|
||||
"@babel/preset-env": "^7.12.0",
|
||||
"@babel/preset-typescript": "^7.21.4",
|
||||
"@commitlint/config-conventional": "^17.6.1",
|
||||
"@commitlint/cz-commitlint": "^17.5.0",
|
||||
"@commitlint/config-conventional": "^19.8.1",
|
||||
"@commitlint/cz-commitlint": "^19.8.1",
|
||||
"@eslint/js": "^9.26.0",
|
||||
"@jest/globals": "^29.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.32.0",
|
||||
"@typescript-eslint/parser": "^8.32.0",
|
||||
"babel-jest": "^29.5.0",
|
||||
"commitizen": "^4.3.0",
|
||||
"eslint": "^8.38.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint": "^9.26.0",
|
||||
"eslint-config-prettier": "^10.1.3",
|
||||
"eslint-define-config": "^1.18.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"husky": "^8.0.3",
|
||||
"inquirer": "^8.0.0",
|
||||
"eslint-plugin-prettier": "^5.4.0",
|
||||
"globals": "^16.1.0",
|
||||
"husky": "^9.1.7",
|
||||
"inquirer": "^12.6.0",
|
||||
"jest": "^29.5.0",
|
||||
"lint-staged": "^13.2.1",
|
||||
"prettier": "^2.8.7",
|
||||
"lint-staged": "^15.5.2",
|
||||
"prettier": "^3.5.3",
|
||||
"standard-version": "^9.5.0",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^5.0.4"
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.32.0",
|
||||
"vite": "^6.0.0",
|
||||
"vite-plugin-dts": "^4.0.0"
|
||||
},
|
||||
"overrides": {
|
||||
"@types/lodash": "4.17.16",
|
||||
"dayjs": "1.11.13",
|
||||
"lodash": "4.17.21",
|
||||
"lodash-es": "4.17.21"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
import rootConfig from "../../eslint.config.js";
|
||||
|
||||
export default [...rootConfig];
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"name": "@r-utils/common",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "js通用工具库",
|
||||
"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": [
|
||||
"utils",
|
||||
"common"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"watch": "vite build --watch",
|
||||
"lint": "eslint --ext .js,ts --fix src",
|
||||
"format": "prettier --write src",
|
||||
"release": "standard-version",
|
||||
"commit": "cz",
|
||||
"lint-staged": "lint-staged",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"dayjs": "^1.11.13",
|
||||
"lodash-es": "^4.17.21",
|
||||
"text-encoding": "^0.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/lodash-es": "^4.17.12"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export * from "./knock-test";
|
||||
export * from "./permission";
|
||||
export * from "./printer";
|
||||
export * from "./time";
|
||||
export * from "./timer";
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
import { debounce } from "lodash-es";
|
||||
|
||||
export class Operation {
|
||||
duration = 1000;
|
||||
delay = 1000;
|
||||
times = 1;
|
||||
}
|
||||
|
||||
export class Config {
|
||||
maxWaitTime = 5000;
|
||||
operations: Operation[] = [];
|
||||
}
|
||||
|
||||
export class KnockTest {
|
||||
config: Config;
|
||||
index = 0;
|
||||
times = 0;
|
||||
/**
|
||||
* 空闲状态:"idle",times从0开始计数,进入duration期
|
||||
* duration期间:"knocking",times达到Operation的指定次数后进入delay期
|
||||
* delay期间:"wait",此期间不能点击,如果点击则重置,否则进入下一个空闲状态
|
||||
*/
|
||||
status: "idle" | "knocking" | "wait" = "idle";
|
||||
durationTimerId: number | null = null;
|
||||
delayTimerId: number | null = null;
|
||||
callbackList: (() => void)[] = [];
|
||||
maxWaitFun: () => void;
|
||||
|
||||
constructor(config: Config) {
|
||||
const c = new Config();
|
||||
this.config = Object.assign(c, config);
|
||||
|
||||
this.maxWaitFun = debounce(() => {
|
||||
console.log("maxWaitFun");
|
||||
this.reset();
|
||||
}, this.config.maxWaitTime);
|
||||
}
|
||||
|
||||
addCallback(cb: () => void) {
|
||||
this.callbackList.push(cb);
|
||||
}
|
||||
|
||||
knock() {
|
||||
console.log("knock");
|
||||
this.maxWaitFun();
|
||||
this.times++;
|
||||
|
||||
switch (this.status) {
|
||||
case "idle":
|
||||
this.handleIdle();
|
||||
break;
|
||||
case "knocking":
|
||||
this.handleKnocking();
|
||||
break;
|
||||
case "wait":
|
||||
this.handleWait();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private reset() {
|
||||
this.status = "idle";
|
||||
this.times = 0;
|
||||
this.index = 0;
|
||||
|
||||
if (this.durationTimerId != null) {
|
||||
clearTimeout(this.durationTimerId);
|
||||
this.durationTimerId = null;
|
||||
}
|
||||
|
||||
if (this.delayTimerId != null) {
|
||||
clearTimeout(this.delayTimerId);
|
||||
}
|
||||
}
|
||||
|
||||
private handleIdle() {
|
||||
if (this.config.operations.length === 0) {
|
||||
throw new Error("至少添加一个操作项");
|
||||
}
|
||||
|
||||
const operation = this.config.operations[this.index];
|
||||
this.status = "knocking";
|
||||
|
||||
this.durationTimerId = window.setTimeout(() => {
|
||||
if (operation.times !== this.times) {
|
||||
this.reset();
|
||||
}
|
||||
}, operation.duration);
|
||||
|
||||
this.checkKnock();
|
||||
}
|
||||
|
||||
private handleKnocking() {
|
||||
this.checkKnock();
|
||||
}
|
||||
|
||||
private checkKnock() {
|
||||
const operation = this.config.operations[this.index];
|
||||
|
||||
if (operation.times === this.times) {
|
||||
if (this.durationTimerId != null) {
|
||||
clearTimeout(this.durationTimerId);
|
||||
this.durationTimerId = null;
|
||||
}
|
||||
this.status = "wait";
|
||||
this.times = 0;
|
||||
this.index++;
|
||||
|
||||
this.delayTimerId = window.setTimeout(() => {
|
||||
this.status = "idle";
|
||||
this.times = 0;
|
||||
|
||||
if (this.delayTimerId != null) {
|
||||
clearTimeout(this.delayTimerId);
|
||||
this.delayTimerId = null;
|
||||
}
|
||||
}, operation.delay);
|
||||
|
||||
if (this.index > this.config.operations.length - 1) {
|
||||
console.log("成功");
|
||||
|
||||
this.reset();
|
||||
this.callbackList.forEach((cb) => cb());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleWait() {
|
||||
if (this.delayTimerId != null) {
|
||||
clearTimeout(this.delayTimerId);
|
||||
this.delayTimerId = null;
|
||||
}
|
||||
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./KnockTest";
|
||||
|
|
@ -13,7 +13,7 @@ export class Permission {
|
|||
}
|
||||
|
||||
isValid(str: string) {
|
||||
const p = new Array(this.level).fill("\\w+?").join(":");
|
||||
const p = new Array(this.level).fill("\\w+?").join(this.separator);
|
||||
// ^\w+?:\w+?:\w+?$
|
||||
const r = new RegExp(`^${p}$`);
|
||||
return r.test(str);
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import { JpPrinter } from "./esc.js";
|
||||
|
||||
export class PrintUtil extends JpPrinter {
|
||||
/**
|
||||
* 添加标题
|
||||
* @param title
|
||||
*/
|
||||
addTitle(title: string) {
|
||||
return this.storeLayout()
|
||||
.setAlign("m")
|
||||
.setLineSpacing(7.5 * 8 * 2)
|
||||
.setFontSize(0x10 + 0x1)
|
||||
.addText(title)
|
||||
.addLF()
|
||||
.restoreLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加文本
|
||||
* @param text
|
||||
* @param align
|
||||
* @returns
|
||||
*/
|
||||
addTextLine(text: string, align = "l") {
|
||||
return this.storeLayout()
|
||||
.setAlign(align)
|
||||
.setFontSize(0x00 + 0x00)
|
||||
.addText(text)
|
||||
.addLF()
|
||||
.restoreLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加条目
|
||||
* @param label
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
addItem(label: string, value: string) {
|
||||
return this.storeLayout()
|
||||
.setAlign("l")
|
||||
.setFontSize(0x00 + 0x00)
|
||||
.addText(label)
|
||||
.addText(":")
|
||||
.addText(value)
|
||||
.addLF()
|
||||
.restoreLayout();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,4 @@
|
|||
export * from "./esc";
|
||||
export * from "./esc-plus";
|
||||
export * from "./tsc-plus";
|
||||
export * from "./tsc";
|
||||
|
|
@ -0,0 +1,307 @@
|
|||
import { TSC } from "./tsc.js";
|
||||
|
||||
const fontInfoList = [
|
||||
{
|
||||
name: "1",
|
||||
width: 8,
|
||||
height: 12,
|
||||
},
|
||||
{
|
||||
name: "2",
|
||||
width: 12,
|
||||
height: 20,
|
||||
},
|
||||
{
|
||||
name: "3",
|
||||
width: 16,
|
||||
height: 24,
|
||||
},
|
||||
{
|
||||
name: "4",
|
||||
width: 24,
|
||||
height: 32,
|
||||
},
|
||||
{
|
||||
name: "5",
|
||||
width: 32,
|
||||
height: 48,
|
||||
},
|
||||
{
|
||||
name: "6",
|
||||
width: 14,
|
||||
height: 19,
|
||||
},
|
||||
{
|
||||
name: "7",
|
||||
width: 21,
|
||||
height: 27,
|
||||
},
|
||||
{
|
||||
name: "8",
|
||||
width: 14,
|
||||
height: 25,
|
||||
},
|
||||
{
|
||||
name: "9",
|
||||
width: 9,
|
||||
height: 17,
|
||||
},
|
||||
{
|
||||
name: "10",
|
||||
width: 12,
|
||||
height: 24,
|
||||
},
|
||||
{
|
||||
name: "TSS16.BF2",
|
||||
width: 16,
|
||||
height: 16,
|
||||
},
|
||||
{
|
||||
name: "TSS20.BF2",
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
{
|
||||
name: "TST24.BF2",
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
{
|
||||
name: "TSS24.BF2",
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
{
|
||||
name: "K",
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
{
|
||||
name: "TSS32.BF2",
|
||||
width: 32,
|
||||
height: 32,
|
||||
},
|
||||
];
|
||||
|
||||
export class TSCPlus extends TSC {
|
||||
static MILLIMETERS_PER_INCH = 25.4;
|
||||
|
||||
constructor(data = []) {
|
||||
super(data);
|
||||
this.font = "TSS24.BF2";
|
||||
this.lineHeight = "1.2";
|
||||
this.dpi = 200;
|
||||
this.nextX = 0;
|
||||
this.nextY = 0;
|
||||
this.widthDot = this.mmToDot(40);
|
||||
this.heightDot = this.mmToDot(30);
|
||||
this.paddingTopDot = 0;
|
||||
this.paddingLeftDot = 0;
|
||||
this.paddingRightDot = 0;
|
||||
this.paddingBottomDot = 0;
|
||||
this.setSizeDot(this.widthDot, this.heightDot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置内边距
|
||||
* @param {number} paddingTopDot
|
||||
* @param {number} paddingRightDot
|
||||
* @param {number} paddingBottomDot
|
||||
* @param {number} paddingLeftDot
|
||||
*/
|
||||
setPadding(paddingTopDot, paddingRightDot, paddingBottomDot, paddingLeftDot) {
|
||||
this.paddingTopDot = paddingTopDot;
|
||||
this.paddingRightDot = paddingRightDot;
|
||||
this.paddingBottomDot = paddingBottomDot;
|
||||
this.paddingLeftDot = paddingLeftDot;
|
||||
|
||||
this.nextX = this.paddingLeftDot;
|
||||
this.nextY = this.paddingTopDot;
|
||||
}
|
||||
|
||||
mmToDot(mm) {
|
||||
mm = Number(mm);
|
||||
return Math.round(this.dpi / TSCPlus.MILLIMETERS_PER_INCH) * mm;
|
||||
}
|
||||
|
||||
setDpi(dpi) {
|
||||
this.dpi = dpi;
|
||||
}
|
||||
|
||||
setSize(w, h) {
|
||||
if (/^\d+(\.\d+)? mm$/.test(w)) {
|
||||
this.widthDot = this.mmToDot(parseInt(w));
|
||||
this.heightDot = this.mmToDot(parseInt(h));
|
||||
} else {
|
||||
this.widthDot = parseInt(w) * this.dpi;
|
||||
this.heightDot = parseInt(h) * this.dpi;
|
||||
}
|
||||
|
||||
return super.setSize(w, h);
|
||||
}
|
||||
|
||||
setSizeMM(w, h) {
|
||||
this.widthDot = w * 8;
|
||||
this.heightDot = h * 8;
|
||||
return super.setSizeMM(w, h);
|
||||
}
|
||||
|
||||
setSizeDot(w, h) {
|
||||
return this.setSizeMM(w / 8, h / 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字体
|
||||
* @param {string} font
|
||||
*/
|
||||
setFont(font) {
|
||||
this.font = String(font);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置行高
|
||||
* @param {string|number} lineHeight
|
||||
*/
|
||||
setLineHeight(lineHeight) {
|
||||
this.lineHeight = String(lineHeight);
|
||||
}
|
||||
|
||||
static getLetterWidthDot(font) {
|
||||
const fontInfo = fontInfoList.find((v) => v.name === font);
|
||||
return fontInfo.width;
|
||||
}
|
||||
|
||||
static getFontLineHeightDot(font, lineHeight) {
|
||||
const fontInfo = fontInfoList.find((v) => v.name === font);
|
||||
if (/^\d+(\.\d+)?dot$/.test(lineHeight)) {
|
||||
const [, lineHeightDot] = /^(\d+(\.\d+))?dot$/.exec(lineHeight);
|
||||
return lineHeightDot;
|
||||
} else if (/^\d+(\.\d+)?$/.test(lineHeight)) {
|
||||
const [, lineHeightNum] = /^(\d+(\.\d+))?$/.exec(lineHeight);
|
||||
const lineHeightDot = fontInfo.height * lineHeightNum;
|
||||
return lineHeightDot;
|
||||
} else {
|
||||
throw new Error("无效行高:" + lineHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文本宽度点数
|
||||
* @param {string} text
|
||||
* @returns {number}
|
||||
*/
|
||||
static getTextDots(text, letterDots = 12) {
|
||||
text = String(text);
|
||||
let ch = 0;
|
||||
text.split("").forEach((c) => {
|
||||
// 是否是汉字,汉字两倍宽
|
||||
const isChinese = /[^\x00-\xff]/.test(c);
|
||||
if (isChinese) {
|
||||
ch += 2;
|
||||
} else {
|
||||
ch++;
|
||||
}
|
||||
});
|
||||
|
||||
return ch * letterDots;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据最大点数获取文本
|
||||
* @param {string} text
|
||||
* @param {number} maxDots
|
||||
* @return {string}
|
||||
*/
|
||||
static getMaxText(text = "", maxDots = 0) {
|
||||
let subStr = "";
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
subStr = text.substring(0, text.length - i);
|
||||
if (TSCPlus.getTextDots(subStr) <= maxDots) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return subStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分割字符串
|
||||
* "123456789" => ["1234", "5678", "9"]
|
||||
* @param {string} str
|
||||
* @param {number} maxDots
|
||||
* @return {string[]}
|
||||
*/
|
||||
static splitString(str, maxDots) {
|
||||
let itemDots = TSCPlus.getTextDots(str);
|
||||
const arr = [];
|
||||
while (itemDots > 0) {
|
||||
const maxStr = TSCPlus.getMaxText(str, maxDots);
|
||||
arr.push(maxStr);
|
||||
str = str.slice(maxStr.length);
|
||||
itemDots = TSCPlus.getTextDots(str);
|
||||
}
|
||||
|
||||
if (str.length > 0) {
|
||||
arr.push(str);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
addTextLn(text, options = {}) {
|
||||
text = String(text);
|
||||
const font = String(options.font ?? this.font);
|
||||
let x = parseInt(options.x ?? this.nextX);
|
||||
this.nextY = parseInt(options.y ?? this.nextY);
|
||||
const rotation = parseInt(options.rotation ?? 0);
|
||||
const sx = parseInt(options.sx ?? 1);
|
||||
const sy = parseInt(options.sy ?? 1);
|
||||
const align = options.align ?? "left";
|
||||
|
||||
const contentWidth =
|
||||
this.widthDot - this.paddingLeftDot - this.paddingRightDot;
|
||||
const textList = TSCPlus.splitString(text, contentWidth);
|
||||
textList.forEach((t) => {
|
||||
const textDots = parseInt(TSCPlus.getTextDots(t) * sx);
|
||||
console.log(align, textDots, contentWidth);
|
||||
if (textDots < contentWidth) {
|
||||
if (align == "center") {
|
||||
x += parseInt((contentWidth - textDots) / 2);
|
||||
} else if (align == "right") {
|
||||
x += parseInt(contentWidth - textDots);
|
||||
}
|
||||
}
|
||||
console.log("x", x);
|
||||
this.setText(x, this.nextY, font, rotation, sx, sy, t);
|
||||
const lineHeightDot =
|
||||
TSCPlus.getFontLineHeightDot(font, this.lineHeight) * sy;
|
||||
this.nextY = parseInt(this.nextY + lineHeightDot);
|
||||
console.log("this.nextY", this.nextY);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
addAddBarCode(content, options = {}) {
|
||||
const x = parseInt(options.x ?? this.nextX);
|
||||
this.nextY = parseInt(options.y ?? this.nextY);
|
||||
const height = parseInt(options.height ?? 80);
|
||||
const rotation = parseInt(options.rotation ?? 0);
|
||||
|
||||
this.setBarCode(
|
||||
x,
|
||||
this.nextY,
|
||||
options.codeType,
|
||||
height,
|
||||
options.readable,
|
||||
rotation,
|
||||
options.narrow,
|
||||
options.wide,
|
||||
content,
|
||||
);
|
||||
|
||||
this.nextY += height + 24;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,616 @@
|
|||
import * as encode from "text-encoding";
|
||||
|
||||
export class TSC {
|
||||
static LINE_BREAK = "\r\n";
|
||||
|
||||
constructor(data = []) {
|
||||
// 打印数据
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
addData(...data) {
|
||||
this.data.push(...data);
|
||||
}
|
||||
|
||||
addDataArray(dataArray) {
|
||||
this.data.push(...dataArray);
|
||||
}
|
||||
|
||||
addCode(code) {
|
||||
const textEncoder = new encode.TextEncoder("gb18030", {
|
||||
NONSTANDARD_allowLegacyEncoding: true,
|
||||
});
|
||||
|
||||
const data = textEncoder.encode(code);
|
||||
this.data.push(...data);
|
||||
return this;
|
||||
}
|
||||
|
||||
getData = function () {
|
||||
return this.data;
|
||||
};
|
||||
|
||||
// 系统设定指令====================
|
||||
/**
|
||||
* 该指令用于设定卷标纸的宽度和长度
|
||||
* @param {number|string} w 标签宽度 单位英寸inch
|
||||
* @param {number|string} h 标签高度 单位英寸inch
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setSize(w, h) {
|
||||
const code = `SIZE ${w},${h}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于设定卷标纸的宽度和长度
|
||||
* @param {number|string} w 标签宽度 单位毫米mm
|
||||
* @param {number|string} h 标签高度 单位毫米mm
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setSizeMM(w, h) {
|
||||
return this.setSize(`${w} mm`, `${h} mm`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于定义两张卷标纸间的垂直间距距离
|
||||
* @param {number|string} m 两标签纸中间的垂直距离 单位英寸inch
|
||||
* @param {number|string} n 垂直间距偏移 单位英寸inch
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setGap(m = 0, n = 0) {
|
||||
const code = `GAP ${m},${n}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于定义两张卷标纸间的垂直间距距离
|
||||
* @param {number|string} m 两标签纸中间的垂直距离 单位毫米mm
|
||||
* @param {number|string} n 垂直间距偏移 单位毫米mm
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setGapMM(m = 0, n = 0) {
|
||||
const code = `GAP ${m} mm,${n} mm${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于设定黑标高度及定义标签印完后标签额外送出的长度
|
||||
* @param {number|string} m 黑标高度 单位英寸inch
|
||||
* @param {number|string} n 额外送出纸张长度 单位英寸inch
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setBLine(m = 0, n = 0) {
|
||||
const code = `BLINE ${m},${n}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于控制在剥离模式时(peel-off mode)每张卷标停止的位置,
|
||||
* 在打印下一张时打印机会将原先多推出或少推出的部分以回拉方式补偿回来。
|
||||
* 该指令仅适用于剥离模式。
|
||||
* @param {number|string} m 纸张停止的距离 单位英寸inch
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setOffset(offset = 0) {
|
||||
const code = `OFFSET ${offset}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于控制打印速度
|
||||
* @param {number|string} speed 打印速度
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setSpeed(speed = 1.5) {
|
||||
const code = `SPEED ${speed}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于控制打印时的浓度
|
||||
* @param {number|string} density 打印浓度
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setDensity(density = 10) {
|
||||
const code = `DENSITY ${density}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于定义打印时出纸和打印字体的方向
|
||||
* @param {number|string} direction
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setDirection(direction = 0) {
|
||||
const code = `DIRECTION ${direction}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于定义卷标的参考坐标原点。坐标原点位置和打印方向有关
|
||||
* @param {number|string} x 水平方向的坐标位置,单位dot
|
||||
* @param {number|string} y 垂直方向的坐标位置,单位dot
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setReference(x = 0, y = 0) {
|
||||
const code = `REFERENCE ${x},${y}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令表示标签打印偏移量多少设置
|
||||
* @param {number|string} n 打印偏移量
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setShift(n = 0) {
|
||||
const code = `SHIFT ${n}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于选择对应的国际字符集
|
||||
* 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 = 0) {
|
||||
const code = `COUNTRY ${country}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于选择对应的国际代码页
|
||||
* 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 = 0) {
|
||||
const code = `CODEPAGE ${n}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于清除图像缓冲区(image buffer)的数据
|
||||
* 注:此项指令必须置于 SIZE 指令之后
|
||||
* @param {number|string} n 代码页
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setCls() {
|
||||
const code = `CLS${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于将标签纸向前推送指定的长度
|
||||
* 打印机分辨率200 DPI:1 mm = 8 dots
|
||||
* 打印机分辨率300 DPI:1 mm = 12 dots
|
||||
* @param {number|string} n 1≤n≤9999,单位dot
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setFeed(n) {
|
||||
const code = `FEED ${n}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于将标签纸向前推送指定的长度
|
||||
* 打印机分辨率200 DPI:1 mm = 8 dots
|
||||
* 打印机分辨率300 DPI:1 mm = 12 dots
|
||||
* @param {number|string} n 1≤n≤9999,单位dot
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setBackFeed(n) {
|
||||
const code = `BACKFEED ${n}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于将标签纸向前推送指定的长度
|
||||
* 打印机分辨率200 DPI:1 mm = 8 dots
|
||||
* 打印机分辨率300 DPI:1 mm = 12 dots
|
||||
* @param {number|string} n 1≤n≤9999,单位dot
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setBackUp(n) {
|
||||
const code = `BACKUP ${n}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于控制打印机进一张标签纸
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setFromFeed() {
|
||||
const code = `FORMFEED${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在使用含有间隙或黑标的标签纸时,
|
||||
* 若不能确定第一张标签纸是否在正确打印位置时,
|
||||
* 此指令可将标签纸向前推送至下一张标签纸的起点开始打印。
|
||||
* 标签尺寸和间隙需要在本条指令前设置
|
||||
* 注:使用该指令时,纸张高度大于或等于30 mm
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setHome() {
|
||||
const code = `HOME${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于打印出存储于影像缓冲区内的数据
|
||||
* @param {number|string} m 指定打印的份数(set)
|
||||
* @param {number|string} n 每张标签需重复打印的张数
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setPrint(m = 1, n = 1) {
|
||||
const code = `PRINT ${m},${n}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于控制蜂鸣器的频率,可设定10阶的声音,
|
||||
* 频率,可设定10阶的声音,每阶声音的长短由第二个参数控制
|
||||
* @param {number|string} m 指定打印的份数(set)
|
||||
* @param {number|string} n 每张标签需重复打印的张数
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setSound(level = 1, interval = 300) {
|
||||
const code = `SOUND ${level},${interval}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于设定打印机进纸时,若经过所设定的长度仍无法侦测到垂直间距,
|
||||
* 则打印机在连续纸模式工作。
|
||||
* @param {number|string} limit 英制系统(inch)
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setLimitFeed(limit = 1) {
|
||||
const code = `LIMITFEED ${limit}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 不经自测动作,直接打印自检页信息。
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setSelfTest() {
|
||||
const code = `SELFTEST${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
// 卷标内容设计指令==============================
|
||||
|
||||
/**
|
||||
* 该指令用于在标签上画线
|
||||
* @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, y, width, height) {
|
||||
const code = `BAR ${x},${y},${width},${height}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于在标签上画线
|
||||
* @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,
|
||||
y,
|
||||
codeType = "128",
|
||||
height = 80,
|
||||
readable = 1,
|
||||
rotation = 0,
|
||||
narrow = 2,
|
||||
wide = 2,
|
||||
content,
|
||||
) {
|
||||
x = parseInt(x);
|
||||
y = parseInt(y);
|
||||
height = parseInt(height);
|
||||
readable = parseInt(readable);
|
||||
rotation = parseInt(rotation);
|
||||
narrow = parseInt(narrow);
|
||||
wide = parseInt(wide);
|
||||
content = String(content);
|
||||
|
||||
const code = `BARCODE ${x},${y},"${codeType}",${height},${readable},${rotation},${narrow},${wide},"${content}"${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于在卷标上绘制矩形方框
|
||||
* @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, y1, x2, y2, thickness) {
|
||||
const code = `BOX ${x1},${y1},${x2},${y2},${thickness}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于在卷标上绘制位图(非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, y, width, height, mode, bitmapData) {
|
||||
const w = width;
|
||||
const h = height;
|
||||
const bitw = parseInt((w + 7) / 8) * 8;
|
||||
// var bitw = (parseInt(w) % 8) == 0 ? (parseInt(w) / 8) :( parseInt(w) / 8+1);
|
||||
const pitch = parseInt(bitw / 8);
|
||||
const bits = new Uint8Array(h * pitch);
|
||||
const code = `BITMAP "${x},${y},${bitw},${pitch},${h},${mode},`;
|
||||
this.addCode(code);
|
||||
|
||||
// for (var i=0; i<bits.length; i++) {
|
||||
// bits[i] = 0;
|
||||
// }
|
||||
|
||||
for (y = 0; y < h; y++) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < bits.length; i++) {
|
||||
this.addData(~bits[i] & 0xff);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于清除影像缓冲区部分区域的数据
|
||||
* @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, y, width, height) {
|
||||
const code = `ERASE ${x},${y},${width},${height}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定的区域反相打印
|
||||
* @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, y, width, height) {
|
||||
const code = `REVERSE ${x},${y},${width},${height}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用于打印字符串
|
||||
* @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, y, font, rotation, sx, sy, content) {
|
||||
x = parseInt(x);
|
||||
y = parseInt(y);
|
||||
rotation = parseInt(rotation);
|
||||
sx = parseInt(sx);
|
||||
sy = parseInt(sy);
|
||||
content = String(content);
|
||||
const code = `TEXT ${x},${y},"${font}",${rotation},${sx},${sy},"${content}"${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用来打印二维码
|
||||
* @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, y, level, width, mode, rotation, content) {
|
||||
const code = `QRCODE ${x},${y},${level},${width},${mode},${rotation},"${content}"${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
// 询问打印机状态指令=========================
|
||||
|
||||
// 打印机外围功能设定指令=========================
|
||||
|
||||
/**
|
||||
* 该指令用来起动Key1 的预设功能
|
||||
* 预设为进纸功能
|
||||
* @param {"ON"|"OFF"} k 开启按键/关闭按键
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setKey1(k) {
|
||||
const code = `SET KYE1 ${k}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用来起动Key2 的预设功能
|
||||
* 预设为暂停功能
|
||||
* @param {"ON"|"OFF"} k 开启按键/关闭按键
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setKey2(k) {
|
||||
const code = `SET KYE2 ${k}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该指令用来启动/关闭剥离模式,默认值为关闭
|
||||
* @param {"ON"|"OFF"} k 开启按键/关闭按键
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setPeel(k) {
|
||||
const code = `SET PEEL ${k}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 此命令是用来启用/禁用撕纸位置走到撕纸处,此设置关掉电源后将保存在打印机内
|
||||
* @param {"ON"|"OFF"} k 开启按键/关闭按键
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setTear(k) {
|
||||
const code = `SET TEAR ${k}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 此命令是用来启用/禁用撕纸位置走到撕纸处,此设置关掉电源后将保存在打印机内
|
||||
* @param {"ON"|"OFF"} k 开启按键/关闭按键
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setStripper(k) {
|
||||
const code = `SET STRIPPER ${k}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 此设置用于启用/禁用打印头合盖传感器。如果禁用合盖传感器,打印机头被打开时,将不会传回错误信息。
|
||||
* 此设置将保存在打印机内存。
|
||||
* @param {"ON"|"OFF"} k 开启按键/关闭按键
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setHead(k) {
|
||||
const code = `SET HEAD ${k}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 此设置用于启用/禁用打印头合盖传感器。如果禁用合盖传感器,打印机头被打开时,将不会传回错误信息。
|
||||
* 此设置将保存在打印机内存。
|
||||
* @param {"ON"|"OFF"|"AUTO"|string|number} k 开启按键/关闭按键
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setHead2(k) {
|
||||
const code = `SET PRINTKEY ${k}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 此命令将禁用/启用标签机在无纸或开盖错误发生后,
|
||||
* 上纸或合盖后重新打印一次标签内容
|
||||
* @param {"ON"|"OFF"} k 开启按键/关闭按键
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setReprint(k) {
|
||||
const code = `SET REPRINT ${k}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设定开启/关闭碳带感应器,即切换热转式/热感印式打印。通常打印机于开启电源时,
|
||||
* 碳带感应器即会自动检测打印机是否已装上碳带,并藉此决定使用热感式或热转式打印。
|
||||
* 此项设定并不会存于打印机中。此方法仅适用于热转式机器。
|
||||
* @param {"ON"|"OFF"} k 开启按键/关闭按键
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setRibbon(k) {
|
||||
const code = `SET RIBBON ${k}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 此命令用于设置切刀状态,关闭打印机电源后,该设置将会被存储在打印机内存中。
|
||||
* @param {"OFF"|"BATCH"|string|number} k 开启按键/关闭按键
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setCutter(k) {
|
||||
const code = `SET CUTTER ${k}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 此指令用于设置打印机自动返回状态
|
||||
* @param {"ON"|"OFF"|"BATCH"} k 开启按键/关闭按键
|
||||
* @param {string} content
|
||||
* @returns {TSC}
|
||||
*/
|
||||
setResponse(k, content) {
|
||||
const code = `SET RESPONSE "${content}",${k}${TSC.LINE_BREAK}`;
|
||||
return this.addCode(code);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* 等待
|
||||
* @param timeout 等待时间,ms
|
||||
*/
|
||||
export function wait(timeout: number): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const id = setTimeout(() => {
|
||||
resolve();
|
||||
clearTimeout(id);
|
||||
}, timeout);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
import dayjs from "dayjs";
|
||||
import { Duration } from "dayjs/plugin/duration";
|
||||
|
||||
type TimerCallback = (time: string) => void;
|
||||
|
||||
export default class Countdown {
|
||||
id = -1;
|
||||
duration: Duration;
|
||||
format = "HH:mm:ss";
|
||||
/**
|
||||
* 倒计时步骤监听器
|
||||
*/
|
||||
stepEventListenerList = <TimerCallback[]>[];
|
||||
/**
|
||||
* 倒计时完成时间监听器
|
||||
*/
|
||||
countdownEventListenerList = <TimerCallback[]>[];
|
||||
initialTime = 0;
|
||||
|
||||
constructor(time: number) {
|
||||
this.initialTime = time;
|
||||
this.duration = dayjs.duration(time);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加倒计时步骤监听器
|
||||
* @param cb
|
||||
* @returns
|
||||
*/
|
||||
addStepEventListener(cb: TimerCallback) {
|
||||
if (!(cb instanceof Function)) {
|
||||
return new TypeError("回调函数类型错误: cb: " + cb);
|
||||
}
|
||||
this.stepEventListenerList.push(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加倒计时完成时间监听器
|
||||
* @param cb
|
||||
* @returns
|
||||
*/
|
||||
addCountdownEventListener(cb: TimerCallback) {
|
||||
if (!(cb instanceof Function)) {
|
||||
return new TypeError("回调函数类型错误: cb: " + cb);
|
||||
}
|
||||
this.countdownEventListenerList.push(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始倒计时
|
||||
*/
|
||||
start() {
|
||||
this.id = window.setInterval(() => {
|
||||
this.duration = this.duration.subtract(1000);
|
||||
const str = this.duration.format(this.format);
|
||||
|
||||
this.stepEventListenerList.forEach((cb) => cb(str));
|
||||
|
||||
if (this.duration.asMilliseconds() <= 0) {
|
||||
this.stop();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束倒计时
|
||||
*/
|
||||
stop() {
|
||||
console.log("stop", this.id);
|
||||
|
||||
const str = this.duration.format(this.format);
|
||||
clearInterval(this.id);
|
||||
this.id = -1;
|
||||
this.countdownEventListenerList.forEach((cb) => cb(str));
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新开始计时
|
||||
*/
|
||||
restart() {
|
||||
clearInterval(this.id);
|
||||
this.id = -1;
|
||||
this.duration = dayjs.duration(this.initialTime);
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
type TimerCallback = (time: number) => void;
|
||||
|
||||
export default class TimeoutTimer {
|
||||
startTime = 0;
|
||||
endTime = 0;
|
||||
interval = 0;
|
||||
time = 0;
|
||||
id = -1;
|
||||
stepEventListenerList = <TimerCallback[]>[];
|
||||
countdownEventListenerList = <TimerCallback[]>[];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param startTime 起始时间
|
||||
* @param endTime 结束时间
|
||||
* @param interval 间隔(ms)
|
||||
*/
|
||||
constructor(startTime = 5, endTime = 0, interval = 1000) {
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* 倒计时步骤监听器
|
||||
* @param cb
|
||||
* @returns
|
||||
*/
|
||||
addStepEventListener(cb: TimerCallback) {
|
||||
if (!(cb instanceof Function)) {
|
||||
return new TypeError("回调函数类型错误: cb: " + cb);
|
||||
}
|
||||
this.stepEventListenerList.push(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 倒计时完成时间监听器
|
||||
* @param cb
|
||||
* @returns
|
||||
*/
|
||||
addCountdownEventListener(cb: TimerCallback) {
|
||||
if (!(cb instanceof Function)) {
|
||||
return new TypeError("回调函数类型错误: cb: " + cb);
|
||||
}
|
||||
this.countdownEventListenerList.push(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始倒计时
|
||||
* @param startTime
|
||||
* @param endTime
|
||||
* @param interval
|
||||
* @param stepEventListener
|
||||
* @param countdownEventListener
|
||||
* @returns
|
||||
*/
|
||||
static start(
|
||||
startTime: number,
|
||||
endTime: number = 0,
|
||||
interval = 1000,
|
||||
stepEventListener?: TimerCallback,
|
||||
countdownEventListener?: TimerCallback,
|
||||
) {
|
||||
const timer = new TimeoutTimer(startTime, endTime, interval);
|
||||
if (stepEventListener != null) {
|
||||
timer.addStepEventListener(stepEventListener);
|
||||
}
|
||||
if (countdownEventListener != null) {
|
||||
timer.addCountdownEventListener(countdownEventListener);
|
||||
}
|
||||
|
||||
return timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始倒计时
|
||||
*/
|
||||
start() {
|
||||
this.time = this.startTime;
|
||||
this.id = window.setInterval(() => {
|
||||
this.time -= this.interval;
|
||||
console.log(this.time);
|
||||
this.stepEventListenerList.forEach((cb) => cb(this.time));
|
||||
if (this.time <= this.endTime && this.id !== -1) {
|
||||
this.stop();
|
||||
}
|
||||
}, this.interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束倒计时
|
||||
*/
|
||||
stop() {
|
||||
console.log("stop", this.id);
|
||||
|
||||
clearInterval(this.id);
|
||||
this.id = -1;
|
||||
this.time = this.endTime;
|
||||
this.countdownEventListenerList.forEach((cb) => cb(this.time));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { default as Countdown } from "./Countdown";
|
||||
export { default as TimeoutTimer } from "./TimeoutTimer";
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { test, expect } from "@jest/globals";
|
||||
import { Permission } from "../src/utils/permission";
|
||||
import { Permission } from "../src/permission";
|
||||
|
||||
test("测试 Permission#isValid()", () => {
|
||||
const permission = new Permission([]);
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"include": ["**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["dom", "esnext"],
|
||||
// "types": ["@dcloudio/types"],
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"allowJs": true,
|
||||
"outDir": "./types"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
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: {
|
||||
external: ['dayjs', 'lodash-es', 'text-encoding', 'tslib'],
|
||||
},
|
||||
sourcemap: true,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
dts({
|
||||
include: ['src'],
|
||||
outDir: 'dist',
|
||||
}),
|
||||
],
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export * from "./bluetooth-utils";
|
||||
export * from "./nfc";
|
||||
export * from "./printer";
|
||||
export * from "./request";
|
||||
export * from "./uni-helper";
|
||||
export * from "./upload";
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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("不支持当前平台");
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./Request";
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
declare module "uview-ui";
|
||||
declare module "uview-plus";
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
declare global {
|
||||
function setInterval(handler: TimerHandler, timeout?: number): number;
|
||||
}
|
||||
|
||||
export {};
|
||||
|
|
@ -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[];
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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',
|
||||
}),
|
||||
],
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "@r-utils/vue2",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Vue2工具库",
|
||||
"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": [
|
||||
"vue2"
|
||||
],
|
||||
"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": {
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vue": "^2.7.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./plugins/visibility";
|
||||
export { default as VisibilityPlugin } from "./plugins/visibility";
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
function install(Vue) {
|
||||
Vue.mixin({
|
||||
created() {
|
||||
const onShowHook = this.$options.onShow;
|
||||
const onHideHook = this.$options.onHide;
|
||||
|
||||
if (onShowHook != null || onHideHook != null) {
|
||||
this.visibilitychangeCallback();
|
||||
document.addEventListener(
|
||||
"visibilitychange",
|
||||
this.visibilitychangeCallback
|
||||
);
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
document.removeEventListener(
|
||||
"visibilitychange",
|
||||
this.visibilitychangeCallback
|
||||
);
|
||||
},
|
||||
methods: {
|
||||
visibilitychangeCallback() {
|
||||
const onShowHook = this.$options.onShow?.bind(this);
|
||||
const onHideHook = this.$options.onHide?.bind(this);
|
||||
|
||||
if (document.visibilityState == "visible") {
|
||||
console.log("visible");
|
||||
if (typeof onShowHook === "function") {
|
||||
onShowHook();
|
||||
}
|
||||
}
|
||||
|
||||
if (document.visibilityState == "hidden") {
|
||||
console.log("hidden");
|
||||
if (typeof onHideHook === "function") {
|
||||
onHideHook();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
install,
|
||||
};
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"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"],
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"allowJs": true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
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: {
|
||||
external: ['vue', 'lodash'],
|
||||
},
|
||||
sourcemap: true,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
dts({
|
||||
include: ['src'],
|
||||
outDir: 'dist',
|
||||
}),
|
||||
],
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"name": "@r-utils/vue3",
|
||||
"version": "1.0.0",
|
||||
"description": "Vue3 工具",
|
||||
"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": [
|
||||
"vue3"
|
||||
],
|
||||
"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"
|
||||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21",
|
||||
"vue": "^3.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.8.0",
|
||||
"@babel/preset-env": "^7.12.0",
|
||||
"@babel/preset-typescript": "^7.21.4",
|
||||
"@commitlint/config-conventional": "^17.6.1",
|
||||
"@commitlint/cz-commitlint": "^17.5.0",
|
||||
"@jest/globals": "^29.7.0",
|
||||
"babel-jest": "^29.7.0",
|
||||
"commitizen": "^4.3.0",
|
||||
"eslint-define-config": "^1.18.0",
|
||||
"husky": "^8.0.3",
|
||||
"inquirer": "^8.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"lint-staged": "^15.4.0",
|
||||
"prettier": "^3.4.2",
|
||||
"standard-version": "^9.5.0"
|
||||
},
|
||||
"packageManager": "pnpm@8.15.6"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./vue-helper"
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
import { ComponentInternalInstance, ComponentPublicInstance } from "vue";
|
||||
|
||||
type CustomClassObj = Record<string, boolean>;
|
||||
type CustomClass = string | Array<string> | CustomClassObj;
|
||||
type DistCustomClass = Record<string, true>;
|
||||
export function createCustomClassObj(customClass: string): DistCustomClass;
|
||||
export function createCustomClassObj(
|
||||
customClass: Array<string>
|
||||
): DistCustomClass;
|
||||
export function createCustomClassObj(
|
||||
customClass: string | Array<string>
|
||||
): DistCustomClass {
|
||||
let customClassObj = <DistCustomClass>{};
|
||||
if (typeof customClass === "string") {
|
||||
const customClassStr = customClass.trim().replace(" +", " ");
|
||||
if (customClassStr.length > 0) {
|
||||
const customClassEntries = customClassStr
|
||||
.split(" ")
|
||||
.map((name) => [name, true]);
|
||||
customClassObj = Object.fromEntries(customClassEntries);
|
||||
}
|
||||
} else if (Array.isArray(customClass)) {
|
||||
if (customClass.length > 0) {
|
||||
const customClassEntries = customClass.map((name) => [name, true]);
|
||||
customClassObj = Object.fromEntries(customClassEntries);
|
||||
}
|
||||
} else {
|
||||
throw new TypeError(
|
||||
`customClass只能是字符串或数组类型,customClass: ${customClass}`
|
||||
);
|
||||
}
|
||||
|
||||
return customClassObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换 vue class 为 vue class 对象
|
||||
* @param sourceClass 原始 class
|
||||
* @returns
|
||||
*/
|
||||
export function convertCustomClass(sourceClass: CustomClass): CustomClassObj {
|
||||
let customClassObj = <CustomClassObj>{};
|
||||
if (typeof sourceClass === "string") {
|
||||
const customClassStr = sourceClass.trim().replace(" +", " ");
|
||||
if (customClassStr.length > 0) {
|
||||
const customClassEntries = customClassStr
|
||||
.split(" ")
|
||||
.map((name) => [name, true]);
|
||||
customClassObj = Object.fromEntries(customClassEntries);
|
||||
}
|
||||
} else if (Array.isArray(sourceClass)) {
|
||||
if (sourceClass.length > 0) {
|
||||
const customClassEntries = sourceClass.map((name) => [name, true]);
|
||||
customClassObj = Object.fromEntries(customClassEntries);
|
||||
}
|
||||
} else if (sourceClass instanceof Object) {
|
||||
customClassObj = sourceClass;
|
||||
} else {
|
||||
throw new TypeError(
|
||||
`sourceClass不是有效的vue class,sourceClass: ${sourceClass}`
|
||||
);
|
||||
}
|
||||
|
||||
return customClassObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并 class
|
||||
* @param customClass vue class
|
||||
* @returns
|
||||
*/
|
||||
export function mergeClass(...customClass: CustomClass[]) {
|
||||
return customClass
|
||||
.map((cc) => convertCustomClass(cc))
|
||||
.reduce((a, b) => Object.assign(a, b));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送祖先组件事件
|
||||
* @param thisArg 调用的源组件实例
|
||||
* @param componentName 需要触发事件的组件名
|
||||
* @param eventName 事件名称
|
||||
* @param params 参数
|
||||
* @returns
|
||||
*/
|
||||
export function dispatch(
|
||||
thisArg: ComponentPublicInstance,
|
||||
componentName: ComponentPublicInstance,
|
||||
eventName: string,
|
||||
params: unknown
|
||||
): void {
|
||||
let parent = thisArg.$parent || thisArg.$root;
|
||||
if (parent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let name = parent.$options.componentName;
|
||||
|
||||
while (parent && (!name || name !== componentName)) {
|
||||
parent = parent.$parent;
|
||||
|
||||
if (parent) {
|
||||
name = parent.$options.componentName;
|
||||
}
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
parent.$emit.call(parent, eventName, params);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"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"],
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"allowJs": true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
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: {
|
||||
external: ['vue', 'lodash'],
|
||||
},
|
||||
sourcemap: true,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
dts({
|
||||
include: ['src'],
|
||||
outDir: 'dist',
|
||||
}),
|
||||
],
|
||||
});
|
||||
8013
pnpm-lock.yaml
8013
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,5 @@
|
|||
packages:
|
||||
- packages/**
|
||||
|
||||
catalog:
|
||||
"webpack": ^5.80.0
|
||||
|
|
@ -1,15 +1,25 @@
|
|||
// https://prettier.io/docs/en/configuration.html
|
||||
module.exports = {
|
||||
export default {
|
||||
tabWidth: 4,
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.js", "*.ts", "*.vue", "*.json"],
|
||||
files: [
|
||||
"*.js",
|
||||
"*.mjs",
|
||||
"*.jsx",
|
||||
"*.ts",
|
||||
"*.tsx",
|
||||
"*.vue",
|
||||
"*.json",
|
||||
"*.yml",
|
||||
"*.yaml",
|
||||
],
|
||||
options: {
|
||||
tabWidth: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["*.html"],
|
||||
files: ["*.html", "*.vue", "*.json"],
|
||||
options: {
|
||||
printWidth: 120,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
export * from "./utils/permission";
|
||||
|
||||
console.log("index");
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||
"include": ["**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["esnext", "dom"],
|
||||
"lib": ["esnext"],
|
||||
// "types": ["@dcloudio/types"],
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.ts',
|
||||
output: {
|
||||
filename: 'index.js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
Loading…
Reference in New Issue