迁移uni-app x到uni-app vue3,蓝牙去第三方插件改uni内置BLE API

- uni-app x官方无内置蓝牙API(文档明确见插件市场),为实现去插件化,工程整体迁移为uni-app vue3(vue/ts),App端内置完整BLE API,标准基座即可运行
- 新建src/BluetoothManager.ts:基于uni内置BLE API(createBLEConnection/notifyBLECharacteristicValueChange/writeBLECharacteristicValue(writeNoResponse)/setBLEMTU/getBLEDeviceRSSI等),保持原插件type约定,页面业务逻辑不变
- 删除uni_modules/android-ble及未使用的lime-*、uni-wifi;保留uni-icons、uni-scss
- uts模块全部移植为ts(src/mcm、api、common):UTSJSONObject改Record<string,any>,适配vue3 getStorageSync空值返回空串的差异
- 7个页面uvue转vue:模板不变,显式import ref与生命周期钩子;OTA固件读取由java.io改为plus.io+uni.base64ToArrayBuffer
- manifest.json去uni-app-x改app-plus结构,补Android 12+蓝牙权限;删除platformConfig.json;运行改用标准基座
This commit is contained in:
evan.liu 2026-09-04 06:02:29 +08:00
parent 4af93dca6c
commit 07509a2387
365 changed files with 736 additions and 36729 deletions

View File

@ -3,7 +3,7 @@
"configurations" : [ "configurations" : [
{ {
"customPlaygroundType" : "local", "customPlaygroundType" : "local",
"playground" : "custom", "playground" : "standard",
"type" : "uni-app:app-android" "type" : "uni-app:app-android"
}, },
{ {

View File

@ -1,22 +1,18 @@
<script lang="uts"> <script lang="ts">
import {BleLib,MyApiResult,BleScanResult,BleServices} from "@/uni_modules/android-ble" import { MyApiResult } from "@/src/BluetoothManager"
import * as Ble from "@/src/BluetoothManager.uts" import * as Ble from "@/src/BluetoothManager"
import * as McmChannel from "@/src/mcm/McmChannel.uts" import * as McmChannel from "@/src/mcm/McmChannel"
// #ifdef APP-ANDROID || APP-HARMONY // #ifdef APP-PLUS
let firstBackTime = 0 let firstBackTime = 0
// #endif // #endif
export default { export default {
globalData: { globalData: {
ble: new BleLib(), // 蓝牙全局变量 // 接收蓝牙数据的回调(显式指定 CAIIC-MCM 服务/notify UUID,基于 uni 内置 BLE API)
// 接收蓝牙数据的回调(显式指定 CAIIC-MCM 服务/notify UUID,不依赖插件自动识别;
// 用当前接口 onNotityBleData,旧接口 onNotityReadBleData 插件已标记弃用)
setBleNotity(){ setBleNotity(){
getApp().globalData.ble.onNotityBleData(Ble.MCM_SERVICE_UUID, Ble.onNotityReadBleData(true,
Ble.MCM_NOTIFY_UUID, function(res: MyApiResult){
true,
function(res:MyApiResult){
if (res.type != 0) { if (res.type != 0) {
// 1000=订阅成功 1001=订阅失败(type=0 的 notify 数据由 McmChannel 打印) // 1000=订阅成功 1001=订阅失败(type=0 的 notify 数据由 McmChannel 打印)
console.log("[MCM] notify subscribe result: type=" + res.type + " msg=" + res.message) console.log("[MCM] notify subscribe result: type=" + res.type + " msg=" + res.message)
@ -28,20 +24,17 @@ import * as McmChannel from "@/src/mcm/McmChannel.uts"
}, },
// 连接蓝牙 // 连接蓝牙
ConnectBle(address: string, autoReconnect: boolean){ ConnectBle(address: string, autoReconnect: boolean){
getApp().globalData.ble.connect(address, autoReconnect, function(res:MyApiResult){ Ble.connect(address, autoReconnect, function(res: MyApiResult){
console.log("[MCM] connect " + address + ": type=" + res.type + " msg=" + res.message) console.log("[MCM] connect " + address + ": type=" + res.type + " msg=" + res.message)
if(res.type==0){ if(res.type==0){
getApp().globalData.ble.scanServices(function(resSevice:MyApiResult){ Ble.scanServices(function(resSevice: MyApiResult){
console.log("[MCM] scanServices: type=" + resSevice.type + " msg=" + resSevice.message) console.log("[MCM] scanServices: type=" + resSevice.type + " msg=" + resSevice.message)
getApp().globalData.setBleNotity() if (resSevice.type == 0) {
(getApp().globalData as any).setBleNotity()
// getApp().globalData.ble.readRssi(function(b:MyApiResult){ uni.$emit("bleState", res.type)
// console.log(b) } else {
// }) uni.$emit("bleState", 10000)
// console.log(getApp().globalData.ble.getwriteUUID()); }
// console.log(getApp().globalData.ble.getNotityUUID());
uni.$emit("bleState",res.type)
}); });
}else{ }else{
@ -61,7 +54,7 @@ import * as McmChannel from "@/src/mcm/McmChannel.uts"
onHide: function () { onHide: function () {
console.log('App Hide') console.log('App Hide')
}, },
// #ifdef APP-ANDROID || APP-HARMONY // #ifdef APP-PLUS
onLastPageBackPress: function () { onLastPageBackPress: function () {
console.log('App LastPageBackPress') console.log('App LastPageBackPress')
if (firstBackTime == 0) { if (firstBackTime == 0) {
@ -75,7 +68,7 @@ import * as McmChannel from "@/src/mcm/McmChannel.uts"
}, 2000) }, 2000)
} else if (Date.now() - firstBackTime < 2000) { } else if (Date.now() - firstBackTime < 2000) {
firstBackTime = Date.now() firstBackTime = Date.now()
uni.exit() plus.runtime.quit()
} }
}, },
// #endif // #endif

View File

@ -1,6 +1,9 @@
import { getToken } from "../common/auth" import { getToken } from "../common/auth"
import { clearAllData } from "../common/auth" import { clearAllData } from "../common/auth"
// 通用 JSON 对象类型(替代 uts 的 UTSJSONObject)
export type JSONObject = Record<string, any>
// 可选后台服务器列表 // 可选后台服务器列表
export type ServerItem = { export type ServerItem = {
name: string name: string
@ -17,10 +20,10 @@ let recordBaseUrl: string | null = null
// 读取当前后台服务器地址(内存 + 本地),未选择过则默认第一个 // 读取当前后台服务器地址(内存 + 本地),未选择过则默认第一个
export function getBaseUrl(): string { export function getBaseUrl(): string {
if (recordBaseUrl != null && (recordBaseUrl as string).length > 0) { if (recordBaseUrl != null && recordBaseUrl.length > 0) {
return recordBaseUrl as string return recordBaseUrl
} }
const t = uni.getStorageSync('serverBaseUrl') as string | null const t = uni.getStorageSync('serverBaseUrl') as string
if (t != null && t.length > 0) { if (t != null && t.length > 0) {
recordBaseUrl = t recordBaseUrl = t
return t return t
@ -41,15 +44,15 @@ export type HttpRequest = {
url: string url: string
method?: HttpMethod method?: HttpMethod
data?: any data?: any
header?: UTSJSONObject // 这里用 UTSJSONObject header?: JSONObject
} }
// 封装请求 // 封装请求
export function $http(params: HttpRequest): Promise<UTSJSONObject> { export function $http(params: HttpRequest): Promise<JSONObject> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 1. 准备 header // 1. 准备 header
let baseHeader: UTSJSONObject = params.header ?? {} as UTSJSONObject let baseHeader: JSONObject = params.header ?? {} as JSONObject
baseHeader["Content-Type"] = "application/json" baseHeader["Content-Type"] = "application/json"
@ -58,16 +61,12 @@ export function $http(params: HttpRequest): Promise<UTSJSONObject> {
if (token != null && token.length > 0) { if (token != null && token.length > 0) {
baseHeader["Authorization"] = token baseHeader["Authorization"] = token
} }
// console.log("url = ", getBaseUrl() + params.url)
// console.log("method = ", params.method)
// console.log("data = ", params.data)
// console.log("header = ", baseHeader)
// 3. 发请求 // 3. 发请求
uni.request({ uni.request({
url: getBaseUrl() + params.url, url: getBaseUrl() + params.url,
method: params.method ?? "GET", method: params.method ?? "GET",
data: params.data ?? {} as UTSJSONObject, data: params.data ?? {} as JSONObject,
header: baseHeader, header: baseHeader,
success: (res) => { success: (res) => {
if(res.statusCode == 401) { if(res.statusCode == 401) {
@ -76,10 +75,10 @@ export function $http(params: HttpRequest): Promise<UTSJSONObject> {
url: '/pages/login/login' url: '/pages/login/login'
}) })
} }
const result: UTSJSONObject = { const result: JSONObject = {
statusCode: res.statusCode, statusCode: res.statusCode,
data: res.data data: res.data
} as UTSJSONObject } as JSONObject
resolve(result) resolve(result)
}, },
fail: (err) => { fail: (err) => {

View File

@ -1,4 +1,4 @@
import { $http } from "@/api/httpInstance.uts" import { $http, JSONObject } from "@/api/httpInstance"
export type RegisteredUserReq = { export type RegisteredUserReq = {
userName: string, userName: string,
@ -6,12 +6,12 @@ export type RegisteredUserReq = {
rndcode: string rndcode: string
} }
// 获取pinCode // 获取pinCode
export async function GetPindCodeReqApi(params: RegisteredUserReq): Promise<UTSJSONObject>{ export async function GetPindCodeReqApi(params: RegisteredUserReq): Promise<JSONObject>{
const body: UTSJSONObject = { const body: JSONObject = {
userName: params.userName, userName: params.userName,
userPassword: params.userPassword, userPassword: params.userPassword,
rndcode: params.rndcode rndcode: params.rndcode
} as UTSJSONObject } as JSONObject
const res = await $http({ const res = await $http({
url: "/ocpp-mtapp-api/api/v1/users/getPinCode", url: "/ocpp-mtapp-api/api/v1/users/getPinCode",
@ -22,12 +22,12 @@ export async function GetPindCodeReqApi(params: RegisteredUserReq): Promise<UTSJ
} }
// 注册用户 // 注册用户
export async function RegisteredUserReqApi(params: RegisteredUserReq): Promise<UTSJSONObject>{ export async function RegisteredUserReqApi(params: RegisteredUserReq): Promise<JSONObject>{
const body: UTSJSONObject = { const body: JSONObject = {
userName: params.userName, userName: params.userName,
userPassword: params.userPassword, userPassword: params.userPassword,
rndcode: params.rndcode rndcode: params.rndcode
} as UTSJSONObject } as JSONObject
const res = await $http({ const res = await $http({
url: "/ocpp-mtapp-api/api/v1/users/add", url: "/ocpp-mtapp-api/api/v1/users/add",
@ -42,12 +42,12 @@ export type LoginReq = {
password: string password: string
} }
// 登录请求 // 登录请求
export async function LoginReqApi(params: LoginReq): Promise<UTSJSONObject>{ export async function LoginReqApi(params: LoginReq): Promise<JSONObject>{
const body: UTSJSONObject = { const body: JSONObject = {
username: params.username, username: params.username,
password: params.password, password: params.password,
expirationTime: 0 expirationTime: 0
} as UTSJSONObject } as JSONObject
const res = await $http({ const res = await $http({
url: "/ocpp-mtapp-api/api/v1/auth/login", url: "/ocpp-mtapp-api/api/v1/auth/login",
@ -64,12 +64,12 @@ export type ModifyUserInfoReq = {
nickName?: string | null nickName?: string | null
} }
// 修改用户信息 // 修改用户信息
export async function ModifyUserInfoReqApi(params: ModifyUserInfoReq): Promise<UTSJSONObject>{ export async function ModifyUserInfoReqApi(params: ModifyUserInfoReq): Promise<JSONObject>{
const body: UTSJSONObject = { const body: JSONObject = {
name: params.name, name: params.name,
phone: params.phone, phone: params.phone,
password: params.password password: params.password
} as UTSJSONObject } as JSONObject
const res = await $http({ const res = await $http({
url: `/ocpp-mtapp-api/api/v1/users/${params.name}`, url: `/ocpp-mtapp-api/api/v1/users/${params.name}`,

View File

@ -2,6 +2,12 @@ let recordToken: string | null = null
let recordUserName: string | null = null let recordUserName: string | null = null
let recordCharging: string | null = null let recordCharging: string | null = null
// 读取本地存储字符串;vue3 的 getStorageSync 无 key 时返回 "",统一转成 null
function getStorageStr(key: string): string | null {
const t = uni.getStorageSync(key) as string
return (t != null && t.length > 0) ? t : null
}
// 保存 token(内存 + 本地) // 保存 token(内存 + 本地)
export function setToken(token: string) { export function setToken(token: string) {
recordToken = token recordToken = token
@ -13,8 +19,7 @@ export function getToken(): string | null {
if (recordToken != null) { if (recordToken != null) {
return recordToken return recordToken
} }
const t = uni.getStorageSync('loginToken') as string | null recordToken = getStorageStr('loginToken')
recordToken = t ?? null
return recordToken return recordToken
} }
@ -49,8 +54,7 @@ export function getUserName(): string | null {
if (recordUserName != null) { if (recordUserName != null) {
return recordUserName return recordUserName
} }
const t = uni.getStorageSync('loginUserName') as string | null recordUserName = getStorageStr('loginUserName')
recordUserName = t ?? null
return recordUserName return recordUserName
} }
@ -66,8 +70,7 @@ export function getCharging(): string | null {
if (recordCharging != null) { if (recordCharging != null) {
return recordCharging return recordCharging
} }
const t = uni.getStorageSync('ChargingFlag') as string | null recordCharging = getStorageStr('ChargingFlag')
recordCharging = t ?? null
return recordCharging return recordCharging
} }
@ -85,14 +88,12 @@ export function setSavedAccount(account: string, password: string) {
// 读取保存的账号 // 读取保存的账号
export function getSavedAccount(): string { export function getSavedAccount(): string {
const t = uni.getStorageSync('savedAccount') as string | null return getStorageStr('savedAccount') ?? ""
return t ?? ""
} }
// 读取保存的密码 // 读取保存的密码
export function getSavedPassword(): string { export function getSavedPassword(): string {
const t = uni.getStorageSync('savedPassword') as string | null return getStorageStr('savedPassword') ?? ""
return t ?? ""
} }
// 清除保存的账号密码 // 清除保存的账号密码
@ -112,12 +113,12 @@ const ACCOUNT_HISTORY_MAX = 5
// 读取历史账号(最新登录的在前) // 读取历史账号(最新登录的在前)
export function getAccountHistory(): AccountRecord[] { export function getAccountHistory(): AccountRecord[] {
const result: AccountRecord[] = [] const result: AccountRecord[] = []
const raw = uni.getStorageSync('accountHistory') as string | null const raw = getStorageStr('accountHistory')
if (raw == null || raw.length == 0) { if (raw == null) {
return result return result
} }
try { try {
const arr = JSON.parse(raw) as UTSJSONObject[] | null const arr = JSON.parse(raw) as Record<string, any>[] | null
if (arr == null) return result if (arr == null) return result
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i++) {
const obj = arr[i] const obj = arr[i]
@ -128,7 +129,7 @@ export function getAccountHistory(): AccountRecord[] {
result.push(rec) result.push(rec)
} }
} }
} catch (e: any) { } catch (e) {
console.log('parse accountHistory error = ', e) console.log('parse accountHistory error = ', e)
} }
return result return result
@ -161,4 +162,3 @@ export function removeAccountHistory(account: string) {
} }
uni.setStorageSync('accountHistory', JSON.stringify(list)) uni.setStorageSync('accountHistory', JSON.stringify(list))
} }

View File

@ -1,6 +1,12 @@
let bleName: string | null = null let bleName: string | null = null
let bleMac: string | null = null let bleMac: string | null = null
// 读取本地存储字符串;vue3 的 getStorageSync 无 key 时返回 "",统一转成 null
function getStorageStr(key: string): string | null {
const t = uni.getStorageSync(key) as string
return (t != null && t.length > 0) ? t : null
}
// 保存蓝牙名字 // 保存蓝牙名字
export function setName(name: string) { export function setName(name: string) {
bleName = name bleName = name
@ -12,8 +18,7 @@ export function getName(): string | null {
if (bleName != null) { if (bleName != null) {
return bleName return bleName
} }
const t = uni.getStorageSync('bleName') as string | null bleName = getStorageStr('bleName')
bleName = t ?? null
return bleName return bleName
} }
@ -28,8 +33,7 @@ export function getMac(): string | null {
if (bleMac != null) { if (bleMac != null) {
return bleMac return bleMac
} }
const t = uni.getStorageSync('bleMac') as string | null bleMac = getStorageStr('bleMac')
bleMac = t ?? null
return bleMac return bleMac
} }

View File

@ -0,0 +1,62 @@
# 修改记录 2026-09-03:迁移 uni-app vue3,蓝牙去第三方插件改 uni 内置 API
## 背景
- 原工程为 uni-app x(uvue/uts),蓝牙依赖第三方插件 `uni_modules/android-ble`。
- 先尝试过"uni-app x 下去插件改 uni 内置 BLE API":不可行——uni-app x 官方未内置
蓝牙 API(官方文档明确"蓝牙:见插件市场"),全量编译确认
`createBLEConnection` 等接口在 app-android 平台不存在。
- 因对渲染性能要求不高,改为**整体迁移到 uni-app vue3(vue/ts)**:
vue3 App 端内置完整 BLE API,蓝牙彻底去插件化;且标准基座即可运行,无需自定义基座。
## 工程骨架
- `main.uts` → `main.js`;`App.uvue` → `App.vue`(`onLastPageBackPress` 中
`uni.exit()` 改 `plus.runtime.quit()`;globalData 保留 `setBleNotity`/`ConnectBle`,
改走新 `src/BluetoothManager.ts`,流程不变:connect → scanServices → 订阅 notify)。
- `manifest.json`:删除 `"uni-app-x": {}`;`app-android`/`app-ios` 合并为 vue3 的
`app-plus` 结构;Android permissions 补 `BLUETOOTH`/`BLUETOOTH_ADMIN`/
`BLUETOOTH_SCAN`(neverForLocation)/`BLUETOOTH_CONNECT`;iOS 蓝牙隐私描述并入
`app-plus.distribute.ios.privacyDescription`。
- `pages.json` 内容不变(globalStyle 标题改为 smartAssister)。
- 删除 `platformConfig.json`(uni-app x 专用)。
- uni_modules:删除 `android-ble`、`lime-*`(8 个,页面未使用且为 uts 组件)、
`uni-wifi`(未使用);保留 `uni-icons`(自带 vue 版本组件)、`uni-scss`。
## 蓝牙:`src/BluetoothManager.ts`(替代插件)
- 基于 uni 内置 BLE API:`openBluetoothAdapter` / `startBluetoothDevicesDiscovery` /
`createBLEConnection` / `getBLEDeviceServices` / `getBLEDeviceCharacteristics` /
`notifyBLECharacteristicValueChange` / `writeBLECharacteristicValue` /
`setBLEMTU` / `getBLEDeviceRSSI` / `closeBLEConnection`。
- 保持原插件的对外约定:`MyApiResult.type==0` 成功;1000=订阅成功 1001=订阅失败;
10000=连接失败 10001=已断开 10002=蓝牙未开 10003=无权限;
notify 数据包装为 `{type:0, data:{data:number[], mac}}` 经 `uni.$emit("bleData")` 分发;
意外断开经 `uni.$emit("bleState", 10001)` 广播(主动断开不广播)。
- `sendDataBytes` 用 `writeType:"writeNoResponse"`(下行特征仅支持无响应写)。
- 自动重连未实现(业务恒传 false),`setBtAutoConnectTime`/`cancelAutoConnectBt` 为空实现。
## uts → ts 移植
- `src/commonFunctions`、`src/mcm/*`(Crc/FrameCodec/McmChannel/McmCommands/McmInfo/
OtaManager)、`api/*`、`common/*`:机械移植,去 `.uts` import 后缀;
`UTSJSONObject` → `Record<string, any>`(httpInstance 统一定义 `JSONObject`)。
- **vue3 的 `uni.getStorageSync` 无 key 返回 `""` 而非 null**:`common/auth.ts`、
`common/bleInfo.ts` 统一经 `getStorageStr()` 读取(空串转 null)。
## 页面 .uvue → .vue(7 个)
- template 不动;`script setup lang="ts"`;显式 `import { ref } from 'vue'`、
生命周期钩子 `import { onLoad, onShow, ... } from '@dcloudio/uni-app'`。
- `pages/tabbar/ota.vue` 固件读取由 java.io 改为 `plus.io`(FileReader.readAsDataURL →
base64 → `uni.base64ToArrayBuffer` → number[]),`#ifdef APP-PLUS` 包裹。
- `getApp().globalData.ConnectBle` 调用处加 `as any` 断言。
## 验证与注意事项
- HBuilderX CLI `launch app-android --compile true` 编译通过(编译器识别为 vue3 工程)。
- 运行用**标准基座**即可(vue3 基座内置 BLE);原自定义基座配置不再适用。
- 真机回归重点:扫描/连接/notify/MTU 247、home 轮询与设备信息、CLI 收发与 rssi、
**OTA 全流程**(高频无响应写,丢包风险最高)。
- OTA 固件路径 `/sdcard/Download/embeddedSrc.bin` 在 Android 11+ 分区存储下可能读不到
(与原 java.io 行为一致,未劣化);如不可用,改用应用私有目录或加文件选择器。

View File

@ -1,4 +1,4 @@
import App from './App.uvue' import App from './App.vue'
import { createSSRApp } from 'vue' import { createSSRApp } from 'vue'
export function createApp() { export function createApp() {

View File

@ -4,7 +4,6 @@
"description": "", "description": "",
"versionName": "1.0.1", "versionName": "1.0.1",
"versionCode": "110", "versionCode": "110",
"uni-app-x": {},
/* 快应用特有相关 */ /* 快应用特有相关 */
"quickapp": {}, "quickapp": {},
/* 小程序特有相关 */ /* 小程序特有相关 */
@ -30,65 +29,50 @@
"enable": false "enable": false
}, },
"vueVersion": "3", "vueVersion": "3",
"app": { "app-plus": {
"usingComponents": true,
"modules": {},
"distribute": { "distribute": {
"android": {
"permissions": [
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.BLUETOOTH\"/>",
"<uses-permission android:name=\"android.permission.BLUETOOTH_ADMIN\"/>",
"<uses-permission android:name=\"android.permission.BLUETOOTH_SCAN\" android:usesPermissionFlags=\"neverForLocation\"/>",
"<uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\"/>"
]
},
"ios": {
"privacyDescription": {
"NSBluetoothPeripheralUsageDescription": "Please allow Bluetooth access. It is used to connect to the mother cup device: displaying runtime parameters such as temperature and fan speed, querying device system status, and upgrading the device firmware over Bluetooth.",
"NSBluetoothAlwaysUsageDescription": "Please allow Bluetooth access. It is used to connect to the mother cup device: displaying runtime parameters such as temperature and fan speed, querying device system status, and upgrading the device firmware over Bluetooth.",
"NSLocationWhenInUseUsageDescription": "Please allow the use of location services to identify the position of charging stations. This app promises that this function will not be used for any other purposes.",
"NSLocalNetworkUsageDescription": "Please allow the use of network connectivity for communication between the app and the cloud service backend to enable charging-related features. This app promises that it will only be used to complete charging-related services with the cloud backend and will not be utilized for any other purposes.",
"NSCameraUsageDescription": "Please allow the use of the camera function. This function is solely used for scanning the QR codes on charging piles to obtain information corresponding to the QR codes. This app promises that the function will not be used for any other purposes."
}
},
"icons": { "icons": {
"android": { "android": {
"hdpi": "", "hdpi": "/static/appIcon.png",
"xhdpi": "", "xhdpi": "/static/appIcon.png",
"xxhdpi": "", "xxhdpi": "/static/appIcon.png",
"xxxhdpi": "" "xxxhdpi": "/static/appIcon.png"
},
"ios": {
"appstore": "/static/appIcon.png"
} }
},
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
} }
} }
}, },
"app-android": {
"distribute": {
"modules": {},
"icons": {
"hdpi": "/static/appIcon.png",
"xhdpi": "/static/appIcon.png",
"xxhdpi": "/static/appIcon.png",
"xxxhdpi": "/static/appIcon.png"
},
"splashScreens": {
"default": {}
},
"permissions": [
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>"
]
}
},
"app-ios": {
"distribute": {
"modules": {
"uni-location": {},
"uni-barcode-scanning": {}
},
"icons": {
"appstore": "/static/appIcon.png"
},
"splashScreens": {},
"privacyDescription": {
"NSBluetoothPeripheralUsageDescription": "Please allow Bluetooth access. It is used to connect to the mother cup device: displaying runtime parameters such as temperature and fan speed, querying device system status, and upgrading the device firmware over Bluetooth.",
"NSBluetoothAlwaysUsageDescription": "Please allow Bluetooth access. It is used to connect to the mother cup device: displaying runtime parameters such as temperature and fan speed, querying device system status, and upgrading the device firmware over Bluetooth.",
"NSLocationWhenInUseUsageDescription": "Please allow the use of location services to identify the position of charging stations. This app promises that this function will not be used for any other purposes.",
"NSLocalNetworkUsageDescription": "Please allow the use of network connectivity for communication between the app and the cloud service backend to enable charging-related features. This app promises that it will only be used to complete charging-related services with the cloud backend and will not be utilized for any other purposes.",
"NSCameraUsageDescription": "Please allow the use of the camera function. This function is solely used for scanning the QR codes on charging piles to obtain information corresponding to the QR codes. This app promises that the function will not be used for any other purposes."
},
"urltypes": "",
"capabilities": {
"entitlements": {
"com.apple.developer.associated-domains": []
}
},
"urlschemewhitelist": "",
"UIBackgroundModes": ""
}
},
"web": { "web": {
"router": { "router": {
"mode": "" "mode": ""

View File

@ -88,7 +88,7 @@
}, },
"globalStyle": { "globalStyle": {
"navigationBarTextStyle": "black", "navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app x", "navigationBarTitleText": "smartAssister",
"navigationBarBackgroundColor": "#F8F8F8", "navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8", "backgroundColor": "#F8F8F8",
"navigationStyle": "default" "navigationStyle": "default"

View File

@ -44,13 +44,15 @@
</view> </view>
</template> </template>
<script setup lang="uts"> <script setup lang="ts">
import { MyApiResult } from "@/uni_modules/android-ble" import { ref } from 'vue'
import { onLoad, onShow, onHide, onUnload } from '@dcloudio/uni-app'
import { MyApiResult } from "../../../src/BluetoothManager"
import * as Ble from "../../../src/BluetoothManager" import * as Ble from "../../../src/BluetoothManager"
import * as McmChannel from "../../../src/mcm/McmChannel.uts" import * as McmChannel from "../../../src/mcm/McmChannel"
import * as McmCmd from "../../../src/mcm/McmCommands.uts" import * as McmCmd from "../../../src/mcm/McmCommands"
import { CliResult } from "../../../src/mcm/McmChannel.uts" import { CliResult } from "../../../src/mcm/McmChannel"
import * as BleINfo from "../../../common/bleInfo.uts" import * as BleINfo from "../../../common/bleInfo"
const bleAddress = ref('') const bleAddress = ref('')
const bleIsConnect = ref(false) // 蓝牙连接标志 true-已连接 false-未连接 const bleIsConnect = ref(false) // 蓝牙连接标志 true-已连接 false-未连接
@ -143,7 +145,7 @@ function onConnect() {
mask: true mask: true
}) })
pushMsg("start", "connect: " + addr) pushMsg("start", "connect: " + addr)
getApp().globalData.ConnectBle(addr, false) (getApp().globalData as any).ConnectBle(addr, false)
} }
// 发送 CLI 命令 // 发送 CLI 命令
@ -186,7 +188,7 @@ function unsubscribe() {
subscribed = false subscribed = false
} }
onLoad((options: UTSJSONObject | null) => { onLoad((options: any) => {
if (options != null) { if (options != null) {
const address = options["address"] as string | null const address = options["address"] as string | null
if (address != null) { if (address != null) {

View File

@ -10,7 +10,7 @@
<!-- #endif --> <!-- #endif -->
</template> </template>
<script setup lang="uts"> <script setup lang="ts">
import { clearAllData } from '../../common/auth'; import { clearAllData } from '../../common/auth';
function OnLogOut() { function OnLogOut() {
clearAllData() clearAllData()

View File

@ -107,7 +107,9 @@
<!-- #endif --> <!-- #endif -->
</template> </template>
<script setup lang="uts"> <script setup lang="ts">
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { LoginReqApi } from '../../api/requestApi' import { LoginReqApi } from '../../api/requestApi'
import { setToken, setUserName, setSavedAccount, getSavedAccount, getSavedPassword, clearSavedAccount, AccountRecord, getAccountHistory, addAccountHistory, removeAccountHistory } from '../../common/auth' import { setToken, setUserName, setSavedAccount, getSavedAccount, getSavedPassword, clearSavedAccount, AccountRecord, getAccountHistory, addAccountHistory, removeAccountHistory } from '../../common/auth'
@ -181,7 +183,7 @@ async function GetPasswordLoginReq(){
// console.log("res = ", JSON.stringify((res))) // console.log("res = ", JSON.stringify((res)))
const statusCode = res["statusCode"] as number const statusCode = res["statusCode"] as number
if(statusCode == 200) { if(statusCode == 200) {
const data = res["data"] as UTSJSONObject const data = res["data"] as Record<string, any>
const token = data["token"] as string | null const token = data["token"] as string | null
const userName = data["username"] as string | null const userName = data["username"] as string | null
if(token != null && token.length > 0){ if(token != null && token.length > 0){
@ -202,7 +204,7 @@ async function GetPasswordLoginReq(){
url: "/pages/tabbar/my" url: "/pages/tabbar/my"
}) })
} else { } else {
const data = res["data"] as UTSJSONObject const data = res["data"] as Record<string, any>
const msg = (data["result"] as string | null) ?? "login fail" const msg = (data["result"] as string | null) ?? "login fail"
uni.showToast({ uni.showToast({
title: msg, title: msg,

View File

@ -93,7 +93,8 @@
</template> </template>
<script setup lang="uts"> <script setup lang="ts">
import { ref } from 'vue'
import { RegisteredUserReqApi, GetPindCodeReqApi } from '../../api/requestApi' import { RegisteredUserReqApi, GetPindCodeReqApi } from '../../api/requestApi'
const showPwd = ref(false) const showPwd = ref(false)
@ -116,7 +117,7 @@ async function GetpindCode() {
}) })
// console.log("res = ", res) // console.log("res = ", res)
const statusCode = res["statusCode"] as number const statusCode = res["statusCode"] as number
const data = res["data"] as UTSJSONObject const data = res["data"] as Record<string, any>
if(statusCode == 200) { if(statusCode == 200) {
resPinCode.value = (data["pinCode"] as string) ?? "" resPinCode.value = (data["pinCode"] as string) ?? ""
uni.showToast({ uni.showToast({
@ -155,7 +156,7 @@ async function GetRegisteredUserReq(){
rndcode: "123" rndcode: "123"
}) })
const statusCode = res["statusCode"] as number const statusCode = res["statusCode"] as number
const data = res["data"] as UTSJSONObject const data = res["data"] as Record<string, any>
// console.log("res = ", res) // console.log("res = ", res)
if(statusCode == 200) { if(statusCode == 200) {
const msg = (data["result"] as string | null) ?? "Registration successful" const msg = (data["result"] as string | null) ?? "Registration successful"

View File

@ -71,13 +71,15 @@
<!-- #endif --> <!-- #endif -->
</template> </template>
<script setup lang="uts"> <script setup lang="ts">
import { MyApiResult, BleScanResult } from "@/uni_modules/android-ble" import { ref } from 'vue'
import { onLoad, onShow, onHide, onUnload, onPullDownRefresh } from '@dcloudio/uni-app'
import { MyApiResult, BleScanResult } from "../../src/BluetoothManager"
import * as Ble from "../../src/BluetoothManager" import * as Ble from "../../src/BluetoothManager"
import * as McmChannel from "../../src/mcm/McmChannel.uts" import * as McmChannel from "../../src/mcm/McmChannel"
import * as McmCmd from "../../src/mcm/McmCommands.uts" import * as McmCmd from "../../src/mcm/McmCommands"
import * as McmInfo from "../../src/mcm/McmInfo.uts" import * as McmInfo from "../../src/mcm/McmInfo"
import * as BleINfo from "../../common/bleInfo.uts" import * as BleINfo from "../../common/bleInfo"
const DEVICE_NAME_FILTER = "CAIIC-MCM-20260902" const DEVICE_NAME_FILTER = "CAIIC-MCM-20260902"
const POLL_INTERVAL = 2000 const POLL_INTERVAL = 2000
@ -226,7 +228,7 @@ function onLinkBluetooth(item: BleScanResult) {
deviceName.value = item.device.name ?? DEVICE_NAME_FILTER deviceName.value = item.device.name ?? DEVICE_NAME_FILTER
BleINfo.setName(deviceName.value) BleINfo.setName(deviceName.value)
BleINfo.setMac(item.device.address) BleINfo.setMac(item.device.address)
getApp().globalData.ConnectBle(item.device.address, false) (getApp().globalData as any).ConnectBle(item.device.address, false)
} }
function onDisconnect() { function onDisconnect() {
@ -252,7 +254,7 @@ function unsubscribe() {
subscribed = false subscribed = false
} }
onLoad((options: UTSJSONObject | null) => { onLoad((options: any) => {
subscribe() subscribe()
}) })

View File

@ -33,10 +33,11 @@
</template> </template>
<script setup lang="uts"> <script setup lang="ts">
import { ref } from 'vue'
import { onLoad, onShow, onHide, onUnload } from '@dcloudio/uni-app'
import { checkToken, getUserName } from '../../common/auth'; import { checkToken, getUserName } from '../../common/auth';
import { SERVER_LIST, getBaseUrl, setBaseUrl } from '../../api/httpInstance'; import { SERVER_LIST, getBaseUrl, setBaseUrl } from '../../api/httpInstance';
import { onMounted } from 'vue'
const isLogin = ref(false) const isLogin = ref(false)
const userInfo = ref("login/register") const userInfo = ref("login/register")
@ -105,7 +106,7 @@ function onCardTap () {
} }
// ======= 生命周期 ======== // ======= 生命周期 ========
onLoad((options: UTSJSONObject | null) => { onLoad((options) => {
if (options != null) { if (options != null) {
} }
@ -124,7 +125,6 @@ onUnload(() => {
}) })
</script> </script>
<style scoped> <style scoped>

View File

@ -70,18 +70,15 @@
<!-- #endif --> <!-- #endif -->
</template> </template>
<script setup lang="uts"> <script setup lang="ts">
import { MyApiResult } from "@/uni_modules/android-ble" import { ref } from 'vue'
import { onLoad, onShow, onHide, onUnload } from '@dcloudio/uni-app'
import { MyApiResult } from "../../src/BluetoothManager"
import * as Ble from "../../src/BluetoothManager" import * as Ble from "../../src/BluetoothManager"
import * as McmCmd from "../../src/mcm/McmCommands.uts" import * as McmCmd from "../../src/mcm/McmCommands"
import * as Ota from "../../src/mcm/OtaManager.uts" import * as Ota from "../../src/mcm/OtaManager"
import { OtaCallbacks } from "../../src/mcm/OtaManager.uts" import { OtaCallbacks } from "../../src/mcm/OtaManager"
import { crc32Ieee } from "../../src/mcm/Crc.uts" import { crc32Ieee } from "../../src/mcm/Crc"
// #ifdef APP-ANDROID
import File from 'java.io.File'
import FileInputStream from 'java.io.FileInputStream'
// #endif
const connected = ref<boolean>(false) const connected = ref<boolean>(false)
const curVersion = ref<string>("--") const curVersion = ref<string>("--")
@ -101,59 +98,70 @@ function addLog(msg: string) {
logs.value.push(msg) logs.value.push(msg)
} }
// ================= 文件读取(Android 本地文件) ================= // ================= 文件读取(App 本地文件,plus.io) =================
function readFileBytes(path: string): number[] | null { function readFileBytes(path: string, cb: (bytes: number[] | null) => void) {
// #ifdef APP-ANDROID // #ifdef APP-PLUS
try { const plusIo = (window as any).plus?.io
const f = new File(path) if (plusIo == null) {
if (!f.exists()) return null cb(null)
const len: Int = f.length().toInt() return
if (len <= 0) return null
const fis = new FileInputStream(f)
const buf = new ByteArray(len)
let readTotal: Int = 0
while (readTotal < len) {
const n = fis.read(buf, readTotal, len - readTotal)
if (n <= 0) break
readTotal += n
}
fis.close()
if (readTotal != len) return null
const out = [] as number[]
var i: Int = 0
while (i < len) {
out.push(buf[i].toInt() & 0xFF)
i++
}
return out
} catch (e) {
console.log("readFileBytes error", e)
return null
} }
plusIo.resolveLocalFileSystemURL(path, (entry: any) => {
entry.file((file: any) => {
const reader = new plusIo.FileReader()
reader.onloadend = (e: any) => {
try {
const dataUrl = e.target.result as string
const base64 = dataUrl.substring(dataUrl.indexOf(",") + 1)
const u8 = new Uint8Array(uni.base64ToArrayBuffer(base64))
const out = [] as number[]
for (let i = 0; i < u8.length; i++) {
out.push(u8[i])
}
cb(out)
} catch (err) {
console.log("readFileBytes parse error", err)
cb(null)
}
}
reader.onerror = (e: any) => {
console.log("readFileBytes read error", e)
cb(null)
}
reader.readAsDataURL(file)
}, (err: any) => {
console.log("readFileBytes file error", err)
cb(null)
})
}, (err: any) => {
console.log("readFileBytes resolve error", err)
cb(null)
})
// #endif // #endif
// #ifndef APP-ANDROID // #ifndef APP-PLUS
return null cb(null)
// #endif // #endif
} }
function onLoadFile() { function onLoadFile() {
const bytes = readFileBytes(filePath.value.trim()) readFileBytes(filePath.value.trim(), (bytes) => {
if (bytes == null) { if (bytes == null) {
fileLoaded.value = false fileLoaded.value = false
uni.showToast({ title: 'Failed to read file, check the path', icon: 'error' }) uni.showToast({ title: 'Failed to read file, check the path', icon: 'error' })
return return
} }
if (bytes.length > 0x38000) { if (bytes.length > 0x38000) {
fileLoaded.value = false fileLoaded.value = false
uni.showToast({ title: 'File exceeds the 224KB limit', icon: 'error' }) uni.showToast({ title: 'File exceeds the 224KB limit', icon: 'error' })
return return
} }
imageBytes = bytes imageBytes = bytes
fileSize.value = bytes.length fileSize.value = bytes.length
fileCrcHex.value = crc32Ieee(bytes).toString(16) fileCrcHex.value = crc32Ieee(bytes).toString(16)
fileLoaded.value = true fileLoaded.value = true
addLog("File loaded: " + bytes.length + " bytes, CRC32=0x" + fileCrcHex.value) addLog("File loaded: " + bytes.length + " bytes, CRC32=0x" + fileCrcHex.value)
})
} }
// ================= 版本号解析 "1.00.02" -> 0x010002 ================= // ================= 版本号解析 "1.00.02" -> 0x010002 =================
@ -245,7 +253,7 @@ function unsubscribe() {
// ================= 生命周期 ================= // ================= 生命周期 =================
onLoad((options: UTSJSONObject | null) => { onLoad((options: any) => {
subscribe() subscribe()
}) })

View File

@ -1,7 +0,0 @@
// 参考链接 https://doc.dcloud.net.cn/uni-app-x/tutorial/ls-plugin.html#setting
{
"targets": [
"APP-ANDROID"
]
}

439
src/BluetoothManager.ts Normal file
View File

@ -0,0 +1,439 @@
/**
* 蓝牙管理模块(基于 uni 内置 BLE API,替代原 android-ble 第三方插件)
*
* 对外约定与原插件保持一致,业务页面无需改动逻辑:
* - MyApiResult.type == 0 表示成功
* - 1000=notify 订阅成功 1001=订阅失败
* - 10000=连接失败 10001=已断开 10002=蓝牙未开/不可用 10003=无蓝牙权限
* - notify 数据通过回调返回 {type:0, data: NotityData},由 App.vue 转发 uni.$emit("bleData")
* - 连接状态变化通过 uni.$emit("bleState", state) 通知(0=已连接 10000=连接失败 10001=已断开)
*/
// CAIIC-MCM 设备 GATT(固件 mcm-ddc-ble app_rdtss.h 实际值,小写与 Android UUID.toString 格式一致)
// 注意:ble_protocol.md V1.1 表格写作 ...C72EE001/...C72EE002,与固件实际 ...C72E0001/...C72E0002 不符,以固件为准
export const MCM_SERVICE_UUID = "00002760-08c2-11e1-9073-0e8ac72e1001"
export const MCM_WRITE_UUID = "00002760-08c2-11e1-9073-0e8ac72e0001" // Write Without Response
export const MCM_NOTIFY_UUID = "00002760-08c2-11e1-9073-0e8ac72e0002" // Notify
export type MyApiResult = {
type: number,
message: string,
data: any
}
export type BleScanDevice = {
name: string | null,
address: string
}
export type BleScanResult = {
device: BleScanDevice,
rssi: number
}
// notify 回调数据结构(对齐原插件 NotityData 的 {data, mac} 字段)
export type NotityData = {
data: number[],
mac: string
}
// ================= 模块状态 =================
let connectedDeviceId: string | null = null
let manualDisconnect = false
let connTimeoutMs = 0 // createBLEConnection 超时,0 表示不设置
let btEnabled = false // 缓存的蓝牙开关状态(由 adapter 状态监听/操作结果维护)
let scanning = false
let scanTimeoutTimer = 0
let stateListenerRegistered = false
let connListenerRegistered = false
let scanListenerRegistered = false
let notifyListenerRegistered = false
let notifyDataCb: ((res: MyApiResult) => void) | null = null
function ok(message: string): MyApiResult {
return { type: 0, message: message, data: null } as MyApiResult
}
function failRes(type: number, err: any): MyApiResult {
return { type: type, message: (err && err.errMsg) ? err.errMsg : "fail", data: null } as MyApiResult
}
// uni BLE 错误 → 本模块 type 约定:蓝牙未开/不可用 10002,权限类 10003
function mapBleError(err: any): number {
const msg: string = (err && err.errMsg) ? err.errMsg : ""
if (msg.indexOf("ermission") >= 0) return 10003
return 10002
}
function uuidEq(a: string, b: string): boolean {
return a.toUpperCase() == b.toUpperCase()
}
// 注册系统蓝牙开关状态监听并同步一次当前状态(幂等)
function ensureStateListener() {
if (stateListenerRegistered) return
stateListenerRegistered = true
uni.onBluetoothAdapterStateChange((res: any) => {
btEnabled = res.available
})
uni.getBluetoothAdapterState({
success: (res: any) => {
btEnabled = res.available
},
fail: () => {}
})
}
// 注册连接状态监听(幂等):意外断开时广播 bleState 10001
function ensureConnListener() {
if (connListenerRegistered) return
connListenerRegistered = true
uni.onBLEConnectionStateChange((res: any) => {
if (res.connected) {
connectedDeviceId = res.deviceId
} else {
const wasConnected = connectedDeviceId != null && connectedDeviceId == res.deviceId
connectedDeviceId = null
if (wasConnected && !manualDisconnect) {
uni.$emit("bleState", 10001)
}
manualDisconnect = false
}
})
}
// ================= 适配器 =================
// 手机蓝牙是否打开 true-打开 false-未打开
export function isEnabled(): boolean {
ensureStateListener()
return btEnabled
}
// 监听系统蓝牙开关状态 res.data 的值为 true-打开 false-关闭
export function OpenStateListener(cb: (res: MyApiResult) => void) {
ensureStateListener()
uni.onBluetoothAdapterStateChange((res: any) => {
cb({ type: 0, message: "adapter state changed", data: res.available } as MyApiResult)
})
}
// 打开蓝牙(初始化蓝牙适配器;蓝牙关闭/无权限时走 fail)
export function openBluetooth(cb: (res: MyApiResult) => void) {
ensureStateListener()
uni.openBluetoothAdapter({
success: () => {
btEnabled = true
cb(ok("bluetooth adapter opened"))
},
fail: (err: any) => {
cb(failRes(mapBleError(err), err))
}
})
}
// ================= 扫描 =================
// 开始蓝牙扫描:每个发现的设备回调一次 {type:0, data: BleScanResult};超时自动停止
export function startScan(timeoutMs: number, cb: (res: MyApiResult) => void) {
ensureStateListener()
uni.openBluetoothAdapter({
success: () => {
btEnabled = true
if (scanning) return
if (!scanListenerRegistered) {
scanListenerRegistered = true
uni.onBluetoothDeviceFound((res: any) => {
const devices = res.devices as any[]
for (let i = 0; i < devices.length; i++) {
const d = devices[i]
let name: string | null = d.name as string
if (name == null || name.length == 0) {
const ln = (d.localName ?? "") as string
name = ln.length > 0 ? ln : null
}
const sr: BleScanResult = {
device: { name: name, address: d.deviceId } as BleScanDevice,
rssi: d.RSSI as number
} as BleScanResult
cb({ type: 0, message: "device found", data: sr } as MyApiResult)
}
})
}
scanning = true
uni.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
success: () => {},
fail: (err: any) => {
scanning = false
cb(failRes(mapBleError(err), err))
}
})
if (scanTimeoutTimer != 0) {
clearTimeout(scanTimeoutTimer)
}
scanTimeoutTimer = setTimeout(() => {
stopScan()
}, timeoutMs)
},
fail: (err: any) => {
cb(failRes(mapBleError(err), err))
}
})
}
// 停止蓝牙扫描
export function stopScan() {
if (scanTimeoutTimer != 0) {
clearTimeout(scanTimeoutTimer)
scanTimeoutTimer = 0
}
if (!scanning) return
scanning = false
uni.stopBluetoothDevicesDiscovery({
success: () => {},
fail: () => {}
})
}
// ================= 连接 =================
// 蓝牙是否已连接
export function isConnected(): boolean {
return connectedDeviceId != null
}
// 获取已连接设备id(未连接返回空串)
export function getConnectMac(): string {
return connectedDeviceId ?? ""
}
// 连接蓝牙(autoReconnect 保留兼容签名,当前业务恒传 false,未实现自动重连)
export function connect(mac: string, autoReconnect: boolean, cb: (res: MyApiResult) => void) {
ensureStateListener()
ensureConnListener()
manualDisconnect = false
if (connTimeoutMs > 0) {
uni.createBLEConnection({
deviceId: mac,
timeout: connTimeoutMs,
success: () => {
connectedDeviceId = mac
cb(ok("connected"))
},
fail: (err: any) => {
cb(failRes(10000, err))
}
})
} else {
uni.createBLEConnection({
deviceId: mac,
success: () => {
connectedDeviceId = mac
cb(ok("connected"))
},
fail: (err: any) => {
cb(failRes(10000, err))
}
})
}
}
// 设置蓝牙连接超时时间(ms)
export function connectTimeout(timeout: number): void {
connTimeoutMs = timeout
}
// 设置自动连接间隔(保留兼容,未实现)
export function setBtAutoConnectTime(timeout: number): void {
}
// 取消自动连接蓝牙(保留兼容,未实现)
export function cancelAutoConnectBt(): void {
}
// 断开蓝牙
export function disConnect() {
const id = connectedDeviceId
manualDisconnect = true
connectedDeviceId = null
if (id != null) {
uni.closeBLEConnection({
deviceId: id,
success: () => {},
fail: () => {}
})
}
}
// ================= 服务与订阅 =================
// 连接后扫描服务:确认 MCM 服务及写/notify 特征存在(替代插件的自动识别)
export function scanServices(cb: (res: MyApiResult) => void) {
const id = connectedDeviceId
if (id == null) {
cb({ type: 10001, message: "not connected", data: null } as MyApiResult)
return
}
uni.getBLEDeviceServices({
deviceId: id,
success: (res: any) => {
const services = res.services as any[]
let found = false
for (let i = 0; i < services.length; i++) {
if (uuidEq(services[i].uuid as string, MCM_SERVICE_UUID)) {
found = true
break
}
}
if (!found) {
cb({ type: 10004, message: "MCM service not found", data: null } as MyApiResult)
return
}
uni.getBLEDeviceCharacteristics({
deviceId: id as string,
serviceId: MCM_SERVICE_UUID,
success: () => {
cb(ok("services ready"))
},
fail: (err: any) => {
cb(failRes(10005, err))
}
})
},
fail: (err: any) => {
cb(failRes(10004, err))
}
})
}
// 开启服务消息通知(显式使用 CAIIC-MCM 的服务/notify UUID)
// 订阅成功回调 type=1000,失败 type=1001;之后 notify 数据以 type=0 持续回调
export function onNotityReadBleData(readNotification: boolean, cb: (res: MyApiResult) => void) {
const id = connectedDeviceId
if (id == null) {
cb({ type: 10001, message: "not connected", data: null } as MyApiResult)
return
}
notifyDataCb = cb
if (!notifyListenerRegistered) {
notifyListenerRegistered = true
uni.onBLECharacteristicValueChange((res: any) => {
if (!uuidEq(res.characteristicId as string, MCM_NOTIFY_UUID)) return
const bytes = new Uint8Array(res.value as ArrayBuffer)
const arr = [] as number[]
for (let i = 0; i < bytes.length; i++) {
arr.push(bytes[i])
}
const nd: NotityData = { data: arr, mac: res.deviceId } as NotityData
const c = notifyDataCb
if (c != null) {
c({ type: 0, message: "notify", data: nd } as MyApiResult)
}
})
}
uni.notifyBLECharacteristicValueChange({
deviceId: id,
serviceId: MCM_SERVICE_UUID,
characteristicId: MCM_NOTIFY_UUID,
state: readNotification,
success: () => {
cb({ type: 1000, message: "notify subscribed", data: null } as MyApiResult)
},
fail: (err: any) => {
cb(failRes(1001, err))
}
})
}
// ================= 写入 =================
// 发送数据(显式写类型):writeType 0=WRITE(有响应) 1=WRITE_NO_RESPONSE(无响应)
// CAIIC-MCM 设备的下行特征仅支持 Write Without Response,必须用 writeType=1。
// 服务/特征 UUID 用固件实际值显式指定(下行特征只有 WRITE_NO_RESPONSE 属性)
export function sendDataBytes(data: number[], writeType: number, cb: (res: MyApiResult) => void) {
const id = connectedDeviceId
if (id == null) {
cb({ type: 10001, message: "not connected", data: null } as MyApiResult)
return
}
const buf = new Uint8Array(data.length)
for (let i = 0; i < data.length; i++) {
buf[i] = data[i] & 0xFF
}
if (writeType == 1) {
uni.writeBLECharacteristicValue({
deviceId: id,
serviceId: MCM_SERVICE_UUID,
characteristicId: MCM_WRITE_UUID,
value: buf.buffer,
writeType: "writeNoResponse",
success: () => {
cb(ok("write ok"))
},
fail: (err: any) => {
cb(failRes(10008, err))
}
})
} else {
uni.writeBLECharacteristicValue({
deviceId: id,
serviceId: MCM_SERVICE_UUID,
characteristicId: MCM_WRITE_UUID,
value: buf.buffer,
writeType: "write",
success: () => {
cb(ok("write ok"))
},
fail: (err: any) => {
cb(failRes(10008, err))
}
})
}
}
// ================= 其他 =================
// 设置 mtu(仅 App 端有效;其他平台回调失败,调用方保持默认 20 字节切片)
export function setMtu(len: number, cb: (res: MyApiResult) => void) {
const id = connectedDeviceId
if (id == null) {
cb({ type: 10001, message: "not connected", data: null } as MyApiResult)
return
}
// #ifdef APP-PLUS
uni.setBLEMTU({
deviceId: id,
mtu: len,
success: (res: any) => {
cb({ type: 0, message: "mtu " + res.mtu, data: null } as MyApiResult)
},
fail: (err: any) => {
cb(failRes(10008, err))
}
})
// #endif
// #ifndef APP-PLUS
cb({ type: 10008, message: "set mtu not supported on this platform", data: null } as MyApiResult)
// #endif
}
// 读取 rssi 信号 需要蓝牙已经连接
export function readRssi(cb: (res: MyApiResult) => void) {
const id = connectedDeviceId
if (id == null) {
cb({ type: 10001, message: "not connected", data: null } as MyApiResult)
return
}
uni.getBLEDeviceRSSI({
deviceId: id,
success: (res: any) => {
cb({ type: 0, message: "rssi", data: res.RSSI } as MyApiResult)
},
fail: (err: any) => {
cb(failRes(10008, err))
}
})
}

View File

@ -1,180 +0,0 @@
import {BleLib,MyApiResult,BleScanResult,BleServices,ScanRssiBR,WNuuid,ScanPara,WriteData} from "@/uni_modules/android-ble"
// CAIIC-MCM 设备 GATT(固件 mcm-ddc-ble app_rdtss.h 实际值,小写与 Android UUID.toString 格式一致)
// 注意:ble_protocol.md V1.1 表格写作 ...C72EE001/...C72EE002,与固件实际 ...C72E0001/...C72E0002 不符,以固件为准
export const MCM_SERVICE_UUID = "00002760-08c2-11e1-9073-0e8ac72e1001"
export const MCM_WRITE_UUID = "00002760-08c2-11e1-9073-0e8ac72e0001" // Write Without Response
export const MCM_NOTIFY_UUID = "00002760-08c2-11e1-9073-0e8ac72e0002" // Notify
// 统一拿到全局 ble 单例(仍然是 App.globalData.ble)
function ble(): BleLib {
const app = getApp()
return app.globalData.ble as BleLib
}
// 手机蓝牙是否打开 true-打开 false-未打开
export function isEnabled(): boolean {
return ble().isEnabled()
}
// 监听系统蓝牙开关状态 res.data的值为true-打开 false-关闭
export function OpenStateListener(cb: (res: MyApiResult) => void) {
ble().onBtOpenStateListener(cb)
}
// 开始蓝牙扫描
export function startScan(timeoutMs: number, cb: (res: MyApiResult) => void) {
// ble().startScanBleDevice(timeoutMs, cb) // 旧版本
ble().onStartScanBle({
scantime:timeoutMs, // 超时时间 ms
showEmptyName:1, // 0表示 不返回空的名称 1 表示可返回空名称蓝牙
btNameFilter:"", // showEmptyName 为0有效 代表过滤相关的名字 为空表示不过滤
onScanResult:(cb)
} as ScanPara)
}
// 蓝牙扫描时监听蓝牙广播信号变化 实时回掉 多次
export function scanDataListener(cb: (res: MyApiResult) => void) {
ble().onScanDataListener(cb)
}
// 停止蓝牙扫描
export function stopScan() {
ble().stopScanBle()
}
// 蓝牙是否已连接
export function isConnected(): boolean {
return ble().isConnected()
}
// 获取已连接设备id
export function getConnectMac(): string {
return ble().getConnectMac()
}
// 打开安卓蓝牙
export function openBluetooth(cb: (res: MyApiResult) => void) {
ble().openBtBluetooth(cb)
}
// 连接蓝牙
export function connect(mac: string, autoReconnect: boolean, cb: (res: MyApiResult) => void) {
ble().connect(mac, autoReconnect, cb)
}
// 设置蓝牙超时时间
export function connectTimeout(timeout: number): void {
ble().setBtConnectTimeout(timeout)
}
// 设置自动连接间隔
export function setBtAutoConnectTime(timeout: number): void {
ble().setBtAutoConnectTime(timeout)
}
// 取消自动连接蓝牙
export function cancelAutoConnectBt(): void {
ble().cancelAutoConnectBt()
}
// 连接蓝牙后需要获扫描蓝牙与特征值
export function scanServices(cb: (res: MyApiResult) => void) {
ble().scanServices(cb)
}
// 选择可用服务
export function setSelectUUID(len: number): void {
ble().setSelectUUID(len) // 长度不可超过
}
// 获取可用写通知服务
export function getUUIDs(): string {
return ble().getUUIDs()
}
// 断开蓝牙
export function disConnect() {
ble().close()
}
// 开启服务消息通知(显式使用 CAIIC-MCM 的服务/notify UUID;onNotityBleData 为当前接口,
// 旧接口 onNotityReadBleData 插件已标记弃用)
export function onNotityReadBleData(readNotification: boolean , cb: (res: MyApiResult) => void) {
const cBle = ble()
cBle.onNotityBleData(MCM_SERVICE_UUID, MCM_NOTIFY_UUID, readNotification, cb)
}
// 获取自动识别读写服务uuid
export function getSericUUID(): string {
return ble().getSericUUID()
}
// 获取自动识别通知uuid
export function getNotityUUID(): string {
return ble().getNotityUUID()
}
// 获取自动识写入uuid
export function getwriteUUID(): string {
return ble().getwriteUUID()
}
// 发送数据 16进制数组 b:number[]=[0x55,0xff,0xAA] as number[];
export function sendData(data: number[], cb: (res: MyApiResult) => void) {
const b = ble()
b.writeDataToBle(b.getSericUUID(), b.getwriteUUID(), data, cb)
}
// 发送数据(显式写类型):writeType 0=WRITE(有响应) 1=WRITE_NO_RESPONSE(无响应)
// CAIIC-MCM 设备的下行特征仅支持 Write Without Response,必须用 writeType=1。
// 服务/特征 UUID 用固件实际值显式指定,不依赖插件自动识别
//(下行特征只有 WRITE_NO_RESPONSE 属性,自动识别的 wuuid 可能为空导致写入失败)
export function sendDataBytes(data: number[], writeType: number, cb: (res: MyApiResult) => void) {
const b = ble()
const wd: WriteData = {
serviceId: MCM_SERVICE_UUID,
characteristicId: MCM_WRITE_UUID,
writeType: writeType,
data: data,
fenbao: false
} as WriteData
b.sendData(wd, cb)
}
// 按服务 UUID 前缀选择 service+notify+write 组合(如 "00002760"),找到返回 true
export function selectService(serviceUuidPrefix: string): boolean {
const b = ble()
const list = b.getUUIDs() as any as WNuuid[]
if (list == null) return false
const prefix = serviceUuidPrefix.toUpperCase()
for (let i = 0; i < list.length; i++) {
if (list[i].serviceId.toUpperCase().indexOf(prefix) == 0) {
b.setSelectUUID(i)
return true
}
}
return false
}
// 发送16进制字符数据 var d="00FFAABB"
export function sendHexString(data: string, cb: (res: MyApiResult) => void) {
const b = ble()
b.writeStringDataToBle(b.getSericUUID(), b.getwriteUUID(), data, cb)
}
// 设置mtu(仅安卓)
export function setMtu(len: number, cb: (res: MyApiResult) => void) {
ble().setMtu(len, cb)
}
// 读取rssi 信号 需要蓝牙已经连接
export function readRssi(cb: (res: MyApiResult) => void) {
ble().readRssi(cb)
}
// 字符转16进制拼接字符 string2ByteStrWithCharset
// 进制拼接字符转文本 byte2StringWithCharset
// 判断对端蓝牙设备的虚拟MAC地址是否有效(鸿蒙) isValidRandomDeviceId

View File

@ -5,7 +5,7 @@
* 帧开销 7 字节(头 5 + CRC 2),payload 上限 480。 * 帧开销 7 字节(头 5 + CRC 2),payload 上限 480。
* 一个协议帧可跨多次 BLE 写入传输,接收侧按字节流重组。 * 一个协议帧可跨多次 BLE 写入传输,接收侧按字节流重组。
*/ */
import { crc16Ccitt } from "./Crc.uts" import { crc16Ccitt } from "./Crc"
export const FRAME_SOF = 0xCA export const FRAME_SOF = 0xCA
export const FRAME_OVERHEAD = 7 export const FRAME_OVERHEAD = 7

View File

@ -7,13 +7,13 @@
* *
* 约定:所有接口的 Promise 只 resolve 不 reject,失败通过返回值表达(uts 兼容性考虑)。 * 约定:所有接口的 Promise 只 resolve 不 reject,失败通过返回值表达(uts 兼容性考虑)。
*/ */
import * as Ble from "../BluetoothManager.uts" import * as Ble from "../BluetoothManager"
import { MyApiResult, NotityData } from "@/uni_modules/android-ble" import { MyApiResult, NotityData } from "../BluetoothManager"
import { import {
FrameDecoder, BleFrame, encodeFrame, FrameDecoder, BleFrame, encodeFrame,
TYPE_CLI_REQ, TYPE_CLI_RSP, TYPE_CLI_RSP_END, TYPE_OTA_RSP, TYPE_INFO_QUERY, TYPE_INFO_RSP TYPE_CLI_REQ, TYPE_CLI_RSP, TYPE_CLI_RSP_END, TYPE_OTA_RSP, TYPE_INFO_QUERY, TYPE_INFO_RSP
} from "./FrameCodec.uts" } from "./FrameCodec"
import { asciiToBytes, bytesToAscii, bytesToHex } from "../commonFunctions.uts" import { asciiToBytes, bytesToAscii, bytesToHex } from "../commonFunctions"
export type CliResult = { export type CliResult = {
text: string, // 聚合的应答文本 text: string, // 聚合的应答文本

View File

@ -3,11 +3,11 @@
* 温度/风扇/固件版本走 INFO_QUERY(协议 V1.1 第 5 节); * 温度/风扇/固件版本走 INFO_QUERY(协议 V1.1 第 5 节);
* sysinfo/bankinfo 等系统状态仍走 CLI 透传,命令名集中在顶部常量。 * sysinfo/bankinfo 等系统状态仍走 CLI 透传,命令名集中在顶部常量。
*/ */
import { sendCli, CliResult } from "./McmChannel.uts" import { sendCli, CliResult } from "./McmChannel"
import { import {
queryDeviceInfo, formatVersion, queryDeviceInfo, formatVersion,
INFO_FW_VERSION, INFO_CHIP_TEMP, INFO_FAN_RPM INFO_FW_VERSION, INFO_CHIP_TEMP, INFO_FAN_RPM
} from "./McmInfo.uts" } from "./McmInfo"
const CMD_SYSINFO = "sysinfo" const CMD_SYSINFO = "sysinfo"
const CMD_BANKINFO = "bankinfo" const CMD_BANKINFO = "bankinfo"

View File

@ -3,8 +3,8 @@
* INFO_RSP payload 为 TLV 序列 {id u8, len u8, value…}×n,数值均小端; * INFO_RSP payload 为 TLV 序列 {id u8, len u8, value…}×n,数值均小端;
* 设备不认识的 id 不出现在应答中;解析时未知 id 按 len 跳过。 * 设备不认识的 id 不出现在应答中;解析时未知 id 按 len 跳过。
*/ */
import { queryInfo } from "./McmChannel.uts" import { queryInfo } from "./McmChannel"
import { readU16LE, readI16LE, readU32LE } from "./FrameCodec.uts" import { readU16LE, readI16LE, readU32LE } from "./FrameCodec"
// 信息项 id // 信息项 id
export const INFO_FW_VERSION = 0x01 // u32 固件版本号,如 0x00010001 = V1.00.01 export const INFO_FW_VERSION = 0x01 // u32 固件版本号,如 0x00010001 = V1.00.01

View File

@ -8,12 +8,12 @@
*/ */
import { import {
sendFrame, setOtaRspHandler, setDisconnectHandler sendFrame, setOtaRspHandler, setDisconnectHandler
} from "./McmChannel.uts" } from "./McmChannel"
import { import {
TYPE_OTA_BEGIN, TYPE_OTA_DATA, TYPE_OTA_END, TYPE_OTA_BEGIN, TYPE_OTA_DATA, TYPE_OTA_END,
MAX_PAYLOAD, pushU32LE, readU32LE MAX_PAYLOAD, pushU32LE, readU32LE
} from "./FrameCodec.uts" } from "./FrameCodec"
import { crc32Ieee } from "./Crc.uts" import { crc32Ieee } from "./Crc"
export type OtaCallbacks = { export type OtaCallbacks = {
onProgress: (percent: number) => void, // 0~100 onProgress: (percent: number) => void, // 0~100

View File

@ -1,170 +0,0 @@
## 1.1.73(2026-08-15)
优化用户反馈插件存在的问题
## 1.1.72(2026-08-12)
优化
## 1.1.71(2026-08-08)
优化demo
## 1.1.70(2026-08-05)
优化ios 连接
## 1.1.69(2026-07-14)
修复用户反馈Android的闪退问题
## 1.1.68(2026-07-07)
优化用户反馈的闪退问题
## 1.1.67(2026-07-03)
增加 Android iOS 鸿蒙 onIndicate 真实订阅与数据读取功能
优化 Android indicate 特征判断与回调
优化 iOS 与鸿蒙 indicate 订阅成功失败和数据回调
## 1.1.66(2026-06-17)
优化ios 与鸿蒙断开连接
## 1.1.65(2026-06-15)
优化群里反馈的闪退问题
## 1.1.64(2026-06-14)
增加Android 鸿蒙 微信小程序扫描通过uuid过滤
优化鸿蒙onReadData
其它群中反馈问题优化
## 1.1.63(2026-05-20)
优化ble连接多连接
修复已知问题
## 1.1.62(2026-04-12)
1.增加主动读取蓝牙数据
2.优化Android 分包发送 数据较大的时候 出现回掉多次的问题
3.优化ios 蓝牙多连接
4 其他优化
## 1.1.61(2026-03-18)
增加安卓phy 2m 模式设置
修复鸿蒙获取信号
ios 增加扫描通过uuid 过滤
## 1.1.60(2026-03-17)
1 增加多蓝牙连接 收消息时候回掉蓝牙对应的uuid 信息
2.增加ios 返回广播中蓝牙的名称 其它端不返回
3.增加扫描多个名称过滤
4,其它优化
## 1.1.59(2026-02-02)
修复鸿蒙端自动连接多次回掉问题
## 1.1.58(2026-01-20)
优化ios发送字节数据错误
## 1.1.57(2026-01-19)
优化蓝牙发送数据 优化数据分包 蓝牙连接
优化已知问题等
## 1.1.56(2026-01-12)
优化ios mtu
## 1.1.55(2026-01-08)
优化鸿蒙mtu 设置
## 1.1.54(2025-12-30)
优化蓝牙扫描与连接
## 1.1.53(2025-12-29)
优化蓝牙扫描
## 1.1.52(2025-12-20)
修复安卓编译错误
## 1.1.51(2025-12-19)
优化
## 1.1.50(2025-12-19)
优化连接 多端错误码同步等
## 1.1.49(2025-12-18)
优化
## 1.1.48(2025-12-18)
优化鸿蒙蓝牙扫描与Android 连接
## 1.1.47(2025-12-17)
优化
## 1.1.46(2025-12-15)
优化鸿蒙
## 1.1.45(2025-12-09)
you hua ios connect
## 1.1.44(2025-12-04)
优化鸿蒙发送
## 1.1.43(2025-12-04)
优化Android 蓝牙权限
## 1.1.42(2025-11-23)
优化
## 1.1.41(2025-11-19)
优化
## 1.1.40(2025-11-18)
优化ios
## 1.1.39(2025-11-18)
优化
## 1.1.38(2025-11-15)
优化链接
## 1.1.37(2025-11-14)
优化鸿蒙端
## 1.1.36(2025-11-11)
优化
## 1.1.35(2025-11-11)
优化
## 1.1.34(2025-10-25)
优化蓝牙扫描问题
## 1.1.33(2025-10-24)
优化
## 1.1.32(2025-09-17)
优化
## 1.1.31(2025-09-12)
优化
## 1.1.30(2025-09-11)
增加微信小程序
## 1.1.29(2025-09-01)
增加鸿蒙 其它优化
## 1.1.28(2025-08-31)
优化
## 1.1.27(2025-06-06)
优化
## 1.1.26(2025-05-30)
优化
## 1.1.25(2025-05-22)
优化
## 1.1.24(2025-05-20)
优化
## 1.1.23(2025-05-20)
优化
## 1.1.22(2025-05-18)
优化
## 1.1.21(2025-05-10)
优化
## 1.1.20(2025-05-10)
优化iOS端
## 1.1.19(2025-05-07)
优化
## 1.1.18(2025-05-07)
优化
## 1.1.17(2025-05-06)
优化
## 1.1.16(2025-05-03)
优化
## 1.1.15(2025-04-30)
优化
## 1.1.14(2025-04-29)
优化
## 1.1.13(2025-04-29)
优化
## 1.1.12(2025-04-22)
优化
## 1.1.11(2025-04-21)
优化
## 1.1.10(2025-04-11)
优化Android蓝牙
## 1.1.9(2025-04-10)
优化蓝牙
## 1.1.8(2025-04-08)
优化低版本不申请定位权限问题
## 1.1.7(2025-04-07)
优化
## 1.1.6(2025-04-04)
优化
## 1.1.5(2025-04-04)
优化
## 1.1.2(2025-03-18)
增加ios
## 1.1.1(2025-03-17)
优化
## 1.0.9(2025-02-28)
优化
## 1.0.8(2025-02-11)
优化
## 1.0.7(2025-01-03)
修复编译报错问题
## 1.0.6(2024-11-19)
修复上次问题
## 1.0.5(2024-11-19)
修复uniapp 问题
## 1.0.4(2024-10-08)
修复偶现链接失败的bug
## 1.0.3(2024-09-26)
修复已知bug

View File

@ -1 +0,0 @@
Πϊρ<EFBFBD>τ,ΨΒtµ <C2B5>&ΐ‘qΨά:ϊSK<53>'«Δξv–Η,¨” ςψF<CF88>³›vQ<76>½P•ΜN<03>Η®'_hΩg­47C·CΘTόhjI›:<3A>ΔX~t<>Δ—t0D©2§ΜiαΥ³ Σδy\d°Αΐ%ύ<>σΖΨ<CE96><CEA8>ΟM@t*ΎλϊςQγ?ΓΏµs<C2B5><73>IVΙ%P

View File

@ -1,126 +0,0 @@
### 开启服务消息通知(旧)
### onNotityReadBleData
参数1 通知服务uuid
参数2 通知属性 uuid
参数3 true 开启通知读取 false 关闭通知读取
参数4 回掉结果res.type 为0 表示通知数据 1000 表示订阅成功 1001 表示 订阅失败
uniappx
~~~
lib.onNotityReadBleData(lib.getSericUUID(),
lib.getNotityUUID(),
true,
function(res:MyApiResult){
if(res.type==0){
console.log(res)
}
}
)
~~~
uniapp
~~~
lib.onNotityReadBleData(lib.getSericUUID(),
lib.getNotityUUID(),
true,
function(res){
if(res.type==0){
console.log(res)
}
}
)
~~~
### 发送数据(弃用)
### writeDataToBle
参数1 服务uuid
参数2 写入属性uuid
参数3 16进制数组
参数4 写入回掉
uniappx
~~~
var b:number[]=[0x55,0xff,oxAA] as number[];
lib.writeDataToBle(
lib.getSericUUID(),
lib.getwriteUUID(),
b,
function(res:MyApiResult){
console.log(res)
}
)
~~~
~~~
var b:number[]=[0x55,0xff,oxAA] as number[];
lib.writeDataToBleWithType(
lib.getSericUUID(),
lib.getwriteUUID(),
b,
0,
function(res:MyApiResult){
console.log(res)
}
)
~~~
uniapp
~~~
var b=[0x55,0xff,oxAA] ;
lib.writeDataToBle(
lib.getSericUUID(),
lib.getwriteUUID(),
b,
function(res){
console.log(res)
}
)
~~~
### 发送16进制字符数据(弃用)
### writeStringDataToBle
参数1 服务uuid
参数2 写入属性uuid
参数3 16进制字符串
参数4 回掉写入结果
uniappx
~~~
var d="00FFAABB"
lib.writeStringDataToBle(
lib.getSericUUID(),
lib.getwriteUUID(),
d,
function(res:MyApiResult){
console.log(res)
}
)
~~~
uniapp
~~~~
var d="00FFAABB"
lib.writeStringDataToBle(
lib.getSericUUID(),
lib.getwriteUUID(),
d,
function(res){
console.log(res)
}
)
~~~~

View File

@ -1,111 +0,0 @@
{
"id": "android-ble",
"displayName": "Ble低功耗蓝牙uts插件 支持安卓ios 鸿蒙 微信小程序(2025插件大赛三等奖)",
"version": "1.1.73",
"description": "蓝牙 ble UTS 插件 支持安卓ios 鸿蒙 微信小程序,蓝牙扫描、蓝牙广播 连接 多蓝牙、读取数据 发送数据,蓝牙状态监听、连接状态、数据写入 支持GBK编码 格式转换等",
"keywords": [
"BLE",
"蓝牙",
"低功耗蓝牙",
"多蓝牙连接",
"bluetooth"
],
"repository": "",
"engines": {
"HBuilderX": "^3.98",
"uni-app": "^4.07",
"uni-app-x": "^4.07"
},
"dcloudext": {
"type": "uts",
"sale": {
"regular": {
"price": "66.00"
},
"sourcecode": {
"price": "888.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": " <uses-permission android:name=\"android.permission.BLUETOOTH_SCAN\" />\n <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>\n <uses-permission android:name=\"android.permission.BLUETOOTH_ADMIN\"/>\n <uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\"/>\n <uses-permission android:name=\"android.permission.BLUETOOTH\"/>\n <uses-permission android:name=\"android.permission.BLUETOOTH_ADVERTISE\" />"
},
"npmurl": "",
"darkmode": "√",
"i18n": "√",
"widescreen": "√"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"client": {
"uni-app": {
"vue": {
"vue2": "-",
"vue3": "-"
},
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"vue": "√",
"nvue": "√",
"android": {
"extVersion": "",
"minVersion": "21"
},
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√",
"alipay": "-",
"toutiao": "-",
"baidu": "-",
"kuaishou": "-",
"jd": "-",
"harmony": "-",
"qq": "-",
"lark": "-",
"xhs": "-"
},
"quickapp": {
"huawei": "-",
"union": "-"
}
},
"uni-app-x": {
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"android": {
"extVersion": "",
"minVersion": "21"
},
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√"
}
}
},
"cloud": {
"tcb": "x",
"aliyun": "x",
"alipay": "x"
}
},
"extends": {
"sn": "AcF3Eo4MCUoCe6Q513VMY+Y6GBtfln8RTEF0kiwL7ae84o3c+KmUKJlF3Fl7kzYpjilw9Lt9erESBkB/qL2pTBr+xP/aWJP3GzC3oHmsg5e7n6fH4in9Mg2quSV5DOseKObFgIDddAiirAi++uXYJy9ElBlBC4WQH+OGw7UqVtbc7x7lQ2LMEh2xOw1nEFrTThc5TaNi6Mr9Jm5bOr2GRx0hTCIW8WSummY7Cs5/nELcJXEBegufNwHv62lF9WBaGoUKwYLGxEkCQDmCgX/yyA=="
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.xtf.ble">
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"
/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
</manifest>

View File

@ -1,7 +0,0 @@
+ž5 µ52W×ŇĄiyi7YŁá±Řużőö <EFBFBD>މžp^w”‡<EFBFBD>µîYä@?ŕ+`¬ĹýĎŠĐX<EFBFBD>ľjp©)
:Ţ\ĺP±č˛Bëw/D š_üŘ*;ÝŤK.S 8vZ6Mv Ť~ Î(KČR]űÝĆđ್H`@\ŚDĹFąMĐXܧŚ^vů\şc ŢZ6˝renôەǤ ŹĆ&Ťx7%¨ŁÓp±ęšŃB§¤JĎŹÁÖ<EFBFBD>űĽ<EFBFBD>‚ Á”š<EFBFBD>oTUI=ĄLMçăü%ĎžiŐDl/y—ÂR:GüF‹Ë…Řűęęé”ĆJUÎZ–ő°gţx)uŐb­ 8–›ŮŘ#"BrÓą›lŢ\éF´µŚN~OF5<46>Y•d4ÝÓ›ôç˝<C3A7>•“J„Č&Pş¶éM;ÖËŘ?='Wť ˛ă¸˙\PŚćţŢ»„qžn]ľ›Ę=qn•tÓň×3^¸ůóŰY„ęÔΕĚmMçŘjĆü .
p\ň5žv̤˘ ŹMżŽ}›t0
äaĽ‡ěnľVéďŔAŚ:Ĺ83.M0“Ý3IÚžAawëoŰ :Úżčţ˛vuČ4¸˘˛*¨' Ť†Ż9ŇčEśÇ
ż­†…Ăĺ,Łš?őx+óË?ŚU¬é><EFBFBD>g‘±Jťçë •q÷¦'<EFBFBD>¦ĂJm’ĚĽ6(§ęŠĎyČUB-)µ+mu’âž›–k`_fO©ÂşcÔˇ¨‹rJńl˙Ő¨)ýNěb„{`I»ČCŃ ĐíCa$<EFBFBD>‘Äu
­ý¨ŠÍ3žôt%<;µJÖ ­%3ŃXWfŰ˝ÁĽ«Š˘jó¸z7ŞcßľKHty=‘Źäw®‹ŚĐćăőÍÚ
ÎdëbÖP<EFBFBD>ç•G&Çű:č^zŽMc eÁEŢĎî…T÷<EFBFBD>ˇË€˘‰0knYĚÜ%řżŠBZé–ˇb2}EüZs

View File

@ -1,3 +0,0 @@
L׃±k<EFBFBD>ֽ™£ָ™iY6ֶ/ƒq<EFBFBD>vpDׂ±yo
;b*r‚?י¢:<EFBFBD>^צzֹo|<EFBFBD>Tn[6heֶ¸¹כ†ׁ<EFBFBD>]ם<EFBFBD><EFBFBD>“<EFBFBD>H^¯÷ש<EFBFBD>ת3 ±9’ב›<EFBFBD>]צב—ר¬¡Wֵ<EFBFBD>0<EFBFBD><EFBFBD>¹ָB<EFBFBD>ע²˜<EFBFBD>{םf<§IiCדu=”uƒG‎±%J<EFBFBD>¨)@<EFBFBD>‚<EFBFBD>ֿ<EFBFBD>tqצ|׀װ‹‰‡“  <EFBFBD>—·<EFBFBD>÷יƒDK<EFBFBD><EFBFBD>.<EFBFBD>"4מל´<EFBFBD><EFBFBD>¥…‘‡G<‡h`ֵצl:^Ggֵ₪&ד(צ<>µהeחֻ*'סN<D7A1>ש<EFBFBD>םןץװ§_w¼¹m$
gד‚ש\j¬װ«CS††©“<EFBFBD>Md1ֱףG<EFBFBD>‰e>ךS<EFBFBD>NLd<EFBFBD>ƒ׳9װ¡§¨₪·

View File

@ -1,2 +0,0 @@
ЖLгБk<EFBFBD>Э<EFBFBD>ЃШ<EFBFBD>iY6Ц/<EFBFBD>q<EFBFBD>vpDвБyo
;b3<EFBFBD>ЕцЇkщК<EFBFBD>%`мBЕцt dx <EFBFBD>В<EFBFBD>ZHtяSg%№5цx<EFBFBD>S<EFBFBD>s<EFBFBD>СY<EFBFBD>>?ЊБrљ5<EFBFBD>ТѕaiШо Л|'GHч<EFBFBD>в5дй<EFBFBD>ўyЪЮ,5тNЖИ§iїєw<EFBFBD>]И=y<EFBFBD> ,6MНЊЕЅї­<EFBFBD>ѕ'FіЁ<EFBFBD>?щљy*Ѓяш3]C)<EFBFBD>Н"Ў=mђа<D192><D0B0>VЪgwNћ<4E>5<EFBFBD>qAKЛ[!Х(/<2F>d/М^\мМ9}<7D>K­%

View File

@ -1,2 +0,0 @@
LÓ±k<EFBFBD>Í™£È™iY6Æ.wüeIO¡QNIZ?Ѳñ|®nVeO<EFBFBD>öV³çO |WOPVZse5éíökÔL9<ZGC‘»ú×[9Ô⺵€bAǸušKçê²¢“ªS*Úæ%zmD0ˆ®æêPCoH‡õ>É…¢ªòoÿZIMâ&1Ö R/ˆ¥µŠË!yà4ƳÞ#½Ë’c³©ú­üÉ¥•ÿ$/:î@òÕAÖªÜê<EFBFBD>—þõê‚AšÔŸé¼žíQ¿w,+·±<EFBFBD>ú<EFBFBD>!6ñÝG¤Á<EFBFBD>죄JùHÕÚŸ÷0ùUÇ{Bcl<EFBFBD>AÌ
¾t ï€<EFBFBD>@HÌ<EFBFBD>–Üm/Nwù

View File

@ -1 +0,0 @@
LÓ±kŹÍ™ŁČ™iY6Ć.wüeIOˇQNIZ?ѲńE ă‘×ţ´NXËŠ˛_Ě‹úCbŇ<EFBFBD>Nâč–z<EFBFBD>Z ;h<EFBFBD>k¸2ŮuýĐ KDyQ@RR¬í† ˘0ֺҜ‹ÔČ6ŻŔZÖšd]|¸7Iě3ĹxI{zÁŻË†"ާĆGá

View File

@ -1 +0,0 @@
LÓ±kŹÍ™ŁČ™iY6Ćú 73ޤi[źa’{ţ^™b_Şřâ<EFBFBD>(je$*\˛R ŚűŇL(d} >ł°98>^ŕ훇uęŤ>n;Ę=Ť]b”1GŽ/WşĺOzďŚ;f>üę¤m<‹*ŐN꼙­5LQjŚ˝ź‘t)ŽW~Ć—}.O<EFBFBD>´ă-˘O§ýös±„;ßâDú;DuqŰ<EFBFBD>y<EFBFBD>=Ý52A]Ĺţ˙f’Š«…š,“÷Nwd)±i¨˛lÂłĚô€q+Źa÷Ë:lÉ

View File

@ -1,6 +0,0 @@
{
"minSdkVersion": "19",
"dependencies": [
"com.google.code.gson:gson:2.8.0"
]
}

View File

@ -1,45 +0,0 @@
{
"module": {
"name": "uni_modules__android_ble",
"type": "har",
"deviceTypes": [
"default",
"tablet",
"2in1"
],
"requestPermissions": [
{
"name": "ohos.permission.LOCATION",
"reason": "$string:Harmony_keep_permission_reason",
"usedScene": {
"when": "always"
}
},
{
"name": "ohos.permission.ACCESS_BLUETOOTH",
"reason": "$string:Harmony_ble_permission_reason",
"usedScene": {
"when": "always"
}
},
{
"name": "ohos.permission.DISCOVER_BLUETOOTH",
"reason": "$string:Harmony_ble_permission_reason",
"usedScene": {
"when": "always"
}
},
{
"name": "ohos.permission.USE_BLUETOOTH",
"reason": "$string:Harmony_ble_permission_reason",
"usedScene": {
"when": "always"
}
}
]
}
}

View File

@ -1,24 +0,0 @@
{
"string": [
{
"name": "Harmony_location_permission_reason",
"value": "获取位置信息需要访问定位权限"
},
{
"name": "Harmony_net_permission_reason",
"value": "获取位置信息需要访问网络权限"
},{
"name": "Harmony_keep_permission_reason",
"value": "需要后台运行以获取权限"
},
{
"name": "Harmony_notification_permission_reason",
"value": "用于发送通知"
},
{
"name": "Harmony_ble_permission_reason",
"value": "需要蓝牙权限"
}
]
}

View File

@ -1,3 +0,0 @@
{
"deploymentTarget": "13"
}

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>需要蓝牙权限以搜索和连接设备</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>需要蓝牙权限进行数据传输</string>
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
<string>bluetooth-peripheral</string>
</array>
</dict>
</plist>

View File

@ -1,110 +0,0 @@
export type WNuuid = {
serviceId: string,
wuuid: string,
nuuid: string,
}
export type ScanPara = {
btNameFilter?: string,
fliterNames?: string[],
scantime?: number,
showEmptyName?: number,
iosFilterUUIDs?: string[],
onScanResult?: (b: MyApiResult) => void,
scanComplate?: () => void,
}
export type WriteData = {
serviceId: string,
characteristicId: string,
writeType?: number,
buffer?: ArrayBuffer,
data?: number[],
hexStrData?: string,
fenbao: boolean,
}
export type ScanRecord = {
deviceName?: string | null,
txPowerLevel?: number | null,
bytes?: string | null,
serviceData?: string | null,
serviceUuids?: string | null,
advertiseFlags?: number | null,
manufacturerSpecificData?: string | null,
serviceSolicitationUuids?: string | null,
}
export type Device = {
name?: string | null,
iosLocalName?: string | null,
alias?: string | null,
bondState?: number | null,
type?: number | null,
address: string,
}
export type BleScanResult = {
connectType?: number | null,
rssi?: number | null,
isLegacy?: boolean | null,
advertisingSid?: number | null,
dataStatus?: number | null,
isConnectable?: boolean | null,
periodicAdvertisingInterval?: number | null,
primaryPhy?: number | null,
secondaryPhy?: number | null,
timestampNanos?: number | null,
scanRecord?: ScanRecord | null,
device: Device,
}
export type MyApiResult = {
type: number,
message: string,
data: any,
}
export type ScanRssiBR = {
mac: string,
rssi: number,
data: string,
iosLocalName?: string | null,
name?: string | null,
manufacturerData?: string,
}
export type BleServices = {
type: number,
uuid: string,
characteristics: Characteristics[],
}
export type Characteristics = {
properties: CharacteristicsProperties,
uuid: string,
propertiesType: number,
}
export type CharacteristicsProperties = {
READ: boolean,
WRITE: boolean,
NOTIFY: boolean,
INDICATE: boolean,
}
// 读数据与通知数据都统一采用这一结构回传。
export type NotityData = {
data: number[],
mac?: string,
serviceId?: string,
characteristicsId?: string,
}
export function bleResult(type1: number, message1: string, data1: any): MyApiResult {
return {
type: type1,
message: message1,
data: data1,
} as MyApiResult
}

View File

@ -1,27 +0,0 @@
## 2.0.0(2026-08-18)
- refactor: 清理 `color.uts` 中注释掉的历史代码(instanceof 检查、Map 实现版本、for/while 循环)
- refactor: `reversedNames` 从实例属性改为模块级常量,避免每个 TinyColor 实例都重建颜色名称反向映射表
- refactor: `setAlpha` 合并重载签名为单一 `string | number` 参数
- refactor: 精简 `format-input.uts` 的 `inputToRGB` 中 `any` 类型为具体联合类型
- fix: 修复 `toString` 的参数类型为 `LimeColorFormats | null`,`override toString()` 正确委托带参版本
- fix: 修复 `conversion.uts` 中 `numberInputToObject` 位运算在大于 `0x7FFFFFFF` 的数值上溢出的问题,改用除法运算
- fix: 删除 `rgbToHsl`/`rgbToHsv` switch 分支中的调试 `console.log`
- chore: 删除空接口 `LimeColor`,从 `LimeColorInput` 联合类型中移除
- chore: 删除未使用的 `utssdk/unierror.uts`
- chore: 删除冗余的 utssdk 平台目录(`app-android-o`、`app-js-o`、`index-o.uts`)
- chore: 删除测试桩文件 `common/test.uts` 和 `components/lime-color/lime-color.vue`
- chore: 修复 readme 赞助图片 CDN 地址(`testingcf.jsdelivr.net` → `cdn.jsdelivr.net`)
## 0.0.7(2025-08-31)
- chore: 更新文档
## 0.0.6(2025-04-21)
- feat: 兼容uniappx 鸿蒙next
## 0.0.5(2024-09-30)
- fix: 修复 vue2类型问题
## 0.0.4(2024-06-25)
- chore: 更改为非`utssdk`结构
## 0.0.3(2024-06-19)
- feat: 支持`uniapp`
## 0.0.2(2024-03-30)
- fix: 修复 因equals导致web可能报错
## 0.0.1(2024-03-12)
- init

View File

@ -1,646 +0,0 @@
// @ts-nocheck
/**
* TinyColor 颜色处理类
* 提供丰富的颜色操作方法,支持多种颜色格式的转换和操作
* @example
* // 创建颜色实例
* const color = new TinyColor('#ff0000');
* // 转换为 HSL 格式
* const hsl = color.toHsl();
* // 调整亮度
* const lighterColor = color.lighten(20);
*/
import { numberInputToObject, rgbaToHex, rgbToHex, rgbToHsl, rgbToHsv } from './conversion.uts';
import { names } from './css-color-names.uts';
import { inputToRGB } from './format-input.uts';
import type { HSL, HSLA, HSV, HSVA, HSBA, RGB, RGBA, RGBAString, LimeColorInfo, LimeColorFormats, LimeColorOptions, LimeColorInput } from '../utssdk/interface.uts';
import { bound01, boundAlpha, clamp01, toBoolean, isNumber } from './util.uts';
// #ifdef APP
// 颜色名称反向映射(hex -> name),模块级常量,避免每个实例重建
const reversedNamesMap: Map<string, string> = new Map<string, string>()
names.forEach((value: string, key: string) => {
reversedNamesMap.set(value, key)
})
// #endif
export class TinyColor {
/** 红色通道值,范围 0-255 */
r : number;
/** 绿色通道值,范围 0-255 */
g : number;
/** 蓝色通道值,范围 0-255 */
b : number;
/** 透明度值,范围 0-1 */
a : number;
/** 用于创建 limeColor 实例的格式 */
format ?: LimeColorFormats;
/** 传递给构造函数以创建 limeColor 实例的输入 */
originalInput : LimeColorInput;
/** 颜色已被成功解析 */
isValid : boolean;
/** 渐变类型 */
gradientType ?: string;
/** 四舍五入后的透明度值,范围 0-1 */
roundA : number;
/**
* 构造函数,创建一个新的 TinyColor 实例
* @param color - 颜色输入值,可以是字符串、数字、对象等多种格式
* @param opts - 选项对象
* @param opts.format - 指定颜色格式
* @param opts.gradientType - 渐变类型
*/
constructor(color : LimeColorInput = '', opts : LimeColorOptions|null = null ) {
let _color : LimeColorInput = color
if (isNumber(color)) {
// 处理数字类型的颜色输入
_color = numberInputToObject(color as number);
}
this.originalInput = _color;
// 将输入转换为 RGB 格式
const rgb = inputToRGB(_color);
this.r = rgb.r;
this.g = rgb.g;
this.b = rgb.b;
this.a = rgb.a;
// 计算四舍五入后的透明度值
this.roundA = Math.round(100 * this.a) / 100;
// 设置格式和渐变类型
this.format = opts?.format ?? rgb.format;
this.gradientType = opts?.gradientType;
// 不要让范围在 [0,255] 中的值返回成 [0,1]。
// 这里可能会失去一些精度,但可以解决原来
// .5 被解释为总数的半数,而不是1的一半的问题
// 如果本来应该是128,那么这个已经在 inputToRgb 中处理过了
if (this.r < 1) {
this.r = Math.round(this.r);
}
if (this.g < 1) {
this.g = Math.round(this.g);
}
if (this.b < 1) {
this.b = Math.round(this.b);
}
// 设置颜色是否有效的标志
this.isValid = rgb.ok ?? false;
}
/**
* 判断当前颜色是否为暗色。
* @returns 一个布尔值,表示当前颜色是否为暗色。
*/
isDark() : boolean {
return this.getBrightness() < 128;
}
/**
* 判断当前颜色是否为亮色。
* @returns 一个布尔值,表示当前颜色是否为亮色。
*/
isLight() : boolean {
return !this.isDark();
}
/**
* 计算当前颜色的亮度值。
* 亮度值是根据 RGB 颜色空间中的红、绿、蓝三个通道的值计算得出的,计算公式为:(r * 299 + g * 587 + b * 114) / 1000。
* @returns 返回颜色的感知亮度,范围从0-255。
*/
getBrightness() : number {
// http://www.w3.org/TR/AERT#color-contrast
const rgb = this.toRgb();
return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
}
/**
* 计算当前颜色的相对亮度值。
* 相对亮度值是根据 RGB 颜色空间中的红、绿、蓝三个通道的值计算得出的,计算公式为:0.2126 * R + 0.7152 * G + 0.0722 * B。
* @returns 返回颜色的感知亮度,范围从0-1。
*/
getLuminance() : number {
// http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
const rgb = this.toRgb();
let R : number;
let G : number;
let B : number;
const RsRGB : number = rgb.r / 255;
const GsRGB : number = rgb.g / 255;
const BsRGB : number = rgb.b / 255;
if (RsRGB <= 0.03928) {
R = RsRGB / 12.92;
} else {
R = Math.pow((RsRGB + 0.055) / 1.055, 2.4);
}
if (GsRGB <= 0.03928) {
G = GsRGB / 12.92;
} else {
G = Math.pow((GsRGB + 0.055) / 1.055, 2.4);
}
if (BsRGB <= 0.03928) {
B = BsRGB / 12.92;
} else {
B = Math.pow((BsRGB + 0.055) / 1.055, 2.4);
}
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
}
/**
* 获取当前颜色的透明度值。
* 透明度值的范围是 0 到 1,其中 0 表示完全透明,1 表示完全不透明。
* @returns 一个数字,表示当前颜色的透明度值。
*/
getAlpha() : number {
return this.a;
}
/**
* 设置当前颜色的透明度值。
* @param alpha - 要设置的透明度值。透明度值的范围是 0 到 1,其中 0 表示完全透明,1 表示完全不透明。
* @returns 一个 `TinyColor` 对象,表示设置透明度后的颜色。
*/
setAlpha(alpha ?: string | number) : TinyColor {
this.a = boundAlpha(alpha);
this.roundA = Math.round(100 * this.a) / 100;
return this;
}
/**
* 判断当前颜色是否为单色。
* 单色是指颜色的饱和度(S)为 0 的颜色,这些颜色只有明度(L)变化,没有颜色变化。
* @returns 一个布尔值,表示当前颜色是否为单色。
*/
isMonochrome() : boolean {
const { s } = this.toHsl();
return s == 0;
}
/**
* 将当前颜色转换为 HSV(色相、饱和度、亮度)颜色空间。
* @returns 一个对象,包含四个属性:`h`(色相)、`s`(饱和度)、`v`(亮度)和 `a`(透明度)。
*/
toHsv() : HSVA {
const hsv = rgbToHsv(this.r, this.g, this.b);
return { h: Math.round(hsv.h * 360), s: hsv.s, v: hsv.v, a: this.a } as HSVA;
}
/**
* 将当前颜色转换为 HSV(色相、饱和度、亮度)颜色空间的字符串表示。
* @returns 一个字符串,表示当前颜色的 HSV 或 HSVA 格式 hsva(xxx, xxx, xxx, xx)。
*/
toHsvString() : string {
const hsv = rgbToHsv(this.r, this.g, this.b);
const h = Math.round(hsv.h * 360);
const s = Math.round(hsv.s * 100);
const v = Math.round(hsv.v * 100);
return this.a == 1 ? `hsv(${h}, ${s}%, ${v}%)` : `hsva(${h}, ${s}%, ${v}%, ${this.roundA})`;
}
/**
* 将当前颜色对象转换为HSBA颜色空间,即Hue(色相)、Saturation(饱和度)、Brightness(亮度)和Alpha(透明度
* @returns {HSBA} 返回一个HSBA对象,表示当前颜色对象在HSBA颜色空间中的值
*/
toHsb() : HSBA {
const hsv = rgbToHsv(this.r, this.g, this.b);
return { h: Math.round(hsv.h * 360), s: hsv.s, b: hsv.v, a: this.a } as HSBA;
}
/**
* 将当前颜色对象转换为CSS风格的HSB或HSVA字符串
* @returns {string} 返回一个CSS风格的HSB或HSVA字符串,表示当前颜色对象的颜色值
*/
toHsbString() : string {
const hsb = this.toHsb();
const h = Math.round(hsb.h);
const s = Math.round(hsb.s * 100);
const b = Math.round(hsb.b * 100);
return this.a == 1
? `hsb(${h}, ${s}%, ${b}%)`
: `hsba(${h}, ${s}%, ${b}%, ${this.roundA})`;
}
/**
* 将当前颜色转换为 HSL(色相、饱和度、明度)颜色空间。
* @returns 一个对象,包含四个属性:`h`(色相)、`s`(饱和度)、`l`(明度)和 `a`(透明度)。
*/
toHsl() : HSLA {
const hsl = rgbToHsl(this.r, this.g, this.b);
return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this.a } as HSLA;
}
/**
* 将当前颜色转换为 HSL(色相、饱和度、明度)颜色空间的字符串表示。
* @returns 一个字符串,表示当前颜色的 HSL 或 HSLA 格式 hsla(xxx, xxx, xxx, xx)。
*/
toHslString() : string {
const hsl = rgbToHsl(this.r, this.g, this.b);
const h = Math.round(hsl.h * 360);
const s = Math.round(hsl.s * 100);
const l = Math.round(hsl.l * 100);
return this.a == 1 ? `hsl(${h}, ${s}%, ${l}%)` : `hsla(${h}, ${s}%, ${l}%, ${this.roundA})`;
}
/**
* 将当前颜色转换为十六进制颜色表示。
* @param allow3Char 是否允许返回简写的十六进制颜色表示(如果可能)。默认值为 `false`。
* @returns 一个字符串,表示当前颜色的十六进制格式。
*/
toHex(allow3Char = false) : string {
return rgbToHex(this.r, this.g, this.b, allow3Char);
}
/**
* 将当前颜色转换为带有井号(`#`)前缀的十六进制颜色表示。
* @param allow3Char 是否允许返回简写的十六进制颜色表示(如果可能)。默认值为 `false`。
* @returns 一个字符串,表示当前颜色的带有井号前缀的十六进制格式。
*/
toHexString(allow3Char = false) : string {
return '#' + this.toHex(allow3Char);
}
/**
* 返回颜色的八位十六进制值.
* @param allow4Char 如果可能的话,将十六进制值缩短为4个字符
*/
toHex8(allow4Char = false) : string {
return rgbaToHex(this.r, this.g, this.b, this.a, allow4Char);
}
/**
* 返回颜色的八位十六进制值,并且值前面带有#符号.
* @param allow4Char 如果可能的话,将十六进制值缩短为4个字符
*/
toHex8String(allow4Char = false) : string {
return '#' + this.toHex8(allow4Char);
}
/**
* 根据颜色的透明度(Alpha值)返回较短的十六进制值,并且值前面带有#符号。
* @param allowShortChar 如果可能的话,将十六进制值缩短至3个或4个字符
*/
toHexShortString(allowShortChar = false) : string {
return this.a == 1 ? this.toHexString(allowShortChar) : this.toHex8String(allowShortChar);
}
/**
* 将当前颜色转换为 RGB(红、绿、蓝)颜色空间的对象表示。
* @returns 一个包含 `r`、`g`、`b` 和 `a` 属性的对象,表示当前颜色的 RGB 格式。
*/
toRgb() : RGBA {
return {
r: Math.round(this.r),
g: Math.round(this.g),
b: Math.round(this.b),
a: this.a,
} as RGBA;
}
/**
* 将当前颜色对象转换为CSS风格的RGB或RGBA字符串
* @returns {string} 返回一个CSS风格的RGB或RGBA字符串,表示当前颜色对象的颜色值
*/
toRgbString() : string {
const r = Math.round(this.r);
const g = Math.round(this.g);
const b = Math.round(this.b);
return this.a == 1 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${this.roundA})`;
}
/**
* 将当前颜色转换为百分比表示的 RGB(红、绿、蓝)颜色空间的对象表示。
* @returns 一个包含 `r`、`g`、`b` 和 `a` 属性的对象,表示当前颜色的百分比表示的 RGB 格式。
*/
toPercentageRgb() : RGBAString {
// 定义一个格式化函数,将颜色值转换为百分比表示
const fmt = (x : number) : string => `${Math.round(bound01(x, 255) * 100)}%`;
// 返回一个RGBA对象,其中颜色值已转换为百分比表示
return {
r: fmt(this.r),
g: fmt(this.g),
b: fmt(this.b),
a: this.a,
} as RGBAString;
}
/**
* 将RGBA相对值插值为一个字符串,颜色值以百分比表示。
*/
toPercentageRgbString() : string {
// 定义一个四舍五入函数,将颜色值转换为百分比表示的整数
const rnd = (x : number) : number => Math.round(bound01(x, 255) * 100);
// 根据alpha值返回不同的字符串表示
return this.a == 1
? `rgb(${rnd(this.r)}%, ${rnd(this.g)}%, ${rnd(this.b)}%)`
: `rgba(${rnd(this.r)}%, ${rnd(this.g)}%, ${rnd(this.b)}%, ${this.roundA})`;
}
/**
* 返回这个颜色的'真实'名称,不存在返回null
*/
toName() : string | null {
if (this.a == 0) {
return 'transparent';
}
if (this.a < 1) {
return null;
}
const hex = this.toHexString()
// #ifndef UNI-APP-X && APP
const _names = Array.from(names.entries())
for (let [key, value] of _names) {
if (hex == value) {
return key;
}
}
return null;
// #endif
// #ifdef UNI-APP-X && APP
return reversedNamesMap.get(hex)
// #endif
}
/**
* 将颜色转换为字符串表示。
*
* @param format - 用于显示字符串表示的格式。
*/
// #ifdef APP-ANDROID
override toString() : string {
return this.toString(null)
}
// #endif
toString(format : LimeColorFormats | null = null) : string {
const formatSet = toBoolean(format);
let _format = format ?? this.format;
let formattedString : string | null = null;
const hasAlpha = this.a < 1 && this.a >= 0;
const needsAlphaFormat = !formatSet && hasAlpha && (_format != null && _format.startsWith('hex') || _format == 'name');
if (needsAlphaFormat) {
// 特殊情况:透明度,所有其他非透明度格式都会在有透明度时返回rgba。
// 当透明度为0时,返回"transparent"。
if (_format == 'name' && this.a == 0) {
return this.toName() ?? 'transparent';
}
return this.toRgbString();
}
if (_format == 'rgb') {
formattedString = this.toRgbString();
}
if (_format == 'prgb') {
formattedString = this.toPercentageRgbString();
}
if (_format == 'hex' || _format == 'hex6') {
formattedString = this.toHexString();
}
if (_format == 'hex3') {
formattedString = this.toHexString(true);
}
if (_format == 'hex4') {
formattedString = this.toHex8String(true);
}
if (_format == 'hex8') {
formattedString = this.toHex8String();
}
if (_format == 'name') {
formattedString = this.toName();
}
if (_format == 'hsl') {
formattedString = this.toHslString();
}
if (_format == 'hsv') {
formattedString = this.toHsvString();
}
if (_format == 'hsb') {
formattedString = this.toHsbString();
}
return formattedString ?? this.toHexString();
}
toNumber() : number {
return (Math.round(this.r) << 16) + (Math.round(this.g) << 8) + Math.round(this.b);
}
clone() : TinyColor {
return new TinyColor(this.toString());
}
/**
* 将颜色变浅指定的量。提供100将始终返回白色。
* @param amount - 有效值介于1-100之间
*/
lighten(amount = 10) : TinyColor {
const hsl = this.toHsl();
hsl.l += amount / 100;
hsl.l = clamp01(hsl.l);
return new TinyColor(hsl, { format: this.format } as LimeColorOptions);
}
/**
* 将颜色变亮一定的量,范围从0到100。
* @param amount - 有效值在1-100之间
*/
brighten(amount = 10) : TinyColor {
const rgb = this.toRgb();
rgb.r = Math.max(0, Math.min(255, rgb.r - Math.round(255 * -(amount / 100))));
rgb.g = Math.max(0, Math.min(255, rgb.g - Math.round(255 * -(amount / 100))));
rgb.b = Math.max(0, Math.min(255, rgb.b - Math.round(255 * -(amount / 100))));
return new TinyColor(rgb, { format: this.format } as LimeColorOptions);
}
/**
* 将颜色变暗一定的量,范围从0到100。
* 提供100将始终返回黑色。
* @param amount - 有效值在1-100之间
*/
darken(amount = 10) : TinyColor {
const hsl = this.toHsl();
hsl.l -= amount / 100;
hsl.l = clamp01(hsl.l);
return new TinyColor(hsl, { format: this.format } as LimeColorOptions);
}
/**
* 将颜色与纯白色混合,范围从0到100。
* 提供0将什么都不做,提供100将始终返回白色。
* @param amount - 有效值在1-100之间
*/
tint(amount = 10) : TinyColor {
return this.mix('white', amount);
}
/**
* 将颜色与纯黑色混合,范围从0到100。
* 提供0将什么都不做,提供100将始终返回黑色。
* @param amount - 有效值在1-100之间
*/
shade(amount = 10) : TinyColor {
return this.mix('black', amount);
}
/**
* 将颜色的饱和度降低一定的量,范围从0到100。
* 提供100与调用greyscale相同
* @param amount - 有效值在1-100之间
*/
desaturate(amount = 10) : TinyColor {
const hsl = this.toHsl();
hsl.s -= amount / 100;
hsl.s = clamp01(hsl.s);
return new TinyColor(hsl, { format: this.format } as LimeColorOptions);
}
/**
* 将颜色饱和度提高一定数量,范围从 0 到 100。
* @param amount - 有效值介于 1 到 100 之间。
*/
saturate(amount = 10) : TinyColor {
const hsl = this.toHsl();
hsl.s += amount / 100;
hsl.s = clamp01(hsl.s);
return new TinyColor(hsl, { format: this.format } as LimeColorOptions);
}
/**
* 将颜色完全去饱和为灰度。
* 等同于调用 `desaturate(100)`。
*/
greyscale() : TinyColor {
return this.desaturate(100);
}
/**
* spin 方法接收一个正数或负数作为参数,表示色相的变化量,变化范围在 [-360, 360] 之间。
* 如果提供的值超出此范围,它将被限制在此范围内。
*/
spin(amount : number) : TinyColor {
const hsl = this.toHsl();
const hue = (hsl.h + amount) % 360;
hsl.h = hue < 0 ? 360 + hue : hue;
return new TinyColor(hsl, { format: this.format } as LimeColorOptions);
}
/**
* 将当前颜色与另一种颜色按给定的比例混合,范围从0到100。
* 0表示不混合(返回当前颜色)
*/
mix(color : LimeColorInput, amount = 50) : TinyColor {
const rgb1 = this.toRgb();
const rgb2 = new TinyColor(color).toRgb();
const p = amount / 100;
const rgba = {
r: (rgb2.r - rgb1.r) * p + rgb1.r,
g: (rgb2.g - rgb1.g) * p + rgb1.g,
b: (rgb2.b - rgb1.b) * p + rgb1.b,
a: (rgb2.a - rgb1.a) * p + rgb1.a,
};
return new TinyColor(rgba, { format: this.format } as LimeColorOptions);
}
/**
* 生成一组与当前颜色相似的颜色。
* 这些颜色在色相环上是相邻的,形成一个类似于彩虹的颜色序列。
* @param results - 要生成的相似颜色的数量,默认值为 6。
* @param slices - 将色相环划分为多少个部分,默认值为 30。
* @returns 一个包含当前颜色及其相似颜色的 TinyColor 对象数组。
*/
analogous(results = 6, slices = 30) : TinyColor[] {
const hsl = this.toHsl();
const part = 360 / slices;
const ret : TinyColor[] = [this];
let _results = results
hsl.h = (hsl.h - ((part * _results) >> 1) + 720) % 360;
while (_results > 0) {
hsl.h = (hsl.h + part) % 360;
ret.push(new TinyColor(hsl));
_results--;
}
return ret;
}
/**
* 计算当前颜色的补色。
* 补色是指在色相环上相对位置的颜色,它们的色相差为 180°。
* taken from https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js
* @returns 一个 TinyColor 对象,表示当前颜色的补色。
*/
complement() : TinyColor {
const hsl = this.toHsl();
hsl.h = (hsl.h + 180) % 360;
return new TinyColor(hsl, { format: this.format } as LimeColorOptions);
}
/**
* 生成一组与当前颜色具有相同色相和饱和度的颜色。
* 这些颜色的亮度值不同,形成一个单色调的颜色序列。
* @param results - 要生成的单色调颜色的数量,默认值为 6。
* @returns 一个包含当前颜色及其单色调颜色的 TinyColor 对象数组。
*/
monochromatic(results = 6) : TinyColor[] {
const hsv = this.toHsv();
const { h } = hsv;
const { s } = hsv;
let { v } = hsv;
const res : TinyColor[] = [];
const modification = 1 / results;
let _results = results
while (_results > 0) {
res.push(new TinyColor({ h, s, v }));
v = (v + modification) % 1;
_results--
}
return res;
}
/**
* 生成当前颜色的分裂补色。
* 分裂补色是指在色相环上位于当前颜色的两侧的颜色,它们的色相差为 180°。
* @returns 一个包含当前颜色及其分裂补色的 TinyColor 对象数组。
*/
splitcomplement() : TinyColor[] {
const hsl = this.toHsl();
const { h } = hsl;
return [
this,
new TinyColor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l }),
new TinyColor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l }),
] as TinyColor[];
}
/**
* 计算当前颜色在给定背景颜色上的显示效果。
* @param background - 背景颜色,可以是任何 LimeColorInput 类型的值。
* @returns 一个 TinyColor 对象,表示当前颜色在给定背景颜色上的显示效果。
*/
onBackground(background : LimeColorInput) : TinyColor {
const fg = this.toRgb();
const bg = new TinyColor(background).toRgb();
const alpha = fg.a + bg.a * (1 - fg.a);
return new TinyColor({
r: (fg.r * fg.a + bg.r * bg.a * (1 - fg.a)) / alpha,
g: (fg.g * fg.a + bg.g * bg.a * (1 - fg.a)) / alpha,
b: (fg.b * fg.a + bg.b * bg.a * (1 - fg.a)) / alpha,
a: alpha,
});
}
/**
* 生成当前颜色的三色调。
* 三色调是指在色相环上位于当前颜色的两侧的颜色,它们的色相差为 120°。
* 这是 `polyad(3)` 方法的别名。
* @returns 一个包含当前颜色及其三色调颜色的 TinyColor 对象数组。
*/
triad() : TinyColor[] {
return this.polyad(3);
}
/**
* 生成当前颜色的四色调。
* 四色调是指在色相环上位于当前颜色的两侧的颜色,它们的色相差为 90°。
* 这是 `polyad(4)` 方法的别名。
* @returns 一个包含当前颜色及其四色调颜色的 TinyColor 对象数组。
*/
tetrad() : TinyColor[] {
return this.polyad(4);
}
/**
* 生成当前颜色的 n 色调。
* n 色调是指在色相环上位于当前颜色的两侧的颜色,它们的色相差为 360° / n。
* Get polyad colors, like (for 1, 2, 3, 4, 5, 6, 7, 8, etc...)
* monad, dyad, triad, tetrad, pentad, hexad, heptad, octad, etc...
* @param n - 一个整数,表示要生成的色调数量。
* @returns 一个包含当前颜色及其 n 色调颜色的 TinyColor 对象数组。
*/
polyad(n : number) : TinyColor[] {
const hsl = this.toHsl();
const { h } = hsl;
const result : TinyColor[] = [this];
const increment = 360 / n;
for (let i = 1; i < n; i++) {
result.push(new TinyColor({ h: (h + i * increment) % 360, s: hsl.s, l: hsl.l }));
}
return result;
}
/**
* 比较当前颜色与给定颜色是否相等。
* @param color - 一个 LimeColorInput 类型的值,表示要比较的颜色。
* @returns 一个布尔值,表示当前颜色与给定颜色是否相等。
*/
// #ifndef APP-ANDROID
equals(other ?: LimeColorInput) : boolean {
if (other == null) {
return false
} else if (other instanceof TinyColor) {
return this.toRgbString() == (other as TinyColor).toRgbString()
}
return this.toRgbString() == new TinyColor(other).toRgbString();
}
// #endif
// #ifdef APP-ANDROID
override equals(other ?: LimeColorInput) : boolean {
if (other == null) {
return false
} else if (other instanceof TinyColor) {
return this.toRgbString() == (other as TinyColor).toRgbString()
}
return this.toRgbString() == new TinyColor(other).toRgbString();
}
// #endif
}
export function tinyColor(color : LimeColorInput = '', opts : LimeColorOptions|null=null) : TinyColor {
return new TinyColor(color, opts);
}

View File

@ -1,307 +0,0 @@
/**
* 颜色转换工具类
* 提供各种颜色空间之间的转换函数,如 RGB、HSL、HSV 等
*/
// @ts-nocheck
import type { RGB,HSL,HSV } from '../utssdk/interface.uts';
import { bound01, pad2 } from './util.uts';
/**
* 处理 RGB 颜色值的边界检查,确保值在有效范围内
* 处理边界/百分比检查以符合 CSS 颜色规范
* <http://www.w3.org/TR/css3-color/>
* *Assumes:* r, g, b 在 [0, 255] 或 [0, 1] 范围内
* *Returns:* { r, g, b } 在 [0, 255] 范围内
*/
export function rgbToRgb(r: string|number, g: string|number, b: string|number) : RGB {
const _r = bound01(r, 255);
const _g = bound01(g, 255);
const _b = bound01(b, 255);
return {
r: _r * 255,
g: _g * 255,
b: _b * 255,
} as RGB;
}
/**
* Converts an RGB color value to HSL.
* 将 RGB 颜色值转换为 HSL。
* *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
* *Returns:* { h, s, l } in [0,1]
*/
export function rgbToHsl(r : string|number, g : string|number, b : string|number) : HSL {
const _r = bound01(r, 255);
const _g = bound01(g, 255);
const _b = bound01(b, 255);
const max = Math.max(_r, _g, _b);
const min = Math.min(_r, _g, _b);
let h = 0;
let s:number// = 0;
const l = (max + min) / 2;
if (max == min) {
s = 0;
h = 0; // achromatic
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case _r:
h = (_g - _b) / d + (_g < _b ? 6 : 0);
break;
case _g:
h = (_b - _r) / d + 2;
break;
case _b:
h = (_r - _g) / d + 4;
break;
default:
console.log('h')
break;
}
h /= 6;
}
return { h, s, l } as HSL;
}
/**
* 将色相值转换为 RGB 分量值
* 这是一个辅助函数,用于 HSL 和 HSV 到 RGB 的转换
* @param p - 中间值1
* @param q - 中间值2
* @param t - 色相分量
* @returns RGB 分量值,范围 [0, 1]
*/
export function hue2rgb(p : number, q : number, t : number) : number {
let _t = t
if (_t < 0) {
_t += 1;
}
if (_t > 1) {
_t -= 1;
}
if (_t < 1 / 6) {
return p + (q - p) * (6 * _t);
}
if (_t < 1 / 2) {
return q;
}
if (_t < 2 / 3) {
return p + (q - p) * (2 / 3 - _t) * 6;
}
return p;
}
/**
* Converts an HSL color value to RGB.
* 将 HSL 颜色值转换为 RGB。
* *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
* *Returns:* { r, g, b } in the set [0, 255]
*/
export function hslToRgb(h : string|number,s : string|number,l : string|number) : RGB {
let r : number;
let g : number;
let b : number;
const _h = bound01(h, 360);
const _s = bound01(s, 100);
const _l = bound01(l, 100);
if (_s == 0) {
// achromatic
g = _l;
b = _l;
r = _l;
} else {
const q = _l < 0.5 ? _l * (1 + _s) : _l + _s - _l * _s;
const p = 2 * _l - q;
r = hue2rgb(p, q, _h + 1 / 3);
g = hue2rgb(p, q, _h);
b = hue2rgb(p, q, _h - 1 / 3);
}
return { r: r * 255, g: g * 255, b: b * 255 } as RGB;
}
/**
* Converts an RGB color value to HSV
* 将RGB颜色值转换为HSV颜色值
* *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
* *Returns:* { h, s, v } in [0,1]
*/
export function rgbToHsv(r : number, g : number, b : number) : HSV {
const _r = bound01(r, 255);
const _g = bound01(g, 255);
const _b = bound01(b, 255);
const max = Math.max(_r, _g, _b);
const min = Math.min(_r, _g, _b);
let h = 0;
const v = max;
const d = max - min;
const s = max == 0 ? 0 : d / max;
if (max == min) {
h = 0; // achromatic
} else {
switch (max) {
case _r:
h = (_g - _b) / d + (_g < _b ? 6 : 0);
break;
case _g:
h = (_b - _r) / d + 2;
break;
case _b:
h = (_r - _g) / d + 4;
break;
default:
console.log('1')
break;
}
h /= 6;
}
return { h, s, v } as HSV;
}
/**
* Converts an HSV color value to RGB.
* 将HSV颜色值转换为RGB。
* *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
* *Returns:* { r, g, b } in the set [0, 255]
*/
export function hsvToRgb( h : string|number, s : string|number, v : string|number) : RGB {
const _h = bound01(h, 360) * 6;
const _s = bound01(s, 100);
const _v = bound01(v, 100);
const i = Math.floor(_h);
const f = _h - i;
const p = _v * (1 - _s);
const q = _v * (1 - f * _s);
const t = _v * (1 - (1 - f) * _s);
const mod = i % 6;
const r = [_v, q, p, p, t, _v][mod];
const g = [t, _v, _v, q, p, p][mod];
const b = [p, p, t, _v, _v, q][mod];
return { r: r * 255, g: g * 255, b: b * 255 } as RGB;
}
/**
* Converts an RGB color to hex
* 将RGB颜色转换为十六进制。
* Assumes r, g, and b are contained in the set [0, 255]
* Returns a 3 or 6 character hex
*/
export function rgbToHex(r : number, g : number, b : number, allow3Char : boolean = false) : string {
const hex = [
pad2(Math.round(r).toString(16)),
pad2(Math.round(g).toString(16)),
pad2(Math.round(b).toString(16)),
];
// Return a 3 character hex if possible
if (
allow3Char &&
hex[0].startsWith(hex[0].charAt(1)) &&
hex[1].startsWith(hex[1].charAt(1)) &&
hex[2].startsWith(hex[2].charAt(1))
) {
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
}
return hex.join('');
}
/**
* Converts an RGBA color plus alpha transparency to hex
* 将带有透明度的RGBA颜色转换为十六进制。
* Assumes r, g, b are contained in the set [0, 255] and
* a in [0, 1]. Returns a 4 or 8 character rgba hex
*/
export function rgbaToHex(r : number, g : number, b : number, a : number, allow4Char : boolean = false) : string {
const hex = [
pad2(Math.round(r).toString(16)),
pad2(Math.round(g).toString(16)),
pad2(Math.round(b).toString(16)),
pad2(convertDecimalToHex(a)),
];
// Return a 4 character hex if possible
if (
allow4Char &&
hex[0].startsWith(hex[0].charAt(1)) &&
hex[1].startsWith(hex[1].charAt(1)) &&
hex[2].startsWith(hex[2].charAt(1)) &&
hex[3].startsWith(hex[3].charAt(1))
) {
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
}
return hex.join('');
}
/**
* Converts an RGBA color to an ARGB Hex8 string
* 将RGBA颜色转换为ARGB十六进制字符串。
* Rarely used, but required for "toFilter()"
*/
export function rgbaToArgbHex(r : number, g : number, b : number, a : number) : string {
const hex = [
pad2(convertDecimalToHex(a)),
pad2(Math.round(r).toString(16)),
pad2(Math.round(g).toString(16)),
pad2(Math.round(b).toString(16)),
];
return hex.join('');
}
/** Converts a decimal to a hex value */
/** 将十进制转换为十六进制值。 */
export function convertDecimalToHex(d : string|number) : string {
return Math.round(parseFloat(`${d}`) * 255).toString(16);
}
/**
* 将十六进制值转换为十进制值
* @param h - 十六进制字符串
* @returns 十进制值,范围 [0, 1]
*/
export function convertHexToDecimal(h : string) : number {
return parseIntFromHex(h) / 255;
}
/**
* 将十六进制字符串解析为十进制整数
* @param val - 十六进制字符串
* @returns 十进制整数
*/
export function parseIntFromHex(val : string) : number {
return parseInt(val, 16);
}
/**
* 将数字类型的颜色输入转换为 RGB 对象
* @param color - 数字类型的颜色值
* @returns RGB 对象,包含 r, g, b 分量
*/
export function numberInputToObject(color : number) : RGB {
return {
r: color >> 16,
g: (color & 0xff00) >> 8,
b: color & 0xff,
} as RGB;
}

View File

@ -1,156 +0,0 @@
/**
* CSS 颜色名称映射
* 包含标准 CSS 颜色名称到十六进制颜色值的映射
* 来源: https://github.com/bahamas10/css-color-names/blob/master/css-color-names.json
*/
// @ts-nocheck
export const names: Map<string, string> = new Map<string, string>([
['aliceblue', '#f0f8ff'],
['antiquewhite', '#faebd7'],
['aqua', '#00ffff'],
['aquamarine', '#7fffd4'],
['azure', '#f0ffff'],
['beige', '#f5f5dc'],
['bisque', '#ffe4c4'],
['black', '#000000'],
['blanchedalmond', '#ffebcd'],
['blue', '#0000ff'],
['blueviolet', '#8a2be2'],
['brown', '#a52a2a'],
['burlywood', '#deb887'],
['cadetblue', '#5f9ea0'],
['chartreuse', '#7fff00'],
['chocolate', '#d2691e'],
['coral', '#ff7f50'],
['cornflowerblue', '#6495ed'],
['cornsilk', '#fff8dc'],
['crimson', '#dc143c'],
['cyan', '#00ffff'],
['darkblue', '#00008b'],
['darkcyan', '#008b8b'],
['darkgoldenrod', '#b8860b'],
['darkgray', '#a9a9a9'],
['darkgreen', '#006400'],
['darkgrey', '#a9a9a9'],
['darkkhaki', '#bdb76b'],
['darkmagenta', '#8b008b'],
['darkolivegreen', '#556b2f'],
['darkorange', '#ff8c00'],
['darkorchid', '#9932cc'],
['darkred', '#8b0000'],
['darksalmon', '#e9967a'],
['darkseagreen', '#8fbc8f'],
['darkslateblue', '#483d8b'],
['darkslategray', '#2f4f4f'],
['darkslategrey', '#2f4f4f'],
['darkturquoise', '#00ced1'],
['darkviolet', '#9400d3'],
['deeppink', '#ff1493'],
['deepskyblue', '#00bfff'],
['dimgray', '#696969'],
['dimgrey', '#696969'],
['dodgerblue', '#1e90ff'],
['firebrick', '#b22222'],
['floralwhite', '#fffaf0'],
['forestgreen', '#228b22'],
['fuchsia', '#ff00ff'],
['gainsboro', '#dcdcdc'],
['ghostwhite', '#f8f8ff'],
['goldenrod', '#daa520'],
['gold', '#ffd700'],
['gray', '#808080'],
['green', '#008000'],
['greenyellow', '#adff2f'],
['grey', '#808080'],
['honeydew', '#f0fff0'],
['hotpink', '#ff69b4'],
['indianred', '#cd5c5c'],
['indigo', '#4b0082'],
['ivory', '#fffff0'],
['khaki', '#f0e68c'],
['lavenderblush', '#fff0f5'],
['lavender', '#e6e6fa'],
['lawngreen', '#7cfc00'],
['lemonchiffon', '#fffacd'],
['lightblue', '#add8e6'],
['lightcoral', '#f08080'],
['lightcyan', '#e0ffff'],
['lightgoldenrodyellow', '#fafad2'],
['lightgray', '#d3d3d3'],
['lightgreen', '#90ee90'],
['lightgrey', '#d3d3d3'],
['lightpink', '#ffb6c1'],
['lightsalmon', '#ffa07a'],
['lightseagreen', '#20b2aa'],
['lightskyblue', '#87cefa'],
['lightslategray', '#778899'],
['lightslategrey', '#778899'],
['lightsteelblue', '#b0c4de'],
['lightyellow', '#ffffe0'],
['lime', '#00ff00'],
['limegreen', '#32cd32'],
['linen', '#faf0e6'],
['magenta', '#ff00ff'],
['maroon', '#800000'],
['mediumaquamarine', '#66cdaa'],
['mediumblue', '#0000cd'],
['mediumorchid', '#ba55d3'],
['mediumpurple', '#9370db'],
['mediumseagreen', '#3cb371'],
['mediumslateblue', '#7b68ee'],
['mediumspringgreen', '#00fa9a'],
['mediumturquoise', '#48d1cc'],
['mediumvioletred', '#c71585'],
['midnightblue', '#191970'],
['mintcream', '#f5fffa'],
['mistyrose', '#ffe4e1'],
['moccasin', '#ffe4b5'],
['navajowhite', '#ffdead'],
['navy', '#000080'],
['oldlace', '#fdf5e6'],
['olive', '#808000'],
['olivedrab', '#6b8e23'],
['orange', '#ffa500'],
['orangered', '#ff4500'],
['orchid', '#da70d6'],
['palegoldenrod', '#eee8aa'],
['palegreen', '#98fb98'],
['paleturquoise', '#afeeee'],
['palevioletred', '#db7093'],
['papayawhip', '#ffefd5'],
['peachpuff', '#ffdab9'],
['peru', '#cd853f'],
['pink', '#ffc0cb'],
['plum', '#dda0dd'],
['powderblue', '#b0e0e6'],
['purple', '#800080'],
['rebeccapurple', '#663399'],
['red', '#ff0000'],
['rosybrown', '#bc8f8f'],
['royalblue', '#4169e1'],
['saddlebrown', '#8b4513'],
['salmon', '#fa8072'],
['sandybrown', '#f4a460'],
['seagreen', '#2e8b57'],
['seashell', '#fff5ee'],
['sienna', '#a0522d'],
['silver', '#c0c0c0'],
['skyblue', '#87ceeb'],
['slateblue', '#6a5acd'],
['slategray', '#708090'],
['slategrey', '#708090'],
['snow', '#fffafa'],
['springgreen', '#00ff7f'],
['steelblue', '#4682b4'],
['tan', '#d2b48c'],
['teal', '#008080'],
['thistle', '#d8bfd8'],
['tomato', '#ff6347'],
['turquoise', '#40e0d0'],
['violet', '#ee82ee'],
['wheat', '#f5deb3'],
['white', '#ffffff'],
['whitesmoke', '#f5f5f5'],
['yellow', '#ffff00'],
['yellowgreen', '#9acd32'],
]);

View File

@ -1,388 +0,0 @@
// @ts-nocheck
/**
* 颜色格式输入处理工具
* 用于解析和转换各种格式的颜色输入,支持 RGB、HSL、HSV、十六进制等多种颜色格式
*/
import type { HSL, HSLA, HSV, HSVA, HSB, HSBA,RGB, RGBA, LimeColorInfo, LimeColorFormats } from '../utssdk/interface.uts';
import { convertHexToDecimal, hslToRgb, hsvToRgb, parseIntFromHex, rgbToRgb } from './conversion.uts';
import { names } from './css-color-names.uts';
import { boundAlpha, convertToPercentage, toBoolean } from './util.uts';
/**
* 颜色匹配器类型
* 定义了各种颜色格式的正则表达式匹配器
*/
type ColorMatchers = {
/** CSS 单位匹配器 */
CSS_UNIT : RegExp,
/** RGB 颜色匹配器 */
rgb : RegExp,
/** RGBA 颜色匹配器 */
rgba : RegExp,
/** HSL 颜色匹配器 */
hsl : RegExp,
/** HSLA 颜色匹配器 */
hsla : RegExp,
/** HSV 颜色匹配器 */
hsv : RegExp,
/** HSVA 颜色匹配器 */
hsva : RegExp,
/** HSB 颜色匹配器 */
hsb : RegExp,
/** HSBA 颜色匹配器 */
hsba : RegExp,
/** 3位十六进制颜色匹配器 */
hex3 : RegExp,
/** 6位十六进制颜色匹配器 */
hex6 : RegExp,
/** 4位十六进制颜色匹配器 */
hex4 : RegExp,
/** 8位十六进制颜色匹配器 */
hex8 : RegExp,
}
// #ifndef UNI-APP-X
/**
* UTS JSON 对象类型
* 用于表示通用的 JSON 对象结构
*/
// type UTSJSONObject = Record<string, any>
// #endif
// <http://www.w3.org/TR/css3-values/#integers>
const CSS_INTEGER = '[-\\+]?\\d+%?';
// <http://www.w3.org/TR/css3-values/#number-value>
const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
// Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
// 允许正负整数/数字。不要捕获要么/或者,只需捕获整个结果。
const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
// Actual matching.
// Parentheses and commas are optional, but not required.
// Whitespace can take the place of commas or opening paren
// 实际匹配。
// 圆括号和逗号是可选的,但不是必需的。
// 空格可以代替逗号或左括号
const PERMISSIVE_MATCH3 = '[\\s|\\(]+(' +
CSS_UNIT +
')[,|\\s]+(' +
CSS_UNIT +
')[,|\\s]+(' +
CSS_UNIT +
')\\s*\\)?';;
// const PERMISSIVE_MATCH3 = `[\\s|\\(]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})\\s*\\)?`;
const PERMISSIVE_MATCH4 = '[\\s|\\(]+(' +
CSS_UNIT +
')[,|\\s]+(' +
CSS_UNIT +
')[,|\\s]+(' +
CSS_UNIT +
')[,|\\s]+(' +
CSS_UNIT +
')\\s*\\)?';
// const PERMISSIVE_MATCH4 = `[\\s|\\(]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})\\s*\\)?`;
/**
* 颜色匹配器对象
* 包含各种颜色格式的正则表达式匹配器,用于解析不同格式的颜色字符串
*/
export const matchers = {
/** CSS 单位匹配器 */
CSS_UNIT: new RegExp(CSS_UNIT),
/** RGB 颜色匹配器 */
rgb: new RegExp('rgb' + PERMISSIVE_MATCH3),
/** RGBA 颜色匹配器 */
rgba: new RegExp('rgba' + PERMISSIVE_MATCH4),
/** HSL 颜色匹配器 */
hsl: new RegExp('hsl' + PERMISSIVE_MATCH3),
/** HSLA 颜色匹配器 */
hsla: new RegExp('hsla' + PERMISSIVE_MATCH4),
/** HSV 颜色匹配器 */
hsv: new RegExp('hsv' + PERMISSIVE_MATCH3),
/** HSVA 颜色匹配器 */
hsva: new RegExp('hsva' + PERMISSIVE_MATCH4),
/** HSB 颜色匹配器 */
hsb: new RegExp('hsb' + PERMISSIVE_MATCH3),
/** HSBA 颜色匹配器 */
hsba: new RegExp('hsba' + PERMISSIVE_MATCH4),
/** 3位十六进制颜色匹配器 */
hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
/** 6位十六进制颜色匹配器 */
hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
/** 4位十六进制颜色匹配器 */
hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
/** 8位十六进制颜色匹配器 */
hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
} as ColorMatchers;
/**
* 检查值是否为有效的 CSS 单位
* 检查它是否看起来像一个 CSS 单位
* (参见上面的 `matchers` 定义)
*/
export function isValidCSSUnit(color : string|number|null) : boolean {
return toBoolean(matchers.CSS_UNIT.exec(`${color}`));
}
/**
* 将各种格式的颜色输入转换为 RGB 格式
* @param color - 颜色输入,可以是字符串、RGB 对象、RGBA 对象、HSL 对象、HSLA 对象、HSV 对象、HSVA 对象、HSB 对象或 HSBA 对象
* @returns 包含 RGB 值、透明度和格式信息的对象
*/
export function inputToRGB(color : string|RGB|RGBA|HSL|HSLA|HSV|HSVA|HSB|HSBA) : LimeColorInfo {
let _color: UTSJSONObject|null = null
let rgb = { r: 0, g: 0, b: 0 } as RGB;
let a:any = 1;
let s: any | null;
let v: any | null;
let l: any | null;
let ok = false;
let format: LimeColorFormats | null = null;
if (typeof color == 'string') {
// 处理字符串类型的颜色输入
_color = stringInputToObject(color as string);
} else if(typeof color == 'object'){
// 处理对象类型的颜色输入
_color = JSON.parse<UTSJSONObject>(JSON.stringify(color)) as UTSJSONObject
}
if(_color != null){
if (isValidCSSUnit(_color['r']) && isValidCSSUnit(_color['g']) && isValidCSSUnit(_color['b'])){
rgb = rgbToRgb(_color['r']!, _color['g']!, _color['b']!);
ok = true;
format = `${_color['r']}`.endsWith('%') ? 'prgb' : 'rgb';
} else if(isValidCSSUnit(_color['h']) && isValidCSSUnit(_color['s']) && (isValidCSSUnit(_color['v']) || isValidCSSUnit(_color['b'])) ){
const isHSV = _color['v'] != null
s = convertToPercentage(_color['s']!);
v = isHSV ? convertToPercentage(_color['v']!) : convertToPercentage(_color['b']!);
rgb = hsvToRgb(_color['h']!, s, v);
ok = true;
format = isHSV ? 'hsv' : 'hsb';
} else if(isValidCSSUnit(_color['h']) && isValidCSSUnit(_color['s']) && isValidCSSUnit(_color['l'])){
s = convertToPercentage(_color['s']!);
l = convertToPercentage(_color['l']!);
rgb = hslToRgb(_color['h']!, s, l);
ok = true;
format = 'hsl';
}
if(_color['a']!=null){
a = _color['a']!;
}
}
a = boundAlpha(a);
return {
ok,
format: _color?.['format'] as (string | null) ?? format,
r: Math.min(255, Math.max(rgb.r, 0)),
g: Math.min(255, Math.max(rgb.g, 0)),
b: Math.min(255, Math.max(rgb.b, 0)),
a,
} as LimeColorInfo
}
/**
* 解析各种格式的颜色字符串,转换为对应的对象格式
* 支持多种颜色格式,包括 RGB、RGBA、HSL、HSLA、HSV、HSVA、HSB、HSBA 和十六进制格式
* @param color - 颜色字符串
* @returns 解析后的颜色对象,可能是 `{ r, g, b }`、`{ h, s, l }` 或 `{ h, s, v}` 格式,解析失败返回 null
*/
export function stringInputToObject(color : string) : UTSJSONObject | null {
let _color = color.trim().toLowerCase();
if (_color.length == 0) {
return null;
}
let named = false;
if (names.get(_color) != null) {
_color = names.get(_color)!;
named = true;
} else if (_color == 'transparent') {
return { r: 0, g: 0, b: 0, a: 0, format: 'name' } as UTSJSONObject;
// return new Map([
// ['r', 0],
// ['g', 0],
// ['b', 0],
// ['a', 0],
// ['format', 'name'],
// ])
}
// 使用正则表达式匹配字符串输入
// 在此函数中不要处理数值范围 - 不需要担心 [0,1] 或 [0,100] 或 [0,360]
// 只需返回一个对象,让转换函数处理这些值
// 这样,无论 tinycolor 是用字符串还是对象初始化,结果都是相同的
let match = matchers.rgb.exec(_color);
if (match != null) {
const r = match[1]
const g = match[2]
const b = match[3]
return { r, g, b } as UTSJSONObject;
// return new Map([
// ['r', match[1]],
// ['g', match[2]],
// ['b', match[3]],
// ])
}
match = matchers.rgba.exec(_color);
if (match != null) {
const r = match[1]
const g = match[2]
const b = match[3]
const a = match[4]
return { r, g, b, a } as UTSJSONObject;
// return new Map([
// ['r', match[1]],
// ['g', match[2]],
// ['b', match[3]],
// ['a', match[4]],
// ])
}
match = matchers.hsl.exec(_color);
if (match != null) {
const h = match[1]
const s = match[2]
const l = match[3]
return { h, s, l } as UTSJSONObject;
// return new Map([
// ['h', match[1]],
// ['s', match[2]],
// ['l', match[3]],
// ])
}
match = matchers.hsla.exec(_color);
if (match != null) {
const h = match[1]
const s = match[2]
const l = match[3]
const a = match[4]
return { h, s, l, a } as UTSJSONObject;
// return new Map([
// ['h', match[1]],
// ['s', match[2]],
// ['l', match[3]],
// ['a', match[4]],
// ])
}
match = matchers.hsv.exec(_color);
if (match != null) {
const h = match[1]
const s = match[2]
const v = match[3]
return { h, s, v } as UTSJSONObject;
// return new Map([
// ['h', match[1]],
// ['s', match[2]],
// ['v', match[3]],
// ])
}
match = matchers.hsva.exec(_color);
if (match != null) {
const h = match[1]
const s = match[2]
const v = match[3]
const a = match[4]
return { h, s, v, a } as UTSJSONObject;
// return new Map([
// ['h', match[1]],
// ['s', match[2]],
// ['v', match[3]],
// ['a', match[4]],
// ])
}
match = matchers.hex8.exec(_color);
if (match != null) {
const r = parseIntFromHex(match[1]!)
const g = parseIntFromHex(match[2]!)
const b = parseIntFromHex(match[3]!)
const a = convertHexToDecimal(match[4]!)
return {
r,
g,
b,
a,
format: named ? 'name' : 'hex8',
} as UTSJSONObject;
// return new Map([
// ['r', parseIntFromHex(match[1])],
// ['g', parseIntFromHex(match[2])],
// ['b', parseIntFromHex(match[3])],
// ['a', convertHexToDecimal(match[4])],
// ['format', named ? 'name' : 'hex8'],
// ])
}
match = matchers.hex6.exec(_color);
if (match != null) {
const r = parseIntFromHex(match[1]!)
const g = parseIntFromHex(match[2]!)
const b = parseIntFromHex(match[3]!)
// const a = parseIntFromHex(match[4])
return {
r,
g,
b,
format: named ? 'name' : 'hex',
} as UTSJSONObject;
// return new Map([
// ['r', parseIntFromHex(match[1])],
// ['g', parseIntFromHex(match[2])],
// ['b', parseIntFromHex(match[3])],
// ['format', named ? 'name' : 'hex'],
// ])
}
match = matchers.hex4.exec(_color);
if (match != null) {
const r = parseIntFromHex((match[1] + match[1]))
const g = parseIntFromHex((match[2] + match[2]))
const b = parseIntFromHex((match[3] + match[3]))
const a = convertHexToDecimal((match[4] + match[4]))
return {
r,
g,
b,
a,
format: named ? 'name' : 'hex8',
} as UTSJSONObject;
// return new Map([
// ['r', parseIntFromHex(match[1] + match[1])],
// ['g', parseIntFromHex(match[2] + match[2])],
// ['b', parseIntFromHex(match[3] + match[3])],
// ['a', convertHexToDecimal(match[4] + match[4])],
// ['format', named ? 'name' : 'hex8'],
// ])
}
match = matchers.hex3.exec(_color);
if (match != null) {
const r = parseIntFromHex((match[1] + match[1]))
const g = parseIntFromHex((match[2] + match[2]))
const b = parseIntFromHex((match[3] + match[3]))
return {
r,
g,
b,
format: named ? 'name' : 'hex',
} as UTSJSONObject;
// return new Map([
// ['r', parseIntFromHex(match[1] + match[1])],
// ['g', parseIntFromHex(match[2] + match[2])],
// ['b', parseIntFromHex(match[3] + match[3])],
// ['format', named ? 'name' : 'hex'],
// ])
}
return null
}

View File

@ -1,240 +0,0 @@
/**
* 颜色生成工具
* 基于 Ant Design 的颜色生成算法
* 用于生成一组基于给定颜色的色彩模式,包括亮色、暗色和深色主题颜色
*/
// @ts-nocheck
// https://github.com/ant-design/ant-design-colors/blob/main/src/generate.ts
import { inputToRGB } from './format-input.uts';
import { rgbToHex, rgbToHsv } from './conversion.uts';
import type { HSV, LimeColorInfo, LimeColorGenerateOptions} from '../utssdk/interface.uts';
/**
* 暗色主题颜色映射项类型
*/
type DarkColorMapItem = {
/** 颜色索引 */
index : number;
/** 透明度 */
opacity : number;
};
const hueStep = 2; // 色相阶梯
const saturationStep = 0.16; // 饱和度阶梯,浅色部分
const saturationStep2 = 0.05; // 饱和度阶梯,深色部分
const brightnessStep1 = 0.05; // 亮度阶梯,浅色部分
const brightnessStep2 = 0.15; // 亮度阶梯,深色部分
const lightColorCount = 5; // 浅色数量,主色上
const darkColorCount = 4; // 深色数量,主色下
// 暗色主题颜色映射关系表
const darkColorMap = [
{ index: 7, opacity: 0.15 },
{ index: 6, opacity: 0.25 },
{ index: 5, opacity: 0.3 },
{ index: 5, opacity: 0.45 },
{ index: 5, opacity: 0.65 },
{ index: 5, opacity: 0.85 },
{ index: 4, opacity: 0.9 },
{ index: 3, opacity: 0.95 },
{ index: 2, opacity: 0.97 },
{ index: 1, opacity: 0.98 },
] as DarkColorMapItem[];
/**
* 将 RGB 颜色信息转换为 HSV 格式
* 从 TinyColor.toHsv 移植的包装函数
* @param colorInfo - 包含 RGB 值的颜色信息对象
* @returns HSV 格式的颜色对象,其中色相值范围为 0-360
*/
function toHsv({ r, g, b } : LimeColorInfo) : HSV {
// 将 RGB 值转换为 HSV 值
const hsv = rgbToHsv(r, g, b);
// 返回一个 HSV 对象,其中 h 值乘以 360
return { h: hsv.h * 360, s: hsv.s, v: hsv.v } as HSV;
}
/**
* 将 RGB 颜色信息转换为十六进制格式
* 从 TinyColor.toHexString 移植的包装函数
* @param colorInfo - 包含 RGB 值的颜色信息对象
* @returns 带有 # 前缀的十六进制颜色字符串
*/
function toHex({ r, g, b }: LimeColorInfo) : string {
// 将 RGB 值转换为十六进制字符串,并添加前缀 `#`
return `#${rgbToHex(r, g, b, false)}`;
}
/**
* 混合两种颜色
* 从 TinyColor.mix 移植的包装函数
* @param rgb1 - 第一种颜色的 RGB 信息
* @param rgb2 - 第二种颜色的 RGB 信息
* @param amount - 混合比例,范围为 [0, 100]
* @returns 混合后的颜色信息对象
*/
function mix(rgb1 : LimeColorInfo, rgb2 : LimeColorInfo, amount : number) : LimeColorInfo {
// 将 amount 除以 100,得到一个范围为 [0, 1] 的值
const p = amount / 100;
// 计算混合后的 RGB 值
const rgb = {
r: (rgb2.r - rgb1.r) * p + rgb1.r,
g: (rgb2.g - rgb1.g) * p + rgb1.g,
b: (rgb2.b - rgb1.b) * p + rgb1.b,
a: 1
} as LimeColorInfo;
// 返回混合后的 RGB 对象
return rgb;
}
/**
* 根据给定的 HSV 对象和索引值计算新的色相值
* @param hsv - HSV 格式的颜色对象
* @param i - 索引值
* @param light - 是否生成亮色,默认为 false
* @returns 计算后的色相值,范围为 0-360
*/
function getHue(hsv : HSV, i : number, light : boolean = false) : number {
let hue : number;
// 根据色相不同,色相转向不同
if (Math.round(hsv.h) >= 60 && Math.round(hsv.h) <= 240) {
// 如果色相在 60 到 240 之间,向左转动
hue = light ? Math.round(hsv.h) - hueStep * i : Math.round(hsv.h) + hueStep * i;
} else {
hue = light ? Math.round(hsv.h) + hueStep * i : Math.round(hsv.h) - hueStep * i;
}
if (hue < 0) {
// 如果新的色相值小于 0,则加上 360
hue += 360;
} else if (hue >= 360) {
// 如果新的色相值大于等于 360,则减去 360
hue -= 360;
}
return hue;
}
/**
* 根据给定的 HSV 对象和索引值计算新的饱和度值
* @param hsv - HSV 格式的颜色对象
* @param i - 索引值
* @param light - 是否生成亮色,默认为 false
* @returns 计算后的饱和度值,范围为 0-1
*/
function getSaturation(hsv : HSV, i : number, light : boolean = false) : number {
// grey color don't change saturation
// 如果颜色是灰色(色相和饱和度都为 0),则饱和度不变
if (hsv.h == 0 && hsv.s == 0) {
return hsv.s;
}
let saturation : number;
// 如果 light 参数为 true,则饱和度减小
if (light) {
saturation = hsv.s - saturationStep * i;
}
// 如果 i 等于 darkColorCount,则饱和度增加
else if (i == darkColorCount) {
saturation = hsv.s + saturationStep;
}
// 否则,饱和度增加
else {
saturation = hsv.s + saturationStep2 * i;
}
// 边界值修正
if (saturation > 1) {
saturation = 1;
}
// 第一格的 s 限制在 0.06-0.1 之间
if (light && i == lightColorCount && saturation > 0.1) {
saturation = 0.1;
}
if (saturation < 0.06) {
saturation = 0.06;
}
return parseFloat(saturation.toFixed(2))
}
/**
* 根据给定的 HSV 对象和索引值计算新的亮度值
* @param hsv - HSV 格式的颜色对象
* @param i - 索引值
* @param light - 是否生成亮色,默认为 false
* @returns 计算后的亮度值,范围为 0-1
*/
function getValue(hsv : HSV, i : number, light : boolean = false) : number {
let value : number;
// 如果 light 参数为 true,则亮度增加
if (light) {
value = hsv.v + brightnessStep1 * i;
} else {
value = hsv.v - brightnessStep2 * i;
}
if (value > 1) {
value = 1;
}
// 返回保留两位小数的亮度值
return parseFloat(value.toFixed(2));
}
/**
* generate 函数用于生成一组基于给定颜色的色彩模式。
* 它可以生成亮色、暗色和深色主题颜色模式。
*
* @param {string} color - 输入的颜色值,可以是十六进制、RGB、RGBA、HSL、HSLA或颜色名称。
* @param {LimeColorGenerateOptions} [opts] - 可选的生成选项。
* @returns {string[]} - 返回一个包含生成的颜色模式的字符串数组。
*/
export function generate(color : string, opts : LimeColorGenerateOptions|null=null) : string[] {
const patterns : string[] = [];
const pColor = inputToRGB(color);
// 生成亮色模式
for (let i = lightColorCount; i > 0; i -= 1) {
const hsv = toHsv(pColor);
const colorString : string = toHex(
inputToRGB({
h: getHue(hsv, i, true),
s: getSaturation(hsv, i, true),
v: getValue(hsv, i, true),
}),
);
patterns.push(colorString);
}
// 添加原始颜色
patterns.push(toHex(pColor));
// 生成暗色模式
for (let i = 1; i <= darkColorCount; i += 1) {
const hsv = toHsv(pColor);
const colorString : string = toHex(
inputToRGB({
h: getHue(hsv, i),
s: getSaturation(hsv, i),
v: getValue(hsv, i),
}),
);
patterns.push(colorString);
}
// 如果选项中指定了 dark 主题,则生成深色主题颜色模式
if (opts?.theme == 'dark') {
return darkColorMap.map(({ index, opacity }, _):string => {
const darkColorString : string = toHex(
mix(
inputToRGB(opts?.backgroundColor ?? '#141414'),
inputToRGB(patterns[index]),
opacity * 100,
),
);
return darkColorString;
});
}
// 返回默认颜色模式
return patterns;
}

View File

@ -1,211 +0,0 @@
// import * as util from './util-test'
// import * as util from './conversion-test'
// import * as util from './format-input-test'
// import * as util from './color-test'
// import * as util from './generate-test'
import { tinyColor } from './color'
// console.log('generate:', generate('red'))
console.log('tinyColor~~~~~~~~~~~~~~test')
console.log('示例1',tinyColor('#000'));
console.log('示例2',tinyColor('000'));
console.log('示例3',tinyColor('#369C'));
console.log('示例4',tinyColor('369C'));
console.log('示例5',tinyColor('#f0f0f6'));
console.log('示例6',tinyColor('f0f0f6'));
console.log('示例7',tinyColor('#f0f0f688'));
console.log(tinyColor('f0f0f688'));
console.log('示例1',tinyColor('hsl(0, 100%, 50%)').toString());
console.log('示例2',tinyColor('hsla(0, 100%, 50%, .5)').toString());
console.log('示例3',tinyColor('hsl(0, 100%, 50%)').toString());
console.log('示例4',tinyColor('hsl 0 1.0 0.5').toString());
console.log('示例5',tinyColor({ h: 0, s: 1, l: 0.5 }).toString());
console.log('示例1',tinyColor('hsv(0, 100%, 100%)').toString());
console.log('示例2',tinyColor('hsva(0, 100%, 100%, .5)').toString());
console.log('示例3',tinyColor('hsv (0 100% 100%)').toString());
console.log('示例4',tinyColor('hsv 0 1 1').toString());
console.log('示例5',tinyColor({ h: 0, s: 100, v: 100 }).toString());
console.log('示例1',tinyColor('RED').toString(), tinyColor('RED'));
console.log('示例2',tinyColor('blanchedalmond').toString());
console.log('示例3',tinyColor('darkblue').toString());
console.log('示例1:',0x0,tinyColor(0x0).toString());
console.log('示例2',tinyColor(0xaabbcc).toString());
console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
const color = tinyColor('red')
const color2 = tinyColor({ r: 255, g: 255, b: 255 })
console.log('示例1:',color.originalInput);
console.log('示例2:',color2.originalInput);
const color3 = tinyColor('red')
const color4 = tinyColor({ r: 255, g: 255, b: 255 })
console.log('示例3:',color3.format);
console.log('示例4:',color4.format);
const color5 = tinyColor('red');
console.log(color5.isValid, color5.toHexString())
const color6 = tinyColor('not a color');
console.log(color6.isValid, color6.toHexString())
const color7 = tinyColor('#fff');
const color8 = tinyColor('#000')
console.log(color7.getBrightness())
console.log(color8.getBrightness())
const color9 = tinyColor('#fff');
const color10 = tinyColor('#000')
console.log(color9.isLight())
console.log(color10.isLight())
const color11 = tinyColor('#fff');
const color12 = tinyColor('#000')
console.log(color11.isDark())
console.log(color12.isDark())
const color13 = tinyColor('#fff');
const color14 = tinyColor('#000')
console.log(color13.getLuminance())
console.log(color14.getLuminance())
const color15 = tinyColor('rgba(255, 0, 0, .5)');
const color16 = tinyColor('rgb(255, 0, 0)')
const color17 = tinyColor('transparent')
console.log(color15.getAlpha())
console.log(color16.getAlpha())
console.log(color17.getAlpha())
const color18 = tinyColor('red');
console.log(color18.getAlpha()) ; // 1
color18.setAlpha(0.5);
console.log(color18.getAlpha()); // .5
console.log(color18.toRgbString()); // "rgba(255, 0, 0, .5)"
const color19 = tinyColor('rgba(255, 0, 0, .5)');
const computedColor = color19.onBackground('rgb(0, 0, 255)');
console.log('color19',computedColor.toRgbString()); // "rgb(128, 0, 128)"
const color20 = tinyColor('red');
console.log('color20',color20.toHsv()); // { h: 0, s: 1, v: 1, a: 1 }
const color21 = tinyColor('red');
console.log(color21.toHsvString()); // "hsv(0, 100%, 100%)"
color21.setAlpha(0.5);
console.log(color21.toHsvString()); // "hsva(0, 100%, 100%, 0.5)"
const color33 = tinyColor('red');
console.log(color33.toHsl()); // { h: 0, s: 1, l: 0.5, a: 1 }
const color34 = tinyColor('red');
console.log(color34.toHsvString()); // "hsl(0, 100%, 50%)"
color34.setAlpha(0.5);
console.log(color34.toHsvString()); // "hsla(0, 100%, 50%, 0.5)"
console.log(tinyColor('#aabbcc').toNumber() == 0xaabbcc)
console.log(tinyColor('rgb(1, 1, 1)').toNumber() == (1 << 16) + (1 << 8) + 1)
const color35 = tinyColor('red');
console.log(color35.toHex()); // "ff0000"
const color36 = tinyColor('red');
console.log(color36.toHexString()); // "#ff0000"
const color37 = tinyColor('red');
console.log(color37.toHex8()); // "ff0000ff"
const color38 = tinyColor('red');
console.log(color38.toHex8String()); // "#ff0000ff"
// const color39 = tinyColor('#ff000000');
// console.log(color39.toHexShortString()); // "#ff000000"
// console.log(color39.toHexShortString(true)); // "#f000"
// const color40 = tinyColor('#ff0000ff');
// console.log(color40.toHexShortString()); // "#ff0000"
// console.log(color40.toHexShortString(true)); // "#f00"
// const color41 = tinyColor('red');
// console.log(color41.toRgb()); // { r: 255, g: 0, b: 0, a: 1 }
// const color42 = tinyColor('red');
// console.log(color42.toRgbString()); // "rgb(255, 0, 0)"
// color42.setAlpha(0.5);
// console.log(color42.toRgbString()); // "rgba(255, 0, 0, 0.5)"
// const color43 = tinyColor('red')
// console.log(color43.toPercentageRgb())
// const color44 = tinyColor('red');
// console.log(color44.toPercentageRgbString()); // "rgb(100%, 0%, 0%)"
// color44.setAlpha(0.5);
// console.log(color44.toPercentageRgbString()); // "rgba(100%, 0%, 0%, 0.5)"
// const color45 = tinyColor('red');
// console.log(color45.toName()); // "red"
// const color46 = tinyColor('red');
// console.log(color46.toString()); // "red"
// console.log(color46.toString('hsv')); // "hsv(0, 100%, 100%)"
// const color47 = tinyColor('rgb(255, 0, 0)');
// console.log(color47.toString()); // "rgb(255, 0, 0)"
// color47.setAlpha(0.5);
// console.log(color47.toString()); // "rgba(255, 0, 0, 0.5)"
// const color48 = tinyColor('red')
// .lighten()
// .desaturate()
// .toHexString()
// console.log(color48)
// console.log(tinyColor('#f00').lighten().toString())
// console.log(tinyColor('#f00').lighten(100).toString())
// console.log(tinyColor('#f00').brighten().toString())
console.log('tinyColor',tinyColor({a: 1, h: 0, l: 0.4, s: 1}))
// console.log(tinyColor('#f00').darken(100).toString())
// console.log(tinyColor('#f00').tint().toString())
// console.log(tinyColor('#f00').tint(100).toString())
// console.log(tinyColor('#f00').shade().toString())
// console.log(tinyColor('#f00').shade(100).toString())
// console.log(tinyColor('#f00').desaturate().toString())
// console.log(tinyColor('#f00').desaturate(100).toString())
// console.log(tinyColor('hsl(0, 10%, 50%)').saturate().toString())
// console.log(tinyColor('#f00').greyscale().toString())
// console.log(tinyColor('#f00').spin(180).toString())
// console.log(tinyColor('#f00').spin(-90).toString())
// console.log(tinyColor('#f00').spin(90).toString())
// console.log(tinyColor('#f00').spin(0).toString())
// console.log(tinyColor('#f00').spin(360).toString())
// let color51 = tinyColor('#f0f');
// let color52 = tinyColor('#0f0');
// console.log(color51.mix(color52).toHexString())
// const colors52 = tinyColor('#f00').analogous()
// console.log(colors52.map((t):string => t.toHexString()))
// const colors53 = tinyColor('#f00').monochromatic();
// console.log(colors53.map((t):string => t.toHexString()))
// const colors54 = tinyColor('#f00').splitcomplement();
// console.log(colors54.map((t):string => t.toHexString()));
// const colors55 = tinyColor('#f00').triad();
// console.log(colors55.map((t):string => t.toHexString()));
// const colors56 = tinyColor('#f00').tetrad();
// console.log(colors56.map((t):string => t.toHexString()));
// const colors57 = tinyColor('#f00').polyad(4);
// console.log(colors57.map((t):string => t.toHexString()));
// console.log(tinyColor('#f00').complement().toHexString())
// let color158 = tinyColor('red');
// let color58 = tinyColor('#f00');
// function a():string{
// return 'rgb(255, 0, 0)'
// }
// console.log('tinyColor equals:', color158.equals(color58), color158.equals('#f00'), a() == a());

View File

@ -1,198 +0,0 @@
/**
* 工具函数库
* 提供类型判断、数值处理、颜色值转换等通用工具函数
*/
// @ts-nocheck
// #ifdef APP-ANDROID && !VUE3-VAPOR
import BigDecimal from 'java.math.BigDecimal'
// #endif
/**
* 判断值是否为数字类型
* @param value - 要判断的值
* @returns 如果值是数字类型,返回 true;否则返回 false
*/
export function isNumber(value: any|null):boolean{
// #ifdef APP-ANDROID
return ['Byte', 'UByte','Short','UShort','Int','UInt','Long','ULong','Float','Double','number'].includes(typeof value)
// #endif
// #ifdef APP-IOS
return ['Int8', 'UInt8','Int16','UInt16','Int32','UInt32','Int64','UInt64','Int','UInt','Float','Float16','Float32','Float64','Double', 'number'].includes(typeof value)
// #endif
// #ifndef APP-ANDROID || APP-IOS
return typeof value == 'number' && !isNaN(value);
// #endif
}
/**
* 判断值是否为字符串类型
* @param value - 要判断的值
* @returns 如果值是字符串类型,返回 true;否则返回 false
*/
export function isString(value: any|null):boolean{
return typeof value == 'string';
}
/**
* 判断值是否为数值类型(数字或数字字符串)
* @param value - 要判断的值
* @returns 如果值是数值类型,返回 true;否则返回 false
*/
export function isNumeric(value: any|null):boolean{
if(isNumber(value)) {
return true
} else if(isString(value)) {
// const regex = "-?\\d+(\\.\\d+)?".toRegex()
const regex = new RegExp("^(-)?\\d+(\\.\\d+)?$")
return regex.test(value as string) //regex.matches(value as string)
}
return false
}
/**
* 将值转换为布尔类型
* @param value - 要转换的值
* @returns 转换后的布尔值
*/
export function toBoolean(value: any|null):boolean{
// #ifdef APP
// 根据输入值的类型,返回相应的布尔值
if(isNumber(value)){
return (value as number) != 0;
}
if(isString(value)){
return `${value}`.length > 0;
}
if(typeof value == 'boolean'){
return value as boolean;
}
return value != null
// #endif
// #ifndef APP
return Boolean(value)
// #endif
}
/**
* 检查传入的值是否为百分比字符串
* @param n - 要检查的值
* @returns 如果值是包含 % 符号的字符串,返回 true;否则返回 false
*/
export function isPercentage(n : any) : boolean {
return isString(n) && (n as string).indexOf('%') != -1;
}
/**
* 检查值是否为 "1.0" 格式的字符串
* 需要特殊处理 1.0 为 100%,因为一旦它是数字,它与 1 没有区别
* <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
* @param n - 要检查的值
* @returns 如果值是包含小数点且值为 1 的字符串,返回 true;否则返回 false
*/
export function isOnePointZero(n : any) : boolean {
return isString(n) && (n as string).indexOf('.') != -1 && parseFloat(n as string) == 1;
}
/**
* 将输入值从 [0, n] 转换为 [0, 1] 范围
* @param n - 输入值
* @param max - 输入值的最大值
* @returns 转换后的值,范围为 [0, 1]
*/
export function bound01(n : string|number, max : number) : number {
if(!(isNumber(n) || isString(n))){
return 1
}
let _n = n;
if (isOnePointZero(_n)) {
_n = '100%';
}
const isPercent = isPercentage(_n);
let numericValue = 0;
if (isNumber(_n)) {
numericValue = _n as number;
} else if (isString(_n)) {
numericValue = parseFloat(_n as string);
}
numericValue = max == 360 ? numericValue : Math.min(max, Math.max(0, numericValue));
// Automatically convert percentage into number
// 自动将百分比转换为数字
if (isPercent) {
numericValue = parseInt(`${Math.min(numericValue, 100) * max}`, 10) / 100;
}
// Handle floating point rounding errors
// 处理浮点数舍入误差
if ( Math.abs(numericValue - max) < 0.000001) {
return 1;
}
// Convert into [0, 1] range if it isn't already
// 如果它还不是,将其转换为 [0, 1] 范围
if (max == 360) {
// If n is a hue given in degrees,
// wrap around out-of-range values into [0, 360] range
// then convert into [0, 1].
// 如果 n 是以度为单位的色调,
// 将超出范围的值环绕到 [0, 360] 范围内
// 然后将其转换为 [0, 1]。
numericValue = (numericValue < 0 ? (numericValue % max) + max : numericValue % max) / max;
} else {
// If n not a hue given in degrees
// Convert into [0, 1] range if it isn't already.
// 如果 n 不是以度为单位的色调
// 如果它还不是,将其转换为 [0, 1] 范围。
numericValue = (numericValue % max) / max;
}
return numericValue;
}
/**
* 将数字限制在 0 和 1 之间
* @param val - 要限制的数字
* @returns 限制后的值,范围为 [0, 1]
*/
export function clamp01(val : number) : number {
return Math.min(1, Math.max(0, val));
}
/**
* 返回一个有效的 alpha 值 [0,1],将所有无效值设置为 1
* @param a - 要处理的 alpha 值
* @returns 处理后的 alpha 值,范围为 [0, 1]
*/
function boundAlpha(a: number|string|null) : number {
let _n = a == null ? 1 : (isString(a) ? parseFloat(a as string) : a as number)
if (isNaN(_n) || _n < 0 || _n > 1) {
_n = 1;
}
return _n;
}
export {
boundAlpha
}
/**
* 将小数转换为百分比值
* @param n - 要转换的值
* @returns 转换后的百分比字符串或原始值
*/
export function convertToPercentage(n:number|string): number | string {
let _n = n;
// #ifdef APP-ANDROID && !VUE3-VAPOR
_n = isNumeric(_n)
? parseFloat(typeof _n == 'string'
? _n as string
: BigDecimal.valueOf((_n as number).toDouble()).toPlainString())
: _n
// #endif
// #ifndef APP-ANDROID && !VUE3-VAPOR
_n = isNumeric(_n) ? parseFloat(`${_n}`) : _n
// #endif
if(isNumber(_n) && (_n as number) <= 1){
return `${_n * 100}%`.replace('.0%','%');
}
return _n;
}
/**
* 确保十六进制值具有 2 个字符
* @param c - 十六进制字符串
* @returns 长度为 2 的十六进制字符串
*/
export function pad2(c : string) : string {
return c.length == 1 ? '0' + c : `${c}`;
}

View File

@ -1,10 +0,0 @@
<template>
<view>lime-color test</view>
</template>
<script setup>
import * as test from '@/uni_modules/lime-color/common/test'
</script>
<style>
</style>

View File

@ -1,5 +0,0 @@
// @ts-nocheck
export * from './common/color'
export * from './common/generate'
export * from './utssdk/interface'
// export {LGenerateOptions} from './utssdk/interface'

View File

@ -1,6 +0,0 @@
// @ts-nocheck
export * from './common/color'
export * from './common/generate'
// #ifndef APP-NVUE
export * from './utssdk/interface.uts'
// #endif

View File

@ -1,87 +0,0 @@
export type RGB = {
r : number;
g : number;
b : number;
}
export type RGBA = {
r : number;
g : number;
b : number;
a : number;
}
export type RGBAString = {
r : string;
g : string;
b : string;
a : number;
}
export type HSL = {
h : number;
s : number;
l : number;
}
export type HSLA = {
h : number;
s : number;
l : number;
a : number;
}
export type HSV = {
h : number;
s : number;
v : number;
}
export type HSVA = {
h : number;
s : number;
v : number;
a : number;
}
// 增加部分
export type HSB = {
h : number;
s : number;
b : number;
}
export type HSBA = {
h : number;
s : number;
b : number;
a : number;
}
export type LColorInfo = {
ok ?: boolean;
format ?: LColorFormats;
r : number;
g : number;
b : number;
a : number;
}
export type LColorFormats =
| 'rgb'
| 'prgb'
| 'hex'
| 'hex3'
| 'hex4'
| 'hex6'
| 'hex8'
| 'name'
| 'hsl'
| 'hsb'
| 'hsv';
export type LColorOptions = {
format ?: LColorFormats;
gradientType ?: string;
}
export type LColorInput = any //string | number | RGB | RGBA | HSL | HSLA | HSV | HSVA | LimeColor;
export type LGenerateOptions = {
theme ?: 'dark' | 'default';
backgroundColor ?: string;
}

View File

@ -1,108 +0,0 @@
{
"id": "lime-color",
"displayName": "lime-color tinycolor颜色转换",
"version": "2.0.0",
"description": "lime-color是tinycolor UTS版的小型库,用于颜色操作和转换,内容Ant Design 的颜色等级生成算法",
"keywords": [
"lime-color",
"TinyColor",
"color",
"颜色转换",
"uts"
],
"repository": "",
"engines": {
"HBuilderX": "^4.0",
"uni-app": "^4.72",
"uni-app-x": "^4.74"
},
"dcloudext": {
"type": "uts",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"darkmode": "x",
"i18n": "x",
"widescreen": "x"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "√",
"aliyun": "√",
"alipay": "√"
},
"client": {
"uni-app": {
"vue": {
"vue2": "√",
"vue3": "√"
},
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"vue": "√",
"nvue": "-",
"android": {
"extVersion": "",
"minVersion": "21"
},
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√",
"alipay": "√",
"toutiao": "√",
"baidu": "√",
"kuaishou": "√",
"jd": "√",
"harmony": "√",
"qq": "√",
"lark": "√",
"xhs": "-"
},
"quickapp": {
"huawei": "√",
"union": "√"
}
},
"uni-app-x": {
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"android": {
"extVersion": "",
"minVersion": "21"
},
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√"
}
}
}
}
}
}

View File

@ -1,557 +0,0 @@
# lime-color
- 颜色转换
## 文档
🚀 [color【站点1】](https://limex.qcoon.cn/uts/color.html)
🌍 [color【站点2】](https://limeui.netlify.app/uts/color.html)
🔥 [color【站点3】](https://limeui.familyzone.top/uts/color.html)
## 安装
插件市场导入插件即可
## 使用
```js
import {tinyColor} from '@/uni_modules/lime-color'
```
## 接受的字符串输入
字符串解析非常宽容。它的目的是使输入颜色尽可能简单。所有的逗号、百分比、括号都是可选的,大多数输入允许使用 0-1、0%-100% 或 0-n(其中 n 取决于值的 100、255 或 360)。
HSL 和 HSV 都需要 0%-100% 或 0-1 作为 S/L/V 属性。H(色相)可以在 0%-100% 或 0-360 之间取值。
RGB 输入需要 0-255 或 0%-100%。
以下是一些字符串输入的示例:
### Hex, 8-digit (RGBA) Hex
```ts
tinyColor('#000');
tinyColor('000');
tinyColor('#369C');
tinyColor('369C');
tinyColor('#f0f0f6');
tinyColor('f0f0f6');
tinyColor('#f0f0f688');
tinyColor('f0f0f688');
```
### RGB, RGBA
```ts
tinyColor('rgb (255, 0, 0)');
tinyColor('rgb 255 0 0');
tinyColor('rgba (255, 0, 0, .5)');
tinyColor({ r: 255, g: 0, b: 0 });
```
### HSL, HSLA
```ts
tinyColor('hsl(0, 100%, 50%)');
tinyColor('hsla(0, 100%, 50%, .5)');
tinyColor('hsl(0, 100%, 50%)');
tinyColor('hsl 0 1.0 0.5');
tinyColor({ h: 0, s: 1, l: 0.5 });
```
### HSV, HSVA
```ts
tinyColor('hsv(0, 100%, 100%)');
tinyColor('hsva(0, 100%, 100%, .5)');
tinyColor('hsv (0 100% 100%)');
tinyColor('hsv 0 1 1');
tinyColor({ h: 0, s: 100, v: 100 });
```
### Named
```ts
tinyColor('RED');
tinyColor('blanchedalmond');
tinyColor('darkblue');
```
### Number
```ts
tinyColor(0x0);
tinyColor(0xaabbcc);
```
## 属性
### originalInput
传递到构造函数中用于创建`tinyColor`实例的原始输入。
```ts
const color = tinyColor('red');
color.originalInput; // "red"
const color2 = tinyColor({ r: 255, g: 255, b: 255 });
color2.originalInput; // "{r: 255, g: 255, b: 255}"
```
### format
返回用于创建`tinyColor`实例的格式
```ts
const color = tinyColor('red');
color.format; // "name"
const color2 = tinyColor({ r: 255, g: 255, b: 255 });
color2.format; // "rgb"
```
### isValid
一个布尔值,指示颜色是否成功被解析。注意:如果颜色无效,则在与其他方法一起使用时将表现得像“黑色”。
```ts
const color1 = tinyColor('red');
color1.isValid; // true
color1.toHexString(); // "#ff0000"
const color2 = tinyColor('not a color');
color2.isValid; // false
color2.toString(); // "#000000"
```
## Methods 方法
### getBrightness
返回颜色的感知亮度,范围从 0-255,这是根据 [Web内容无障碍指南(第1版)](http://www.w3.org/TR/AERT#color-contrast) 定义的。
```ts
const color1 = tinyColor('#fff');
color1.getBrightness(); // 255
const color2 = tinyColor('#000');
color2.getBrightness(); // 0
```
### isLight
返回一个布尔值,指示颜色的感知亮度是否为浅色。
```ts
const color1 = tinyColor('#fff');
color1.isLight(); // true
const color2 = tinyColor('#000');
color2.isLight(); // false
```
### isDark
返回一个布尔值,指示颜色的感知亮度是否为深色。
```ts
const color1 = tinyColor('#fff');
color1.isDark(); // false
const color2 = tinyColor('#000');
color2.isDark(); // true
```
### getLuminance
返回颜色的感知亮度(luminance),范围从 0-1,这是根据 [Web内容无障碍指南(第2版)](http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef) 定义的。
```ts
const color1 = tinyColor('#fff');
color1.getLuminance(); // 1
const color2 = tinyColor('#000');
color2.getLuminance(); // 0
```
### getAlpha
返回颜色的`alpha`(透明度)值,范围从 `0-1`。
```ts
const color1 = tinyColor('rgba(255, 0, 0, .5)');
color1.getAlpha(); // 0.5
const color2 = tinyColor('rgb(255, 0, 0)');
color2.getAlpha(); // 1
const color3 = tinyColor('transparent');
color3.getAlpha(); // 0
```
### setAlpha
在当前颜色上设置`alpha`(透明度)值。接受的范围是 `0-1` 之间。
```ts
const color = tinyColor('red');
color.getAlpha(); // 1
color.setAlpha(0.5);
color.getAlpha(); // .5
color.toRgbString(); // "rgba(255, 0, 0, .5)"
```
### onBackground
计算颜色在背景上的显示效果。当颜色完全透明(即 `getAlpha() == 0`)时,结果将是背景颜色。当颜色完全不透明(即 `getAlpha() == 1`)时,结果将是颜色本身。否则,你将得到一个计算结果。
```ts
const color = tinyColor('rgba(255, 0, 0, .5)');
const computedColor = color.onBackground('rgb(0, 0, 255)');
computedColor.toRgbString(); // "rgb(128, 0, 128)"
```
### toHsv
```ts
const color = tinyColor('red');
color.toHsv(); // { h: 0, s: 1, v: 1, a: 1 }
```
### toHsvString
```ts
const color = tinyColor('red');
color.toHsvString(); // "hsv(0, 100%, 100%)"
color.setAlpha(0.5);
color.toHsvString(); // "hsva(0, 100%, 100%, 0.5)"
```
### toHsl
```ts
const color = tinyColor('red');
color.toHsl(); // { h: 0, s: 1, l: 0.5, a: 1 }
```
### toHslString
```ts
const color = tinyColor('red');
color.toHslString(); // "hsl(0, 100%, 50%)"
color.setAlpha(0.5);
color.toHslString(); // "hsla(0, 100%, 50%, 0.5)"
```
### toNumber
```ts
tinyColor('#aabbcc').toNumber() === 0xaabbcc // true
tinyColor('rgb(1, 1, 1)').toNumber() === (1 << 16) + (1 << 8) + 1 // true
```
### toHex
```ts
const color = tinyColor('red');
color.toHex(); // "ff0000"
```
### toHexString
```ts
const color = tinyColor('red');
color.toHexString(); // "#ff0000"
```
### toHex8
```ts
const color = tinyColor('red');
color.toHex8(); // "ff0000ff"
```
### toHex8String
```ts
const color = tinyColor('red');
color.toHex8String(); // "#ff0000ff"
```
### toHexShortString
根据颜色的透明度(Alpha值)返回较短的十六进制值,并且值前面带有#符号
```ts
const color1 = tinyColor('#ff000000');
color1.toHexShortString(); // "#ff000000"
color1.toHexShortString(true); // "#f000"
const color2 = tinyColor('#ff0000ff');
color2.toHexShortString(); // "#ff0000"
color2.toHexShortString(true); // "#f00"
```
### toRgb
```ts
const color = tinyColor('red');
color.toRgb(); // { r: 255, g: 0, b: 0, a: 1 }
```
### toRgbString
```ts
const color = tinyColor('red');
color.toRgbString(); // "rgb(255, 0, 0)"
color.setAlpha(0.5);
color.toRgbString(); // "rgba(255, 0, 0, 0.5)"
```
### toPercentageRgb
将当前颜色转换为百分比表示的 RGB
```ts
const color = tinyColor('red');
color.toPercentageRgb(); // { r: "100%", g: "0%", b: "0%", a: 1 }
```
### toPercentageRgbString
```ts
const color = tinyColor('red');
color.toPercentageRgbString(); // "rgb(100%, 0%, 0%)"
color.setAlpha(0.5);
color.toPercentageRgbString(); // "rgba(100%, 0%, 0%, 0.5)"
```
### toName
```ts
const color = tinyColor('red');
color.toName(); // "red"
```
### toString
根据输入格式打印成字符串。你也可以通过向函数中传入以下之一来覆盖这个行为:`"rgb", "prgb", "hex6", "hex3", "hex8", "name", "hsl", "hsv"`
```ts
const color1 = tinyColor('red');
color1.toString(); // "red"
color1.toString('hsv'); // "hsv(0, 100%, 100%)"
const color2 = tinyColor('rgb(255, 0, 0)');
color2.toString(); // "rgb(255, 0, 0)"
color2.setAlpha(0.5);
color2.toString(); // "rgba(255, 0, 0, 0.5)"
```
### 颜色修改
这些方法操纵当前颜色,并返回它以进行链式调用。例如:
```ts
tinyColor('red')
.lighten()
.desaturate()
.toHexString(); // '#f53d3d'
```
### lighten
`lighten: function(amount = 10) -> TinyColor`.根据给定的量(从0到100)调亮颜色。提供100将始终返回白色.
```ts
tinyColor('#f00').lighten().toString(); // '#ff3333'
tinyColor('#f00').lighten(100).toString(); // '#ffffff'
```
### brighten
`brighten: function(amount = 10) -> TinyColor`. 根据给定的量(从0到100)提高颜色的亮度。
```ts
tinyColor('#f00').brighten().toString(); // '#ff1919'
```
### darken
`darken: function(amount = 10) -> TinyColor`. 根据给定的量(从0到100)调暗颜色。提供100将始终返回黑色.
```ts
tinyColor('#f00').darken().toString(); // '#cc0000'
tinyColor('#f00').darken(100).toString(); // '#000000'
```
### tint
将颜色与纯白色混合,范围从0到100。提供0将不进行任何操作,提供100将始终返回白色.
```ts
tinyColor('#f00').tint().toString(); // "#ff1a1a"
tinyColor('#f00').tint(100).toString(); // "#ffffff"
```
### shade
将颜色与纯黑色混合,范围从0到100。提供0将不进行任何操作,提供100将始终返回黑色。
```ts
tinyColor('#f00').shade().toString(); // "#e60000"
tinyColor('#f00').shade(100).toString(); // "#000000"
```
### desaturate
`desaturate: function(amount = 10) -> TinyColor`. 根据给定的量(从0到100)降低颜色的饱和度。提供100将与调用`greyscale`相同。
```ts
tinyColor('#f00').desaturate().toString(); // "#f20d0d"
tinyColor('#f00').desaturate(100).toString(); // "#808080"
```
### saturate
`saturate: function(amount = 10) -> TinyColor`. 根据给定的量(从0到100)增加颜色的饱和度。
```ts
tinyColor('hsl(0, 10%, 50%)').saturate().toString(); // "hsl(0, 20%, 50%)"
```
### greyscale
`greyscale: function() -> TinyColor`. 完全降低颜色的饱和度,使其变为灰度。与调用`desaturate(100)`相同。
```ts
tinyColor('#f00').greyscale().toString(); // "#808080"
```
### spin
`spin: function(amount = 0) -> TinyColor`. 根据给定的量(从-360到360)旋转色相。调用时使用0、360或-360将不进行任何操作(因为它将色相设置回原来的值)。
```ts
tinyColor('#f00').spin(180).toString(); // "#00ffff"
tinyColor('#f00').spin(-90).toString(); // "#7f00ff"
tinyColor('#f00').spin(90).toString(); // "#80ff00"
// spin(0) and spin(360) do nothing
tinyColor('#f00').spin(0).toString(); // "#ff0000"
tinyColor('#f00').spin(360).toString(); // "#ff0000"
```
### mix
`mix: function(amount = 50) => TinyColor`. 将当前颜色与另一种颜色按给定量(从0到100)混合。0表示不混合(返回当前颜色)。
```ts
let color1 = tinyColor('#f0f');
let color2 = tinyColor('#0f0');
color1.mix(color2).toHexString(); // #808080
```
### 颜色组合
组合函数除非特别说明,否则返回一个`TinyColor`对象的数组。
### analogous
生成一组与当前颜色相似的颜色。
`analogous: function(results = 6, slices = 30) -> array<TinyColor>`.
```ts
const colors = tinyColor('#f00').analogous();
colors.map((t):string => t.toHexString()); // [ "#ff0000", "#ff0066", "#ff0033", "#ff0000", "#ff3300", "#ff6600" ]
```
### monochromatic
生成一组与当前颜色具有相同色相和饱和度的颜色。
`monochromatic: function(, results = 6) -> array<TinyColor>`.
```ts
const colors = tinyColor('#f00').monochromatic();
colors.map((t):string => t.toHexString()); // [ "#ff0000", "#2a0000", "#550000", "#800000", "#aa0000", "#d40000" ]
```
### splitcomplement
生成当前颜色的分裂补色。
`splitcomplement: function() -> array<TinyColor>`.
```ts
const colors = tinyColor('#f00').splitcomplement();
colors.map((t):string => t.toHexString()); // [ "#ff0000", "#ccff00", "#0066ff" ]
```
### triad
生成当前颜色的三色调。
`triad: function() -> array<TinyColor>`. Alias for `polyad(3)`.
```ts
const colors = tinyColor('#f00').triad();
colors.map((t):string => t.toHexString()); // [ "#ff0000", "#00ff00", "#0000ff" ]
```
### tetrad
生成当前颜色的四色调。
`tetrad: function() -> array<TinyColor>`. Alias for `polyad(4)`.
```ts
const colors = tinyColor('#f00').tetrad();
colors.map((t):string => t.toHexString()); // [ "#ff0000", "#80ff00", "#00ffff", "#7f00ff" ]
```
### polyad
生成当前颜色的 n 色调。
`polyad: function(number) -> array<TinyColor>`.
```ts
const colors = tinyColor('#f00').polyad(4);
colors.map((t):string => t.toHexString()); // [ "#ff0000", "#80ff00", "#00ffff", "#7f00ff" ]
```
### complement
计算当前颜色的补色。
`complement: function() -> TinyColor`.
```ts
tinyColor('#f00').complement().toHexString(); // "#00ffff"
```
## 颜色工具
### equals
判断两色是否相同
```ts
let color1 = tinyColor('red');
let color2 = tinyColor('#f00');
color1.equals(color2); // true
```
## 常见操作
### clone
`clone: function() -> TinyColor`.
使用相同的颜色实例化一个新的`TinyColor`对象。对新的对象的任何更改都不会影响旧的对象。
```ts
const color1 = tinyColor('#F00');
const color2 = color1.clone();
color2.setAlpha(0.5);
color1.toString(); // "#ff0000"
color2.toString(); // "rgba(255, 0, 0, 0.5)"
```
### generate
通过一个主色生成10个等级的颜色数组,使用 Ant Design 的颜色生成算法。
```ts
import {generate, LGenerateOptions} from '@/uni_modules/lime-color'
// 第二个参数为选填,如果不填则默认为 'default'
generate('red',{ theme: 'default'|'dark'} as LGenerateOptions)
// ['#2c1113', '#450f11', '#5b0e0e', '#7e0b0b', '#ad0707', '#dc0303', '#e82d27', '#f3594f', '#f88577', '#faaca0']
```
## 打赏
如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/alipay.png)
![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/wpay.png)

View File

@ -1,342 +0,0 @@
---
name: lime-color 颜色工具
description: 功能丰富、易于使用的颜色处理工具库,支持颜色转换、操作和生成功能。
tags:
- color
- 颜色
- tinycolor
- 转换
plugin: lime-color
category: API 插件
dependencies: []
---
# lime-color 颜色工具
> 一个功能丰富、易于使用的颜色处理工具库,提供全面的颜色转换、操作和生成功能
## 📋 功能特性
- **多格式支持**:兼容 HEX、RGB、HSL、HSV、HSB 等多种颜色格式
- **丰富的转换方法**:在不同颜色空间之间自由转换
- **强大的颜色操作**:支持亮度调整、饱和度修改、色相旋转等操作
- **智能颜色组合**:生成相似色、互补色、三色组合等多种颜色方案
- **主题色生成**:基于 Ant Design 颜色算法的主题色系列生成
- **跨平台兼容**:支持 uni-app 多端开发
## 📚 文档链接
详细使用文档请访问以下站点:
- [颜色工具文档 - 站点1](https://limex.qcoon.cn/uts/color.html)
- [颜色工具文档 - 站点2](https://limeui.netlify.app/uts/color.html)
- [颜色工具文档 - 站点3](https://limeui.familyzone.top/uts/color.html)
## 🛠️ 安装方法
1. 在 uni-app 插件市场中搜索并导入 `lime-color`
2. 导入后重新编译项目以确保插件正确集成
## 🚀 快速开始
### 基础用法
最简单的颜色工具用法,直接导入并使用:
```ts
import { tinyColor } from '@/uni_modules/lime-color'
```
### 颜色解析
tinyColor 接受多种类型的输入,包括字符串、数字、对象等:
```ts
// Hex 格式
tinyColor('#000'); // 6位十六进制
tinyColor('000'); // 无 # 前缀的十六进制
tinyColor('#369C'); // 4位十六进制(带透明度)
tinyColor('369C'); // 无 # 前缀的4位十六进制
tinyColor('#f0f0f688'); // 8位十六进制(带透明度)
// RGB 格式
tinyColor('rgb (255, 0, 0)'); // 带括号和空格的 RGB
tinyColor('rgb 255 0 0'); // 无括号的 RGB
tinyColor('rgba (255, 0, 0, .5)'); // RGBA 格式
tinyColor({ r: 255, g: 0, b: 0 }); // RGB 对象
// HSL 格式
tinyColor('hsl(0, 100%, 50%)'); // 标准 HSL 格式
tinyColor('hsla(0, 100%, 50%, .5)'); // HSLA 格式
tinyColor('hsl 0 1.0 0.5'); // 无括号的 HSL
tinyColor({ h: 0, s: 1, l: 0.5 }); // HSL 对象
// HSV 格式
tinyColor('hsv(0, 100%, 100%)'); // 标准 HSV 格式
tinyColor('hsva(0, 100%, 100%, .5)'); // HSVA 格式
tinyColor('hsv 0 1 1'); // 无括号的 HSV
tinyColor({ h: 0, s: 100, v: 100 }); // HSV 对象
// 颜色名称
tinyColor('RED'); // 大写颜色名称
tinyColor('blanchedalmond'); // 小写颜色名称
tinyColor('darkblue'); // 复合颜色名称
// 数字格式
tinyColor(0x0); // 数字 0
tinyColor(0xaabbcc); // 十六进制数字
```
## 🎨 颜色操作
### 颜色转换
将颜色转换为不同的格式:
```ts
const color = tinyColor('red');
// 转换为 HSV
color.toHsv(); // { h: 0, s: 1, v: 1, a: 1 }
color.toHsvString(); // "hsv(0, 100%, 100%)"
// 转换为 HSL
color.toHsl(); // { h: 0, s: 1, l: 0.5, a: 1 }
color.toHslString(); // "hsl(0, 100%, 50%)"
// 转换为 Hex
color.toHex(); // "ff0000"
color.toHexString(); // "#ff0000"
color.toHex8(); // "ff0000ff"
color.toHex8String(); // "#ff0000ff"
// 转换为 RGB
color.toRgb(); // { r: 255, g: 0, b: 0, a: 1 }
color.toRgbString(); // "rgb(255, 0, 0)"
// 转换为百分比 RGB
color.toPercentageRgb(); // { r: "100%", g: "0%", b: "0%", a: 1 }
color.toPercentageRgbString(); // "rgb(100%, 0%, 0%)"
// 转换为颜色名称
color.toName(); // "red"
```
### 颜色修改
修改颜色的亮度、饱和度、色相等属性:
```ts
const color = tinyColor('red');
// 调亮颜色
color.lighten().toString(); // "#ff3333"
color.lighten(100).toString(); // "#ffffff"
// 提高亮度
color.brighten().toString(); // "#ff1919"
// 调暗颜色
color.darken().toString(); // "#cc0000"
color.darken(100).toString(); // "#000000"
// 添加白色
color.tint().toString(); // "#ff1a1a"
color.tint(100).toString(); // "#ffffff"
// 添加黑色
color.shade().toString(); // "#e60000"
color.shade(100).toString(); // "#000000"
// 降低饱和度
color.desaturate().toString(); // "#f20d0d"
color.desaturate(100).toString(); // "#808080"
// 提高饱和度
tinyColor('hsl(0, 10%, 50%)').saturate().toString(); // "hsl(0, 20%, 50%)"
// 转为灰度
color.greyscale().toString(); // "#808080"
// 旋转色相
color.spin(180).toString(); // "#00ffff"
color.spin(-90).toString(); // "#7f00ff"
color.spin(90).toString(); // "#80ff00"
// 混合颜色
let color1 = tinyColor('#f0f');
let color2 = tinyColor('#0f0');
color1.mix(color2).toHexString(); // #808080
```
### 颜色组合
生成一组相关的颜色:
```ts
// 生成相似颜色
const analogousColors = tinyColor('#f00').analogous();
analogousColors.map(t => t.toHexString());
// [ "#ff0000", "#ff0066", "#ff0033", "#ff0000", "#ff3300", "#ff6600" ]
// 生成单色系列
const monochromaticColors = tinyColor('#f00').monochromatic();
monochromaticColors.map(t => t.toHexString());
// [ "#ff0000", "#2a0000", "#550000", "#800000", "#aa0000", "#d40000" ]
// 生成三色组合
const triadColors = tinyColor('#f00').triad();
triadColors.map(t => t.toHexString());
// [ "#ff0000", "#00ff00", "#0000ff" ]
// 生成四色组合
const tetradColors = tinyColor('#f00').tetrad();
tetradColors.map(t => t.toHexString());
// [ "#ff0000", "#80ff00", "#00ffff", "#7f00ff" ]
// 生成补色
tinyColor('#f00').complement().toHexString(); // "#00ffff"
```
## 🎯 颜色主题生成
使用 Ant Design 的颜色生成算法生成主题色系列:
```ts
import { generate, LimeColorGenerateOptions } from '@/uni_modules/lime-color'
// 生成默认主题色系列
generate('red')
// ['#2c1113', '#450f11', '#5b0e0e', '#7e0b0b', '#ad0707', '#dc0303', '#e82d27', '#f3594f', '#f88577', '#faaca0']
// 生成暗色主题色系列
generate('red', { theme: 'dark' })
// 生成亮色主题色系列
generate('blue', { theme: 'light' })
```
## 📖 API 文档
### 构造函数
| 方法 | 说明 | 参数 | 返回值 |
| --- | --- | --- | --- |
| `tinyColor(color, opts)` | 创建一个 TinyColor 实例 | color: 颜色值, opts: 选项 | TinyColor 实例 |
### 属性
| 属性名 | 说明 | 类型 |
| --- | --- | --- |
| `originalInput` | 传递到构造函数中用于创建实例的原始输入 | any |
| `format` | 用于创建实例的格式 | string |
| `isValid` | 颜色是否成功被解析 | boolean |
### 颜色判断方法
| 方法 | 说明 | 返回值 |
| --- | --- | --- |
| `getBrightness()` | 返回颜色的感知亮度,范围从 0-255 | number |
| `isLight()` | 返回一个布尔值,指示颜色的感知亮度是否为浅色 | boolean |
| `isDark()` | 返回一个布尔值,指示颜色的感知亮度是否为深色 | boolean |
| `getLuminance()` | 返回颜色的感知亮度,范围从 0-1 | number |
| `isMonochrome()` | 判断当前颜色是否为单色 | boolean |
### 透明度操作
| 方法 | 说明 | 参数 | 返回值 |
| --- | --- | --- | --- |
| `getAlpha()` | 返回颜色的透明度值,范围从 0-1 | - | number |
| `setAlpha(alpha)` | 设置颜色的透明度值 | alpha: 0-1 之间的数值 | TinyColor 实例 |
| `onBackground(background)` | 计算颜色在背景上的显示效果 | background: 背景颜色 | TinyColor 实例 |
### 颜色转换方法
| 方法 | 说明 | 参数 | 返回值 |
| --- | --- | --- | --- |
| `toHsv()` | 转换为 HSV 对象 | - | HSVA 对象 |
| `toHsvString()` | 转换为 HSV 字符串 | - | string |
| `toHsb()` | 转换为 HSB 对象 | - | HSBA 对象 |
| `toHsbString()` | 转换为 HSB 字符串 | - | string |
| `toHsl()` | 转换为 HSL 对象 | - | HSLA 对象 |
| `toHslString()` | 转换为 HSL 字符串 | - | string |
| `toHex(allow3Char)` | 转换为十六进制字符串 | allow3Char: 是否允许3字符简写 | string |
| `toHexString(allow3Char)` | 转换为带#的十六进制字符串 | allow3Char: 是否允许3字符简写 | string |
| `toHex8(allow4Char)` | 转换为8位十六进制字符串 | allow4Char: 是否允许4字符简写 | string |
| `toHex8String(allow4Char)` | 转换为带#的8位十六进制字符串 | allow4Char: 是否允许4字符简写 | string |
| `toHexShortString(allowShortChar)` | 根据透明度返回较短的十六进制值 | allowShortChar: 是否允许简写 | string |
| `toRgb()` | 转换为 RGB 对象 | - | RGBA 对象 |
| `toRgbString()` | 转换为 RGB 字符串 | - | string |
| `toPercentageRgb()` | 转换为百分比表示的 RGB 对象 | - | RGBAString 对象 |
| `toPercentageRgbString()` | 转换为百分比表示的 RGB 字符串 | - | string |
| `toName()` | 转换为颜色名称 | - | string 或 null |
| `toNumber()` | 转换为数字 | - | number |
| `toString(format)` | 根据指定格式转换为字符串 | format: 格式名称 | string |
### 颜色修改方法
| 方法 | 说明 | 参数 | 返回值 |
| --- | --- | --- | --- |
| `lighten(amount)` | 调亮颜色 | amount: 0-100 之间的数值 | TinyColor 实例 |
| `brighten(amount)` | 提高亮度 | amount: 0-100 之间的数值 | TinyColor 实例 |
| `darken(amount)` | 调暗颜色 | amount: 0-100 之间的数值 | TinyColor 实例 |
| `tint(amount)` | 与白色混合 | amount: 0-100 之间的数值 | TinyColor 实例 |
| `shade(amount)` | 与黑色混合 | amount: 0-100 之间的数值 | TinyColor 实例 |
| `desaturate(amount)` | 降低饱和度 | amount: 0-100 之间的数值 | TinyColor 实例 |
| `saturate(amount)` | 提高饱和度 | amount: 0-100 之间的数值 | TinyColor 实例 |
| `greyscale()` | 转为灰度 | - | TinyColor 实例 |
| `spin(amount)` | 旋转色相 | amount: -360 到 360 之间的数值 | TinyColor 实例 |
| `mix(color, amount)` | 与另一种颜色混合 | color: 颜色值, amount: 0-100 之间的数值 | TinyColor 实例 |
### 颜色组合方法
| 方法 | 说明 | 参数 | 返回值 |
| --- | --- | --- | --- |
| `analogous(results, slices)` | 生成相似颜色 | results: 结果数量, slices: 分片数量 | TinyColor[] |
| `monochromatic(results)` | 生成单色系列 | results: 结果数量 | TinyColor[] |
| `splitcomplement()` | 生成分裂补色 | - | TinyColor[] |
| `triad()` | 生成三色组合 | - | TinyColor[] |
| `tetrad()` | 生成四色组合 | - | TinyColor[] |
| `polyad(n)` | 生成 n 色组合 | n: 颜色数量 | TinyColor[] |
| `complement()` | 生成补色 | - | TinyColor 实例 |
### 其他方法
| 方法 | 说明 | 参数 | 返回值 |
| --- | --- | --- | --- |
| `clone()` | 克隆颜色实例 | - | TinyColor 实例 |
| `equals(color)` | 判断两个颜色是否相等 | color: 颜色值 | boolean |
### 颜色主题生成
| 方法 | 说明 | 参数 | 返回值 |
| --- | --- | --- | --- |
| `generate(color, options)` | 生成颜色主题 | color: 颜色值, options: 选项 | string[] |
## 🌟 应用场景
- **颜色选择器**:支持多种颜色格式的输入和输出
- **主题定制**:生成完整的主题色系列
- **数据可视化**:创建和谐的颜色方案
- **UI 设计工具**:提供颜色操作和转换功能
- **游戏开发**:生成游戏内的颜色方案
## 🤝 支持与赞赏
如果你觉得本插件解决了你的问题,可以考虑支持作者:
| 支付宝赞助 | 微信赞助 |
|------------|------------|
| ![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/alipay.png) | ![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/wpay.png) |
## 📄 许可证
本项目采用 MIT 许可证,详见 LICENSE 文件。
## 🔗 相关链接
- [uni-app 插件市场](https://ext.dcloud.net.cn/)
- [Ant Design 颜色系统](https://ant.design/docs/spec/colors-cn)
---
**lime-color** - 让颜色处理变得简单而强大!

View File

@ -1,3 +0,0 @@
{
"minSdkVersion": "21"
}

Some files were not shown because too many files have changed in this diff Show More