commit 1bf7232929b1a7cb08a2d95c465e4348ee5bbb36
Author: evan.liu <583229071@qq.com>
Date: Wed Sep 2 16:10:57 2026 +0800
初初稿
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..165c177
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/unpackage
\ No newline at end of file
diff --git a/.hbuilderx/launch.json b/.hbuilderx/launch.json
new file mode 100644
index 0000000..7bdd7d6
--- /dev/null
+++ b/.hbuilderx/launch.json
@@ -0,0 +1,15 @@
+{
+ "version" : "1.0",
+ "configurations" : [
+ {
+ "customPlaygroundType" : "local",
+ "playground" : "custom",
+ "type" : "uni-app:app-android"
+ },
+ {
+ "customPlaygroundType" : "local",
+ "playground" : "custom",
+ "type" : "uni-app:app-ios"
+ }
+ ]
+}
diff --git a/App.uvue b/App.uvue
new file mode 100644
index 0000000..3d59d38
--- /dev/null
+++ b/App.uvue
@@ -0,0 +1,98 @@
+
+
+
\ No newline at end of file
diff --git a/api/httpInstance.uts b/api/httpInstance.uts
new file mode 100644
index 0000000..52d39b9
--- /dev/null
+++ b/api/httpInstance.uts
@@ -0,0 +1,90 @@
+import { getToken } from "../common/auth"
+import { clearAllData } from "../common/auth"
+
+// 可选后台服务器列表
+export type ServerItem = {
+ name: string
+ url: string
+}
+
+export const SERVER_LIST: ServerItem[] = [
+ { name: "正式 ssl (evmtapp.clenergy.com)", url: "https://evmtapp.clenergy.com:20096" } as ServerItem,
+ { name: "测试 (47.107.245.120)", url: "http://47.107.245.120:30081" } as ServerItem,
+ { name: "DEBUG (192.168.13.158)", url: "http://192.168.13.158:30081" } as ServerItem
+]
+
+let recordBaseUrl: string | null = null
+
+// 读取当前后台服务器地址(内存 + 本地),未选择过则默认第一个
+export function getBaseUrl(): string {
+ if (recordBaseUrl != null && (recordBaseUrl as string).length > 0) {
+ return recordBaseUrl as string
+ }
+ const t = uni.getStorageSync('serverBaseUrl') as string | null
+ if (t != null && t.length > 0) {
+ recordBaseUrl = t
+ return t
+ }
+ return SERVER_LIST[0].url
+}
+
+// 保存后台服务器地址(内存 + 本地),下次启动直接加载
+export function setBaseUrl(url: string) {
+ recordBaseUrl = url
+ uni.setStorageSync('serverBaseUrl', url)
+}
+
+export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE"
+
+// 请求参数类型
+export type HttpRequest = {
+ url: string
+ method?: HttpMethod
+ data?: any
+ header?: UTSJSONObject // 这里用 UTSJSONObject
+}
+
+// 封装请求
+export function $http(params: HttpRequest): Promise {
+ return new Promise((resolve, reject) => {
+
+ // 1. 准备 header
+ let baseHeader: UTSJSONObject = params.header ?? {} as UTSJSONObject
+
+ baseHeader["Content-Type"] = "application/json"
+
+ // 2. 读 token,有就加 Authorization
+ const token = getToken()
+ if (token != null && token.length > 0) {
+ baseHeader["Authorization"] = token
+ }
+ // console.log("url = ", getBaseUrl() + params.url)
+ // console.log("method = ", params.method)
+ // console.log("data = ", params.data)
+ // console.log("header = ", baseHeader)
+
+ // 3. 发请求
+ uni.request({
+ url: getBaseUrl() + params.url,
+ method: params.method ?? "GET",
+ data: params.data ?? {} as UTSJSONObject,
+ header: baseHeader,
+ success: (res) => {
+ if(res.statusCode == 401) {
+ clearAllData()
+ uni.reLaunch({
+ url: '/pages/login/login'
+ })
+ }
+ const result: UTSJSONObject = {
+ statusCode: res.statusCode,
+ data: res.data
+ } as UTSJSONObject
+ resolve(result)
+ },
+ fail: (err) => {
+ reject(err)
+ }
+ })
+ })
+}
\ No newline at end of file
diff --git a/api/requestApi.uts b/api/requestApi.uts
new file mode 100644
index 0000000..2a10dad
--- /dev/null
+++ b/api/requestApi.uts
@@ -0,0 +1,80 @@
+import { $http } from "@/api/httpInstance.uts"
+
+export type RegisteredUserReq = {
+ userName: string,
+ userPassword: string,
+ rndcode: string
+}
+// 获取pinCode
+export async function GetPindCodeReqApi(params: RegisteredUserReq): Promise{
+ const body: UTSJSONObject = {
+ userName: params.userName,
+ userPassword: params.userPassword,
+ rndcode: params.rndcode
+ } as UTSJSONObject
+
+ const res = await $http({
+ url: "/ocpp-mtapp-api/api/v1/users/getPinCode",
+ method: "POST",
+ data: body
+ })
+ return res
+}
+
+// 注册用户
+export async function RegisteredUserReqApi(params: RegisteredUserReq): Promise{
+ const body: UTSJSONObject = {
+ userName: params.userName,
+ userPassword: params.userPassword,
+ rndcode: params.rndcode
+ } as UTSJSONObject
+
+ const res = await $http({
+ url: "/ocpp-mtapp-api/api/v1/users/add",
+ method: "POST",
+ data: body
+ })
+ return res
+}
+
+export type LoginReq = {
+ username: string
+ password: string
+}
+// 登录请求
+export async function LoginReqApi(params: LoginReq): Promise{
+ const body: UTSJSONObject = {
+ username: params.username,
+ password: params.password,
+ expirationTime: 0
+ } as UTSJSONObject
+
+ const res = await $http({
+ url: "/ocpp-mtapp-api/api/v1/auth/login",
+ method: "POST",
+ data: body
+ })
+ return res
+}
+
+export type ModifyUserInfoReq = {
+ name?: string | null,
+ phone?: string | null,
+ password?: string | null,
+ nickName?: string | null
+}
+// 修改用户信息
+export async function ModifyUserInfoReqApi(params: ModifyUserInfoReq): Promise{
+ const body: UTSJSONObject = {
+ name: params.name,
+ phone: params.phone,
+ password: params.password
+ } as UTSJSONObject
+
+ const res = await $http({
+ url: `/ocpp-mtapp-api/api/v1/users/${params.name}`,
+ method: "GET",
+ data: body
+ })
+ return res
+}
diff --git a/api/responseData.uts b/api/responseData.uts
new file mode 100644
index 0000000..14bce7c
--- /dev/null
+++ b/api/responseData.uts
@@ -0,0 +1,32 @@
+
+// 注册用户信息
+export type RegisteredUserResponse = {
+ result: string | null
+}
+
+// 登录应答
+export type LoginResponse = {
+ refresh_token: string
+ token_type: string
+ username: string
+ token: string
+}
+
+// 修改用户信息应答
+export type ModifyUserInfoResponse = {
+ userName: string | null,
+ userPassword: string | null,
+ salt: string | null,
+ sessionKey: string | null,
+ nickName: string | null,
+ avatarUrl: string | null,
+ gender: string | null,
+ country: string | null,
+ province: string | null,
+ city: string | null,
+ language: string | null,
+ created: string | null,
+ timestamp: string | null,
+ email: string | null,
+ phone: string | null
+}
diff --git a/common/auth.uts b/common/auth.uts
new file mode 100644
index 0000000..a31b38d
--- /dev/null
+++ b/common/auth.uts
@@ -0,0 +1,164 @@
+let recordToken: string | null = null
+let recordUserName: string | null = null
+let recordCharging: string | null = null
+
+// 保存 token(内存 + 本地)
+export function setToken(token: string) {
+ recordToken = token
+ uni.setStorageSync('loginToken', token)
+}
+
+// 读取 token
+export function getToken(): string | null {
+ if (recordToken != null) {
+ return recordToken
+ }
+ const t = uni.getStorageSync('loginToken') as string | null
+ recordToken = t ?? null
+ return recordToken
+}
+
+// 退出登录时清掉
+export function clearAllData() {
+ recordToken = null
+ recordUserName = null
+ recordCharging = null
+ uni.removeStorageSync('loginToken')
+ uni.removeStorageSync('loginUserName')
+ uni.removeStorageSync('ChargingFlag')
+
+}
+
+export function checkToken(): boolean {
+ const t = getToken()
+ if(t == null || (t.length) == 0) {
+ return false;
+ }
+ return true;
+}
+
+
+// 保存用户名
+export function setUserName(userName: string) {
+ recordUserName = userName
+ uni.setStorageSync('loginUserName', userName)
+}
+
+// 读取用户名
+export function getUserName(): string | null {
+ if (recordUserName != null) {
+ return recordUserName
+ }
+ const t = uni.getStorageSync('loginUserName') as string | null
+ recordUserName = t ?? null
+ return recordUserName
+}
+
+
+// 保存充电标记
+export function setCharging(flag: string) {
+ recordCharging = flag
+ uni.setStorageSync('ChargingFlag', flag)
+}
+
+// 读取充电标记
+export function getCharging(): string | null {
+ if (recordCharging != null) {
+ return recordCharging
+ }
+ const t = uni.getStorageSync('ChargingFlag') as string | null
+ recordCharging = t ?? null
+ return recordCharging
+}
+
+// 清除充电标记
+export function clearCharging() {
+ recordCharging = null
+ uni.removeStorageSync('ChargingFlag')
+}
+
+// 保存登录账号密码(记住密码)
+export function setSavedAccount(account: string, password: string) {
+ uni.setStorageSync('savedAccount', account)
+ uni.setStorageSync('savedPassword', password)
+}
+
+// 读取保存的账号
+export function getSavedAccount(): string {
+ const t = uni.getStorageSync('savedAccount') as string | null
+ return t ?? ""
+}
+
+// 读取保存的密码
+export function getSavedPassword(): string {
+ const t = uni.getStorageSync('savedPassword') as string | null
+ return t ?? ""
+}
+
+// 清除保存的账号密码
+export function clearSavedAccount() {
+ uni.removeStorageSync('savedAccount')
+ uni.removeStorageSync('savedPassword')
+}
+
+// 历史账号记录
+export type AccountRecord = {
+ account: string,
+ password: string
+}
+
+const ACCOUNT_HISTORY_MAX = 5
+
+// 读取历史账号(最新登录的在前)
+export function getAccountHistory(): AccountRecord[] {
+ const result: AccountRecord[] = []
+ const raw = uni.getStorageSync('accountHistory') as string | null
+ if (raw == null || raw.length == 0) {
+ return result
+ }
+ try {
+ const arr = JSON.parse(raw) as UTSJSONObject[] | null
+ if (arr == null) return result
+ for (let i = 0; i < arr.length; i++) {
+ const obj = arr[i]
+ const acc = obj["account"] as string | null
+ const pwd = obj["password"] as string | null
+ if (acc != null && pwd != null) {
+ const rec: AccountRecord = { account: acc, password: pwd }
+ result.push(rec)
+ }
+ }
+ } catch (e: any) {
+ console.log('parse accountHistory error = ', e)
+ }
+ return result
+}
+
+// 保存一条历史账号(同账号去重置顶,最多保留 5 条)
+export function addAccountHistory(account: string, password: string) {
+ const old = getAccountHistory()
+ const list: AccountRecord[] = []
+ const first: AccountRecord = { account: account, password: password }
+ list.push(first)
+ for (let i = 0; i < old.length && list.length < ACCOUNT_HISTORY_MAX; i++) {
+ const item = old[i]
+ if (item.account != account) {
+ list.push(item)
+ }
+ }
+ uni.setStorageSync('accountHistory', JSON.stringify(list))
+}
+
+// 删除指定账号的历史记录
+export function removeAccountHistory(account: string) {
+ const old = getAccountHistory()
+ const list: AccountRecord[] = []
+ for (let i = 0; i < old.length; i++) {
+ const item = old[i]
+ if (item.account != account) {
+ list.push(item)
+ }
+ }
+ uni.setStorageSync('accountHistory', JSON.stringify(list))
+}
+
diff --git a/common/bleInfo.uts b/common/bleInfo.uts
new file mode 100644
index 0000000..f58fc2a
--- /dev/null
+++ b/common/bleInfo.uts
@@ -0,0 +1,41 @@
+let bleName: string | null = null
+let bleMac: string | null = null
+
+// 保存蓝牙名字
+export function setName(name: string) {
+ bleName = name
+ uni.setStorageSync('bleName', name)
+}
+
+// 读取蓝牙名字
+export function getName(): string | null {
+ if (bleName != null) {
+ return bleName
+ }
+ const t = uni.getStorageSync('bleName') as string | null
+ bleName = t ?? null
+ return bleName
+}
+
+// 保存蓝牙mac
+export function setMac(mac: string) {
+ bleMac = mac
+ uni.setStorageSync('bleMac', mac)
+}
+
+// 读取蓝牙mac
+export function getMac(): string | null {
+ if (bleMac != null) {
+ return bleMac
+ }
+ const t = uni.getStorageSync('bleMac') as string | null
+ bleMac = t ?? null
+ return bleMac
+}
+
+export function clearAllData() {
+ bleName = null
+ bleMac = null
+ uni.removeStorageSync('bleName')
+ uni.removeStorageSync('bleMac')
+}
\ No newline at end of file
diff --git a/docs/修改记录_2026-07-17_服务器切换与记住密码.md b/docs/修改记录_2026-07-17_服务器切换与记住密码.md
new file mode 100644
index 0000000..97f76b4
--- /dev/null
+++ b/docs/修改记录_2026-07-17_服务器切换与记住密码.md
@@ -0,0 +1,52 @@
+# 修改记录:后台服务器切换 + 登录记住密码
+
+日期:2026-07-17
+
+本次未提交的修改主要包含两块功能:**我的页面可切换后台服务器**、**登录页记住密码**,另附登录页图标颜色调整。
+
+## 1. 后台服务器动态切换
+
+### `api/httpInstance.uts`
+
+- 原固定常量 `BASE_URL` 移除,改为可配置的服务器列表 `SERVER_LIST`:
+ - 正式 ssl(evmtapp.clenergy.com:20096,默认)
+ - 正式(47.107.245.120:30081)
+ - 测试 ssl(miniapp.test.springecloud.com:20095)
+ - 测试(192.168.13.243:30081)
+- 新增 `getBaseUrl()`:按「内存缓存 → 本地存储(`serverBaseUrl`)→ 列表第一项」的顺序取当前服务器地址。
+- 新增 `setBaseUrl(url)`:同时写入内存和本地存储,下次启动直接生效。
+- `$http()` 请求地址由 `BASE_URL + params.url` 改为 `getBaseUrl() + params.url`。
+
+### `pages/tabbar/my.uvue`
+
+- 「我的」页面新增「后台服务器」卡片,显示当前服务器名称。
+- 点击弹出 ActionSheet 选择服务器,选择后调用 `setBaseUrl()` 保存并 Toast 提示。
+- `onShow` 中调用 `initServerList()` 回显当前选择。
+
+## 2. 登录页记住密码
+
+### `common/auth.uts`
+
+新增 4 个本地存储方法:
+
+- `setSavedAccount(account, password)`:保存账号密码(`savedAccount` / `savedPassword`)。
+- `getSavedAccount()` / `getSavedPassword()`:读取,无则返回空串。
+- `clearSavedAccount()`:清除保存的账号密码。
+
+### `pages/login/login.uvue`
+
+- 密码登录表单下方新增「remember password」勾选行(`rememberPwd`)。
+- `onLoad` 时若本地已保存密码,则自动勾选并回填账号、密码。
+- 登录成功后:勾选则 `setSavedAccount()` 保存,未勾选则 `clearSavedAccount()` 清除。
+- 顺带调整:输入框及眼睛图标的 `uni-icons` 统一加上 `color="#3A6FA1"`,并新增 `.remember-row` / `.remember-text` 样式。
+
+## 3. 其他新增文件(未跟踪)
+
+- `static/androidApp22.png`
+- `static/miniApp22.png`
+- `uni_modules/android-ble/utssdk/mp-weixin.zip`
+
+## 注意事项
+
+- 密码以**明文**存储在本地 storage(`savedPassword`),仅适合内部测试用途;如需对外发布,建议改为加密存储或仅保存账号。
+- 服务器地址本地存储 key 为 `serverBaseUrl`,清除应用数据后会恢复默认(列表第一项)。
diff --git a/docs/修改记录_2026-07-17_登录页历史账号选择.md b/docs/修改记录_2026-07-17_登录页历史账号选择.md
new file mode 100644
index 0000000..9d2f319
--- /dev/null
+++ b/docs/修改记录_2026-07-17_登录页历史账号选择.md
@@ -0,0 +1,31 @@
+# 修改记录:登录页历史账号选择
+
+日期:2026-07-17
+
+本次修改在登录页新增**历史账号记录**功能:登录成功的账号密码都会保存到本地历史列表,下次登录时可直接选择回填,无需再次输入。
+
+## 1. 历史账号存储层
+
+### `common/auth.uts`
+
+新增 `AccountRecord` 类型(`account` + `password`)及 3 个方法:
+
+- `getAccountHistory()`:读取历史账号列表(最新登录的在前)。数据以 JSON 字符串存于本地存储 key `accountHistory`,解析失败时安全返回空数组。
+- `addAccountHistory(account, password)`:保存一条历史记录,同账号去重并置顶,最多保留 5 条(`ACCOUNT_HISTORY_MAX`)。
+- `removeAccountHistory(account)`:删除指定账号的历史记录。
+
+## 2. 登录页 UI 与逻辑
+
+### `pages/login/login.uvue`
+
+- 密码登录表单「remember password」下方新增「saved accounts (n)」折叠行,点击展开/收起历史账号列表(`historyOpen`);无历史记录时整个入口不显示。
+- 点击列表中某条账号:自动回填账号 + 密码,自动勾选「remember password」,并收起列表(`onPickHistory`)。
+- 每条记录右侧「✕」可单独删除该条历史(`onRemoveHistory`,`@tap.stop` 阻止触发选中)。
+- `onLoad` 时调用 `getAccountHistory()` 加载历史列表(`historyList`)。
+- 密码登录成功后无条件调用 `addAccountHistory()` 写入历史;原有单账号「记住密码」(`savedAccount` / `savedPassword`)逻辑保持不变。
+- 新增样式:`.history-row` / `.history-text` / `.history-list` / `.history-item` / `.history-account` / `.history-del`。
+
+## 注意事项
+
+- 历史账号的密码以**明文**存储在本地 storage(`accountHistory`),与已有 `savedPassword` 相同,仅适合内部测试用途;如需对外发布,建议改为加密存储或仅保存账号。
+- 历史列表上限 5 条,超出时最旧的记录被移除;清除应用数据后历史记录丢失。
diff --git a/docs/修改记录_2026-09-02_母乳杯蓝牙改造.md b/docs/修改记录_2026-09-02_母乳杯蓝牙改造.md
new file mode 100644
index 0000000..dea101c
--- /dev/null
+++ b/docs/修改记录_2026-09-02_母乳杯蓝牙改造.md
@@ -0,0 +1,77 @@
+# 修改记录 2026-09-02:母乳杯(CAIIC-MCM)蓝牙改造
+
+将原充电桩 App 改造为母乳杯设备 App:通过蓝牙显示设备参数(温度、风扇转速)、
+查询设备系统运行状态、本地固件文件经 BLE OTA 升级设备。
+设备协议依据 `D:\caic-workspace\mothercup\mcm-ddc-04\docs\ble_protocol.md`(CAIIC BLE 通信协议 V1.0)。
+
+## 协议要点(设备侧已实现)
+
+- GATT:Service `0000CA10-CA11-4B11-8000-CA11CA11CA11`,下行写特征 `...CA11...`
+ (**仅 Write Without Response**),上行 notify `...CA12...`;广播名 `CAIIC-MCM`;建议 MTU=247。
+- 二进制帧(小端):`[0xCA, TYPE, SEQ, LEN(u16), PAYLOAD, CRC16-CCITT(0x1021/初值0xFFFF)]`,
+ 开销 7 字节,payload ≤480;一帧可跨多次 BLE 写入,设备按字节流重组。
+- 帧类型:0x01 CLI_REQ / 0x02 CLI_RSP / 0x03 CLI_RSP_END;0x10 OTA_BEGIN /
+ 0x11 OTA_DATA / 0x12 OTA_END / 0x1F OTA_RSP。
+- OTA:双 bank 直写,设备 4KB 扇区缓冲,满扇区回 OTA_RSP(0x11) 作流控;status=2 按应答
+ offset 重发重同步;OTA_END 整镜像 CRC32(zlib/IEEE)校验通过后设备 200ms 自动复位切 bank。
+
+## 新增文件
+
+- `src/mcm/Crc.uts` — CRC16-CCITT 与 CRC32-IEEE(zlib)实现。注意 uts number 位运算按
+ 32 位有符号处理,CRC32 中间值保持在 Int 范围、无符号右移用 `(x>>1)&0x7FFFFFFF`,
+ 多项式用补码字面量 `-306674912`(0xEDB88320)。
+- `src/mcm/FrameCodec.uts` — 帧常量、`encodeFrame` 组帧、`FrameDecoder` 字节流重组
+ (找 SOF → 头 → payload+CRC,错帧静默丢弃重找)、`pushU32LE/readU32LE`(纯算术实现,
+ 避免大数值位运算符号问题)。
+- `src/mcm/McmChannel.uts` — 连接级单例通道:`attach()` 挂全局 `bleData/bleState` 事件;
+ `sendFrame` 写队列串行化、整帧按 (att_mtu-3) 切片、写类型 WRITE_NO_RESPONSE;
+ `sendCli` CLI 命令透传串行执行(聚合 CLI_RSP 至 CLI_RSP_END,超时/断开兜底);
+ OTA_RSP 转发给 OtaManager。所有 Promise 只 resolve 不 reject。
+- `src/mcm/McmCommands.uts` — 业务命令:version/sysinfo/bankinfo/temp/fan,
+ 命令名集中在顶部常量。**温度/风扇命令(默认 temp/fan)固件尚未实现,解析失败页面显示 `--`**。
+- `src/mcm/OtaManager.uts` — OTA 状态机:BEGIN→按 4KB 扇区发 DATA 等 ack(流控/重同步)→END;
+ 断连或 `abort()` 协作式中止;进度/状态回调。
+- `pages/tabbar/sysStatus.uvue` — 系统状态页:version/bankinfo 概要 + sysinfo 原文控制台
+ (运行时间/任务数/heap/任务表),下拉刷新。
+- `pages/tabbar/ota.uvue` — 固件升级页:手动输入本地 bin 路径(默认
+ `/sdcard/Download/embeddedSrc.bin`),UTS 直调 Android `java.io.File/FileInputStream`
+ 读文件(`#ifdef APP-ANDROID`);显示大小与 CRC32;输入目标版本号(如 1.00.02→0x010002);
+ 进度条 + 日志;升级期间禁止断连/退出,离开页面自动中止。
+
+## 修改文件
+
+- `src/BluetoothManager.uts` — 新增 `sendDataBytes(data, writeType, cb)`(显式写类型,
+ 设备下行特征必须 writeType=1)与 `selectService(uuidPrefix)`(按服务 UUID 前缀
+ `0000CA10` 选择 service+notify+write 组合,规避自动识别选错)。
+- `App.uvue` — `ConnectBle()` 在 scanServices 后先 `selectService("0000CA10")` 再订阅 notify;
+ `onLaunch` 调 `McmChannel.attach()`。
+- `pages/tabbar/home.uvue` — 重写为设备主页:扫描(过滤 CAIIC-MCM)/连接/断开;
+ 连接后温度、风扇转速每 2s 轮询显示,固件版本与运行 Bank 展示;notify 订阅成功
+ (bleData type=1000)后协商 MTU=247。
+- `pages/tabbar/my.uvue` — 删除充电/离线行为入口(原已注释),启用"CLI 调试终端"入口。
+- `pages/bleTest/bluetoothOperation.uvue` — 重写为 CLI 调试终端:inline 命令输入 +
+ 应答文本流展示,复用全局 ConnectBle 连接流程。
+- `pages.json` — tabbar 改为 设备/状态/升级/my 四 tab(图标复用 static/tabbar 现有资源);
+ 移除 charge/order 及全部充电页面注册。
+- `manifest.json` — 应用名改为"母乳杯助手";iOS 蓝牙用途文案改为母乳杯描述。
+- `api/requestApi.uts` — 仅保留用户类接口(登录/注册/pinCode/修改资料),删除全部
+ OCPP 充电与蓝牙绑定分享接口。
+- `api/responseData.uts` — 仅保留用户类响应类型。
+
+## 删除
+
+- 目录:`pages/startCharge/`、`pages/stopCharge/`、`pages/orderDetails/`、
+ `pages/offlineBehavior/`、`pages/tabbar/charge.uvue`、`pages/tabbar/order.uvue`、
+ `src/espBlufi/`(充电桩 JSON 分片协议,与 CAIIC 二进制帧协议不兼容)。
+
+## 遗留事项
+
+- **固件需新增温度/风扇 CLI 命令**(设备仓库 `D:\caic-workspace\mothercup`,
+ 在 `cli_core.c` 命令表注册;当前 APP 侧默认命令名 `temp`/`fan`,定名不同改
+ `McmCommands.uts` 顶部常量即可)。
+- **云端固件接口未接入**:本期为本地 bin 文件升级;`api/httpInstance.uts`($http/token/
+ 服务器切换)保留,后续按约定接口(查询最新版本 + 下载 URL)接入 ota 页。
+- uni-app x 无 CLI 构建手段,需在 HBuilderX 编译并真机(Android)验证:
+ version/sysinfo/bankinfo 显示、temp/fan(待固件)、本地 bin OTA 后 bankinfo 确认切 bank。
+- OTA 大数据量发送走 Write Without Response + 扇区级 ack 流控,若实测丢包导致
+ status=2 重同步频繁,可在 OtaManager 扇区内加小窗口节流。
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..8793fef
--- /dev/null
+++ b/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main.uts b/main.uts
new file mode 100644
index 0000000..8bdcc86
--- /dev/null
+++ b/main.uts
@@ -0,0 +1,9 @@
+import App from './App.uvue'
+
+import { createSSRApp } from 'vue'
+export function createApp() {
+ const app = createSSRApp(App)
+ return {
+ app
+ }
+}
\ No newline at end of file
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..a4865e1
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,97 @@
+{
+ "name": "smartassister",
+ "appid": "__UNI__884AC41",
+ "description": "",
+ "versionName": "1.0.1",
+ "versionCode": "110",
+ "uni-app-x": {},
+ /* 快应用特有相关 */
+ "quickapp": {},
+ /* 小程序特有相关 */
+ "mp-weixin": {
+ "appid": "wx50ffc3eab938ff4c",
+ "setting": {
+ "urlCheck": true,
+ "minified": true
+ },
+ "usingComponents": true,
+ "permission": {}
+ },
+ "mp-alipay": {
+ "usingComponents": true
+ },
+ "mp-baidu": {
+ "usingComponents": true
+ },
+ "mp-toutiao": {
+ "usingComponents": true
+ },
+ "uniStatistics": {
+ "enable": false
+ },
+ "vueVersion": "3",
+ "app": {
+ "distribute": {
+ "icons": {
+ "android": {
+ "hdpi": "",
+ "xhdpi": "",
+ "xxhdpi": "",
+ "xxxhdpi": ""
+ }
+ }
+ }
+ },
+ "app-android": {
+ "distribute": {
+ "modules": {},
+ "icons": {
+ "hdpi": "/static/androidApp.png",
+ "xhdpi": "/static/androidApp.png",
+ "xxhdpi": "/static/androidApp.png",
+ "xxxhdpi": "/static/androidApp.png"
+ },
+ "splashScreens": {
+ "default": {}
+ },
+ "permissions": [
+ "",
+ "",
+ "",
+ ""
+ ]
+ }
+ },
+ "app-ios": {
+ "distribute": {
+ "modules": {
+ "uni-location": {},
+ "uni-barcode-scanning": {}
+ },
+ "icons": {
+ "appstore": "/static/androidApp.png"
+ },
+ "splashScreens": {},
+ "privacyDescription": {
+ "NSBluetoothPeripheralUsageDescription": "请允许使用蓝牙功能。该功能用于:连接母乳杯设备,显示温度、风扇转速等运行参数,查询设备系统状态,并通过蓝牙为设备升级固件。",
+ "NSBluetoothAlwaysUsageDescription": "请允许使用蓝牙功能。该功能用于:连接母乳杯设备,显示温度、风扇转速等运行参数,查询设备系统状态,并通过蓝牙为设备升级固件。",
+ "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": {
+ "router": {
+ "mode": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/pages.json b/pages.json
new file mode 100644
index 0000000..71eb819
--- /dev/null
+++ b/pages.json
@@ -0,0 +1,111 @@
+{
+ "pages": [ //pages数组中第一项表示应用启动页,参考:https://doc.dcloud.net.cn/uni-app-x/collocation/pagesjson.html
+ {
+ "path": "pages/tabbar/home",
+ "style": {
+ "navigationBarTitleText": "母乳杯设备",
+ "navigationBarBackgroundColor": "#3A6FA1",
+ "enablePullDownRefresh": true
+ }
+ },
+ {
+ "path": "pages/tabbar/sysStatus",
+ "style": {
+ "navigationBarTitleText": "系统状态",
+ "navigationBarBackgroundColor": "#3A6FA1",
+ "enablePullDownRefresh": true
+ }
+ },
+ {
+ "path": "pages/tabbar/ota",
+ "style": {
+ "navigationBarTitleText": "固件升级",
+ "navigationBarBackgroundColor": "#3A6FA1"
+ }
+ },
+ {
+ "path": "pages/tabbar/my",
+ "style": {
+ "navigationBarTitleText": "my",
+ "navigationBarBackgroundColor": "#3A6FA1"
+ }
+ },
+ {
+ "path": "pages/register/register",
+ "style": {
+ "navigationBarTitleText": "register",
+ "navigationStyle": "default",
+ "navigationBarBackgroundColor": "#3A6FA1"
+ }
+ },
+ {
+ "path": "pages/login/login",
+ "style": {
+ "navigationBarTitleText": "login",
+ "navigationStyle": "default",
+ "navigationBarBackgroundColor": "#3A6FA1"
+ }
+ },
+ {
+ "path": "pages/displayUserInfo/displayUserInfo",
+ "style": {
+ "navigationBarTitleText": "user info",
+ "navigationBarBackgroundColor": "#3A6FA1"
+ }
+ },
+ {
+ "path": "pages/bleTest/bluetoothList/bluetoothList",
+ "style": {
+ "navigationBarTitleText": "bluetooth list",
+ "enablePullDownRefresh": true
+ }
+ },
+ {
+ "path": "pages/bleTest/bluetoothOperation/bluetoothOperation",
+ "style": {
+ "navigationBarTitleText": "CLI 调试终端"
+ }
+ }
+ ],
+ "tabBar": {
+ "color": "#999999",
+ "selectedColor": "#3A6FA1",
+ "backgroundColor": "#ffffff",
+ "borderStyle": "black",
+ "list": [
+ {
+ "pagePath": "pages/tabbar/home",
+ "text": "设备",
+ "iconPath": "/static/tabbar/homeDefault.png",
+ "selectedIconPath": "/static/tabbar/homeActive.png"
+ },
+ {
+ "pagePath": "pages/tabbar/sysStatus",
+ "text": "状态",
+ "iconPath": "/static/tabbar/mapDefault.png",
+ "selectedIconPath": "/static/tabbar/mapActive.png"
+ },
+ {
+ "pagePath": "pages/tabbar/ota",
+ "text": "升级",
+ "iconPath": "/static/tabbar/chargeDefault.png",
+ "selectedIconPath": "/static/tabbar/chargeActive.png"
+ },
+ {
+ "pagePath": "pages/tabbar/my",
+ "text": "my",
+ "iconPath": "/static/tabbar/myDefault.png",
+ "selectedIconPath": "/static/tabbar/myActive.png"
+ }
+ ]
+ }
+ },
+ "globalStyle": {
+ "navigationBarTextStyle": "black",
+ "navigationBarTitleText": "uni-app x",
+ "navigationBarBackgroundColor": "#F8F8F8",
+ "backgroundColor": "#F8F8F8",
+ "navigationStyle": "default"
+ },
+ "uniIdRouter": {}
+}
diff --git a/pages/bleTest/bluetoothList/bluetoothList.uvue b/pages/bleTest/bluetoothList/bluetoothList.uvue
new file mode 100644
index 0000000..1480fcd
--- /dev/null
+++ b/pages/bleTest/bluetoothList/bluetoothList.uvue
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.device.name }}
+ {{ item.device.address }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/bleTest/bluetoothOperation/bluetoothOperation.uvue b/pages/bleTest/bluetoothOperation/bluetoothOperation.uvue
new file mode 100644
index 0000000..42f11ee
--- /dev/null
+++ b/pages/bleTest/bluetoothOperation/bluetoothOperation.uvue
@@ -0,0 +1,321 @@
+
+
+
+
+
+
+ {{ it.text }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/displayUserInfo/displayUserInfo.uvue b/pages/displayUserInfo/displayUserInfo.uvue
new file mode 100644
index 0000000..b27ff23
--- /dev/null
+++ b/pages/displayUserInfo/displayUserInfo.uvue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/login/login.uvue b/pages/login/login.uvue
new file mode 100644
index 0000000..6cffa76
--- /dev/null
+++ b/pages/login/login.uvue
@@ -0,0 +1,408 @@
+
+
+
+
+
+
+
+
+
+ Password login
+
+
+
+
+
+ SMS login
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ get code
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ remember password
+
+
+
+
+ saved accounts ({{ historyList.length }})
+
+
+
+
+ {{ item.account }}
+
+
+
+
+
+
+
+
+
+
+ Don't have an account yet? Quick registration
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/register/register.uvue b/pages/register/register.uvue
new file mode 100644
index 0000000..5aebe43
--- /dev/null
+++ b/pages/register/register.uvue
@@ -0,0 +1,344 @@
+
+
+
+
+
+
+
+
+ email
+
+
+
+
+
+
+ pinCode
+
+
+
+
+
+ {{ isCountingDown ? `${countdown}s` : "get code" }}
+
+
+
+
+
+
+
+
+ password
+
+
+
+
+
+
+
+
+
+
+
+ enter the password again
+
+
+
+
+
+
+
+
+
+
+ tips: The password should be at least six characters long and include both uppercase and lowercase letters as well as numbers.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/tabbar/home.uvue b/pages/tabbar/home.uvue
new file mode 100644
index 0000000..81aeca1
--- /dev/null
+++ b/pages/tabbar/home.uvue
@@ -0,0 +1,429 @@
+
+
+
+
+
+
+
+
+
+
+ {{ connected ? '已连接' : (connecting ? '连接中...' : '未连接') }}
+ {{ deviceName }} ({{ deviceMac }})
+
+
+
+
+
+
+
+
+
+
+ 温度
+ {{ temperature }} ℃
+
+
+ 风扇转速
+ {{ fanSpeed }} RPM
+
+
+
+
+
+
+ 固件版本
+ {{ fwVersion }}
+
+
+ 运行 Bank
+ {{ bankInfo }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.device.name }}
+ {{ item.device.address }}
+
+
+
+
+ 未发现 CAIIC-MCM 设备,点击"扫描设备"重试
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/tabbar/my.uvue b/pages/tabbar/my.uvue
new file mode 100644
index 0000000..57584a0
--- /dev/null
+++ b/pages/tabbar/my.uvue
@@ -0,0 +1,252 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{userInfo}}
+
+
+
+
+ 后台服务器
+
+ {{currentServerName}}
+
+
+
+
+ {{ "version: 1.0.1" }}
+
+
+
+
+
+
+ CLI 调试终端
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/tabbar/ota.uvue b/pages/tabbar/ota.uvue
new file mode 100644
index 0000000..b72b984
--- /dev/null
+++ b/pages/tabbar/ota.uvue
@@ -0,0 +1,380 @@
+
+
+
+
+
+
+
+ 设备未连接,请先在"设备"页连接 CAIIC-MCM
+
+
+
+
+
+
+ 当前固件版本
+ {{ curVersion }}
+
+
+ 运行 Bank
+ {{ bankInfo }}
+
+
+
+
+
+ 固件文件(Keil 产物 bin,≤224KB)
+
+
+
+
+
+ 文件大小
+ {{ fileSize }} 字节
+
+
+ 镜像 CRC32
+ 0x{{ fileCrcHex }}
+
+
+
+
+
+ 目标版本号(如 1.00.02)
+
+
+
+
+
+
+
+
+ {{ progress }}%
+
+
+
+
+ 升级中请勿断开连接或退出应用
+
+
+
+
+ 升级日志
+ {{ log }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/tabbar/sysStatus.uvue b/pages/tabbar/sysStatus.uvue
new file mode 100644
index 0000000..5c1b53a
--- /dev/null
+++ b/pages/tabbar/sysStatus.uvue
@@ -0,0 +1,196 @@
+
+
+
+
+
+
+
+ 设备未连接,请先在"设备"页连接 CAIIC-MCM
+
+
+
+
+
+
+ 固件版本
+ {{ version }}
+
+
+ Bank 信息
+ {{ bankInfo }}
+
+
+
+
+
+
+
+
+ sysinfo(系统运行状态)
+ {{ sysInfo }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/platformConfig.json b/platformConfig.json
new file mode 100644
index 0000000..896f669
--- /dev/null
+++ b/platformConfig.json
@@ -0,0 +1,7 @@
+
+// 参考链接 https://doc.dcloud.net.cn/uni-app-x/tutorial/ls-plugin.html#setting
+{
+ "targets": [
+ "APP-ANDROID"
+ ]
+}
\ No newline at end of file
diff --git a/src/BluetoothManager.uts b/src/BluetoothManager.uts
new file mode 100644
index 0000000..2f722ac
--- /dev/null
+++ b/src/BluetoothManager.uts
@@ -0,0 +1,171 @@
+import {BleLib,MyApiResult,BleScanResult,BleServices,ScanRssiBR,WNuuid,ScanPara,WriteData} from "@/uni_modules/android-ble"
+
+// 统一拿到全局 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:0, // 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()
+}
+
+// 开启服务消息通知
+export function onNotityReadBleData(readNotification: boolean , cb: (res: MyApiResult) => void) {
+ const cBle = ble()
+ cBle.onNotityReadBleData(cBle.getSericUUID(), cBle.getNotityUUID(), 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
+export function sendDataBytes(data: number[], writeType: number, cb: (res: MyApiResult) => void) {
+ const b = ble()
+ const wd: WriteData = {
+ serviceId: b.getServiceUUID(),
+ characteristicId: b.getWriteUUID(),
+ writeType: writeType,
+ data: data,
+ fenbao: false
+ } as WriteData
+ b.sendData(wd, cb)
+}
+
+// 按服务 UUID 前缀选择 service+notify+write 组合(如 "0000CA10"),找到返回 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
\ No newline at end of file
diff --git a/src/commonFunctions.uts b/src/commonFunctions.uts
new file mode 100644
index 0000000..954ce65
--- /dev/null
+++ b/src/commonFunctions.uts
@@ -0,0 +1,49 @@
+export function bytesToAscii(bytes: number[]): string {
+ let s = ''
+ for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i] & 0xFF)
+ return s
+}
+
+export function asciiToBytes(s: string): Array {
+ const out = [] as Array
+ for (let i = 0; i < s.length; i++) {
+ const c = s.charCodeAt(i)
+ out.push(c == null ? 0 : c)
+ }
+ return out
+}
+
+export function bytesToHex(bytes: number[]): string {
+ let s = ''
+ for (let i = 0; i < bytes.length; i++) {
+ const v = bytes[i] & 0xFF
+ s += (v < 16 ? '0' : '') + v.toString(16)
+ }
+ return s
+}
+
+// "4D 00 00 01 01" => 0x4d 0x00 0x00 0x01 0x01
+export function parseHexBytes(input: string): Array | null {
+ let s = input.trim()
+ if (s.length == 0) return [] as Array
+
+ // 逗号、换行、制表符 全部变空格(全局)
+ s = s.replace(/[,\n\t]/g, " ")
+
+ // 去掉 0x / 0X(全局)
+ s = s.replace(/0x/ig, "")
+
+ const parts = s.split(" ")
+ const out = [] as Array
+
+ for (let i = 0; i < parts.length; i++) {
+ const p = parts[i].trim()
+ if (p.length == 0) continue
+
+ const token = (p.length == 1) ? ("0" + p) : p
+ const v = parseInt(token, 16)
+ if (isNaN(v) || v < 0 || v > 255) return null
+ out.push(v)
+ }
+ return out
+}
\ No newline at end of file
diff --git a/src/mcm/Crc.uts b/src/mcm/Crc.uts
new file mode 100644
index 0000000..6795e13
--- /dev/null
+++ b/src/mcm/Crc.uts
@@ -0,0 +1,47 @@
+/**
+ * CRC 校验算法(CAIIC BLE 协议 V1.0)
+ * - crc16Ccitt: 帧校验,CRC16-CCITT poly 0x1021 初值 0xFFFF
+ * - crc32Ieee : 固件镜像校验,zlib CRC32 poly 0xEDB88320 初值/异或出 0xFFFFFFFF
+ *
+ * 注意:uts number 位运算按 32 位有符号处理,CRC32 中间值全部保持在 Int 范围内,
+ * 无符号右移用 (x >> 1) & 0x7FFFFFFF 实现;最终返回值转为无符号(0..2^32-1)。
+ */
+
+// 0xEDB88320 的 32 位补码形式(避免字面量超出 Int 范围)
+const CRC32_POLY = -306674912
+
+export function crc16Ccitt(data: number[], offset: number, len: number): number {
+ let crc = 0xFFFF
+ for (let i = offset; i < offset + len; i++) {
+ crc = crc ^ ((data[i] & 0xFF) << 8)
+ for (let k = 0; k < 8; k++) {
+ if ((crc & 0x8000) != 0) {
+ crc = ((crc << 1) ^ 0x1021) & 0xFFFF
+ } else {
+ crc = (crc << 1) & 0xFFFF
+ }
+ }
+ }
+ return crc
+}
+
+export function crc32Ieee(data: number[]): number {
+ let crc = -1 // 0xFFFFFFFF
+ for (let i = 0; i < data.length; i++) {
+ crc = crc ^ (data[i] & 0xFF)
+ for (let k = 0; k < 8; k++) {
+ const shifted = (crc >> 1) & 0x7FFFFFFF
+ if ((crc & 1) != 0) {
+ crc = shifted ^ CRC32_POLY
+ } else {
+ crc = shifted
+ }
+ }
+ }
+ crc = crc ^ -1
+ // 转为无符号返回值(避免后续 >> 算术右移出问题)
+ if (crc < 0) {
+ return crc + 4294967296
+ }
+ return crc
+}
diff --git a/src/mcm/FrameCodec.uts b/src/mcm/FrameCodec.uts
new file mode 100644
index 0000000..ea7c6a9
--- /dev/null
+++ b/src/mcm/FrameCodec.uts
@@ -0,0 +1,113 @@
+/**
+ * CAIIC BLE 协议 V1.0 帧编解码
+ * 帧格式(小端):[0]=0xCA SOF, [1]=TYPE, [2]=SEQ, [3:4]=LEN(u16 LE),
+ * [5..]=PAYLOAD(LEN 字节), 末尾 2B CRC16-CCITT(覆盖 TYPE..PAYLOAD, LE)
+ * 帧开销 7 字节(头 5 + CRC 2),payload 上限 480。
+ * 一个协议帧可跨多次 BLE 写入传输,接收侧按字节流重组。
+ */
+import { crc16Ccitt } from "./Crc.uts"
+
+export const FRAME_SOF = 0xCA
+export const FRAME_OVERHEAD = 7
+export const MAX_PAYLOAD = 480
+
+// 帧类型
+export const TYPE_CLI_REQ = 0x01 // 手机→设备 命令文本(≤63B)
+export const TYPE_CLI_RSP = 0x02 // 设备→手机 应答文本分块
+export const TYPE_CLI_RSP_END = 0x03 // 设备→手机 {status u8} 0=ok 1=未知命令/行超长
+export const TYPE_OTA_BEGIN = 0x10 // {total_size u32, image_crc32 u32, version u32}
+export const TYPE_OTA_DATA = 0x11 // {offset u32, data…}
+export const TYPE_OTA_END = 0x12 // {crc32 u32}
+export const TYPE_OTA_RSP = 0x1F // {cmd_echo u8, status u8, offset u32}
+
+export type BleFrame = {
+ type: number,
+ seq: number,
+ payload: number[]
+}
+
+/** 组帧:SOF + TYPE + SEQ + LEN + PAYLOAD + CRC16 */
+export function encodeFrame(type: number, seq: number, payload: number[]): number[] {
+ const len = payload.length
+ const out = [] as number[]
+ out.push(FRAME_SOF)
+ out.push(type & 0xFF)
+ out.push(seq & 0xFF)
+ out.push(len & 0xFF)
+ out.push((len >> 8) & 0xFF)
+ for (let i = 0; i < len; i++) {
+ out.push(payload[i] & 0xFF)
+ }
+ // CRC 覆盖 TYPE..PAYLOAD,即 out 中索引 [1, 5+len)
+ const crc = crc16Ccitt(out, 1, 4 + len)
+ out.push(crc & 0xFF)
+ out.push((crc >> 8) & 0xFF)
+ return out
+}
+
+/** 字节流组包器:跨 notify 分片重组,CRC 错/超长静默丢弃并重新找 SOF */
+export class FrameDecoder {
+ buf = [] as number[]
+
+ reset() {
+ this.buf = [] as number[]
+ }
+
+ feed(data: number[]): BleFrame[] {
+ const frames = [] as BleFrame[]
+ for (let i = 0; i < data.length; i++) {
+ this.buf.push(data[i] & 0xFF)
+ }
+ while (true) {
+ // 重新找 SOF
+ while (this.buf.length > 0 && this.buf[0] != FRAME_SOF) {
+ this.buf.shift()
+ }
+ if (this.buf.length < 5) break
+ const len = this.buf[3] | (this.buf[4] << 8)
+ if (len > MAX_PAYLOAD) {
+ this.buf.shift() // 非法长度,丢弃该 SOF 继续找
+ continue
+ }
+ const total = 5 + len + 2
+ if (this.buf.length < total) break // 帧未收完,等下一包
+ const crc = crc16Ccitt(this.buf, 1, 4 + len)
+ const rxCrc = this.buf[5 + len] | (this.buf[5 + len + 1] << 8)
+ if (crc != rxCrc) {
+ this.buf.shift() // CRC 错,丢弃该 SOF 重新找
+ continue
+ }
+ const payload = [] as number[]
+ for (let i = 0; i < len; i++) {
+ payload.push(this.buf[5 + i])
+ }
+ frames.push({
+ type: this.buf[1],
+ seq: this.buf[2],
+ payload: payload
+ } as BleFrame)
+ this.buf.splice(0, total)
+ }
+ return frames
+ }
+}
+
+/** 小端 u32 打包(纯算术实现,避免大数值位运算的符号问题) */
+export function pushU32LE(out: number[], v: number) {
+ let x = v % 4294967296
+ out.push(x % 256)
+ x = Math.floor(x / 256)
+ out.push(x % 256)
+ x = Math.floor(x / 256)
+ out.push(x % 256)
+ x = Math.floor(x / 256)
+ out.push(x % 256)
+}
+
+/** 小端 u32 解包(纯算术实现) */
+export function readU32LE(data: number[], offset: number): number {
+ return (data[offset] & 0xFF) +
+ (data[offset + 1] & 0xFF) * 256 +
+ (data[offset + 2] & 0xFF) * 65536 +
+ (data[offset + 3] & 0xFF) * 16777216
+}
diff --git a/src/mcm/McmChannel.uts b/src/mcm/McmChannel.uts
new file mode 100644
index 0000000..7504f20
--- /dev/null
+++ b/src/mcm/McmChannel.uts
@@ -0,0 +1,209 @@
+/**
+ * CAIIC-MCM 连接级通道(单例)
+ * - 经 uni.$on("bleData") 接收原始字节,FrameDecoder 字节流重组后按帧类型分发
+ * - sendFrame:整帧按 (att_mtu-3) 切片串行写入(写特征为 Write Without Response)
+ * - sendCli :CLI 命令透传,串行执行(协议要求一条命令应答完再发下一条)
+ * - OTA_RSP 帧转发给 OtaManager 注册的 handler
+ *
+ * 约定:所有接口的 Promise 只 resolve 不 reject,失败通过返回值表达(uts 兼容性考虑)。
+ */
+import * as Ble from "../BluetoothManager.uts"
+import { MyApiResult } from "@/uni_modules/android-ble"
+import {
+ FrameDecoder, BleFrame, encodeFrame,
+ TYPE_CLI_REQ, TYPE_CLI_RSP, TYPE_CLI_RSP_END, TYPE_OTA_RSP
+} from "./FrameCodec.uts"
+import { asciiToBytes, bytesToAscii } from "../commonFunctions.uts"
+
+export type CliResult = {
+ text: string, // 聚合的应答文本
+ status: number // 0=ok 1=未知命令/行超长 -1=超时 -2=命令超长 -3=发送失败 -4=连接断开
+}
+
+type CliJob = {
+ textChunks: string[],
+ resolve: (r: CliResult) => void,
+ timer: number
+}
+
+const CLI_MAX_CMD_BYTES = 63
+
+let txSeq = 0
+let maxWriteBytes = 20 // 单次 BLE 写入上限 = att_mtu - 3,未协商 MTU 时按 20 兼容
+let decoder = new FrameDecoder()
+let attached = false
+
+let cliJob: CliJob | null = null
+let cliChain: Promise = Promise.resolve(true)
+let writeChain: Promise = Promise.resolve(true)
+
+let otaRspHandler: ((payload: number[]) => void) | null = null
+let disconnectHandler: (() => void) | null = null
+
+// ================= 接收 =================
+
+function dispatchFrame(frame: BleFrame) {
+ if (frame.type == TYPE_CLI_RSP) {
+ const job = cliJob
+ if (job != null) {
+ job.textChunks.push(bytesToAscii(frame.payload))
+ }
+ } else if (frame.type == TYPE_CLI_RSP_END) {
+ const job = cliJob
+ if (job != null) {
+ cliJob = null
+ clearTimeout(job.timer)
+ let text = ""
+ for (let i = 0; i < job.textChunks.length; i++) {
+ text += job.textChunks[i]
+ }
+ const status = frame.payload.length > 0 ? frame.payload[0] : 1
+ job.resolve({ text: text, status: status } as CliResult)
+ }
+ } else if (frame.type == TYPE_OTA_RSP) {
+ const h = otaRspHandler
+ if (h != null) {
+ h(frame.payload)
+ }
+ }
+}
+
+function onBleData(res: MyApiResult) {
+ if (res.type != 0) return
+ const data = res.data as number[]
+ const frames = decoder.feed(data)
+ for (let i = 0; i < frames.length; i++) {
+ dispatchFrame(frames[i])
+ }
+}
+
+function onBleState(state: number) {
+ // 10000=连接失败 10001=已断开(见 home 页既有约定)
+ if (state == 10000 || state == 10001) {
+ resetChannel()
+ const h = disconnectHandler
+ if (h != null) h()
+ }
+}
+
+/** 断连/异常时复位通道:挂起的 CLI 命令以 -4 结束 */
+export function resetChannel() {
+ decoder.reset()
+ txSeq = 0
+ const job = cliJob
+ if (job != null) {
+ cliJob = null
+ clearTimeout(job.timer)
+ let text = ""
+ for (let i = 0; i < job.textChunks.length; i++) {
+ text += job.textChunks[i]
+ }
+ job.resolve({ text: text, status: -4 } as CliResult)
+ }
+}
+
+// ================= 生命周期 =================
+
+/** 注册全局蓝牙事件监听(幂等,App onLaunch 时调用一次) */
+export function attach() {
+ if (attached) return
+ attached = true
+ uni.$on("bleData", (res: any) => onBleData(res as MyApiResult))
+ uni.$on("bleState", (state: any) => onBleState(state as number))
+}
+
+/** 记录协商后的 att_mtu,写切片大小 = mtu - 3(协议上限 244) */
+export function setMtuSize(mtu: number) {
+ let w = mtu - 3
+ if (w > 244) w = 244
+ if (w < 20) w = 20
+ maxWriteBytes = w
+}
+
+export function getMaxWriteBytes(): number {
+ return maxWriteBytes
+}
+
+export function setOtaRspHandler(h: ((payload: number[]) => void) | null) {
+ otaRspHandler = h
+}
+
+export function setDisconnectHandler(h: (() => void) | null) {
+ disconnectHandler = h
+}
+
+// ================= 发送 =================
+
+function sendOnce(chunk: number[]): Promise {
+ return new Promise((resolve) => {
+ // 设备下行特征仅支持 Write Without Response → writeType=1
+ Ble.sendDataBytes(chunk, 1, (res: MyApiResult) => resolve(res))
+ })
+}
+
+async function sendFrameImpl(type: number, payload: number[]): Promise {
+ const frame = encodeFrame(type, txSeq & 0xFF, payload)
+ txSeq = (txSeq + 1) & 0xFF
+ let offset = 0
+ while (offset < frame.length) {
+ const chunkLen = Math.min(frame.length - offset, maxWriteBytes)
+ const chunk = [] as number[]
+ for (let i = 0; i < chunkLen; i++) {
+ chunk.push(frame[offset + i])
+ }
+ const res = await sendOnce(chunk)
+ if (res.type != 0) {
+ return false
+ }
+ offset += chunkLen
+ }
+ return true
+}
+
+/** 发送一个协议帧(写队列串行化,帧间不会交错) */
+export function sendFrame(type: number, payload: number[]): Promise {
+ const p = writeChain.then((): Promise => sendFrameImpl(type, payload))
+ writeChain = p
+ return p
+}
+
+function sendCliImpl(cmd: string, timeoutMs: number): Promise {
+ return new Promise((resolve) => {
+ const payload = asciiToBytes(cmd)
+ if (payload.length > CLI_MAX_CMD_BYTES) {
+ resolve({ text: "", status: -2 } as CliResult)
+ return
+ }
+ const job: CliJob = {
+ textChunks: [] as string[],
+ resolve: resolve,
+ timer: 0
+ } as CliJob
+ job.timer = setTimeout(() => {
+ if (cliJob == job) {
+ cliJob = null
+ let text = ""
+ for (let i = 0; i < job.textChunks.length; i++) {
+ text += job.textChunks[i]
+ }
+ resolve({ text: text, status: -1 } as CliResult)
+ }
+ }, timeoutMs)
+ cliJob = job
+ sendFrame(TYPE_CLI_REQ, payload).then((ok) => {
+ if (!ok && cliJob == job) {
+ cliJob = null
+ clearTimeout(job.timer)
+ resolve({ text: "", status: -3 } as CliResult)
+ }
+ })
+ })
+}
+
+/** CLI 命令透传:串行执行,聚合 CLI_RSP 文本直到 CLI_RSP_END;只 resolve 不 reject */
+export function sendCli(cmd: string, timeoutMs: number = 3000): Promise {
+ attach()
+ const p = cliChain.then((): Promise => sendCliImpl(cmd, timeoutMs))
+ cliChain = p.then((): boolean => true)
+ return p
+}
diff --git a/src/mcm/McmCommands.uts b/src/mcm/McmCommands.uts
new file mode 100644
index 0000000..490c8ca
--- /dev/null
+++ b/src/mcm/McmCommands.uts
@@ -0,0 +1,65 @@
+/**
+ * CAIIC-MCM 业务命令封装(基于 CLI 透传)
+ * 命令名集中在顶部常量,固件侧命令定名后只需改这里。
+ * 注意:固件当前已实现 version/sysinfo/bankinfo;
+ * 温度/风扇命令(默认 temp/fan)待固件新增,解析失败返回 null 由页面显示 "--"。
+ */
+import { sendCli, CliResult } from "./McmChannel.uts"
+
+const CMD_VERSION = "version"
+const CMD_SYSINFO = "sysinfo"
+const CMD_BANKINFO = "bankinfo"
+const CMD_TEMPERATURE = "temp"
+const CMD_FAN_SPEED = "fan"
+
+/** 从应答文本中提取第一个数值(整数或小数),提取不到返回 null */
+function parseFirstNumber(text: string): number | null {
+ const m = text.match(/-?\d+(\.\d+)?/)
+ if (m == null) return null
+ const m0 = m[0]
+ if (m0 == null) return null
+ const v = parseFloat(m0)
+ if (isNaN(v)) return null
+ return v
+}
+
+/** 固件版本号原文,如 "V1.00.01";失败返回 null */
+export async function getVersion(): Promise {
+ const r: CliResult = await sendCli(CMD_VERSION)
+ if (r.status != 0) return null
+ const t = r.text.trim()
+ return t.length > 0 ? t : null
+}
+
+/** 系统运行状态原文(运行时间/任务数/heap/任务表),失败返回 null */
+export async function getSysInfo(): Promise {
+ const r: CliResult = await sendCli(CMD_SYSINFO, 5000)
+ if (r.status != 0) return null
+ return r.text
+}
+
+/** bank 信息原文(当前运行 bank 与 bootsetting),失败返回 null */
+export async function getBankInfo(): Promise {
+ const r: CliResult = await sendCli(CMD_BANKINFO)
+ if (r.status != 0) return null
+ return r.text
+}
+
+/** 温度(单位由固件定义,℃),失败返回 null */
+export async function getTemperature(): Promise {
+ const r: CliResult = await sendCli(CMD_TEMPERATURE)
+ if (r.status != 0) return null
+ return parseFirstNumber(r.text)
+}
+
+/** 风扇转速(RPM),失败返回 null */
+export async function getFanSpeed(): Promise {
+ const r: CliResult = await sendCli(CMD_FAN_SPEED)
+ if (r.status != 0) return null
+ return parseFirstNumber(r.text)
+}
+
+/** 通用 CLI 命令(调试终端用) */
+export async function execCli(cmd: string, timeoutMs: number = 5000): Promise {
+ return await sendCli(cmd, timeoutMs)
+}
diff --git a/src/mcm/OtaManager.uts b/src/mcm/OtaManager.uts
new file mode 100644
index 0000000..7c33280
--- /dev/null
+++ b/src/mcm/OtaManager.uts
@@ -0,0 +1,214 @@
+/**
+ * CAIIC-MCM BLE OTA 升级状态机(协议第 5 节:双 bank 直写)
+ * 流程:OTA_BEGIN{size,crc32,version} → 等 OTA_RSP(0x10,ok)
+ * → OTA_DATA{offset,data} 按 4KB 扇区发送,满扇区等 OTA_RSP(0x11,ok) 作流控
+ * (status=2 时按应答 offset 重发重同步)
+ * → OTA_END{crc32} → 等 OTA_RSP(0x12,ok) → 设备 200ms 后自动复位切 bank
+ * 约定:Promise 只 resolve 不 reject;断连或 abort() 协作式中止。
+ */
+import {
+ sendFrame, setOtaRspHandler, setDisconnectHandler
+} from "./McmChannel.uts"
+import {
+ TYPE_OTA_BEGIN, TYPE_OTA_DATA, TYPE_OTA_END,
+ MAX_PAYLOAD, pushU32LE, readU32LE
+} from "./FrameCodec.uts"
+import { crc32Ieee } from "./Crc.uts"
+
+export type OtaCallbacks = {
+ onProgress: (percent: number) => void, // 0~100
+ onState: (msg: string) => void
+}
+
+type OtaAck = {
+ received: boolean, // false=超时
+ status: number, // 0 ok / 1 bad_frame / 2 bad_state(offset 乱序) / 3 size_too_big / 4 crc_fail / 5 flash_fail
+ offset: number
+}
+
+type OtaWaiter = {
+ echo: number,
+ timer: number,
+ resolve: (ack: OtaAck) => void
+}
+
+const BANK_MAX = 0x38000 // 224KB bank 上限
+const SECTOR_SIZE = 4096 // 设备 4KB 扇区缓冲,攒满回 ack
+const DATA_PER_FRAME = MAX_PAYLOAD - 4 // OTA_DATA 帧数据段上限(4 字节 offset + 476 数据 = 480)
+
+let waiter: OtaWaiter | null = null
+let aborted = false
+let running = false
+
+function statusText(status: number): string {
+ switch (status) {
+ case 1: return "bad_frame"
+ case 2: return "bad_state"
+ case 3: return "size_too_big"
+ case 4: return "crc_fail"
+ case 5: return "flash_fail"
+ }
+ return "unknown(" + status + ")"
+}
+
+function onOtaRsp(payload: number[]) {
+ // payload: {cmd_echo u8, status u8, offset u32 LE}
+ if (payload.length < 6) return
+ const w = waiter
+ if (w == null) return
+ if (payload[0] != w.echo) return // 非当前等待的应答(如末扇区 ack 迟到),忽略
+ waiter = null
+ clearTimeout(w.timer)
+ w.resolve({ received: true, status: payload[1], offset: readU32LE(payload, 2) } as OtaAck)
+}
+
+function waitRsp(echo: number, timeoutMs: number): Promise {
+ return new Promise((resolve) => {
+ const w: OtaWaiter = { echo: echo, timer: 0, resolve: resolve } as OtaWaiter
+ w.timer = setTimeout(() => {
+ if (waiter == w) {
+ waiter = null
+ resolve({ received: false, status: -1, offset: 0 } as OtaAck)
+ }
+ }, timeoutMs)
+ waiter = w
+ })
+}
+
+export function isRunning(): boolean {
+ return running
+}
+
+/** 协作式中止:发送循环在下一扇区边界停止 */
+export function abort() {
+ aborted = true
+ const w = waiter
+ if (w != null) {
+ waiter = null
+ clearTimeout(w.timer)
+ w.resolve({ received: false, status: -2, offset: 0 } as OtaAck)
+ }
+}
+
+async function run(image: number[], version: number, cb: OtaCallbacks): Promise {
+ const total = image.length
+ const crc = crc32Ieee(image)
+
+ // 1. OTA_BEGIN
+ const pb = [] as number[]
+ pushU32LE(pb, total)
+ pushU32LE(pb, crc)
+ pushU32LE(pb, version)
+ cb.onState("发送 OTA_BEGIN(" + total + " 字节)...")
+ if (!await sendFrame(TYPE_OTA_BEGIN, pb)) {
+ cb.onState("OTA_BEGIN 发送失败")
+ return false
+ }
+ let ack = await waitRsp(TYPE_OTA_BEGIN, 5000)
+ if (!ack.received) {
+ cb.onState(ack.status == -2 ? "升级已中止" : "等待 OTA_BEGIN 应答超时")
+ return false
+ }
+ if (ack.status != 0) {
+ cb.onState("设备拒绝升级: " + statusText(ack.status))
+ return false
+ }
+
+ // 2. OTA_DATA:按 4KB 扇区发送,满扇区等 ack(流控)
+ let offset = 0
+ while (offset < total) {
+ if (aborted) {
+ cb.onState("升级已中止")
+ return false
+ }
+ const sectorEnd = Math.min(offset + SECTOR_SIZE, total)
+ let pos = offset
+ while (pos < sectorEnd) {
+ const n = Math.min(sectorEnd - pos, DATA_PER_FRAME)
+ const payload = [] as number[]
+ pushU32LE(payload, pos)
+ for (let i = 0; i < n; i++) {
+ payload.push(image[pos + i])
+ }
+ if (!await sendFrame(TYPE_OTA_DATA, payload)) {
+ cb.onState("OTA_DATA 写入失败 offset=" + pos)
+ return false
+ }
+ pos += n
+ cb.onProgress(Math.floor(pos * 100 / total))
+ }
+ if (sectorEnd - offset >= SECTOR_SIZE) {
+ // 满扇区:设备擦写完成后回 OTA_RSP(0x11)(擦写期间关中断数十 ms,超时给足)
+ ack = await waitRsp(TYPE_OTA_DATA, 8000)
+ if (!ack.received) {
+ cb.onState(ack.status == -2 ? "升级已中止" : "等待扇区应答超时 offset=" + offset)
+ return false
+ }
+ if (ack.status == 2) {
+ // offset 乱序:从设备期望的 offset 重发重同步
+ offset = ack.offset
+ cb.onState("重同步至 offset=" + offset)
+ continue
+ }
+ if (ack.status != 0) {
+ cb.onState("设备写入错误: " + statusText(ack.status))
+ return false
+ }
+ }
+ offset = sectorEnd
+ }
+
+ // 3. OTA_END:设备 flush 尾部并整镜像回读校验 CRC32
+ const pe = [] as number[]
+ pushU32LE(pe, crc)
+ cb.onState("发送 OTA_END,设备校验镜像 ...")
+ if (!await sendFrame(TYPE_OTA_END, pe)) {
+ cb.onState("OTA_END 发送失败")
+ return false
+ }
+ ack = await waitRsp(TYPE_OTA_END, 15000)
+ if (!ack.received) {
+ cb.onState(ack.status == -2 ? "升级已中止" : "等待 OTA_END 应答超时")
+ return false
+ }
+ if (ack.status != 0) {
+ cb.onState("镜像校验失败: " + statusText(ack.status))
+ return false
+ }
+ cb.onProgress(100)
+ cb.onState("升级成功,设备约 200ms 后自动复位运行新固件,请重新连接")
+ return true
+}
+
+/**
+ * 开始 OTA 升级
+ * @param image 固件 bin 文件全部字节
+ * @param version 版本号 u32,如 0x010002 表示 V1.00.02
+ */
+export async function start(image: number[], version: number, cb: OtaCallbacks): Promise {
+ if (running) {
+ cb.onState("已有升级任务进行中")
+ return false
+ }
+ if (image.length < 4 || image.length > BANK_MAX) {
+ cb.onState("镜像大小非法(4 ~ " + BANK_MAX + " 字节)")
+ return false
+ }
+ running = true
+ aborted = false
+ setOtaRspHandler((payload: number[]) => onOtaRsp(payload))
+ setDisconnectHandler(() => {
+ aborted = true
+ abort()
+ })
+ const ok = await run(image, version, cb)
+ running = false
+ setOtaRspHandler(null)
+ setDisconnectHandler(null)
+ const w = waiter
+ if (w != null) {
+ waiter = null
+ clearTimeout(w.timer)
+ }
+ return ok
+}
diff --git a/static/androidApp.png b/static/androidApp.png
new file mode 100644
index 0000000..8737512
Binary files /dev/null and b/static/androidApp.png differ
diff --git a/static/androidApp22.png b/static/androidApp22.png
new file mode 100644
index 0000000..baceb27
Binary files /dev/null and b/static/androidApp22.png differ
diff --git a/static/charge/UnAvailable.png b/static/charge/UnAvailable.png
new file mode 100644
index 0000000..55442c6
Binary files /dev/null and b/static/charge/UnAvailable.png differ
diff --git a/static/charge/Unkonw.png b/static/charge/Unkonw.png
new file mode 100644
index 0000000..987b654
Binary files /dev/null and b/static/charge/Unkonw.png differ
diff --git a/static/charge/addDevice.png b/static/charge/addDevice.png
new file mode 100644
index 0000000..0a5f92c
Binary files /dev/null and b/static/charge/addDevice.png differ
diff --git a/static/charge/charging.png b/static/charge/charging.png
new file mode 100644
index 0000000..b0701e5
Binary files /dev/null and b/static/charge/charging.png differ
diff --git a/static/charge/connectGun.png b/static/charge/connectGun.png
new file mode 100644
index 0000000..fd5e9d1
Binary files /dev/null and b/static/charge/connectGun.png differ
diff --git a/static/charge/error.png b/static/charge/error.png
new file mode 100644
index 0000000..7ad8276
Binary files /dev/null and b/static/charge/error.png differ
diff --git a/static/charge/finishing.png b/static/charge/finishing.png
new file mode 100644
index 0000000..a4e321a
Binary files /dev/null and b/static/charge/finishing.png differ
diff --git a/static/charge/insertGun.png b/static/charge/insertGun.png
new file mode 100644
index 0000000..7ac80ba
Binary files /dev/null and b/static/charge/insertGun.png differ
diff --git a/static/charge/noDevice.png b/static/charge/noDevice.png
new file mode 100644
index 0000000..2fa1a1f
Binary files /dev/null and b/static/charge/noDevice.png differ
diff --git a/static/charge/reserve.png b/static/charge/reserve.png
new file mode 100644
index 0000000..3f11c73
Binary files /dev/null and b/static/charge/reserve.png differ
diff --git a/static/charge/scanCode.png b/static/charge/scanCode.png
new file mode 100644
index 0000000..430a6bd
Binary files /dev/null and b/static/charge/scanCode.png differ
diff --git a/static/logo.png b/static/logo.png
new file mode 100644
index 0000000..b5771e2
Binary files /dev/null and b/static/logo.png differ
diff --git a/static/miniApp.png b/static/miniApp.png
new file mode 100644
index 0000000..8737512
Binary files /dev/null and b/static/miniApp.png differ
diff --git a/static/miniApp22.png b/static/miniApp22.png
new file mode 100644
index 0000000..79bd8fe
Binary files /dev/null and b/static/miniApp22.png differ
diff --git a/static/tabbar/chargeActive.png b/static/tabbar/chargeActive.png
new file mode 100644
index 0000000..1be9f60
Binary files /dev/null and b/static/tabbar/chargeActive.png differ
diff --git a/static/tabbar/chargeDefault.png b/static/tabbar/chargeDefault.png
new file mode 100644
index 0000000..2f2df6b
Binary files /dev/null and b/static/tabbar/chargeDefault.png differ
diff --git a/static/tabbar/homeActive.png b/static/tabbar/homeActive.png
new file mode 100644
index 0000000..254070a
Binary files /dev/null and b/static/tabbar/homeActive.png differ
diff --git a/static/tabbar/homeDefault.png b/static/tabbar/homeDefault.png
new file mode 100644
index 0000000..4b25dcd
Binary files /dev/null and b/static/tabbar/homeDefault.png differ
diff --git a/static/tabbar/mapActive.png b/static/tabbar/mapActive.png
new file mode 100644
index 0000000..4a687b8
Binary files /dev/null and b/static/tabbar/mapActive.png differ
diff --git a/static/tabbar/mapDefault.png b/static/tabbar/mapDefault.png
new file mode 100644
index 0000000..45263f9
Binary files /dev/null and b/static/tabbar/mapDefault.png differ
diff --git a/static/tabbar/myActive.png b/static/tabbar/myActive.png
new file mode 100644
index 0000000..5fdf9d2
Binary files /dev/null and b/static/tabbar/myActive.png differ
diff --git a/static/tabbar/myDefault.png b/static/tabbar/myDefault.png
new file mode 100644
index 0000000..6e82cff
Binary files /dev/null and b/static/tabbar/myDefault.png differ
diff --git a/static/tabbar/orderActive.png b/static/tabbar/orderActive.png
new file mode 100644
index 0000000..9021628
Binary files /dev/null and b/static/tabbar/orderActive.png differ
diff --git a/static/tabbar/orderDefault.png b/static/tabbar/orderDefault.png
new file mode 100644
index 0000000..62fa210
Binary files /dev/null and b/static/tabbar/orderDefault.png differ
diff --git a/static/tabbar/scanCode.png b/static/tabbar/scanCode.png
new file mode 100644
index 0000000..750f029
Binary files /dev/null and b/static/tabbar/scanCode.png differ
diff --git a/static/user.png b/static/user.png
new file mode 100644
index 0000000..1e07409
Binary files /dev/null and b/static/user.png differ
diff --git a/static/userDefault.png b/static/userDefault.png
new file mode 100644
index 0000000..a141aa3
Binary files /dev/null and b/static/userDefault.png differ
diff --git a/uni.scss b/uni.scss
new file mode 100644
index 0000000..b9249e9
--- /dev/null
+++ b/uni.scss
@@ -0,0 +1,76 @@
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+
+/* 颜色变量 */
+
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color:#333;//基本色
+$uni-text-color-inverse:#fff;//反色
+$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable:#c0c0c0;
+
+/* 背景颜色 */
+$uni-bg-color:#ffffff;
+$uni-bg-color-grey:#f8f8f8;
+$uni-bg-color-hover:#f1f1f1;//点击状态颜色
+$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color:#c8c7cc;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm:12px;
+$uni-font-size-base:14px;
+$uni-font-size-lg:16px;
+
+/* 图片尺寸 */
+$uni-img-size-sm:20px;
+$uni-img-size-base:26px;
+$uni-img-size-lg:40px;
+
+/* Border Radius */
+$uni-border-radius-sm: 2px;
+$uni-border-radius-base: 3px;
+$uni-border-radius-lg: 6px;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 5px;
+$uni-spacing-row-base: 10px;
+$uni-spacing-row-lg: 15px;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 4px;
+$uni-spacing-col-base: 8px;
+$uni-spacing-col-lg: 12px;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 文章场景相关 */
+$uni-color-title: #2C405A; // 文章标题颜色
+$uni-font-size-title:20px;
+$uni-color-subtitle: #555555; // 二级标题颜色
+$uni-font-size-subtitle:26px;
+$uni-color-paragraph: #3F536E; // 文章段落颜色
+$uni-font-size-paragraph:15px;
diff --git a/uni_modules/android-ble/changelog.md b/uni_modules/android-ble/changelog.md
new file mode 100644
index 0000000..3e934d8
--- /dev/null
+++ b/uni_modules/android-ble/changelog.md
@@ -0,0 +1,170 @@
+## 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
diff --git a/uni_modules/android-ble/encrypt b/uni_modules/android-ble/encrypt
new file mode 100644
index 0000000..efbd413
--- /dev/null
+++ b/uni_modules/android-ble/encrypt
@@ -0,0 +1 @@
+Ðúñÿô,ØÂtµ ÿ&À‘qØÜ:úSK�'«Äîv–Ç,¨”
òøFҳ›vQ�½P•ÌN�Ǯ'_hÙg47C·CÈTühjI›:žÄX~tˆėt0D©2§ÌiáÕ³ Óäy\d°ÁÀ%ý�óÆ؟ŒÏM@t*¾ëúòQã?ÿµsŠÒIVÉ%P
\ No newline at end of file
diff --git a/uni_modules/android-ble/old.md b/uni_modules/android-ble/old.md
new file mode 100644
index 0000000..142d3f6
--- /dev/null
+++ b/uni_modules/android-ble/old.md
@@ -0,0 +1,126 @@
+
+
+
+### 开启服务消息通知(旧)
+### 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)
+ }
+)
+~~~~
\ No newline at end of file
diff --git a/uni_modules/android-ble/package.json b/uni_modules/android-ble/package.json
new file mode 100644
index 0000000..e7ac47b
--- /dev/null
+++ b/uni_modules/android-ble/package.json
@@ -0,0 +1,111 @@
+{
+ "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": " \n \n \n \n \n "
+ },
+ "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=="
+ }
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/android-ble/readme.md b/uni_modules/android-ble/readme.md
new file mode 100644
index 0000000..d7bf662
--- /dev/null
+++ b/uni_modules/android-ble/readme.md
@@ -0,0 +1,1240 @@
+# android-ble
+
+`android-ble` 是一个面向 `uni-app` / `uni-app x` 的 BLE 低功耗蓝牙插件,支持 Android、iOS、鸿蒙、微信小程序。
+
+插件市场相关资源:
+
+- BLE 插件:`android-ble` 支付多端 安卓 ios 鸿蒙 微信小程序
+- 经典蓝牙 SPP 插件:[安卓Spp经典蓝牙插件](https://ext.dcloud.net.cn/plugin?id=26830)
+- Ble服务端插件[Android ble低功耗蓝牙周边服务蓝牙服务端](https://ext.dcloud.net.cn/plugin?id=22510)
+- `uni-app` 示例工程压缩包:`bt_uniapp.zip`
+- `uni-app x` 示例页面:`App.uvue`、`pages/Multi/Multi.uvue`
+
+## 功能概览
+
+- 支持 BLE 扫描、停止扫描、扫描结果过滤
+- 支持连接、断开、自动重连、连接超时设置
+- 支持扫描服务与特征值、自动识别可写/可通知 UUID
+- 支持通知订阅、indicate 订阅、主动读取、RSSI 读取、MTU 设置
+- 支持十六进制字符串、数字数组、`ArrayBuffer` 写入
+- 支持分包发送
+- 支持多蓝牙连接场景
+- iOS 支持按服务 UUID 获取系统已连接设备,并接管到当前 `BleLib` 实例
+- 支持监听系统蓝牙开关状态
+
+## 平台支持
+
+| 平台 | 支持情况 |
+| --- | --- |
+| uni-app Android | 支持 |
+| uni-app iOS | 支持 |
+| uni-app 鸿蒙 | 支持 |
+| uni-app 微信小程序 | 支持 |
+| uni-app x Android | 支持 |
+| uni-app x iOS | 支持 |
+| uni-app x 鸿蒙 | 支持 |
+| uni-app x 微信小程序 | 支持 |
+| Web | 不支持 |
+
+补充说明:
+
+- iOS 需 `iOS 13` 及以上版本。
+- Android / iOS / 鸿蒙运行原生能力时,建议使用自定义基座验证。
+- 微信小程序端按 `BleLib` 实例维护连接状态,适合一个对象对应一个蓝牙连接。
+- `getConnectedDevicesWithServices` 与 `redictToConnect` 是 iOS 专属方法,只有 iOS 可以调用,Android、鸿蒙、微信小程序、Web 都不支持调用。
+
+## 安装与运行
+
+### 插件试用与下载
+
+1. 在插件市场选择试用,并绑定目标项目 `appid`。
+2. 下载插件到本地项目。
+3. 在页面中从 `@/uni_modules/android-ble` 引入 `BleLib` 与相关类型。
+
+## 快速上手
+
+本节用于帮助开发者快速跑通 BLE 标准流程,下面给出 `uni-app x` 与 `uni-app` 双端示例。
+
+### 最短接入步骤
+
+1. 引入 `BleLib`,创建蓝牙对象实例。
+2. 调用 `openBtBluetooth` 初始化蓝牙适配器。
+3. 调用 `onStartScanBle` 开始扫描设备。
+4. 拿到目标设备地址后调用 `connect`。
+5. 连接成功后调用 `scanServices`。
+6. 调用 `getUUIDs` 查看自动识别出的可用 UUID 组合。
+7. 必要时调用 `setSelectUUID` 切换到目标 `service/notify/write` 组合。
+8. 调用 `onNotityBleData` 开启通知,或调用 `onIndicate` 开启 indicate 订阅。
+9. 调用 `sendData` 发送数据。
+10. 结束时调用 `close` 断开连接。
+
+### `uni-app x` 快速上手
+
+```ts
+import { BleLib, MyApiResult, BleScanResult, WNuuid, WriteData, NotityData, ScanPara } from "@/uni_modules/android-ble"
+
+let lib = new BleLib()
+let firstMac = ""
+
+lib.openBtBluetooth(function(res:MyApiResult){
+ console.log("openBtBluetooth", res)
+})
+
+lib.onBtOpenStateListener(function(res:MyApiResult){
+ console.log("bt state", res)
+})
+
+let scanPara:ScanPara = {
+ scantime:10 * 1000,
+ showEmptyName:0,
+ btNameFilter:"",
+ onScanResult:function(res:MyApiResult){
+ // type 为0 扫描成功 10001 表示无蓝牙权限 10002 表示蓝牙未开启
+ if(res.type == 0){
+ let scan = res.data as BleScanResult
+ console.log("scan", scan)
+ if(firstMac == ""){
+ firstMac = scan.device.address
+ }
+ }
+ },
+ scanComplate:function(){
+ console.log("scan complete")
+ if(firstMac != ""){
+ lib.connect(firstMac,false,function(res:MyApiResult){
+ console.log("connect", res)
+ // type 0 成功 10000 蓝牙连接失败 10001 蓝牙异常断开 10002 表示蓝牙未开启 10003表示无蓝牙权限 10004 表示蓝牙已经连接 10005 鸿蒙虚拟蓝牙地址无效
+ if(res.type == 0){
+ lib.scanServices(function(serviceRes:MyApiResult){
+ console.log("services", serviceRes)
+ if(serviceRes.type == 0){
+ let uuidList = lib.getUUIDs() as WNuuid[]
+ console.log("uuidList", uuidList)
+ let uuids = lib.getUUIDs()
+ console.log("uuids", uuids)
+ if(uuids.length > 0){
+ lib.setSelectUUID(0)
+ lib.onNotityBleData(
+ lib.getServiceUUID(),
+ lib.getNotityUUID(),
+ true,
+ function(notifyRes:MyApiResult){
+ // type 为0 表示通知数据 1000 表示订阅成功 1001 表示订阅失败
+ if(notifyRes.type == 0){
+ let data = notifyRes.data as NotityData
+ console.log("notify", data)
+ }
+ }
+ )
+ let writeData:WriteData = {
+ serviceId:lib.getServiceUUID(),
+ characteristicId:lib.getwriteUUID(),
+ fenbao:false,
+ hexStrData:"FF001122"
+ }
+ lib.sendData(writeData,function(sendRes:MyApiResult){
+ console.log("send", sendRes)
+ })
+ let readData:WriteData = {
+ serviceId:lib.getServiceUUID(),
+ characteristicId:lib.getwriteUUID(),
+ fenbao:false,
+ data:[0xff,0x00,0x11,0x22] as number[]
+ }
+ lib.sendData(readData,function(sendRes:MyApiResult){
+ console.log("send", sendRes)
+ })
+ }
+ }
+ })
+ }
+ })
+ }
+ }
+}
+
+lib.onStartScanBle(scanPara)
+```
+
+### `uni-app` 快速上手
+
+```js
+import { BleLib } from "@/uni_modules/android-ble"
+
+var lib = new BleLib()
+var firstMac = ""
+
+lib.openBtBluetooth(function(res){
+ console.log("openBtBluetooth", res)
+})
+
+lib.onBtOpenStateListener(function(res){
+ console.log("bt state", res)
+})
+
+lib.onStartScanBle({
+ scantime:10 * 1000,
+ showEmptyName:0,
+ btNameFilter:"",
+ onScanResult:function(res){
+ // type 为0 扫描成功 10001 表示无蓝牙权限 10002 表示蓝牙未开启
+ if(res.type == 0){
+ var scan = res.data
+ console.log("scan", scan)
+ if(firstMac == ""){
+ firstMac = scan.device.address
+ }
+ }
+ },
+ scanComplate:function(){
+ console.log("scan complete")
+ if(firstMac != ""){
+ lib.connect(firstMac,false,function(res){
+ console.log("connect", res)
+ // type 0 成功 10000 蓝牙连接失败 10001 蓝牙异常断开 10002 表示蓝牙未开启 10003表示无蓝牙权限 10004 表示蓝牙已经连接 10005 鸿蒙虚拟蓝牙地址无效
+ if(res.type == 0){
+ lib.scanServices(function(serviceRes){
+ console.log("services", serviceRes)
+ var uuids = lib.getUUIDs()
+ console.log("uuids", uuids)
+ if(uuids.length > 0){
+ lib.setSelectUUID(0)
+ lib.onNotityBleData(
+ lib.getServiceUUID(),
+ lib.getNotityUUID(),
+ true,
+ function(notifyRes){
+ // type 为0 表示通知数据 1000 表示订阅成功 1001 表示订阅失败
+ console.log("notify", notifyRes)
+ }
+ )
+ lib.sendData({
+ serviceId:lib.getServiceUUID(),
+ characteristicId:lib.getwriteUUID(),
+ fenbao:false,
+ hexStrData:"FF001122"
+ },function(sendRes){
+ console.log("send", sendRes)
+ })
+ }
+ })
+ }
+ })
+ }
+ }
+})
+```
+
+### 自定义基座
+
+1. 在 HBuilderX 中执行云打包并制作自定义基座。
+2. 基座制作完成后,运行到手机或模拟器。
+3. 选择运行到 Android App 基座,并选择自定义基座。
+4. 如果之前安装过旧基座,请先卸载旧版本后再安装。
+
+### iOS 蓝牙权限配置
+
+iOS 端除了引入插件,还需要在项目 `manifest.json` 的 `app-ios.distribute.privacyDescription` 中配置蓝牙用途说明,否则首次访问蓝牙时可能不弹权限框,系统设置里也不会出现应用的蓝牙权限开关。
+
+参考配置:
+
+```json
+{
+ "app-ios": {
+ "distribute": {
+ "privacyDescription": {
+ "NSBluetoothAlwaysUsageDescription": "应用需要使用蓝牙扫描、连接和通信外设设备",
+ "NSBluetoothPeripheralUsageDescription": "应用需要使用蓝牙扫描、连接和通信外设设备"
+ }
+ }
+ }
+}
+```
+
+说明:
+
+- 修改后需要重新云打包或重新生成 iOS 安装包才会生效。
+- 建议卸载旧包后再安装新包。
+- iOS 一般在首次扫描、连接等真正访问蓝牙时弹权限框,不一定在应用启动瞬间弹出。
+
+## 推荐调用流程
+
+建议按下面顺序接入:
+
+1. 创建 `BleLib` 实例。
+2. 调用 `openBtBluetooth` 初始化蓝牙适配器。
+3. 可选:调用 `onBtOpenStateListener` 监听系统蓝牙开关状态。
+4. 调用 `onStartScanBle` 或 `startScanBleDevice` 扫描设备。
+5. 拿到设备地址后调用 `connect` 连接。
+6. 连接成功后调用 `scanServices` 扫描服务。
+7. 通过 `getUUIDs` 获取自动识别出的可用服务组合,必要时调用 `setSelectUUID` 切换。
+8. 调用 `onNotityBleData` 开启通知。
+9. 调用 `sendData` 发送数据,或调用 `onReadData` 主动读取。
+10. 完成后调用 `close` 断开连接。
+
+iOS 已连接设备恢复场景:
+
+- 先调用 `getConnectedDevicesWithServices` 按服务 UUID 获取系统当前已连接设备。
+- 再把返回结果中的 `device.address` 传给 `redictToConnect`,让当前 `BleLib` 实例接管连接。
+
+多设备场景建议:
+
+- 每个蓝牙连接使用一个独立的 `BleLib` 实例。
+- 可参考 `pages/Multi/Multi.uvue` 的多连接演示页面。
+
+## 主要类型
+
+### `ScanPara`
+
+扫描参数,常用字段如下:
+
+- `btNameFilter`:按名称关键字过滤
+- `fliterNames`:按多个名称前缀过滤
+- `scantime`:扫描时长,单位毫秒
+- `showEmptyName`:是否返回空名称设备,`0` 表示不返回,`1` 表示返回
+- `iosFilterUUIDs`:按 service UUID 数组过滤扫描,当前支持 iOS、Android、鸿蒙、微信小程序
+- `onScanResult`:扫描结果回调
+- `scanComplate`:扫描结束回调
+
+### `WNuuid`
+
+自动识别出的可用服务组合:
+
+- `serviceId`
+- `wuuid`
+- `nuuid`
+
+### `WriteData`
+
+写入参数:
+
+- `serviceId`:服务 UUID
+- `characteristicId`:写入特征 UUID
+- `writeType`:写入类型,`0` 为 `WRITE`,`1` 为 `WRITE_NO_RESPONSE`
+- `buffer`:原始二进制数据,优先级最高
+- `data`:数字数组
+- `hexStrData`:十六进制字符串
+- `fenbao`:是否自动分包发送
+
+写入优先级:`buffer` > `data` > `hexStrData`
+
+### `MyApiResult`
+
+统一回调结构:
+
+- `type`:状态码
+- `message`:状态说明
+- `data`:返回数据
+
+### `NotityData`
+
+通知 / indicate / 读取回调数据结构:
+
+- `data`:字节数组
+- `mac`:设备地址,部分平台返回
+- `serviceId`:服务 UUID
+- `characteristicsId`:特征 UUID
+
+## API 清单
+
+### 适配器与状态
+
+- `isEnabled`:检测蓝牙是否开启
+- `openBtBluetooth`:初始化蓝牙适配器
+- `openBluetooth`:打开或关闭蓝牙
+- `openBluetoothSettings`:打开蓝牙设置页
+- `onBtOpenStateListener`:监听系统蓝牙开关状态
+
+### 扫描相关
+
+- `onStartScanBle`:新版扫描接口,推荐使用
+- `startScanBleDevice`:旧版扫描接口,兼容保留
+- `onScanDataListener`:监听广播信号变化
+- `stopScanBle`:停止扫描
+
+### 连接相关
+
+- `connect`:连接蓝牙
+- `close`:断开蓝牙
+- `isConnected`:是否已连接
+- `getConnectMac`:获取当前已连接设备地址
+- `setBtConnectTimeout`:设置连接超时时间
+- `setBtAutoConnectTime`:设置自动连接间隔
+- `cancelAutoConnectBt`:取消自动重连
+
+### iOS 专属连接恢复
+
+- 本小节两个方法只有 iOS 可以调用,其他平台都不支持。
+
+- `getConnectedDevicesWithServices`:按服务 UUID 获取系统当前已连接设备
+- `redictToConnect`:接管 `getConnectedDevicesWithServices` 返回的已连接设备
+
+### 服务与特征值
+
+- `scanServices`:扫描当前连接设备的服务与特征值
+- `getUUIDs`:获取自动识别出的可用 `service + notify + write` 组合
+- `setSelectUUID`:切换当前使用的 UUID 组合
+- `getSericUUID`:获取当前服务 UUID,旧命名,兼容保留
+- `getServiceUUID`:获取当前服务 UUID
+- `getNotityUUID`:获取当前通知 UUID
+- `getWriteUUID`:获取当前写入 UUID
+- `getwriteUUID`:获取当前写入 UUID,旧命名,兼容保留
+
+### 数据通信
+
+- `onNotityBleData`:开启或关闭通知订阅
+- `onIndicate`:开启 indicate 订阅并监听设备主动上报数据
+- `onReadData`:主动读取蓝牙数据
+- `sendData`:写入数据,支持字符串、数组、`ArrayBuffer`
+- `readRssi`:读取 RSSI
+- `setMtu`:设置 MTU,仅 Android 有效
+- `setPhy2MMode`:设置 Android PHY 2M 模式,仅 Android 有效
+
+### 调试与线程
+
+- `setDebugMode`:打开调试日志
+- `setThreadTypeName`:设置回调线程名
+
+## 方法说明与调用示例
+
+本节按方法逐个列出用途、参数、返回值与调用示例,并同时给出 `uni-app x` 与 `uni-app` 参考写法。
+
+### `isEnabled`
+
+- 用途:检测当前系统蓝牙是否处于开启状态。
+- 参数:无。
+- 返回:`boolean`。
+- 常见场景:进入页面后先判断是否需要提示用户开蓝牙。
+- `uni-app x` / `uni-app`
+
+```js
+var open = lib.isEnabled()
+console.log(open)
+```
+
+### `openBtBluetooth`
+
+- 用途:初始化蓝牙适配器,并触发回调告知初始化结果。
+- 参数:`callback`。
+- 返回:无,结果通过回调返回。
+- iOS 说明:iOS 端该方法主要用于初始化和检查蓝牙状态,不会像 Android 一样主动打开系统蓝牙。
+- iOS 回调说明:`type=0` 表示蓝牙已开启,`type=10002` 表示系统蓝牙未开启,`type=10003` 表示蓝牙权限被拒绝、受限或待确认。
+- 常见场景:扫描和连接前先调用一次。
+- `uni-app x`
+
+```ts
+lib.openBtBluetooth(function(res:MyApiResult){
+ // type 为0 表示初始化成功 其它值可结合 message 查看失败原因
+ console.log(res)
+})
+```
+
+- `uni-app`
+
+```js
+lib.openBtBluetooth(function(res){
+ // type 为0 表示初始化成功 其它值可结合 message 查看失败原因
+ console.log(res)
+})
+```
+
+### `openBluetooth`
+
+- 用途:按布尔值控制打开或关闭蓝牙。
+- 参数:`on:boolean`。
+- 返回:无。
+- 常见场景:Android 或小程序场景下快捷控制蓝牙状态。
+- `uni-app x` / `uni-app`
+
+```js
+lib.openBluetooth(true)
+lib.openBluetooth(false)
+```
+
+### `openBluetoothSettings`
+
+- 用途:跳转到系统蓝牙相关设置页。
+- 参数:无。
+- 返回:无。
+- iOS 说明:iOS 端会跳转到当前应用的系统设置页,适合在用户拒绝蓝牙权限后引导其手动开启。
+- 常见场景:蓝牙关闭且需要引导用户手动开启时使用。
+- `uni-app x` / `uni-app`
+
+```js
+lib.openBluetoothSettings()
+```
+
+- 推荐搭配 `openBtBluetooth` 使用
+
+```js
+lib.openBtBluetooth(function(res){
+ if(res.type == 10003){
+ // iOS 用户拒绝蓝牙权限后,可引导用户跳转系统设置手动开启
+ lib.openBluetoothSettings()
+ }
+})
+```
+
+### `onBtOpenStateListener`
+
+- 用途:监听系统蓝牙开关状态变化。
+- 参数:`callback`。
+- 返回:无,结果通过回调返回。
+- 常见场景:页面常驻监听蓝牙状态变化。
+- `uni-app x`
+
+```ts
+lib.onBtOpenStateListener(function(res:MyApiResult){
+ // data 为 true 表示蓝牙开启 false 表示蓝牙关闭
+ console.log(res)
+})
+```
+
+- `uni-app`
+
+```js
+lib.onBtOpenStateListener(function(res){
+ // data 为 true 表示蓝牙开启 false 表示蓝牙关闭
+ console.log(res)
+})
+```
+
+### `onStartScanBle`
+
+- 用途:新版 BLE 扫描接口,支持过滤条件与扫描完成回调。
+- 参数:`ScanPara`。
+- 返回:无,扫描结果通过 `onScanResult` 返回。
+- 常见场景:绝大多数新项目建议优先使用该接口。
+- 补充说明:如需按服务 UUID 过滤扫描结果,可通过 `iosFilterUUIDs` 传入 UUID 数组,当前支持 iOS、Android、鸿蒙、微信小程序。
+- `uni-app x`
+
+```ts
+let scanPara:ScanPara = {
+ scantime:10 * 1000,
+ showEmptyName:0,
+ btNameFilter:"",
+ onScanResult:function(res:MyApiResult){
+ // type 为0 扫描成功 10001 表示无蓝牙权限 10002 表示蓝牙未开启
+ if(res.type == 0){
+ let scan = res.data as BleScanResult
+ console.log(scan)
+ }
+ },
+ scanComplate:function(){
+ console.log("scan complete")
+ }
+} as ScanPara
+lib.onStartScanBle(scanPara)
+```
+
+- `uni-app`
+
+```js
+lib.onStartScanBle({
+ scantime:10 * 1000,
+ showEmptyName:0,
+ btNameFilter:"",
+ onScanResult:function(res){
+ // type 为0 扫描成功 10001 表示无蓝牙权限 10002 表示蓝牙未开启
+ if(res.type == 0){
+ var scan = res.data
+ console.log(scan)
+ }
+ },
+ scanComplate:function(){
+ console.log("scan complete")
+ }
+})
+```
+
+### `startScanBleDevice`
+
+- 用途:旧版扫描接口,保留给旧项目兼容使用。
+- 参数:扫描时长、回调函数。
+- 返回:无,结果通过回调返回。
+- 常见场景:已有老代码迁移成本较高时继续使用。
+- `uni-app x`
+
+```ts
+lib.startScanBleDevice(15000,function(res:MyApiResult){
+ // type 为0 扫描成功 10001 表示无蓝牙权限 10002 表示蓝牙未开启
+ if(res.type == 0){
+ let scan = res.data as BleScanResult
+ console.log(scan)
+ }
+})
+```
+
+- `uni-app`
+
+```js
+lib.startScanBleDevice(15000,function(res){
+ // type 为0 扫描成功 10001 表示无蓝牙权限 10002 表示蓝牙未开启
+ if(res.type == 0){
+ var scan = res.data
+ console.log(scan)
+ }
+})
+```
+
+### `onScanDataListener`
+
+- 用途:实时监听扫描过程中的广播变化与 RSSI 数据。
+- 参数:`callback`。
+- 返回:无,数据通过回调返回。
+- 常见场景:需要显示信号强度变化或原始广播数据。
+- `uni-app x`
+
+```ts
+lib.onScanDataListener(function(res:MyApiResult){
+ let data = res.data as ScanRssiBR
+ console.log(data)
+})
+```
+
+- `uni-app`
+
+```js
+lib.onScanDataListener(function(res){
+ console.log(res)
+})
+```
+
+### `stopScanBle`
+
+- 用途:停止当前 BLE 扫描。
+- 参数:无。
+- 返回:无。
+- 常见场景:页面离开、用户手动停止扫描、连接前停止扫描。
+- `uni-app x` / `uni-app`
+
+```js
+lib.stopScanBle()
+```
+
+### `connect`
+
+- 用途:连接指定蓝牙设备。
+- 参数:设备地址、是否自动连接、回调函数。
+- 返回:无,结果通过回调返回。
+- 回调 `type` 说明:`0` 成功,`10000` 蓝牙连接失败,`10001` 蓝牙异常断开,`10002` 表示蓝牙未开启,`10003` 表示无蓝牙权限,`10004` 表示蓝牙已经连接,`10005` 表示鸿蒙虚拟蓝牙地址无效。
+- 常见场景:用户点击扫描结果后发起连接。
+- iOS 说明:若你希望控制应用退到后台后,连接成功、连接断开、收到通知时系统是否弹出唤回应用提示,请在 `connect` 前先调用 `setIOSConnectNotifyAlertEnabled`。
+- `uni-app x`
+
+```ts
+lib.connect(device.device.address,false,function(res:MyApiResult){
+ console.log(res)
+ // type 0 成功 10000 蓝牙连接失败 10001 蓝牙异常断开 10002 表示蓝牙未开启 10003表示无蓝牙权限 10004 表示蓝牙已经连接 10005 鸿蒙虚拟蓝牙地址无效
+ if(res.type == 0){
+
+ }else{
+
+ }
+})
+```
+
+### `getConnectedDevicesWithServices`
+
+- 用途:按服务 UUID 查询 iOS 系统当前已经连接的 BLE 外设。
+- 参数:`string[]`,传入目标设备对应的服务 UUID 数组。
+- 返回:`MyApiResult`,`data` 为 `BleScanResult[]`。
+- 平台说明:只有 iOS 可以调用;Android、鸿蒙、微信小程序、Web 都不支持该方法。
+- 返回说明:返回结果中的 `device.address` 为 iOS 外设标识 `identifier.uuidString`,后续需要原样传给 `redictToConnect`。
+- 常见场景:App 重启后恢复已连接设备、需要接管系统层已保持连接的外设。
+- `uni-app x` / `uni-app`(仅 iOS)
+
+```js
+var serviceIds = ["FFF0"]
+var res = lib.getConnectedDevicesWithServices(serviceIds)
+console.log(res)
+
+if(res.type == 0){
+ var list = res.data || []
+ if(list.length > 0){
+ console.log(list[0].device.address)
+ }
+}
+```
+
+### `redictToConnect`
+
+- 用途:把 `getConnectedDevicesWithServices` 查到的已连接 iOS 外设接管到当前 `BleLib` 实例。
+- 参数:设备地址、回调函数。
+- 返回:无,结果通过回调返回。
+- 平台说明:只有 iOS 可以调用;Android、鸿蒙、微信小程序、Web 都不支持该方法。
+- 调用前提:必须先调用 `getConnectedDevicesWithServices`,并使用其返回结果中的 `device.address` 作为第一个参数。
+- 回调 `type` 说明:`0` 表示接管成功,`10001` 表示设备已断开。
+- 常见场景:业务层拿到系统已连接设备后,继续调用 `scanServices`、`onNotityBleData`、`sendData` 等插件能力。
+- `uni-app x` / `uni-app`(仅 iOS)
+
+```js
+var res = lib.getConnectedDevicesWithServices(["FFF0"])
+if(res.type == 0){
+ var list = res.data || []
+ if(list.length > 0){
+ var deviceId = list[0].device.address
+ lib.redictToConnect(deviceId,function(connectRes){
+ console.log(connectRes)
+ if(connectRes.type == 0){
+ lib.scanServices(function(serviceRes){
+ console.log(serviceRes)
+ })
+ }
+ })
+ }
+}
+```
+
+### `setIOSConnectNotifyAlertEnabled`
+
+- 用途:控制 iOS 端在应用进入后台后,连接成功、连接断开、收到通知时系统是否显示唤回应用提示。
+- 参数:`enabled:boolean`。
+- 返回:无。
+- 默认值:`true`,即默认允许系统显示相关提示。
+- 生效时机:必须在 `connect` 前调用;连接发起后再修改,不影响当前这次连接。
+- 平台说明:仅 iOS 生效,Android、鸿蒙、微信小程序为空实现,用于保持多端调用一致。
+- `uni-app x` / `uni-app`
+
+```js
+lib.setIOSConnectNotifyAlertEnabled(false)
+lib.connect(device.device.address,false,function(res){
+ console.log(res)
+})
+```
+
+- `uni-app`
+
+```js
+lib.connect(device.device.address,false,function(res){
+ console.log(res)
+ // type 0 成功 10000 蓝牙连接失败 10001 蓝牙异常断开 10002 表示蓝牙未开启 10003表示无蓝牙权限 10004 表示蓝牙已经连接 10005 鸿蒙虚拟蓝牙地址无效
+ if(res.type == 0){
+
+ }else{
+
+ }
+})
+```
+
+### `close`
+
+- 用途:断开当前蓝牙连接并释放连接状态。
+- 参数:无。
+- 返回:无。
+- 常见场景:页面卸载、切换设备、手动断开。
+- `uni-app x` / `uni-app`
+
+```js
+lib.close()
+```
+
+### `isConnected`
+
+- 用途:判断当前对象是否已连接蓝牙设备。
+- 参数:无。
+- 返回:`boolean`。
+- 常见场景:发送、读取、订阅前做前置判断。
+- `uni-app x` / `uni-app`
+
+```js
+var isconnect = lib.isConnected()
+console.log(isconnect)
+```
+
+### `getConnectMac`
+
+- 用途:获取当前已连接设备地址。
+- 参数:无。
+- 返回:设备地址字符串,未连接时通常为空字符串。
+- 常见场景:业务层记录当前连接目标。
+- `uni-app x` / `uni-app`
+
+```js
+var deviceId = lib.getConnectMac()
+console.log(deviceId)
+```
+
+### `setBtConnectTimeout`
+
+- 用途:设置蓝牙连接超时时间。
+- 参数:超时时间,单位毫秒。
+- 返回:无。
+- 常见场景:外设连接较慢,需要调整默认超时值。
+- `uni-app x` / `uni-app`
+
+```js
+lib.setBtConnectTimeout(12000)
+```
+
+### `setBtAutoConnectTime`
+
+- 用途:设置自动重连的时间间隔。
+- 参数:时间间隔,单位毫秒。
+- 返回:无。
+- 常见场景:需要控制自动连接重试节奏时使用。
+- `uni-app x` / `uni-app`
+
+```js
+lib.setBtAutoConnectTime(12000)
+```
+
+### `cancelAutoConnectBt`
+
+- 用途:取消自动重连。
+- 参数:无。
+- 返回:无。
+- 常见场景:用户主动退出当前连接流程。
+- `uni-app x` / `uni-app`
+
+```js
+lib.cancelAutoConnectBt()
+```
+
+### `scanServices`
+
+- 用途:扫描当前连接设备的服务与特征值。
+- 参数:回调函数。
+- 返回:无,服务列表通过回调返回。
+- 常见场景:连接成功后必须执行一次,供后续通知与写入使用。
+- `uni-app x`
+
+```ts
+lib.scanServices(function(resSevice:MyApiResult){
+ console.log(resSevice)
+ if(resSevice.type == 0){
+ let d = resSevice.data as BleServices[]
+ console.log(d)
+ }
+})
+```
+
+- `uni-app`
+
+```js
+lib.scanServices(function(resSevice){
+ console.log(resSevice)
+})
+```
+
+### `getUUIDs`
+
+- 用途:获取插件自动识别出的可用 `service + notify + write` 组合列表。
+- 参数:无。
+- 返回:`WNuuid[]`。
+- 常见场景:设备存在多个服务组合时供业务层选择。
+- `uni-app x` / `uni-app`
+
+```js
+var uuids = lib.getUUIDs()
+console.log(uuids)
+```
+
+### `setSelectUUID`
+
+- 用途:切换当前使用的 UUID 组合。
+- 参数:组合下标。
+- 返回:无。
+- 常见场景:自动识别到多组候选服务时手动切换。
+- `uni-app x` / `uni-app`
+
+```js
+lib.setSelectUUID(0)
+```
+
+### `getSericUUID`
+
+- 用途:获取当前选中的服务 UUID,旧命名接口。
+- 参数:无。
+- 返回:`string`。
+- 常见场景:兼容旧项目代码。
+- `uni-app x` / `uni-app`
+
+```js
+console.log(lib.getSericUUID())
+```
+
+### `getServiceUUID`
+
+- 用途:获取当前选中的服务 UUID。
+- 参数:无。
+- 返回:`string`。
+- 常见场景:写入、订阅、主动读取前获取服务 UUID。
+- `uni-app x` / `uni-app`
+
+```js
+console.log(lib.getServiceUUID())
+```
+
+### `getNotityUUID`
+
+- 用途:获取当前选中的通知特征 UUID。
+- 参数:无。
+- 返回:`string`。
+- 常见场景:开启通知前读取当前通知 UUID。
+- `uni-app x` / `uni-app`
+
+```js
+console.log(lib.getNotityUUID())
+```
+
+### `getWriteUUID`
+
+- 用途:获取当前选中的写入特征 UUID。
+- 参数:无。
+- 返回:`string`。
+- 常见场景:发送数据前获取写入 UUID。
+- `uni-app x` / `uni-app`
+
+```js
+console.log(lib.getWriteUUID())
+```
+
+### `getwriteUUID`
+
+- 用途:获取当前选中的写入特征 UUID,旧命名接口。
+- 参数:无。
+- 返回:`string`。
+- 常见场景:兼容旧项目代码。
+- `uni-app x` / `uni-app`
+
+```js
+console.log(lib.getwriteUUID())
+```
+
+### `onNotityBleData`
+
+- 用途:开启或关闭通知订阅,并监听设备主动上报数据。
+- 参数:服务 UUID、通知 UUID、开关状态、回调函数。
+- 返回:无,订阅状态与通知数据通过回调返回。
+- 常见场景:连接成功并完成服务扫描后开启通知。
+- `uni-app x`
+
+```ts
+lib.onNotityBleData(
+ lib.getSericUUID(),
+ lib.getNotityUUID(),
+ true,
+ function(res:MyApiResult){
+ // type 为0 表示通知数据 1000 表示订阅成功 1001 表示订阅失败
+ if(res.type == 0){
+ let data = res.data as NotityData
+ console.log(data)
+ }
+ }
+)
+```
+
+### `onIndicate`
+
+- 用途:开启 indicate 订阅,并监听设备主动上报的 indicate 数据。
+- 参数:服务 UUID、indicate 特征 UUID、回调函数。
+- 返回:无,订阅状态与 indicate 数据通过回调返回。
+- 常见场景:设备特征值属性为 `INDICATE`,需要带确认机制的数据上报时使用。
+- 平台说明:Android、iOS、鸿蒙已支持真实 indicate 订阅;微信小程序端当前未实现该接口。
+- `uni-app x`
+
+```ts
+lib.onIndicate(
+ "serviceUUID",
+ "indicateUUID",
+ function(res:MyApiResult){
+ // type 为0 表示 indicate 数据 1000 表示订阅成功 1001 表示订阅失败
+ if(res.type == 0){
+ let data = res.data as NotityData
+ console.log(data)
+ }
+ }
+)
+```
+
+- `uni-app`
+
+```js
+lib.onIndicate(
+ "serviceUUID",
+ "indicateUUID",
+ function(res){
+ // type 为0 表示 indicate 数据 1000 表示订阅成功 1001 表示订阅失败
+ console.log(res)
+ }
+)
+```
+
+- `uni-app`
+
+```js
+lib.onNotityBleData(
+ lib.getSericUUID(),
+ lib.getNotityUUID(),
+ true,
+ function(res){
+ // type 为0 表示通知数据 1000 表示订阅成功 1001 表示订阅失败
+ console.log(res)
+ }
+)
+```
+
+### `onReadData`
+
+- 用途:主动读取某个特征值的数据。
+- 参数:服务 UUID、读取 UUID、回调函数。
+- 返回:无,读取结果通过回调返回。
+- 常见场景:设备不主动通知,需要业务端手动拉取数据。
+- `uni-app x`
+
+```ts
+lib.onReadData("sericeUUID","readUUID",function(res:MyApiResult){
+ console.log(res)
+})
+```
+
+- `uni-app`
+
+```js
+lib.onReadData("sericeUUID","readUUID",function(res){
+ console.log(res)
+})
+```
+
+### `sendData`
+
+- 用途:向指定特征值写入数据。
+- 参数:`WriteData`、回调函数。
+- 返回:无,写入结果通过回调返回。
+- 常见场景:发送控制指令、配置参数、透传数据。
+- `uni-app x` 发送 16 进制字符串
+
+```ts
+let writeData:WriteData = {
+ serviceId:lib.getServiceUUID(),
+ characteristicId:lib.getwriteUUID(),
+ fenbao:false,
+ hexStrData:"FF001122"
+}
+lib.sendData(writeData,function(res:MyApiResult){
+ // 发送结果可结合 type 和 message 判断是否成功
+ console.log(res)
+})
+```
+
+- `uni-app` 发送 16 进制字符串
+
+```js
+lib.sendData({
+ serviceId:lib.getServiceUUID(),
+ characteristicId:lib.getwriteUUID(),
+ fenbao:false,
+ hexStrData:"FF001122"
+},function(res){
+ console.log(res)
+})
+```
+
+- `uni-app x` 发送字节数组
+
+```ts
+let writeBytes:WriteData = {
+ serviceId:lib.getServiceUUID(),
+ characteristicId:lib.getwriteUUID(),
+ fenbao:false,
+ data:[0xff,0x00,0x11,0x22] as number[]
+}
+lib.sendData(writeBytes,function(res:MyApiResult){
+ // 发送结果可结合 type 和 message 判断是否成功
+ console.log(res)
+})
+```
+
+- `uni-app` 发送字节数组
+
+```js
+lib.sendData({
+ serviceId:lib.getServiceUUID(),
+ characteristicId:lib.getwriteUUID(),
+ fenbao:false,
+ data:[0xff,0x00,0x11,0x22]
+},function(res){
+ console.log(res)
+})
+```
+
+- `uni-app x` 发送 `ArrayBuffer`
+
+```ts
+let buffer = new ArrayBuffer(4)
+let view = new Uint8Array(buffer)
+view[0] = 0xff
+view[1] = 0x00
+view[2] = 0x11
+view[3] = 0x22
+let writeBuffer:WriteData = {
+ serviceId:lib.getServiceUUID(),
+ characteristicId:lib.getwriteUUID(),
+ fenbao:false,
+ buffer:buffer
+}
+lib.sendData(writeBuffer,function(res:MyApiResult){
+ console.log(res)
+})
+```
+
+### `readRssi`
+
+- 用途:读取当前连接设备的 RSSI 信号强度。
+- 参数:回调函数。
+- 返回:无,读取结果通过回调返回。
+- 常见场景:显示连接质量或调试信号强度。
+- `uni-app x`
+
+```ts
+lib.readRssi(function(res:MyApiResult){
+ console.log(res)
+})
+```
+
+- `uni-app`
+
+```js
+lib.readRssi(function(res){
+ console.log(res)
+})
+```
+
+### `setMtu`
+
+- 用途:设置 BLE 连接的 MTU。
+- 参数:MTU 值、回调函数。
+- 返回:无,设置结果通过回调返回。
+- 常见场景:需要发送较大数据包时在 Android 端提高 MTU。
+- `uni-app x`
+
+```ts
+lib.setMtu(512,function(res:MyApiResult){
+ console.log(res)
+})
+```
+
+- `uni-app`
+
+```js
+lib.setMtu(512,function(res){
+ console.log(res)
+})
+```
+
+### `setPhy2MMode`
+
+- 用途:切换 Android BLE PHY 2M 模式。
+- 参数:无。
+- 返回:无。
+- 常见场景:Android 设备支持高物理层速率时启用。
+- `uni-app x` / `uni-app`
+
+```js
+lib.setPhy2MMode()
+```
+
+### `setDebugMode`
+
+- 用途:打开或关闭插件内部调试日志。
+- 参数:`debug:boolean`。
+- 返回:无。
+- 常见场景:联调阶段查看内部扫描、连接、通知过程。
+- `uni-app x` / `uni-app`
+
+```js
+lib.setDebugMode(true)
+lib.setDebugMode(false)
+```
+
+### `setThreadTypeName`
+
+- 用途:设置插件回调线程名。
+- 参数:线程名字符串。
+- 返回:无。
+- 常见场景:需要和现有线程调度策略保持一致时使用。
+- `uni-app x` / `uni-app`
+
+```js
+lib.setThreadTypeName("main")
+```
+
+## 常用返回码
+
+不同接口返回码略有差异,常见约定如下:
+
+| type | 说明 |
+| --- | --- |
+| `0` | 成功 |
+| `1000` | 通知或 indicate 订阅成功 |
+| `1001` | 通知或 indicate 订阅失败,或部分平台表示蓝牙异常断开 |
+| `10000` | 连接失败、读取失败或通用失败 |
+| `10001` | 蓝牙异常断开,或部分场景表示无权限 |
+| `10002` | 蓝牙未开启 |
+| `10003` | 无蓝牙权限 |
+| `10004` | 蓝牙已经连接 |
+| `10005` | 部分平台用于地址无效或未连接 |
+
+说明:不同平台和不同接口对错误码的语义可能略有差异,建议结合 `message` 一起判断。
+
+## 平台注意事项
+
+### Android
+
+- 运行原生 BLE 能力建议使用自定义基座。
+- `setMtu` 与 `setPhy2MMode` 为 Android 特有能力。
+- Android 12 及以上系统请注意蓝牙权限授权。
+- Android 端支持通过 `iosFilterUUIDs` 按服务 UUID 过滤扫描。
+- Android 端现已支持真实 `onIndicate` 订阅与数据回调。
+
+### iOS
+
+- 仅支持 `iOS 13` 及以上。
+- iOS 扫描在部分系统版本下建议配合 `iosFilterUUIDs` 使用。
+- 多连接与多通知场景已做兼容,但仍建议每个连接使用独立实例。
+- 若应用进入后台后不希望系统在连接、断开、通知事件发生时弹出唤回应用提示,可在连接前调用 `setIOSConnectNotifyAlertEnabled(false)`。
+- 该开关底层对应 iOS 连接参数中的 `CBConnectPeripheralOptionNotifyOnConnectionKey`、`CBConnectPeripheralOptionNotifyOnDisconnectionKey`、`CBConnectPeripheralOptionNotifyOnNotificationKey`。
+- iOS 端现已支持真实 `onIndicate` 订阅与数据回调。
+- `getConnectedDevicesWithServices` 与 `redictToConnect` 只有 iOS 可以调用,Android、鸿蒙、微信小程序、Web 都不支持;推荐按“先查已连接设备,再传 `device.address` 接管连接”的顺序调用。
+
+### 鸿蒙
+
+- 部分虚拟蓝牙地址场景可能返回特定错误码。
+- 建议在真机环境验证扫描、连接、RSSI 与发送流程。
+- 鸿蒙端支持通过 `iosFilterUUIDs` 按服务 UUID 过滤扫描。
+- 鸿蒙端现已支持真实 `onIndicate` 订阅与数据回调。
+
+### 微信小程序
+
+- 需在微信小程序蓝牙权限与机型能力允许的前提下运行。
+- 建议一个 `BleLib` 实例绑定一个设备连接。
+- 支持多对象多蓝牙连接。
+- 微信小程序端支持通过 `iosFilterUUIDs` 按服务 UUID 过滤扫描。
+
+## 示例入口
+
+- `App.uvue`:基础单连接示例
+- `pages/Multi/Multi.uvue`:多连接、UUID 选择、通知与发送示例
+
+如果你需要查看完整调用方式,优先参考项目内这两个页面的实际示例。
+
+## 旧接口说明
+
+历史接口与旧版示例已保留在 `old.md` 中,主要用于兼容旧项目:
+
+- `onNotityReadBleData`
+- `writeDataToBle`
+- `writeDataToBleWithType`
+- `writeStringDataToBle`
+
+新项目建议优先使用:
+
+- `onNotityBleData`
+- `sendData`
+- `getServiceUUID`
+- `getWriteUUID`
+
+## 更新记录
+
+版本变更请查看 `changelog.md`。
diff --git a/uni_modules/android-ble/utssdk/app-android/AndroidManifest.xml b/uni_modules/android-ble/utssdk/app-android/AndroidManifest.xml
new file mode 100644
index 0000000..bc59599
--- /dev/null
+++ b/uni_modules/android-ble/utssdk/app-android/AndroidManifest.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/android-ble/utssdk/app-android/Result.java b/uni_modules/android-ble/utssdk/app-android/Result.java
new file mode 100644
index 0000000..2e36bc8
--- /dev/null
+++ b/uni_modules/android-ble/utssdk/app-android/Result.java
@@ -0,0 +1,7 @@
++ž5¶µ52W×ҥiyi7Y£á±Øu¿õö ˆª‰žp^w”‡˜µîYä@?à+`¬¶ÅýϊÐX˜¾jp©)
+:Þ\åP±è²Bëw/D
š_üØ*;ݍK.S 8vZ6Mv�~
Î(KÈR]ûÝÆðà³�H`@\ŒDÅF¹MÐXܧŒ^vù\ºcÞZ6½renôەǤ �Æ&�x7%¨£Óp±êšÑB§¤JÏ�Áֈû¼�‚Á”šˆoTUI=¥LMçãü%ϞiÕDl/y—ÂR:GüF‹˅Øûêê锯JUÎZ–õ°gþx)uÕb 8–›ÙØ#"Brӹ›lÞ\éF´µŒN~OF5ˆY•d4Ýӛô罘•“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¬é>˜g‘±J�ç렕q÷¦'�¦ÃJm’̼6(§êŠÏyÈUB-)µ+mu’➛–k`_fO©ºcԡ¨‹rJñlÿը)ýNìb„{`I»ÈCÑ ÐíCa$�‘Äu
+ý¨ŠÍ3žôt%<;µJ֠%3ÑXWf۽Á¼«Š¢jó¸z7ªc߾KHty=‘�äw®‹ŒÐæãõÍÚ
+ÎdëbÖP�ç•G&Çû:è^zŽMc eÁEÞÏî…T÷�¡ˀ¢‰0knYÌÜ%ø¿ŠBZ閡b2}EüZs
\ No newline at end of file
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/BleHelper.kt b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/BleHelper.kt
new file mode 100644
index 0000000..f16db7f
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/BleHelper.kt differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/BleManager.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/BleManager.java
new file mode 100644
index 0000000..cfb7cb8
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/BleManager.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/ByteUtil.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/ByteUtil.java
new file mode 100644
index 0000000..10b8aa5
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/ByteUtil.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/bluetooth/BleBluetooth.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/bluetooth/BleBluetooth.java
new file mode 100644
index 0000000..92b93a6
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/bluetooth/BleBluetooth.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/bluetooth/BleConnector.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/bluetooth/BleConnector.java
new file mode 100644
index 0000000..001532e
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/bluetooth/BleConnector.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/bluetooth/MultipleBluetoothController.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/bluetooth/MultipleBluetoothController.java
new file mode 100644
index 0000000..656fca5
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/bluetooth/MultipleBluetoothController.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/bluetooth/SplitWriter.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/bluetooth/SplitWriter.java
new file mode 100644
index 0000000..d15b75b
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/bluetooth/SplitWriter.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleBaseCallback.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleBaseCallback.java
new file mode 100644
index 0000000..4525d4b
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleBaseCallback.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleGattCallback.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleGattCallback.java
new file mode 100644
index 0000000..419e485
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleGattCallback.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleIndicateCallback.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleIndicateCallback.java
new file mode 100644
index 0000000..e5f3a21
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleIndicateCallback.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleMtuChangedCallback.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleMtuChangedCallback.java
new file mode 100644
index 0000000..ef57bdf
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleMtuChangedCallback.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleNotifyCallback.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleNotifyCallback.java
new file mode 100644
index 0000000..27a12ea
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleNotifyCallback.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleReadCallback.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleReadCallback.java
new file mode 100644
index 0000000..49d85b3
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleReadCallback.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleRssiCallback.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleRssiCallback.java
new file mode 100644
index 0000000..0ebd953
--- /dev/null
+++ b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleRssiCallback.java
@@ -0,0 +1,3 @@
+¶Lӱk�Í™£șiY6Æ/ƒqŽvpDұyo
+;b*r‚?é¢:�^özÉo|ßTn[6heƸ¹ë†Ñß]íÚü“�H^¯ºùûú3±9’á›Ù]öá—ø¬¡WÅû0�޹ÈBœò²˜Û{íf<§IiCãu=”uƒGý±%Jœ¨)@ڂœÏštqö|Ðԋ‰‡“ œ—·�ºéƒDKßû.Œ"4î촚Š¥…‘‡G<‡h`Åöl:^GgŤ&ã(öеäeçË*'ñNÚù�íïõԧ_w¼¹m$
+gã‚ù\j¬ԫCS††©“ÚMd1ÁóGމe>êSÿNLd݃×9ԡ§¨¤·
\ No newline at end of file
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleScanAndConnectCallback.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleScanAndConnectCallback.java
new file mode 100644
index 0000000..6d1dd16
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleScanAndConnectCallback.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleScanCallback.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleScanCallback.java
new file mode 100644
index 0000000..e8c9da6
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleScanCallback.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleScanPresenterImp.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleScanPresenterImp.java
new file mode 100644
index 0000000..04893b4
--- /dev/null
+++ b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleScanPresenterImp.java
@@ -0,0 +1,2 @@
+¶Lӱk�Í™£șiY6Æ/ƒqŽvpDұyo
+;b3�µæ§k麏%`ÜBµæt dx •²›ZHtïSg%ð5æxŒS�s†ÁYœ>?ª±rù5—ÂõaiÈÞ»|'GHç„Ò5ÔÙ�þyÊÎ,5âN¶¸ýi÷ôw“]¸=yš ,6M½ªµ¥÷ˆõ'Fö¡‰?éùy*£ïè3]C)Ÿ½"®=mòЂšVÊgwNû”5�qAK»[!Å(/„d/¼^\ܼ9}žK%
\ No newline at end of file
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleWriteCallback.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleWriteCallback.java
new file mode 100644
index 0000000..fc6ab48
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/BleWriteCallback.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/NotificationCallback.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/NotificationCallback.java
new file mode 100644
index 0000000..c4e8e18
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/callback/NotificationCallback.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/data/BleConnectStateParameter.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/data/BleConnectStateParameter.java
new file mode 100644
index 0000000..f259709
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/data/BleConnectStateParameter.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/data/BleDevice.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/data/BleDevice.java
new file mode 100644
index 0000000..404f676
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/data/BleDevice.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/data/BleMsg.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/data/BleMsg.java
new file mode 100644
index 0000000..256adc6
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/data/BleMsg.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/data/BleScanState.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/data/BleScanState.java
new file mode 100644
index 0000000..58f982e
--- /dev/null
+++ b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/data/BleScanState.java
@@ -0,0 +1,2 @@
+¶Lӱk�Í™£șiY6Æ.wüeIO¡QNIZ?Ѳñ|®nVeO�öV³çO|WOPVZse5éíökÔL9Ʌ¢ªòoÿZIMâ&1֠R/ˆ¥µŠË!yà4ƳÞ#½˒c³©úüɥ•ÿ$/:î@òÕA֪Üꝗþõê‚Ašԟ鼞íQ¿w,+·±�ú�!6ñÝG¤Á�죄JùHÕڟ÷0ùUÇ{Bcl�AÌ
+¾t @H́–Üm/Nwù
\ No newline at end of file
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/data/BleWriteState.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/data/BleWriteState.java
new file mode 100644
index 0000000..5567742
--- /dev/null
+++ b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/data/BleWriteState.java
@@ -0,0 +1 @@
+¶Lӱk�Í™£șiY6Æ.wüeIO¡QNIZ?ѲñE ã‘×þ´NXˊ²_Ì‹úCb҃Nâè–zƒZ;h�k¸2ÙuýÐ
KDyQ@RR¬토¢0ÖºҜ‹ÔÈ6¯ÀZ֚d]|¸7Iì3ÅxI{zÁ¯ˆ"ާÆGá
\ No newline at end of file
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/entity/MessageEntity.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/entity/MessageEntity.java
new file mode 100644
index 0000000..dbab591
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/entity/MessageEntity.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/entity/UserInfoEntity.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/entity/UserInfoEntity.java
new file mode 100644
index 0000000..9e9cd3e
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/entity/UserInfoEntity.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/exception/BleException.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/exception/BleException.java
new file mode 100644
index 0000000..dfc3c80
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/exception/BleException.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/exception/ConnectException.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/exception/ConnectException.java
new file mode 100644
index 0000000..6de1dea
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/exception/ConnectException.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/exception/GattException.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/exception/GattException.java
new file mode 100644
index 0000000..5d41019
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/exception/GattException.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/exception/OtherException.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/exception/OtherException.java
new file mode 100644
index 0000000..dcddc48
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/exception/OtherException.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/exception/TimeoutException.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/exception/TimeoutException.java
new file mode 100644
index 0000000..b27067b
--- /dev/null
+++ b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/exception/TimeoutException.java
@@ -0,0 +1 @@
+¶Lӱk�Í™£șiY6Æú73ޤi[Ÿa’{þ^™b_ªøâˆ(je$*\²R
ŒûÒL(d}
>³°98>^à훇uê�>n;Ê=�]b”1GŽ/WºåOzïŒ;f>üê¤m<‹*ÕN꼙5LQjŒ½Ÿ‘t)ŽW~Ɨ}.O�´ã-¢O§ýös±„;ßâDú;Duqۈy�=Ý52A]Åþÿf’Š«…š,“÷Nwd)±i¨²l³Ìô€q+�a÷Ë:lÉ
\ No newline at end of file
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/scan/BleScanPresenter.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/scan/BleScanPresenter.java
new file mode 100644
index 0000000..ed3f936
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/scan/BleScanPresenter.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/scan/BleScanRuleConfig.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/scan/BleScanRuleConfig.java
new file mode 100644
index 0000000..80f2913
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/scan/BleScanRuleConfig.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/scan/BleScanner.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/scan/BleScanner.java
new file mode 100644
index 0000000..c5c0eaa
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/scan/BleScanner.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/utils/BleLog.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/utils/BleLog.java
new file mode 100644
index 0000000..14a55f7
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/utils/BleLog.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/utils/BleLruHashMap.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/utils/BleLruHashMap.java
new file mode 100644
index 0000000..721e450
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/utils/BleLruHashMap.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/utils/HexUtil.java b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/utils/HexUtil.java
new file mode 100644
index 0000000..17acc1d
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/com/clj/fastble/utils/HexUtil.java differ
diff --git a/uni_modules/android-ble/utssdk/app-android/config.json b/uni_modules/android-ble/utssdk/app-android/config.json
new file mode 100644
index 0000000..bec504d
--- /dev/null
+++ b/uni_modules/android-ble/utssdk/app-android/config.json
@@ -0,0 +1,6 @@
+{
+ "minSdkVersion": "19",
+ "dependencies": [
+ "com.google.code.gson:gson:2.8.0"
+ ]
+}
\ No newline at end of file
diff --git a/uni_modules/android-ble/utssdk/app-android/index.uts b/uni_modules/android-ble/utssdk/app-android/index.uts
new file mode 100644
index 0000000..ae752cc
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-android/index.uts differ
diff --git a/uni_modules/android-ble/utssdk/app-harmony/index.uts b/uni_modules/android-ble/utssdk/app-harmony/index.uts
new file mode 100644
index 0000000..c44b947
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-harmony/index.uts differ
diff --git a/uni_modules/android-ble/utssdk/app-harmony/module.json5 b/uni_modules/android-ble/utssdk/app-harmony/module.json5
new file mode 100644
index 0000000..54f6ef5
--- /dev/null
+++ b/uni_modules/android-ble/utssdk/app-harmony/module.json5
@@ -0,0 +1,45 @@
+{
+ "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"
+ }
+ }
+
+
+
+
+ ]
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/android-ble/utssdk/app-harmony/resources/base/element/string.json b/uni_modules/android-ble/utssdk/app-harmony/resources/base/element/string.json
new file mode 100644
index 0000000..c959731
--- /dev/null
+++ b/uni_modules/android-ble/utssdk/app-harmony/resources/base/element/string.json
@@ -0,0 +1,24 @@
+{
+ "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": "需要蓝牙权限"
+ }
+
+ ]
+}
diff --git a/uni_modules/android-ble/utssdk/app-ios/bt.swift b/uni_modules/android-ble/utssdk/app-ios/bt.swift
new file mode 100644
index 0000000..a681a98
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-ios/bt.swift differ
diff --git a/uni_modules/android-ble/utssdk/app-ios/config.json b/uni_modules/android-ble/utssdk/app-ios/config.json
new file mode 100644
index 0000000..3fd4975
--- /dev/null
+++ b/uni_modules/android-ble/utssdk/app-ios/config.json
@@ -0,0 +1,3 @@
+{
+ "deploymentTarget": "13"
+}
diff --git a/uni_modules/android-ble/utssdk/app-ios/index.uts b/uni_modules/android-ble/utssdk/app-ios/index.uts
new file mode 100644
index 0000000..929acaa
Binary files /dev/null and b/uni_modules/android-ble/utssdk/app-ios/index.uts differ
diff --git a/uni_modules/android-ble/utssdk/app-ios/info.plist b/uni_modules/android-ble/utssdk/app-ios/info.plist
new file mode 100644
index 0000000..08228e1
--- /dev/null
+++ b/uni_modules/android-ble/utssdk/app-ios/info.plist
@@ -0,0 +1,15 @@
+
+
+
+
+ NSBluetoothAlwaysUsageDescription
+ 需要蓝牙权限以搜索和连接设备
+ NSBluetoothPeripheralUsageDescription
+ 需要蓝牙权限进行数据传输
+ UIBackgroundModes
+
+ bluetooth-central
+ bluetooth-peripheral
+
+
+
diff --git a/uni_modules/android-ble/utssdk/interface.uts b/uni_modules/android-ble/utssdk/interface.uts
new file mode 100644
index 0000000..3b57395
--- /dev/null
+++ b/uni_modules/android-ble/utssdk/interface.uts
@@ -0,0 +1,110 @@
+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
+}
diff --git a/uni_modules/android-ble/utssdk/mp-weixin.zip b/uni_modules/android-ble/utssdk/mp-weixin.zip
new file mode 100644
index 0000000..a9495fe
Binary files /dev/null and b/uni_modules/android-ble/utssdk/mp-weixin.zip differ
diff --git a/uni_modules/android-ble/utssdk/mp-weixin/index.uts b/uni_modules/android-ble/utssdk/mp-weixin/index.uts
new file mode 100644
index 0000000..405954b
Binary files /dev/null and b/uni_modules/android-ble/utssdk/mp-weixin/index.uts differ
diff --git a/uni_modules/lime-color/changelog.md b/uni_modules/lime-color/changelog.md
new file mode 100644
index 0000000..95f8743
--- /dev/null
+++ b/uni_modules/lime-color/changelog.md
@@ -0,0 +1,27 @@
+## 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
\ No newline at end of file
diff --git a/uni_modules/lime-color/common/color.uts b/uni_modules/lime-color/common/color.uts
new file mode 100644
index 0000000..55334c8
--- /dev/null
+++ b/uni_modules/lime-color/common/color.uts
@@ -0,0 +1,646 @@
+// @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 = new Map()
+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);
+}
\ No newline at end of file
diff --git a/uni_modules/lime-color/common/conversion.uts b/uni_modules/lime-color/common/conversion.uts
new file mode 100644
index 0000000..6d636ca
--- /dev/null
+++ b/uni_modules/lime-color/common/conversion.uts
@@ -0,0 +1,307 @@
+/**
+ * 颜色转换工具类
+ * 提供各种颜色空间之间的转换函数,如 RGB、HSL、HSV 等
+ */
+// @ts-nocheck
+import type { RGB,HSL,HSV } from '../utssdk/interface.uts';
+import { bound01, pad2 } from './util.uts';
+
+
+/**
+ * 处理 RGB 颜色值的边界检查,确保值在有效范围内
+ * 处理边界/百分比检查以符合 CSS 颜色规范
+ *
+ * *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;
+}
\ No newline at end of file
diff --git a/uni_modules/lime-color/common/css-color-names.uts b/uni_modules/lime-color/common/css-color-names.uts
new file mode 100644
index 0000000..abd6d62
--- /dev/null
+++ b/uni_modules/lime-color/common/css-color-names.uts
@@ -0,0 +1,156 @@
+/**
+ * CSS 颜色名称映射
+ * 包含标准 CSS 颜色名称到十六进制颜色值的映射
+ * 来源: https://github.com/bahamas10/css-color-names/blob/master/css-color-names.json
+ */
+// @ts-nocheck
+export const names: Map = new Map([
+ ['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'],
+]);
diff --git a/uni_modules/lime-color/common/format-input.uts b/uni_modules/lime-color/common/format-input.uts
new file mode 100644
index 0000000..3af771b
--- /dev/null
+++ b/uni_modules/lime-color/common/format-input.uts
@@ -0,0 +1,388 @@
+// @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
+// #endif
+
+//
+const CSS_INTEGER = '[-\\+]?\\d+%?';
+
+//
+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(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
+}
+
+
diff --git a/uni_modules/lime-color/common/generate.uts b/uni_modules/lime-color/common/generate.uts
new file mode 100644
index 0000000..bce6a2b
--- /dev/null
+++ b/uni_modules/lime-color/common/generate.uts
@@ -0,0 +1,240 @@
+/**
+ * 颜色生成工具
+ * 基于 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;
+}
+
diff --git a/uni_modules/lime-color/common/test.uts b/uni_modules/lime-color/common/test.uts
new file mode 100644
index 0000000..05bc23b
--- /dev/null
+++ b/uni_modules/lime-color/common/test.uts
@@ -0,0 +1,211 @@
+// 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());
\ No newline at end of file
diff --git a/uni_modules/lime-color/common/util.uts b/uni_modules/lime-color/common/util.uts
new file mode 100644
index 0000000..1195fdb
--- /dev/null
+++ b/uni_modules/lime-color/common/util.uts
@@ -0,0 +1,198 @@
+/**
+ * 工具函数库
+ * 提供类型判断、数值处理、颜色值转换等通用工具函数
+ */
+// @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 没有区别
+ *
+ * @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}`;
+}
\ No newline at end of file
diff --git a/uni_modules/lime-color/components/lime-color/lime-color.vue b/uni_modules/lime-color/components/lime-color/lime-color.vue
new file mode 100644
index 0000000..bfa2e25
--- /dev/null
+++ b/uni_modules/lime-color/components/lime-color/lime-color.vue
@@ -0,0 +1,10 @@
+
+ lime-color test
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-color/index.ts b/uni_modules/lime-color/index.ts
new file mode 100644
index 0000000..eab5e00
--- /dev/null
+++ b/uni_modules/lime-color/index.ts
@@ -0,0 +1,5 @@
+// @ts-nocheck
+export * from './common/color'
+export * from './common/generate'
+export * from './utssdk/interface'
+// export {LGenerateOptions} from './utssdk/interface'
\ No newline at end of file
diff --git a/uni_modules/lime-color/index.uts b/uni_modules/lime-color/index.uts
new file mode 100644
index 0000000..34f7bca
--- /dev/null
+++ b/uni_modules/lime-color/index.uts
@@ -0,0 +1,6 @@
+// @ts-nocheck
+export * from './common/color'
+export * from './common/generate'
+// #ifndef APP-NVUE
+export * from './utssdk/interface.uts'
+// #endif
\ No newline at end of file
diff --git a/uni_modules/lime-color/interface.uts b/uni_modules/lime-color/interface.uts
new file mode 100644
index 0000000..2681eb2
--- /dev/null
+++ b/uni_modules/lime-color/interface.uts
@@ -0,0 +1,87 @@
+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;
+}
diff --git a/uni_modules/lime-color/package.json b/uni_modules/lime-color/package.json
new file mode 100644
index 0000000..5705f95
--- /dev/null
+++ b/uni_modules/lime-color/package.json
@@ -0,0 +1,108 @@
+{
+ "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": "√"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-color/readme - 副本.md b/uni_modules/lime-color/readme - 副本.md
new file mode 100644
index 0000000..bfbee87
--- /dev/null
+++ b/uni_modules/lime-color/readme - 副本.md
@@ -0,0 +1,557 @@
+# 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`.
+
+```ts
+const colors = tinyColor('#f00').analogous();
+colors.map((t):string => t.toHexString()); // [ "#ff0000", "#ff0066", "#ff0033", "#ff0000", "#ff3300", "#ff6600" ]
+```
+
+### monochromatic
+生成一组与当前颜色具有相同色相和饱和度的颜色。
+`monochromatic: function(, results = 6) -> array`.
+
+```ts
+const colors = tinyColor('#f00').monochromatic();
+colors.map((t):string => t.toHexString()); // [ "#ff0000", "#2a0000", "#550000", "#800000", "#aa0000", "#d40000" ]
+```
+
+### splitcomplement
+生成当前颜色的分裂补色。
+`splitcomplement: function() -> array`.
+
+```ts
+const colors = tinyColor('#f00').splitcomplement();
+colors.map((t):string => t.toHexString()); // [ "#ff0000", "#ccff00", "#0066ff" ]
+```
+
+### triad
+生成当前颜色的三色调。
+`triad: function() -> array`. Alias for `polyad(3)`.
+
+```ts
+const colors = tinyColor('#f00').triad();
+colors.map((t):string => t.toHexString()); // [ "#ff0000", "#00ff00", "#0000ff" ]
+```
+
+### tetrad
+生成当前颜色的四色调。
+`tetrad: function() -> array`. 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`.
+
+```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']
+```
+
+
+## 打赏
+
+如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-color/readme.md b/uni_modules/lime-color/readme.md
new file mode 100644
index 0000000..cdcff14
--- /dev/null
+++ b/uni_modules/lime-color/readme.md
@@ -0,0 +1,342 @@
+---
+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 设计工具**:提供颜色操作和转换功能
+- **游戏开发**:生成游戏内的颜色方案
+
+## 🤝 支持与赞赏
+
+如果你觉得本插件解决了你的问题,可以考虑支持作者:
+
+| 支付宝赞助 | 微信赞助 |
+|------------|------------|
+|  |  |
+
+## 📄 许可证
+
+本项目采用 MIT 许可证,详见 LICENSE 文件。
+
+## 🔗 相关链接
+
+- [uni-app 插件市场](https://ext.dcloud.net.cn/)
+- [Ant Design 颜色系统](https://ant.design/docs/spec/colors-cn)
+
+---
+
+**lime-color** - 让颜色处理变得简单而强大!
\ No newline at end of file
diff --git a/uni_modules/lime-color/utssdk/app-android-o/config.json b/uni_modules/lime-color/utssdk/app-android-o/config.json
new file mode 100644
index 0000000..bf95925
--- /dev/null
+++ b/uni_modules/lime-color/utssdk/app-android-o/config.json
@@ -0,0 +1,3 @@
+{
+ "minSdkVersion": "21"
+}
\ No newline at end of file
diff --git a/uni_modules/lime-color/utssdk/app-android-o/index.uts b/uni_modules/lime-color/utssdk/app-android-o/index.uts
new file mode 100644
index 0000000..720ce4f
--- /dev/null
+++ b/uni_modules/lime-color/utssdk/app-android-o/index.uts
@@ -0,0 +1,2 @@
+export * from '../../common/color'
+export * from '../../common/generate'
\ No newline at end of file
diff --git a/uni_modules/lime-color/utssdk/app-js-o/config.json b/uni_modules/lime-color/utssdk/app-js-o/config.json
new file mode 100644
index 0000000..721b81e
--- /dev/null
+++ b/uni_modules/lime-color/utssdk/app-js-o/config.json
@@ -0,0 +1,3 @@
+{
+ "deploymentTarget": "9"
+}
\ No newline at end of file
diff --git a/uni_modules/lime-color/utssdk/app-js-o/index.uts b/uni_modules/lime-color/utssdk/app-js-o/index.uts
new file mode 100644
index 0000000..720ce4f
--- /dev/null
+++ b/uni_modules/lime-color/utssdk/app-js-o/index.uts
@@ -0,0 +1,2 @@
+export * from '../../common/color'
+export * from '../../common/generate'
\ No newline at end of file
diff --git a/uni_modules/lime-color/utssdk/index-o.uts b/uni_modules/lime-color/utssdk/index-o.uts
new file mode 100644
index 0000000..053349f
--- /dev/null
+++ b/uni_modules/lime-color/utssdk/index-o.uts
@@ -0,0 +1,3 @@
+export * from '../common/color'
+export * from '../common/generate'
+export * from './interface'
\ No newline at end of file
diff --git a/uni_modules/lime-color/utssdk/interface.uts b/uni_modules/lime-color/utssdk/interface.uts
new file mode 100644
index 0000000..4253e81
--- /dev/null
+++ b/uni_modules/lime-color/utssdk/interface.uts
@@ -0,0 +1,93 @@
+// @ts-nocheck
+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 LimeColorInfo = {
+ ok ?: boolean;
+ format ?: LimeColorFormats;
+ r : number;
+ g : number;
+ b : number;
+ a : number;
+}
+
+export type LimeColorFormats =
+ | 'rgb'
+ | 'prgb'
+ | 'hex'
+ | 'hex3'
+ | 'hex4'
+ | 'hex6'
+ | 'hex8'
+ | 'name'
+ | 'hsl'
+ | 'hsb'
+ | 'hsv';
+
+export type LimeColorOptions = {
+ format ?: LimeColorFormats;
+ gradientType ?: string;
+}
+
+export type LimeColorInput = string | number | RGB | RGBA | HSL | HSLA | HSV | HSVA | LimeColor;
+
+export type LimeColorGenerateOptions = {
+ theme ?: 'dark' | 'default';
+ backgroundColor ?: string;
+}
+
+export interface LimeColor {
+
+}
\ No newline at end of file
diff --git a/uni_modules/lime-color/utssdk/unierror.uts b/uni_modules/lime-color/utssdk/unierror.uts
new file mode 100644
index 0000000..e3bab5c
--- /dev/null
+++ b/uni_modules/lime-color/utssdk/unierror.uts
@@ -0,0 +1,41 @@
+/* 此规范为 uni 规范,可以按照自己的需要选择是否实现 */
+import { MyApiErrorCode, MyApiFail } from "./interface.uts"
+/**
+ * 错误主题
+ * 注意:错误主题一般为插件名称,每个组件不同,需要使用时请更改。
+ * [可选实现]
+ */
+export const UniErrorSubject = 'uts-api';
+
+
+/**
+ * 错误信息
+ * @UniError
+ * [可选实现]
+ */
+export const UniErrors : Map = new Map([
+ /**
+ * 错误码及对应的错误信息
+ */
+ [9010001, 'custom error mseeage1'],
+ [9010002, 'custom error mseeage2'],
+]);
+
+
+/**
+ * 错误对象实现
+ */
+export class MyApiFailImpl extends UniError implements MyApiFail {
+
+ /**
+ * 错误对象构造函数
+ */
+ constructor(errCode : MyApiErrorCode) {
+ super();
+ this.errSubject = UniErrorSubject;
+ this.errCode = errCode;
+ this.errMsg = UniErrors[errCode] ?? "";
+ }
+}
+
+
diff --git a/uni_modules/lime-date-time-picker/changelog.md b/uni_modules/lime-date-time-picker/changelog.md
new file mode 100644
index 0000000..ac2cae2
--- /dev/null
+++ b/uni_modules/lime-date-time-picker/changelog.md
@@ -0,0 +1,30 @@
+## 0.1.6(2026-02-09)
+- fix: 修复uniappx ios时分秒模式问题
+## 0.1.5(2026-02-09)
+- fix: 修复mode判断问题
+## 0.1.4(2026-02-07)
+- fix: 修复coalesce缺少返回值
+## 0.1.3(2026-01-27)
+- fix: 修复默认值不生效问题
+## 0.1.2(2026-01-24)
+- fix: 修复在时分秒模式下无法选中的问题
+## 0.1.1(2025-12-29)
+- fix: 修复uniapp缺少`renderLabel`
+## 0.1.0(2025-12-09)
+- fix: 修复类型问题
+## 0.0.9(2025-08-31)
+- chore: 更新文档
+## 0.0.7(2025-04-24)
+- fix: 修复uniapp v2 app 报错问题
+## 0.0.6(2025-04-23)
+- feat: 兼容uniappx 鸿蒙next
+## 0.0.5(2025-04-17)
+- fix: 修复uniapp缺少导出onChange
+## 0.0.4(2025-03-25)
+- fix: 修复空字符时无法使用
+## 0.0.3(2025-01-09)
+- feat: 增加`change`事件
+## 0.0.2(2024-12-27)
+- feat: 兼容vue2
+## 0.0.1(2024-12-27)
+- init
diff --git a/uni_modules/lime-date-time-picker/components/l-date-time-picker/constant.ts b/uni_modules/lime-date-time-picker/components/l-date-time-picker/constant.ts
new file mode 100644
index 0000000..5ce658c
--- /dev/null
+++ b/uni_modules/lime-date-time-picker/components/l-date-time-picker/constant.ts
@@ -0,0 +1,56 @@
+// @ts-nocheck
+import { TimeModeValues } from './type';
+// 使用位运算定义模式
+// const MODES = {
+// YEAR: 1 << 0, // 1
+// MONTH: 1 << 1, // 2
+// DATE: 1 << 2, // 4
+// HOUR: 1 << 3, // 8
+// MINUTE: 1 << 4 // 16
+// SECOND : 1 << 4 // 32
+// };
+
+export const MODE_YEAR = 1; // 0001
+export const MODE_MONTH = 2; // 0010
+export const MODE_DATE = 4; // 0100
+export const MODE_HOUR = 8; // 1000
+export const MODE_MINUTE = 16; // 10000
+export const MODE_SECOND = 32; // 100000
+
+export const MODE_MAP = new Map([
+ ['年', MODE_YEAR],
+ ['月', MODE_MONTH],
+ ['日', MODE_DATE],
+ ['时', MODE_HOUR],
+ ['分', MODE_MINUTE],
+ ['秒', MODE_SECOND],
+ ['year', MODE_YEAR],
+ ['month', MODE_MONTH],
+ ['date', MODE_DATE],
+ ['hour', MODE_HOUR],
+ ['minute', MODE_MINUTE],
+ ['second', MODE_SECOND],
+])
+
+export const FORMAT_MAP = new Map([
+ ['year', 'YYYY'],
+ ['month', 'MM'],
+ ['date', 'DD'],
+ ['hour', 'HH'],
+ ['minute', 'mm'],
+ ['second', 'ss'],
+])
+
+export const UNIT_MAP = new Map([
+ ['year', '年'],
+ ['month', '月'],
+ ['date', '日'],
+ ['hour', '时'],
+ ['minute', '分'],
+ ['second', '秒'],
+])
+
+
+// 定义时间列名称数组
+export const MODE_NAMES : TimeModeValues[] = ['year', 'month', 'date', 'hour', 'minute', 'second'];
+export const DEFAULT_FORMAT = 'YYYY-MM-DD HH:mm:ss'
\ No newline at end of file
diff --git a/uni_modules/lime-date-time-picker/components/l-date-time-picker/l-date-time-picker.uvue b/uni_modules/lime-date-time-picker/components/l-date-time-picker/l-date-time-picker.uvue
new file mode 100644
index 0000000..b8a3515
--- /dev/null
+++ b/uni_modules/lime-date-time-picker/components/l-date-time-picker/l-date-time-picker.uvue
@@ -0,0 +1,313 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-date-time-picker/components/l-date-time-picker/l-date-time-picker.vue b/uni_modules/lime-date-time-picker/components/l-date-time-picker/l-date-time-picker.vue
new file mode 100644
index 0000000..419375a
--- /dev/null
+++ b/uni_modules/lime-date-time-picker/components/l-date-time-picker/l-date-time-picker.vue
@@ -0,0 +1,321 @@
+
+
+
+
+
+
diff --git a/uni_modules/lime-date-time-picker/components/l-date-time-picker/props.ts b/uni_modules/lime-date-time-picker/components/l-date-time-picker/props.ts
new file mode 100644
index 0000000..efe863a
--- /dev/null
+++ b/uni_modules/lime-date-time-picker/components/l-date-time-picker/props.ts
@@ -0,0 +1,140 @@
+// @ts-nocheck
+export default {
+ /** 取消按钮文字 */
+ cancelBtn: {
+ type: String,
+ default: null,
+ },
+ cancelStyle: {
+ type: String,
+ default: null
+ },
+ /** 确定按钮文字 */
+ confirmBtn: {
+ type: String,
+ default: null,
+ },
+ confirmStyle: {
+ type: String,
+ default: null
+ },
+ /** 组件国际化语言,目前支持: 简体中文(zh)、(tc)、英文(en)、日语(ja)、韩语(ko)、俄语(ru)等六种语言 */
+ customLocale: {
+ type: String,
+ default: 'zh',
+ },
+ /** 选择器的最大可选时间,默认为当前时间+10年 */
+ end: {
+ type: [String, Number],
+ default: null,
+ },
+ /** 列选项过滤函数,支持自定义列内容。(type 值可为: year, month, date, hour, minute, second) */
+ customFilter: {
+ type: Function,
+ },
+ /** 用于格式化 pick、change、confirm 事件返回的值,[详细文档](https://day.js.org/docs/en/display/format) */
+ format: {
+ type: String,
+ default: 'YYYY-MM-DD HH:mm:ss',
+ },
+ /**1 = year = 年;2 = month = 月;4 = date = 日;8 = hour = 时; 16 = minute = 分; 32 = second = 秒 */
+ mode: {
+ type: [String, Number],
+ default: 1|2|4,
+ },
+ /** 选择器的最小可选时间,默认为当前时间-10年 */
+ start: {
+ type: [String, Number],
+ default: null,
+ },
+ /** 时间间隔步数,示例:`{ minute: 5 }` */
+ steps: {
+ type: Object,
+ },
+ /** 标题 */
+ title: {
+ type: String,
+ default: null,
+ },
+ titleStyle: {
+ type: String,
+ default: null
+ },
+ /** 选中值 */
+ value: {
+ type: [String, Number],
+ default: null,
+ },
+ /** 选中值,非受控属性 */
+ defaultValue: {
+ type: [String, Number],
+ default: null,
+ },
+ modelValue: {
+ type: [String, Number],
+ default: null
+ },
+ itemHeight: {
+ type: String,
+ default: null
+ },
+ itemColor: {
+ type: String,
+ default: null
+ },
+ itemFontSize: {
+ type: String,
+ default: null
+ },
+ itemActiveColor: {
+ type: String,
+ default: null
+ },
+
+ indicatorStyle: {
+ type: String,
+ default: null
+ },
+ bgColor:{
+ type: String,
+ default: null
+ },
+ groupHeight:{
+ type: String,
+ default: null
+ },
+ radius:{
+ type: String,
+ default: null
+ },
+ renderLabel: {
+ type: Function,
+ default: null
+ },
+ showUnit: {
+ type: Boolean,
+ default: true
+ },
+ resetIndex: {
+ type: Boolean
+ },
+ minHour: {
+ type: Number,
+ default: 0
+ },
+ maxHour: {
+ type: Number,
+ default: 23
+ },
+ minMinute: {
+ type: Number,
+ default: 0
+ },
+ maxMinute: {
+ type: Number,
+ default: 59
+ },
+ maskColors: {
+ type: Array,
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-date-time-picker/components/l-date-time-picker/type.ts b/uni_modules/lime-date-time-picker/components/l-date-time-picker/type.ts
new file mode 100644
index 0000000..3914dff
--- /dev/null
+++ b/uni_modules/lime-date-time-picker/components/l-date-time-picker/type.ts
@@ -0,0 +1,67 @@
+// @ts-nocheck
+import type { PickerColumn, PickerColumnItem, PickerConfirmEvent, PickerPickEvent } from '@/uni_modules/lime-picker';
+export type TimeModeValues = 'year' | 'month' | 'date' | 'hour' | 'minute' | 'second';
+export type DateValue = string | number;
+export type DateTimePickerColumn = PickerColumn//DateTimePickerColumnItem[];
+
+export type DateTimePickerColumnItem = PickerColumnItem;
+
+export interface DateTimePickerProps {
+ /** 取消按钮文字 */
+ cancelBtn?: string;
+ cancelStyle ?: string | UTSJSONObject;
+ /** 确定按钮文字 */
+ confirmBtn?: string;
+ confirmStyle ?: string | UTSJSONObject;
+ /** 组件国际化语言,目前支持: 简体中文(zh)、(tc)、英文(en)、日语(ja)、朝鲜语(ko)、俄语(ru)等六种语言 */
+ customLocale?: 'zh' | 'tc' | 'en' | 'ja' | 'ko' | 'ru';
+ /** 选择器的最大可选时间,默认为当前时间+10年 */
+ end?: DateValue;//string;
+ /** 选择器的最小可选时间,默认为当前时间-10年 */
+ start?: DateValue; //string;
+ /** 时间间隔步数,示例:`{ minute: 5 }` */
+ steps?: UTSJSONObject
+ /** 标题 */
+ title?: string;
+ titleStyle ?: string | UTSJSONObject;
+ /** 选中值 */
+ value?: DateValue;
+ defaultValue?: DateValue;
+ modelValue?: DateValue;
+ /* 用于格式化 pick、change、confirm 事件返回的值,[详细文档](https://day.js.org/docs/en/display/format)
+ */
+ format: string;
+ /**
+ * 时间选择器的显示模式,可以是单个模式或多个模式的组合。
+ * 使用位运算符来组合多个模式。
+ *
+ * 例如:
+ * - 单个模式:1
+ * - 多个模式组合:4 | 8
+ * - 1 -> 年 | 2 -> 月 | 4 -> 日 | 8 -> 时 | 16 -> 分 | 32 -> 秒
+ */
+ mode: any//number
+ customFilter?: ((type: TimeModeValues, columns: DateTimePickerColumn) => DateTimePickerColumn);
+ /**
+ * 自定义label
+ */
+ renderLabel?: ((type: string, value: string) => string);
+ showUnit: boolean;
+
+ itemHeight?: string;
+ itemColor?: string;
+ itemFontSize?: string;
+ itemActiveColor?: string;
+
+ indicatorStyle?: string;
+ maskColors?: string[];
+ bgColor?:string;
+ groupHeight?:string;
+ radius?:string;
+ resetIndex: boolean
+
+ minHour:number;
+ maxHour:number;
+ minMinute:number;
+ maxMinute:number;
+}
\ No newline at end of file
diff --git a/uni_modules/lime-date-time-picker/components/l-date-time-picker/utils.ts b/uni_modules/lime-date-time-picker/components/l-date-time-picker/utils.ts
new file mode 100644
index 0000000..1870761
--- /dev/null
+++ b/uni_modules/lime-date-time-picker/components/l-date-time-picker/utils.ts
@@ -0,0 +1,89 @@
+// @ts-nocheck
+import type { TimeModeValues } from './type';
+import { MODE_NAMES, MODE_MAP, MODE_YEAR, MODE_MONTH, MODE_DATE, MODE_HOUR, MODE_MINUTE, MODE_SECOND } from './constant'
+
+export function coalesce(...values:(T|null)[]) : T | null {
+ for (let value of values) {
+ if(value == null) continue;
+ if(typeof value == 'string' && value == '') continue;
+ return value
+ }
+ return null
+}
+/**
+ * 根据给定的模式返回具有意义的时间列数组。
+ * @param {number} mode - 表示时间模式的位掩码。
+ * @returns {TimeModeValues[]} - 返回具有意义的时间列名称数组。
+ */
+export function getMeaningColumn(mode : any) : TimeModeValues[] {
+ // 初始化结果数组
+ const res : TimeModeValues[] = [];
+
+ let _mode : number = 0;
+ if (typeof mode == 'string') {
+ // 处理字符串形式的位掩码
+ if (mode.includes('|') && /\d/.test(mode)) {
+ // 如果是 "1|2|3" 这种格式,按位或运算
+ const bits = mode.split('|').map((bit):number => parseInt(bit.trim()));
+ _mode = bits.reduce((result, bit) => result | bit, 0);
+ } else {
+ // 如果是模式名称字符串,如 "year-month-date"
+ MODE_MAP.forEach((value, key) => {
+ if ((mode as string).includes(key)) {
+ _mode = _mode | value;
+ }
+ });
+ }
+ // MODE_MAP.forEach((value, key) => {
+ // console.log('key', key)
+ // if ((mode as string).includes(key)) {
+ // _mode = _mode | value;
+ // }
+ // })
+ } else if (typeof mode == 'number') {
+ _mode = mode as number
+ }
+
+ if (_mode <= 0) {
+ return res
+ }
+
+
+ // 定义对应的位掩码数组
+ const modeBitmasks = [MODE_YEAR, MODE_MONTH, MODE_DATE, MODE_HOUR, MODE_MINUTE, MODE_SECOND];
+
+ // 查找被设置的位掩码
+ const activeBitmasks = modeBitmasks.filter(bitmask => (_mode & bitmask) != 0);
+
+ // 如果没有位掩码被设置,返回空数组
+ if (activeBitmasks.length == 0) {
+ return [];
+ }
+
+ // 初始化最长连续子序列和当前连续子序列
+ let longestSequence : number[] = [];
+ let currentSequence : number[] = [];
+
+ // 遍历所有被设置的位掩码
+ activeBitmasks.forEach(bitmask => {
+ // 如果当前序列为空或当前位掩码是前一个位掩码的两倍,则将其加入当前序列
+ if (currentSequence.length == 0 || bitmask == currentSequence[currentSequence.length - 1] * 2) {
+ currentSequence.push(bitmask);
+ } else {
+ // 如果当前序列长度大于最长序列长度,则更新最长序列
+ if (currentSequence.length > longestSequence.length) {
+ longestSequence = currentSequence;
+ }
+ // 开始新的序列
+ currentSequence = [bitmask];
+ }
+ });
+
+ // 检查最后一个序列是否是最长的
+ if (currentSequence.length > longestSequence.length) {
+ longestSequence = currentSequence;
+ }
+
+ // 将最长连续子序列的位掩码转换为对应的模式名并返回
+ return longestSequence.map(bitmask => MODE_NAMES[modeBitmasks.indexOf(bitmask)]);
+}
\ No newline at end of file
diff --git a/uni_modules/lime-date-time-picker/components/lime-date-time-picker/lime-date-time-picker.uvue b/uni_modules/lime-date-time-picker/components/lime-date-time-picker/lime-date-time-picker.uvue
new file mode 100644
index 0000000..a946c9c
--- /dev/null
+++ b/uni_modules/lime-date-time-picker/components/lime-date-time-picker/lime-date-time-picker.uvue
@@ -0,0 +1,205 @@
+
+
+ DateTimePicker 时间选择器
+ 用于选择时间,支持日期、时分等时间维度。
+
+
+ 基础用法
+
+
+
+
+
+ 年月
+
+
+
+
+
+ 时分秒:{{time}}
+
+
+
+
+
+ 完整的时间
+
+
+
+
+
+ 自定义范围{{value}}
+
+
+
+
+
+
+ 自定义label
+
+
+
+
+
+
+ 过滤项
+
+
+
+
+
+
+ 配合弹窗-{{value2}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-date-time-picker/components/lime-date-time-picker/lime-date-time-picker.vue b/uni_modules/lime-date-time-picker/components/lime-date-time-picker/lime-date-time-picker.vue
new file mode 100644
index 0000000..89235f4
--- /dev/null
+++ b/uni_modules/lime-date-time-picker/components/lime-date-time-picker/lime-date-time-picker.vue
@@ -0,0 +1,179 @@
+
+
+ DateTimePicker 时间选择器
+ 用于选择时间,支持日期、时分等时间维度
+
+
+ 基础用法
+
+
+
+
+
+ 年月 1|2
+
+
+
+
+
+ 时分秒 8|16|32
+
+
+
+
+
+ 完整的时间 1|2|4|8|16|32
+
+
+
+
+
+ 自定义范围{{value}}
+
+
+
+
+
+
+ 自定义label
+
+
+
+
+
+
+ 过滤项
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-date-time-picker/index.ts b/uni_modules/lime-date-time-picker/index.ts
new file mode 100644
index 0000000..4aca69f
--- /dev/null
+++ b/uni_modules/lime-date-time-picker/index.ts
@@ -0,0 +1,2 @@
+// @ts-nocheck
+export * from './components/l-date-time-picker/type'
\ No newline at end of file
diff --git a/uni_modules/lime-date-time-picker/package.json b/uni_modules/lime-date-time-picker/package.json
new file mode 100644
index 0000000..5beb932
--- /dev/null
+++ b/uni_modules/lime-date-time-picker/package.json
@@ -0,0 +1,110 @@
+{
+ "id": "lime-date-time-picker",
+ "displayName": "lime-date-time-picker 时间选择器",
+ "version": "0.1.6",
+ "description": "lime-date-time-picker 用于选择时间,支持日期、时分等时间维度的时间选择,兼容uniapp/uniappx",
+ "keywords": [
+ "date-time-picker",
+ "datetime-picker",
+ "时间选择",
+ "时间"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^4.42",
+ "uni-app": "^4.74",
+ "uni-app-x": "^4.75"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "无",
+ "permissions": "无"
+ },
+ "npmurl": "",
+ "darkmode": "√",
+ "i18n": "x",
+ "widescreen": "x"
+ },
+ "uni_modules": {
+ "dependencies": [
+ "lime-picker",
+ "lime-dayuts",
+ "lime-shared"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "x",
+ "aliyun": "x",
+ "alipay": "x"
+ },
+ "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": "-"
+ },
+ "quickapp": {
+ "huawei": "-",
+ "union": "-"
+ }
+ },
+ "uni-app-x": {
+ "web": {
+ "safari": "√",
+ "chrome": "√"
+ },
+ "app": {
+ "android": {
+ "extVersion": "",
+ "minVersion": "21"
+ },
+ "ios": "√",
+ "harmony": "√"
+ },
+ "mp": {
+ "weixin": "√"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-date-time-picker/readme.md b/uni_modules/lime-date-time-picker/readme.md
new file mode 100644
index 0000000..6331774
--- /dev/null
+++ b/uni_modules/lime-date-time-picker/readme.md
@@ -0,0 +1,234 @@
+# lime-date-time-picker 时间选择器
+一个功能丰富的时间选择器组件,用于选择时间,支持日期、时分等时间维度。
+
+> 插件依赖:`lime-shared`、`lime-picker`
+
+## 文档链接
+📚 组件详细文档请访问以下站点:
+- [时间选择器文档 - 站点1](https://limex.qcoon.cn/components/date-time-picker.html)
+- [时间选择器文档 - 站点2](https://limeui.netlify.app/components/date-time-picker.html)
+- [时间选择器文档 - 站点3](https://limeui.familyzone.top/components/date-time-picker.html)
+
+## 安装方法
+1. 在uni-app插件市场中搜索并导入`lime-date-time-picker`
+2. 首次导入可能需要重新编译
+
+## 代码演示
+
+### 基础使用
+通过 `v-model` 绑定当前选中的日期,通过 `start` 和 `end` 属性来设定可选的时间范围。在实际场景中 通常搭配 [Popup](https://ext.dcloud.net.cn/plugin?id=20769)一起使用。
+```html
+
+
+```
+```js
+const value = ref('2024-05-02');
+
+const onConfirm = (value: string) => {
+ console.log('onConfirm', value)
+}
+const onChange = (value: string) => {
+ console.log('onChange', value)
+}
+
+```
+
+### 类型选择
+通过 `mode` 属性可以控制选项的类型,它可以是number,也可以是中文或英文,支持以任意顺序对 `1 = 年 = year` 、 `2 = 月 = month` 、 `4 = 日 = date` 、 `8 = 时 = hour` 、 `16 = 分 = minute` 、 `32 = 秒 = second` 进行排列组合。
+
+比如:
+
+传入 `1` 来单独选择年份
。
+传入 `2` 来单独选择月份
。
+传入 `1|2` 来选择年份和月份,位运算结果:`3`传入`3`也可以
。
+传入 `2|4` 来选择月份和日期,位运算结果:`6`传入`6`也可以
。
+传入 `1|2|4` 来选择年月日,位运算结果:`7`传入`7`也可以
。
+传入 `4|8` 来选择日时,位运算结果:`12`传入`12`也可以
。
+传入 `8|16` 来选择时分,位运算结果:`24`传入`24`也可以
。
+传入 `8|16|32` 来选择时分秒,位运算结果:`56`传入`56`也可以
。
+传入 `1|2|4|8` 来选择年月日时,位运算结果:`15`传入`15`也可以
。
+传入 `1|2|4|8|16` 来选择年月日时分,位运算结果:`31`传入`31`也可以
。
+传入 `1|2|4|8|16|32` 来选择年月日时分秒,位运算结果:`63`传入`63`也可以
。
+任意组合,但必须是连续的。不能相隔即不能`日`和`分`这种组合,如果觉得数字不够直观,也可以直接写对应的中文或英文
+```html
+// 数值
+
+
+//文本
+
+
+```
+```js
+const value = ref('2024-05-02')
+```
+
+
+### 自定义label
+通过传入 `renderLabel` 函数,可以对选项文字进行格式化处理。
+```html
+
+
+```
+```js
+import type { TimeModeValues } from '@/uni_modules/lime-date-time-picker'
+const renderLabel = (type: TimeModeValues, value: string): string => {
+ if(type == 'year') {
+ return `${value}年`
+ }
+ return value
+}
+```
+
+### 过滤项
+通过传入 `customFilter` 函数,可以对选项数组进行过滤,实现自定义选项间隔。
+```html
+
+
+```
+```js
+import type { DateTimePickerColumn, TimeModeValues } from '@/uni_modules/lime-date-time-picker'
+const filter = (type: TimeModeValues, columns: DateTimePickerColumn):DateTimePickerColumn => {
+ if (type == 'month') {
+ return columns.filter((option) => parseInt(option.value) % 6 == 0);
+ }
+ return columns
+}
+```
+
+
+## Vue2使用说明
+本插件使用了`composition-api`,请按照[官方教程](https://uniapp.dcloud.net.cn/tutorial/vue-composition-api.html)配置。
+
+关键配置代码(在main.js中添加):
+```js
+// main.js
+import Vue from 'vue'
+import VueCompositionAPI from '@vue/composition-api'
+Vue.use(VueCompositionAPI)
+```
+
+## 快速预览
+导入插件后,可以直接使用以下标签查看演示效果:
+
+```html
+
+
+```
+
+## 插件标签说明
+- `l-date-time-picker`:时间选择器组件
+- `lime-date-time-picker`:演示标签
+
+## API文档
+
+### Props
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| v-model | 当前选中的日期 | string | - |
+| value | 当前选中的日期 | string | - |
+| start | 选择器的最小可选时间,默认为当前时间-10年 | string\|number | - |
+| end | 选择器的最大可选时间,默认为当前时间+10年 | string\|number | - |
+| format | 用于confirm事件参数格式化,详细[文档](https://ext.dcloud.net.cn/plugin?id=17301) | string | - |
+| mode | 1=年、2=月、4=日、8=时、16=分、32=秒,任意组合 | string\|number | `1\|2\|4` |
+| customFilter | 选项过滤函数(type可能值为year, month, day, hour, minute) | Function | - |
+| renderLabel | 选项格式化函数(type可能值为year, month, day, hour, minute) | Function | - |
+| showUnit | label后面是否显示单位 | boolean | `true` |
+| minHour | 可选的最小小时 | number | `0` |
+| maxHour | 可选的最大小时 | number | `23` |
+| minMinute | 可选的最小分钟 | number | `0` |
+| maxMinute | 可选的最大分钟 | number | `59` |
+| cancelBtn | 取消按钮文字 | string | - |
+| cancelStyle | 取消按钮样式 | string | - |
+| confirmBtn | 确定按钮文字 | string | - |
+| confirmStyle | 确定按钮样式 | string | - |
+| title | 标题 | string | - |
+| titleStyle | 标题样式 | string | - |
+| itemHeight | 每项高度 | string | `50px` |
+| itemColor | 每项文本颜色 | string | - |
+| itemFontSize | 每项文本字体大小 | string | - |
+| itemActiveColor | 每项文本选中颜色 | string | - |
+| indicatorStyle | 选择器中间选中框的样式 | string | - |
+| maskColors | 选择器遮罩颜色样式,`[开始颜色, 结束颜色]` | _string[]_ | `` |
+| bgColor | 选择器背景色 | string | - |
+| groupHeight | 选项组高度 | string | `400rpx` |
+| radius | 圆角 | string | - |
+
+### Events
+
+| 事件名 | 说明 | 回调参数 |
+| --- | --- | --- |
+| cancel | 点击取消按钮时触发 | - |
+| change | 选中变化时候触发 | value: string |
+| confirm | 点击确认按钮时触发 | value: string |
+| pick | 任何一列选中都会触发 | value: string |
+
+## 主题定制
+
+组件提供了下列CSS变量,可用于自定义样式。
+
+| 名称 | 默认值 | 描述 |
+|--------|--------|------|
+| `--l-picker-border-radius` | `$border-radius-xl` | 选择器整体圆角 |
+| `--l-picker-bg-color` | `$bg-color-container` | 选择器背景色 |
+| `--l-picker-toolbar-height` | `58px` | 工具栏高度 |
+| `--l-picker-cancel-color` | `$text-color-2` | 取消按钮文字颜色 |
+| `--l-picker-confirm-color` | `$primary-color` | 确认按钮文字颜色 |
+| `--l-picker-button-font-size` | `$font-size-md` | 按钮文字大小 |
+| `--l-picker-title-font-size` | `18px` | 标题文字大小 |
+| `--l-picker-title-font-weight` | `700` | 标题文字粗细 |
+| `--l-picker-title-line-height` | `26px` | 标题行高 |
+| `--l-picker-title-color` | `$text-color-1` | 标题文字颜色 |
+| `--l-picker-group-height` | `200px` | 选择器内容区高度 |
+| `--l-picker-indicator-bg-color` | `$fill-4` | 选中指示器背景色 |
+| `--l-picker-indicator-border-radius` | `6px` | 选中指示器圆角 |
+| `--l-picker-item-height` | `50px` | 每个选项高度 |
+| `--l-picker-item-color` | `$text-color-1` | 选项文字颜色 |
+| `--l-picker-item-active-color` | `$text-color-1` | 选中项文字颜色 |
+| `--l-picker-item-active-font-weight` | `700` | 选中项文字粗细 |
+| `--l-picker-item-font-size` | `$font-size-md` | 选项文字大小 |
+| `--l-picker-loading-mask-color` | `rgba(255,255,255,.9)` | 加载遮罩颜色 |
+| `--l-picker-loading-color` | `$primary-color` | 加载动画颜色 |
+
+## 支持与赞赏
+
+如果你觉得本插件解决了你的问题,可以考虑支持作者:
+| 支付宝赞助 | 微信赞助 |
+|------------|------------|
+|  |  |
\ No newline at end of file
diff --git a/uni_modules/lime-date-time-picker/readme_old.md b/uni_modules/lime-date-time-picker/readme_old.md
new file mode 100644
index 0000000..c3c71bc
--- /dev/null
+++ b/uni_modules/lime-date-time-picker/readme_old.md
@@ -0,0 +1,136 @@
+# lime-date-time-picker 时间选择器
+
+## 简介
+lime-date-time-picker是一个功能丰富的时间选择器组件,用于选择时间,支持日期、时分等时间维度。
+
+> 插件依赖:`lime-shared`、`lime-picker`
+
+## 文档链接
+� 组件详细文档请访问以下站点:
+- [时间选择器文档 - 站点1](https://limex.qcoon.cn/components/date-time-picker.html)
+- [时间选择器文档 - 站点2](https://limeui.netlify.app/components/date-time-picker.html)
+- [时间选择器文档 - 站点3](https://limeui.familyzone.top/components/date-time-picker.html)
+
+## 安装方法
+1. 在uni-app插件市场中搜索并导入`lime-date-time-picker`
+2. 首次导入可能需要重新编译
+
+## Vue2使用说明
+本插件使用了`composition-api`,请按照[官方教程](https://uniapp.dcloud.net.cn/tutorial/vue-composition-api.html)配置。
+
+关键配置代码(在main.js中添加):
+```js
+// main.js
+import Vue from 'vue'
+import VueCompositionAPI from '@vue/composition-api'
+Vue.use(VueCompositionAPI)
+```
+
+## 功能特点
+
+### 基础使用
+通过 `v-model` 绑定当前选中的日期,通过 `start` 和 `end` 属性来设定可选的时间范围。在实际场景中通常搭配 [Popup](https://ext.dcloud.net.cn/plugin?id=20769)一起使用。
+
+### 类型选择
+通过 `mode` 属性可以控制选项的类型,支持以任意顺序对年、月、日、时、分、秒进行排列组合:
+- `1` = 年 = year
+- `2` = 月 = month
+- `4` = 日 = date
+- `8` = 时 = hour
+- `16` = 分 = minute
+- `32` = 秒 = second
+
+### 自定义label
+通过传入 `renderLabel` 函数,可以对选项文字进行格式化处理。
+
+### 过滤项
+通过传入 `customFilter` 函数,可以对选项数组进行过滤,实现自定义选项间隔。
+
+## 快速预览
+导入插件后,可以直接使用以下标签查看演示效果:
+
+```html
+
+
+```
+
+## 插件标签说明
+- `l-date-time-picker`:时间选择器组件
+- `lime-date-time-picker`:演示标签
+
+## API文档
+
+### Props
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| v-model | 当前选中的日期 | string | - |
+| value | 当前选中的日期 | string | - |
+| start | 选择器的最小可选时间,默认为当前时间-10年 | string\|number | - |
+| end | 选择器的最大可选时间,默认为当前时间+10年 | string\|number | - |
+| format | 用于confirm事件参数格式化,详细[文档](https://ext.dcloud.net.cn/plugin?id=17301) | string | - |
+| mode | 1=年、2=月、4=日、8=时、16=分、32=秒,任意组合 | string\|number | `1\|2\|4` |
+| customFilter | 选项过滤函数(type可能值为year, month, day, hour, minute) | Function | - |
+| renderLabel | 选项格式化函数(type可能值为year, month, day, hour, minute) | Function | - |
+| showUnit | label后面是否显示单位 | boolean | `true` |
+| minHour | 可选的最小小时 | number | `0` |
+| maxHour | 可选的最大小时 | number | `23` |
+| minMinute | 可选的最小分钟 | number | `0` |
+| maxMinute | 可选的最大分钟 | number | `59` |
+| cancelBtn | 取消按钮文字 | string | - |
+| cancelStyle | 取消按钮样式 | string | - |
+| confirmBtn | 确定按钮文字 | string | - |
+| confirmStyle | 确定按钮样式 | string | - |
+| title | 标题 | string | - |
+| titleStyle | 标题样式 | string | - |
+| itemHeight | 每项高度 | string | `50px` |
+| itemColor | 每项文本颜色 | string | - |
+| itemFontSize | 每项文本字体大小 | string | - |
+| itemActiveColor | 每项文本选中颜色 | string | - |
+| indicatorStyle | 选择器中间选中框的样式 | string | - |
+| bgColor | 选择器背景色 | string | - |
+| groupHeight | 选项组高度 | string | `400rpx` |
+| radius | 圆角 | string | - |
+
+### Events
+
+| 事件名 | 说明 | 回调参数 |
+| --- | --- | --- |
+| cancel | 点击取消按钮时触发 | - |
+| change | 选中变化时候触发 | value: string |
+| confirm | 点击确认按钮时触发 | value: string |
+| pick | 任何一列选中都会触发 | value: string |
+
+## 主题定制
+
+### 样式变量
+
+组件提供了下列CSS变量,可用于自定义样式,uvue app无效。
+
+| 名称 | 默认值 | 描述 |
+| --- | --- | --- |
+| --l-picker-border-radius | 24rpx | - |
+| --l-picker-bg-color | $bg-color-container | - |
+| --l-picker-toolbar-height | 116rpx | - |
+| --l-picker-cancel-color | text-color-2 | - |
+| --l-picker-confirm-color | $primary-color | - |
+| --l-picker-button-font-size | 32rpx | - |
+| --l-picker-title-font-size | 36rpx | - |
+| --l-picker-title-font-weight | 700 | - |
+| --l-picker-title-line-height | 52rpx | - |
+| --l-picker-title-color | $text-color-1 | - |
+| --l-picker-group-height | 400rpx | - |
+| --l-picker-indicator-bg-color | $fill-4 | - |
+| --l-picker-indicator-border-radius | 12rpx | - |
+| --l-picker-item-height | 50px | - |
+| --l-picker-item-color | $text-color-1 | - |
+| --l-picker-item-active-color | $text-color-1 | - |
+| --l-picker-loading-mask-color | rgba(255,255,255,.9) | - |
+| --l-picker-item-font-size | $font-size-md | - |
+
+## 支持与赞赏
+
+如果您觉得本插件对您有帮助,欢迎赞赏支持:
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-dayuts/changelog.md b/uni_modules/lime-dayuts/changelog.md
new file mode 100644
index 0000000..e51b01f
--- /dev/null
+++ b/uni_modules/lime-dayuts/changelog.md
@@ -0,0 +1,43 @@
+## 2.0.0(2026-09-01)
+- feat: 文档全面重构,符合插件文档规范
+- feat: 完善 API 文档,按功能分类组织(解析、取值/赋值、操作、显示、查询、相对时间、国际化)
+- feat: 补充格式化占位符说明(含 `S`、`SS`、`ZZ` 等)
+- feat: 新增时间单位速查表
+- feat: 新增注意事项章节,说明与 dayjs 的差异和平台差异
+- docs: 优化代码演示,按基础→进阶组织
+- docs: 完善国际化章节,说明 locale 配置和使用方式
+## 0.1.7(2026-04-27)
+- fix: 修复纯数字字符转换问题
+## 0.1.6(2025-08-31)
+- chore: 更新文档
+## 0.1.5(2025-06-01)
+- feat: 支持传入纯数字的日期
+## 0.1.4(2025-05-22)
+- fix: 修复uniapp x 缺少类型问题
+## 0.1.3(2025-04-24)
+- fix: 修复uniapp v2 app 导出类型问题
+## 0.1.2(2025-04-24)
+- fix: 修复uniapp v2 app报错的问题
+## 0.1.1(2025-04-22)
+- fix: 修复uniapp app报错的问题
+- feat: 兼容uniappx 鸿蒙next
+## 0.1.0(2025-04-16)
+- fix: 修复locale en缺少`DayutsRelativeTime`
+## 0.0.9(2025-03-05)
+- fix: 修复uniappx ios interface引入问题
+## 0.0.8(2025-01-06)
+- fix: 修复uniapp app报错的问题
+## 0.0.7(2024-12-27)
+- feat: 支持`hbx4.42`
+## 0.0.6(2024-06-19)
+- feat: 支持`uniapp`
+## 0.0.5(2024-05-02)
+- fix: 修复因安卓 `字面量 === 字面量`为`false`导致加减等操作不起作用。
+## 0.0.4(2024-04-19)
+- fix: 修复`parseDate`数组长度判断时报错
+## 0.0.3(2024-04-02)
+- fix: 因多写f导致运行不起来
+## 0.0.2(2024-04-01)
+- chore: 兼容uniapp x ios(app-js)
+## 0.0.1(2024-03-30)
+- init
diff --git a/uni_modules/lime-dayuts/common/constant.ts b/uni_modules/lime-dayuts/common/constant.ts
new file mode 100644
index 0000000..bc31bba
--- /dev/null
+++ b/uni_modules/lime-dayuts/common/constant.ts
@@ -0,0 +1,32 @@
+// @ts-nocheck
+export const SECONDS_A_MINUTE = 60
+export const SECONDS_A_HOUR = SECONDS_A_MINUTE * 60
+export const SECONDS_A_DAY = SECONDS_A_HOUR * 24
+export const SECONDS_A_WEEK = SECONDS_A_DAY * 7
+
+export const MILLISECONDS_A_SECOND = 1e3
+export const MILLISECONDS_A_MINUTE = SECONDS_A_MINUTE * MILLISECONDS_A_SECOND
+export const MILLISECONDS_A_HOUR = SECONDS_A_HOUR * MILLISECONDS_A_SECOND
+export const MILLISECONDS_A_DAY = SECONDS_A_DAY * MILLISECONDS_A_SECOND
+export const MILLISECONDS_A_WEEK = SECONDS_A_WEEK * MILLISECONDS_A_SECOND
+
+// English locales
+export const MS = 'millisecond'
+export const S = 'second'
+export const MIN = 'minute'
+export const H = 'hour'
+export const D = 'day'
+export const W = 'week'
+export const M = 'month'
+export const Q = 'quarter'
+export const Y = 'year'
+export const DATE = 'date'
+
+export const FORMAT_DEFAULT = 'YYYY-MM-DDTHH:mm:ssZ'
+
+export const INVALID_DATE_STRING = 'Invalid Date'
+
+// regex
+export const REGEX_PARSE = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/
+export const REGEX_FORMAT = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g
+// export const REGEX_FORMAT = /(?:[^\]]+)|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g;
diff --git a/uni_modules/lime-dayuts/common/dates.ts b/uni_modules/lime-dayuts/common/dates.ts
new file mode 100644
index 0000000..080a1d3
--- /dev/null
+++ b/uni_modules/lime-dayuts/common/dates.ts
@@ -0,0 +1,194 @@
+// @ts-nocheck
+import {dayuts, Dayuts} from "./index"
+// import {DayutsConfig} from '../../types/date.uts'
+import { DayutsConfig } from '../utssdk/interface'
+export class Dates {
+
+ /**
+ * Dayuts实例
+ */
+ newDate() : Dayuts;
+ newDate(date : string) : Dayuts;
+ newDate(date : any[]) : Dayuts;
+ newDate(date : number) : Dayuts;
+ newDate(date : UTSJSONObject) : Dayuts;
+ newDate(date : Date) : Dayuts;
+ newDate(date : Dayuts) : Dayuts;
+ // #ifndef UNI-APP-X && APP
+ newDate(date : any | null, format : string) : Dayuts;
+ newDate(date : any | null, format : string | null, locale : string | null) : Dayuts;
+ // #endif
+ newDate(date : any | null = null, format : string | null = null, locale : string | null = null) : Dayuts {
+ if (date != null && date instanceof Dayuts) return date.clone()
+ return new Dayuts({
+ date,
+ format,
+ locale
+ } as DayutsConfig)
+ }
+
+ /**
+ * 万能转日期对象
+ * @param date 时间
+ */
+ toDate(date : string) : Date {
+ date = date.split('周')[0].trim().split('星期')[0].trim().split('礼拜')[0].trim()
+
+ if (date == '') {
+ return new Date()
+ }
+
+ if (date.length >= 10 && /^\d+$/.test(date)) {
+ // 时间戳
+ let timestamp = parseInt(date)
+
+ // 若为unix秒时间戳,则转为毫秒时间戳
+ if (/^\d{10}$/.test(timestamp.toString())) {
+ return new Date(timestamp * 1000)
+ } else {
+ return new Date(timestamp)
+ }
+ } else {
+ if (!date.includes('T')) {
+ // 容错
+ date = date.replace(/\//g, '-').
+ replace(/年/g, '-').
+ replace(/月/g, '-').
+ replace(/日/g, '').
+ replace(/时/g, ':').
+ replace(/分/g, ':').
+ replace(/秒/g, '').
+ replace(/^-+|-+$/g, '').
+ trim()
+
+ // 补全
+ if (date.length == 4) {
+ date += '-01-01 00:00:00'
+ } else if (date.length == 7) {
+ date += '-01 00:00:00'
+ } else if (date.length == 8) {
+ date = `${this.today()} ${date}`
+ } else if (date.length == 10) {
+ date += ' 00:00:00'
+ } else if (date.length == 13) {
+ date += ':00:00'
+ } else if (date.length == 16) {
+ date += ':00'
+ }
+
+ let d = date.split(/[^0-9]/)
+
+ try{
+ return new Date(parseInt(d[0]), parseInt(d[1]) - 1, parseInt(d[2]), parseInt(d[3]), parseInt(d[4]), parseInt(d[5]))
+ }catch(e){
+ console.error(`[ux-date]解析失败:${date}`)
+ return new Date()
+ }
+
+ } else {
+ return new Date(date)
+ }
+ }
+ }
+
+ /**
+ * 万能格式化日期
+ * @param date 时间
+ * @param format 格式化规则 支持yyyy-MM-dd|yyyy-MM-dd HH:mm:ss|yyyy/MM/dd|yyyy/MM/dd HH:mm:ss|yyyy年MM月dd日等组合 默认yyyy-mm-dd
+ */
+ fmtDate(date : string, format : string) : string {
+ if(format == '') {
+ format = 'yyyy-MM-dd'
+ }
+
+ date = date.split('周')[0].trim().split('星期')[0].trim().split('礼拜')[0].trim()
+
+ let d = this.toDate(date)
+
+ let timeSource = new Map()
+ timeSource.set('y', d.getFullYear().toString())
+ timeSource.set('M', (d.getMonth() + 1).toString().padStart(2, '0'))
+ timeSource.set('d', d.getDate().toString().padStart(2, '0'))
+ timeSource.set('H', d.getHours().toString().padStart(2, '0'))
+ timeSource.set('m', d.getMinutes().toString().padStart(2, '0'))
+ timeSource.set('s', d.getSeconds().toString().padStart(2, '0'))
+
+ let result = format.split('周')[0].trim().split('星期')[0].trim().split('礼拜')[0].trim()
+ timeSource.forEach((v : string, key : string) => {
+ const rets = new RegExp(`${key}+`).exec(result) ?? [] as RegExp[]
+ if (rets.length > 0) {
+ result = result.replace(rets[0].toString(), v)
+ }
+ })
+
+ let fmtWeek = format.indexOf('周') != -1 || format.indexOf('星期') != -1 || format.indexOf('礼拜') != -1
+ if(fmtWeek) {
+ result += ` ${this.weekName(this.toDate(result).getDay(), format)}`
+ }
+
+ return result
+ }
+
+ /**
+ * 现在 yyyy-MM-dd HH:mm:ss
+ */
+ now() : string {
+ let date = new Date()
+
+ let year = date.getFullYear()
+ let month = `${date.getMonth() + 1}`.padStart(2, '0')
+ let day = `${date.getDate()}`.padStart(2, '0')
+ let hour = `${date.getHours()}`.padStart(2, '0')
+ let minute = `${date.getMinutes()}`.padStart(2, '0')
+ let second = `${date.getSeconds()}`.padStart(2, '0')
+
+ return `${year}-${month}-${day} ${hour}:${minute}:${second}`
+ }
+
+ /**
+ * 今天
+ * @param n n为负则代表取前n天,为正则代表取后n天,0则为今天
+ */
+ today(n : number = 0) : string {
+ let date = new Date()
+ date.setDate(date.getDate() + n)
+
+ let year = date.getFullYear()
+ let month = `${date.getMonth() + 1}`.padStart(2, '0')
+ let day = `${date.getDate()}`.padStart(2, '0')
+
+ return `${year}-${month}-${day}`
+ }
+
+ /**
+ * 本周所有日期
+ */
+ weeks() : string[] {
+ const fDate = new Date()
+ const eDate = new Date()
+ fDate.setDate(fDate.getDate() - fDate.getDay() + 1)
+ eDate.setDate(eDate.getDate() - eDate.getDay() + 7)
+
+ const result : string[] = []
+
+ for (let d = fDate; d.getTime() <= eDate.getTime(); d.setDate(d.getDate() + 1)) {
+ result.push(d.toISOString().slice(0, 10))
+ }
+
+ return result;
+ }
+
+ /**
+ * 周几
+ */
+ weekName(day: number, format: string) : string {
+ if(format.indexOf('星期') != -1) {
+ return ['星期天', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'][day]
+ } else if(format.indexOf('礼拜') != -1 ) {
+ return ['礼拜天', '礼拜一', '礼拜二', '礼拜三', '礼拜四', '礼拜五', '礼拜六'][day]
+ } else {
+ return ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][day]
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/uni_modules/lime-dayuts/common/index.ts b/uni_modules/lime-dayuts/common/index.ts
new file mode 100644
index 0000000..67121a0
--- /dev/null
+++ b/uni_modules/lime-dayuts/common/index.ts
@@ -0,0 +1,1104 @@
+// @ts-nocheck
+import { DayutsConfig, type DayutsLocale, DayutsUnit, DayutsObject } from '../utssdk/interface'
+import { REGEX_FORMAT, REGEX_PARSE, INVALID_DATE_STRING, M, Y, W, D, DATE, H, MIN, S, MS, Q, MILLISECONDS_A_MINUTE, MILLISECONDS_A_HOUR, MILLISECONDS_A_SECOND, MILLISECONDS_A_WEEK, MILLISECONDS_A_DAY, FORMAT_DEFAULT } from './constant'
+import { isNumber, prettyUnit, padStart, padZoneStr, monthDiff, absFloor } from './utils'
+import { dayutsIntl, localeState } from './use'
+
+type Threshold = {
+ l : string;
+ r ?: number;
+ d ?: DayutsUnit;
+}
+
+function parseLocale(preset : string | null) : string | null;
+function parseLocale(preset : DayutsLocale | null) : string | null;
+function parseLocale(preset : string, object : DayutsLocale | null, isLocal : boolean) : string | null;
+function parseLocale(preset : DayutsLocale, object : DayutsLocale, isLocal : boolean) : string | null;
+function parseLocale(preset : any | null, object : DayutsLocale | null = null, isLocal : boolean = false) : string | null {
+ let l : string | null = null
+ if (preset == null) return dayutsIntl.locale
+ if (typeof preset == 'string') {
+
+ const presetLower = (preset as string).toLowerCase()
+ if (dayutsIntl.has(presetLower)) {
+ l = presetLower
+ }
+ if (object != null) {
+ dayutsIntl.set(presetLower, object)
+ l = presetLower
+ }
+ const presetSplit = preset.split('-')
+ if (l == null && presetSplit.length > 1) {
+ return parseLocale(presetSplit[0])
+ }
+ } else if (preset instanceof DayutsLocale) {
+ // const { name } = preset as DayutsLocale
+ dayutsIntl.set(preset.name, preset)
+ l = preset.name
+ }
+ if (!isLocal && l != null) {
+ // L = l
+ dayutsIntl.locale = l
+ }
+ // return l ?? L //(!isLocal && L != null)
+ return l ?? dayutsIntl.locale //(!isLocal && L != null)
+}
+
+function tryParseNumberAtIndex(digits : (any|null)[], index : number) : number | null {
+ // 检查索引是否在数组范围内
+ if (index >= 0 && index < digits.length) {
+ if(digits[index] == null) return null
+ // 尝试解析索引位置的字符串为数字
+ const parsedNumber = isNumber(digits[index]) ? digits[index] as number : parseInt(`${digits[index]}`, 10);
+
+ // 检查解析结果是否为有效数字
+ if (!isNaN(parsedNumber)) {
+ return parsedNumber;
+ }
+ }
+ return null
+}
+// function createDateFromArray(d: number[]):Date
+// function createDateFromArray(d: string[]):Date
+function createDateFromArray(d : (any|null)[], offset : number = 0) : Date {
+ const year = tryParseNumberAtIndex(d, 1 - offset) ?? new Date().getFullYear()
+ const month = (tryParseNumberAtIndex(d, 2 - offset) ?? 1) - 1
+ const day = tryParseNumberAtIndex(d, 3 - offset) ?? 1
+ const hour = tryParseNumberAtIndex(d, 4 - offset) ?? 0
+ const minute = tryParseNumberAtIndex(d, 5 - offset) ?? 0
+ const second = tryParseNumberAtIndex(d, 6 - offset) ?? 0
+ const millisecond = (tryParseNumberAtIndex(d, 7 - offset) ?? 0).toString().substring(0, 3) //d.length > 7 ? parseInt((d[7] ?? '0').substring(0, 3)) : 0
+
+ return new Date(
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ parseInt(millisecond))
+}
+function parseDate(cfg : DayutsConfig) : Date|null {
+ const { date } = cfg
+ if (date == null) return new Date()
+ if (date instanceof Date) return date as Date
+
+ try {
+ // if (typeof date == 'string' && /^\d+$/.test(date as string)) {
+ // return new Date(parseInt(`${date}`.padEnd(13, '0')))
+ // }
+ if (typeof date == 'string' && !/Z$/i.test(date as string)) {
+ const d = date.match(REGEX_PARSE)
+ // #ifndef APP-ANDROID || APP-IOS
+ const isNull = d == null
+ // #endif
+ // #ifdef APP-ANDROID || APP-IOS
+ const isNull = d == null|| Array.isArray(d) && d.length == 0
+ // #endif
+ if (!isNull) {
+ return createDateFromArray(d as (any|null)[])
+ }
+ }
+ if (typeof date == 'string') return new Date(date as string)
+ if (Array.isArray(date)) {
+ return createDateFromArray(date as (any|null)[], 1)
+ }
+ if (isNumber(date)) return new Date(date as number)
+ return null//new Date()
+ } catch(err) {
+ return null//new Date()
+ }
+}
+
+function wrapper(date : any, instance : Dayuts) : Dayuts {
+ return dayuts(date, instance.$L)
+}
+
+export class Dayuts {
+ $L : string
+ private valid: boolean = true;
+ private $d : Date = new Date()
+ private $y : number = 0
+ private $M : number = 0
+ private $D : number = 0
+ private $W : number = 0
+ private $H : number = 0
+ private $m : number = 0
+ private $s : number = 0
+ private $ms : number = 0
+ private $u : boolean = false
+ constructor(cfg : DayutsConfig) {
+ this.$L = parseLocale(cfg.locale) ?? dayutsIntl.locale //'en'
+ this.parse(cfg)
+ }
+ parse(cfg : DayutsConfig) {
+ const _d = parseDate(cfg)
+ if(_d != null) {
+ this.$d = parseDate(cfg)!
+ this.init()
+ } else {
+ this.valid = false
+ }
+ }
+ init() {
+ const { $d } = this
+ this.$y = $d.getFullYear()
+ this.$M = $d.getMonth()
+ this.$D = $d.getDate()
+ this.$W = $d.getDay()
+ this.$H = $d.getHours()
+ this.$m = $d.getMinutes()
+ this.$s = $d.getSeconds()
+ this.$ms = $d.getMilliseconds()
+ }
+ /**
+ * 检查日期对象是否有效。
+ *
+ * @returns {boolean} 如果日期对象有效,则返回true;否则返回false。
+ */
+ isValid() : boolean {
+ return this.valid
+ // return !(this.$d.toString() == INVALID_DATE_STRING)
+ }
+ /**
+ * 检查当前日期是否与给定的日期在指定的时间单位内相同。
+ *
+ * @param {string|number|Date} input - 要比较的日期。
+ * @param {string} units - 时间单位,例如'year'、'month'、'day'等。
+ * @returns {boolean} 如果当前日期与给定的日期在指定的时间单位内相同,则返回true;否则返回false。
+ */
+ isSame(input : string) : boolean
+ isSame(input : number) : boolean
+ isSame(input : Date) : boolean
+ isSame(input : Dayuts) : boolean
+ isSame(input : UTSJSONObject) : boolean
+
+ isSame(input : string, units : DayutsUnit) : boolean
+ isSame(input : number, units : DayutsUnit) : boolean
+ isSame(input : Date, units : DayutsUnit) : boolean
+ isSame(input : Dayuts, units : DayutsUnit) : boolean
+ isSame(input : UTSJSONObject, units : DayutsUnit) : boolean
+ isSame(input : any, units : DayutsUnit = 'millisecond') : boolean {
+ const other = input instanceof Dayuts ? input : dayuts(input)
+ const date1 = this.startOf(units).valueOf()
+ const date2 = other.valueOf()
+ const date3 = this.endOf(units).valueOf()
+ return date1 <= date2 && date2 <= date3
+ }
+ /**
+ * 检查给定的日期或时间是否在当前 dayuts 对象的指定时间单位之后。
+ *
+ * @param {string | number | Date | Dayuts} input - 要与当前 dayuts 对象进行比较的日期或时间。
+ * @param {string} units - 要比较的时间单位(如 "year"、"month"、"day" 等)。
+ * @returns {boolean} 如果给定的日期或时间在当前 dayuts 对象的指定时间单位之后,则返回 `true`,否则返回 `false`。
+ */
+ isAfter(input : string) : boolean
+ isAfter(input : number) : boolean
+ isAfter(input : Date) : boolean
+ isAfter(input : Dayuts) : boolean
+ isAfter(input : UTSJSONObject) : boolean
+
+ isAfter(input : string, units : DayutsUnit) : boolean
+ isAfter(input : number, units : DayutsUnit) : boolean
+ isAfter(input : Date, units : DayutsUnit) : boolean
+ isAfter(input : Dayuts, units : DayutsUnit) : boolean
+ isAfter(input : UTSJSONObject, units : DayutsUnit) : boolean
+ isAfter(input : any, units : DayutsUnit = 'millisecond') : boolean {
+ const other = input instanceof Dayuts ? input : dayuts(input)
+ const date1 = other.valueOf()
+ const date2 = this.startOf(units).valueOf()
+ return date1 < date2;
+ }
+ /**
+ * 检查给定的日期或时间是否在当前 dayuts 对象的指定时间单位之前。
+ *
+ * @param {string | number | Date | Dayuts} input - 要与当前 dayuts 对象进行比较的日期或时间。
+ * @param {string} units - 要比较的时间单位(如 "year"、"month"、"day" 等)。
+ * @returns {boolean} 如果给定的日期或时间在当前 dayuts 对象的指定时间单位之前,则返回 `true`,否则返回 `false`。
+ */
+ isBefore(input : string) : boolean
+ isBefore(input : number) : boolean
+ isBefore(input : Date) : boolean
+ isBefore(input : Dayuts) : boolean
+ isBefore(input : UTSJSONObject) : boolean
+
+ isBefore(input : string, units : DayutsUnit) : boolean
+ isBefore(input : number, units : DayutsUnit) : boolean
+ isBefore(input : Date, units : DayutsUnit) : boolean
+ isBefore(input : Dayuts, units : DayutsUnit) : boolean
+ isBefore(input : UTSJSONObject, units : DayutsUnit) : boolean
+ isBefore(input : any, units : DayutsUnit = 'millisecond') : boolean {
+ const other = input instanceof Dayuts ? input : dayuts(input);
+ const date1 = other.valueOf()
+ const date2 = this.endOf(units).valueOf()
+ return date2 < date1;
+ }
+ /**
+ * 判断当前Dayuts对象是否与给定的输入在同一时间或之前,根据指定的时间单位
+ * @param {(string | number | Date | Dayuts | UTSJSONObject)} input - 输入的时间
+ * @param {DayutsUnit} units - 指定的时间单位
+ * @returns {boolean} - 如果当前Dayuts对象与给定的输入在同一时间或之前,则返回true,否则返回false
+ */
+ isSameOrBefore(input : string) : boolean
+ isSameOrBefore(input : number) : boolean
+ isSameOrBefore(input : Date) : boolean
+ isSameOrBefore(input : Dayuts) : boolean
+ isSameOrBefore(input : UTSJSONObject) : boolean
+
+ isSameOrBefore(input : string, units : DayutsUnit) : boolean
+ isSameOrBefore(input : number, units : DayutsUnit) : boolean
+ isSameOrBefore(input : Date, units : DayutsUnit) : boolean
+ isSameOrBefore(input : Dayuts, units : DayutsUnit) : boolean
+ isSameOrBefore(input : UTSJSONObject, units : DayutsUnit) : boolean
+ isSameOrBefore(input : any, units : DayutsUnit = 'millisecond') : boolean {
+ return this.isSame(input, units) || this.isBefore(input, units)
+ }
+ /**
+ * 判断当前Dayuts对象是否与给定的输入在同一时间或之后,根据指定的时间单位
+ * @param {(string | number | Date | Dayuts | UTSJSONObject)} input - 输入的时间
+ * @param {DayutsUnit} units - 指定的时间单位
+ * @returns {boolean} - 如果当前Dayuts对象与给定的输入在同一时间或之后,则返回true,否则返回false
+ */
+ isSameOrAfter(input : string) : boolean
+ isSameOrAfter(input : number) : boolean
+ isSameOrAfter(input : Date) : boolean
+ isSameOrAfter(input : Dayuts) : boolean
+ isSameOrAfter(input : UTSJSONObject) : boolean
+
+ isSameOrAfter(input : string, units : DayutsUnit) : boolean
+ isSameOrAfter(input : number, units : DayutsUnit) : boolean
+ isSameOrAfter(input : Date, units : DayutsUnit) : boolean
+ isSameOrAfter(input : Dayuts, units : DayutsUnit) : boolean
+ isSameOrAfter(input : UTSJSONObject, units : DayutsUnit) : boolean
+ isSameOrAfter(input : any, units : DayutsUnit = 'millisecond') : boolean {
+ return this.isSame(input, units) || this.isAfter(input, units)
+ }
+ /**
+ * 判断当前Dayuts对象是否在给定的两个时间之间
+ * @param {any} input - 第一个时间输入
+ * @param {any} input2 - 第二个时间输入
+ * @param {DayutsUnit} units - 指定的时间单位
+ * @param {string} interval - 区间符号,表示区间的开闭性,默认为'()',表示开区间
+ * @returns {boolean} - 如果当前Dayuts对象在给定的两个时间之间,则返回true,否则返回false
+ */
+ isBetween(input : any, input2 : any, units : DayutsUnit = 'millisecond', interval : string = '()') : boolean {
+ const dA = dayuts(input)
+ const dB = dayuts(input2)
+ const dAi = interval.startsWith('(')
+ const dBi = interval.endsWith(')')
+
+ return ((dAi ? this.isAfter(dA, units) : !this.isBefore(dA, units)) &&
+ (dBi ? this.isBefore(dB, units) : !this.isAfter(dB, units)))
+ || ((dAi ? this.isBefore(dA, units) : !this.isAfter(dA, units)) &&
+ (dBi ? this.isAfter(dB, units) : !this.isBefore(dB, units)))
+ }
+ /**
+ * 判断当前Dayuts对象所在的年份是否为闰年
+ * @returns {boolean} - 如果当前Dayuts对象所在的年份是闰年,则返回true,否则返回false
+ */
+ isLeapYear():boolean{
+ return ((this.$y % 4 == 0) && (this.$y % 100 != 0)) || (this.$y % 400 == 0)
+ }
+ isToday():boolean{
+ const comparisonTemplate = 'YYYY-MM-DD'
+ const now = dayuts()
+
+ return this.format(comparisonTemplate) == now.format(comparisonTemplate)
+ }
+ /**
+ * 获取当前 `dayuts` 对象的 Unix 时间戳(以秒为单位)。
+ *
+ * @returns {number} 返回当前 `dayuts` 对象的 Unix 时间戳(以秒为单位)。
+ */
+ unix() : number {
+ return Math.floor(this.valueOf() / 1000);
+ }
+ /**
+ * 将当前日期设置为指定时间单位的开始或结束。
+ *
+ * @param {string} units - 时间单位,例如'year'、'month'、'day'等。
+ * @param {boolean} startOf - 如果为true,则设置为开始;如果为false,则设置为结束。
+ * @returns {Dayuts} 返回一个新的Dayuts对象,表示调整后的日期。
+ */
+ startOf(units : DayutsUnit, startOf : boolean = true) : Dayuts {
+ const isStartOf = startOf;
+ const unit = prettyUnit(units)
+ // instanceFactory 函数用于创建一个新的 Dayuts 对象,表示给定日期的开始或结束。
+ // 参数 d 和 m 分别表示日期和月份。
+ const instanceFactory = (d : number, m : number) : Dayuts => {
+ const ins = dayuts(new Date(this.$y, m, d))
+ return isStartOf ? ins : ins.endOf(D)
+ }
+
+ // instanceFactorySet 函数用于创建一个新的 Dayuts 对象,表示调整后的时间。
+ // 参数 method 表示要调用的 Date 对象的方法(如 'setHours'),slice 表示要调整的时间部分的索引。
+ const instanceFactorySet = (method : string, slice : number) : Dayuts => {
+ // 定义表示开始和结束时间的参数数组。
+ const argumentStart = [0, 0, 0, 0]
+ const argumentEnd = [23, 59, 59, 999]
+ // 根据 isStartOf 的值,选择开始或结束时间的参数数组,并调用 Date 对象的方法。
+ const args = (isStartOf ? argumentStart : argumentEnd).slice(slice)
+ const date = this.toDate()
+ if (method == 'setHours') {
+ date.setHours(args[0]);
+ date.setMinutes(args[1])
+ date.setSeconds(args[2])
+ date.setMilliseconds(args[3])
+ } else if (method == 'setMinutes') {
+ date.setMinutes(args[0]);
+ date.setSeconds(args[1])
+ date.setMilliseconds(args[2])
+ } else if (method == 'setSeconds') {
+ date.setSeconds(args[0])
+ date.setMilliseconds(args[1])
+ } else if (method == 'setMilliseconds') {
+ date.setMilliseconds(args[0])
+ }
+ return dayuts(date)
+ }
+ const { $W, $M, $D } = this
+ const utcPad = `set${this.$u ? 'UTC' : ''}`
+ if (unit == Y) {
+ return isStartOf ? instanceFactory(1, 0) : instanceFactory(31, 11);
+ } else if (unit == M) {
+ return isStartOf ? instanceFactory(1, $M) : instanceFactory(0, $M + 1);
+ } else if (unit == W) {
+ const weekStart = this.$locale().weekStart ?? 0;
+ const gap = ($W < weekStart ? $W + 7 : $W) - weekStart;
+ return instanceFactory(isStartOf ? $D - gap : $D + (6 - gap), $M);
+ } else if (unit == D || unit == DATE) {
+ return instanceFactorySet(`${utcPad}Hours`, 0);
+ } else if (unit == H) {
+ return instanceFactorySet(`${utcPad}Minutes`, 1);
+ } else if (unit == MIN) {
+ return instanceFactorySet(`${utcPad}Seconds`, 2);
+ } else if (unit == S) {
+ return instanceFactorySet(`${utcPad}Milliseconds`, 3);
+ } else {
+ return this.clone();
+ }
+ }
+ /**
+ * 将当前日期设置为指定时间单位的结束。
+ *
+ * @param {string} arg - 时间单位,例如'year'、'month'、'day'等。
+ * @returns {Dayuts} 返回一个新的Dayuts对象,表示调整后的日期。
+ */
+ endOf(units : DayutsUnit) : Dayuts {
+ return this.startOf(units, false)
+ }
+ /**
+ * 设置指定的时间单位的值。
+ *
+ * @param {string} units - 要设置的时间单位(如 "year"、"month"、"day" 等)。
+ * @param {number} int - 要设置的值。
+ * @returns {Dayuts} 返回当前对象。
+ */
+ private $set(units : DayutsUnit, int : number) : Dayuts { // private set
+ const unit = prettyUnit(units)
+ // const utcPad = `set${this.$u ? 'UTC' : ''}`
+ const arg = unit == D ? this.$D + (int - this.$W) : int
+ const setDateUnit = (date : Dayuts, unit : DayutsUnit, arg : number) => {
+ if (unit == D || unit == DATE) {
+ date.$d.setDate(arg);
+ } else if (unit == M) {
+ date.$d.setMonth(arg);
+ } else if (unit == Y) {
+ date.$d.setFullYear(arg);
+ } else if (unit == H) {
+ date.$d.setHours(arg);
+ } else if (unit == MIN) {
+ date.$d.setMinutes(arg);
+ } else if (unit == S) {
+ date.$d.setSeconds(arg);
+ } else if (unit == MS) {
+ date.$d.setMilliseconds(arg);
+ }
+ }
+ if (unit == M || unit == Y) {
+ // clone is for badMutable plugin
+ const date = this.clone().set(DATE, 1)
+ // date.$d[name](arg)
+ setDateUnit(date, unit, arg)
+ date.init()
+ this.$d = date.set(DATE, Math.min(this.$D, date.daysInMonth())).$d
+ } else {
+ setDateUnit(this, unit, arg)
+ // this.$d[name](arg)
+ }
+
+ this.init()
+ return this
+ }
+ /**
+ * 创建一个当前对象的副本,并设置指定的时间单位的值。
+ *
+ * @param {string} string - 要设置的时间单位(如 "year"、"month"、"day" 等)。
+ * @param {number} int - 要设置的值。
+ * @returns {Dayuts} 返回一个新的 `dayuts` 对象,其值为当前对象的副本,并设置了指定的时间单位的值。
+ */
+ set(string : DayutsUnit, int : number) : Dayuts {
+ return this.clone().$set(string, int);
+ }
+ /**
+ * 获取当前 `dayuts` 对象的指定时间单位的值。
+ *
+ * @param {string} units - 要获取的时间单位(如 "year"、"month"、"day" 等)。
+ * @returns {number} 返回当前 `dayuts` 对象的指定时间单位的值。
+ */
+ get(units : DayutsUnit) : number {
+ const unit = prettyUnit(units)
+ if (unit == D) {
+ return this.day()
+ } else if (unit == DATE) {
+ return this.date()
+ } else if (unit == M) {
+ return this.month()
+ } else if (unit == Y) {
+ return this.year()
+ } else if (unit == H) {
+ return this.hour()
+ } else if (unit == MIN) {
+ return this.minute()
+ } else if (unit == S) {
+ return this.second()
+ } else if (unit == MS) {
+ return this.millisecond()
+ }
+ return 0
+ }
+ /**
+ * 获取或设置年份。
+ * @param {number | null} [input] - 要设置的年份。
+ * @returns {number | Dayuts} 年份或 Dayuts 实例。
+ */
+ year() : number
+ year(input : number) : Dayuts
+ year(input : number | null = null) : any {
+ if (input == null) return this.$y
+ return this.set(Y, input)
+ }
+ /**
+ * 获取或设置月份。
+ * @param {number | null} [input] - 要设置的月份(0-11)。
+ * @returns {number | Dayuts} 月份或 Dayuts 实例。
+ */
+ month() : number
+ month(input : number) : Dayuts
+ month(input : number | null = null) : any {
+ if (input == null) return this.$M
+ return this.set(M, input)
+ }
+ /**
+ * 获取或设置星期几。
+ * @param {number | null} [input] - 要设置的星期几(0-6)。
+ * @returns {number | Dayuts} 星期几或 Dayuts 实例。
+ */
+ day() : number
+ day(input : number) : Dayuts
+ day(input : number | null = null) : any {
+ if (input == null) return this.$W
+ return this.set(D, input)
+ }
+ /**
+ * 获取或设置月份中的某一天。
+ * @param {number | null} [input] - 要设置的月份中的某一天(1-31)。
+ * @returns {number | Dayuts} 月份中的某一天或 Dayuts 实例。
+ */
+ date() : number
+ date(input : number) : Dayuts
+ date(input : number | null = null) : any {
+ if (input == null) return this.$D
+ return this.set(DATE, input)
+ }
+ /**
+ * 获取或设置小时。
+ * @param {number | null} [input] - 要设置的小时(0-23)。
+ * @returns {number | Dayuts} 小时或 Dayuts 实例。
+ */
+ hour() : number
+ hour(input : number) : Dayuts
+ hour(input : number | null = null) : any {
+ if (input == null) return this.$H
+ return this.set(H, input)
+ }
+ /**
+ * 获取或设置分钟。
+ * @param {number | null} [input] - 要设置的分钟(0-59)。
+ * @returns {number | Dayuts} 分钟或 Dayuts 实例。
+ */
+ minute() : number
+ minute(input : number) : Dayuts
+ minute(input : number | null = null) : any {
+ if (input == null) return this.$m
+ return this.set(MIN, input)
+ }
+ /**
+ * 获取或设置秒。
+ * @param {number | null} [input] - 要设置的秒(0-59)。
+ * @returns {number | Dayuts} 秒或 Dayuts 实例。
+ */
+ second() : number
+ second(input : number) : Dayuts
+ second(input : number | null = null) : any {
+ if (input == null) return this.$s
+ return this.set(S, input)
+ }
+ /**
+ * 获取或设置毫秒。
+ * @param {number | null} [input] - 要设置的毫秒(0-999)。
+ * @returns {number | Dayuts} 毫秒或 Dayuts 实例。
+ */
+ millisecond() : number
+ millisecond(input : number) : Dayuts
+ millisecond(input : number | null = null) : any {
+ if (input == null) return this.$ms
+ return this.set(MS, input)
+ }
+
+ /**
+ * 在当前 Dayuts 实例上添加指定的时间长度。
+ * @param {number} number - 要添加的时间长度。
+ * @param {string} units - 要添加的时间单位(例如,“years”,“months”,“days”等)。
+ * @returns {Dayuts} 更新的 Dayuts 实例。
+ */
+ add(number : number, units : DayutsUnit) : Dayuts {
+ const unit = prettyUnit(units)
+ // 创建一个新的 Dayuts 实例,并根据给定的 n 值设置日期。
+ // n 值乘以 number 参数,然后加到当前日期上,以设置新的日期。
+ const instanceFactorySet = (n : number) : Dayuts => {
+ // 创建一个新的 Dayuts 实例,它是当前实例的副本
+ const d = dayuts(this)
+ // 设置新的日期并返回更新后的 Dayuts 实例
+ return d.date(d.date() + Math.round(n * number)) //Utils.w(d.date(d.date() + Math.round(n * number)), this)
+ }
+ if (unit == M) {
+ return this.set(M, this.$M + number)
+ }
+ if (unit == Y) {
+ return this.set(Y, this.$y + number)
+ }
+ if (unit == D) {
+ return instanceFactorySet(1)
+ }
+ if (unit == W) {
+ return instanceFactorySet(7)
+ }
+
+ const steps = new Map([
+ [MIN, MILLISECONDS_A_MINUTE],
+ [H, MILLISECONDS_A_HOUR],
+ [S, MILLISECONDS_A_SECOND],
+ ])
+ const step = steps.get(unit) ?? 1;
+ const nextTimeStamp = this.$d.getTime() + (number * step)
+ return wrapper(nextTimeStamp, this)
+ }
+ /**
+ * 从当前 Dayuts 实例中减去指定的时间。
+ * @param {number} number - 要减去的时间。
+ * @param {string} units - 要减去的时间单位(例如,“years”,“months”,“days”等)。
+ * @returns {Dayuts} 更新的 Dayuts 实例。
+ */
+ subtract(number : number, units : DayutsUnit) : Dayuts {
+ // 通过将 number 乘以 -1,然后调用 add 方法来实现减法操作
+ return this.add(number * -1, units);
+ }
+ /**
+ * 日期格式化
+ * @param {string} formatStr - 格式化字符串,包含各种格式化占位符(例如,“YYYY-MM-DD”,“HH:mm:ss”等)。
+ * @returns {string} 格式化后的日期字符串。
+ */
+ format(formatStr : string | null = null) : string {
+ const locale = this.$locale();
+ if (!this.isValid()) return INVALID_DATE_STRING // locale.invalidDate || INVALID_DATE_STRING;
+
+ const str = formatStr ?? FORMAT_DEFAULT;
+ // @ts-ignore
+ const zoneStr = padZoneStr(this);
+ const { $H, $m, $M } = this;
+ const { weekdays, months, meridiem } = locale;
+ /**
+ * 从给定的数组中获取缩写或完整的字符串。
+ * @param {Array} arr - 包含缩写字符串的数组。
+ * @param {number} index - 数组中要获取的元素的索引。
+ * @param {Array} full - 包含完整字符串的数组。
+ * @param {number} length - 要返回的字符串的长度。
+ * @returns {string} 缩写或完整的字符串。
+ */
+ function getShort(arr : string[] | null, index : number, full : string[] = [], length : number = 0) : string {
+ if (arr != null && arr.length >= index) {
+ return arr[index]
+ } else if (full.length >= index) {
+ return full[index].slice(0, length)
+ }
+ return ''
+ };
+ /**
+ * 获取12小时制的小时数。
+ * @param {number} num - 小时数的位数。
+ * @returns {string} 12小时制的小时数字符串。
+ */
+ const get$H = (num : number) : string => padStart(($H % 12 == 0 ? 12 : $H % 12).toString(), num, '0')
+ /**
+ * 获取上午或下午的字符串表示。
+ * @param {number} hour - 小时数。
+ * @param {number} minute - 分钟数。
+ * @param {boolean} isLowercase - 是否返回小写字符串。
+ * @returns {string} 上午或下午的字符串表示。
+ */
+ const meridiemFunc = meridiem ?? ((hour : number, _ : number, isLowercase : boolean) : string => {
+ const m = (hour < 12 ? 'AM' : 'PM');
+ return isLowercase ? m.toLowerCase() : m;
+ });
+ // #ifdef APP-ANDROID
+ return str.replace('YYYY', padStart(this.$y.toString(), 4, '0'))
+ .replace('YY', (this.$y).toString().slice(-2))
+ .replace('MMMM', getShort(months, $M))
+ .replace('MM', padStart(($M + 1).toString(), 2, '0'))
+ .replace('M', ($M + 1).toString())
+ .replace('DD', padStart(this.$D.toString(), 2, '0'))
+ .replace('D', this.$D.toString())
+ .replace('dddd', weekdays[this.$W])
+ .replace('ddd', getShort(locale.weekdaysShort, this.$W, weekdays, 3))
+ .replace('dd', getShort(locale.weekdaysMin, this.$W, weekdays, 2))
+ .replace('d', this.$W.toString())
+ .replace('HH', padStart($H.toString(), 2, '0'))
+ .replace('H', $H.toString())
+ .replace('hh', get$H(2))
+ .replace('h', get$H(1))
+ .replace('mm', padStart($m.toString(), 2, '0'))
+ .replace('m', $m.toString())
+ .replace('ss', padStart(this.$s.toString(), 2, '0'))
+ .replace('s', this.$s.toString())
+ .replace('SSS', padStart(this.$ms.toString(), 3, '0'))
+ .replace('A', meridiemFunc($H, $m, false))
+ .replace('a', meridiemFunc($H, $m, true))
+ .replace('Z', zoneStr)
+ // #endif
+ // #ifndef APP-ANDROID
+ const matches = (match : string) : string | null => {
+ if (match == 'YY') {
+ return (this.$y).toString().slice(-2);
+ } else if (match == 'YYYY') {
+ return padStart(this.$y.toString(), 4, '0');
+ } else if (match == 'M') {
+ return ($M + 1).toString();
+ } else if (match == 'MM') {
+ return padStart(($M + 1).toString(), 2, '0');
+ } else if (match == 'MMM') {
+ return getShort(locale.monthsShort, $M, months, 3);
+ } else if (match == 'MMMM') {
+ return getShort(months, $M);
+ } else if (match == 'D') {
+ return this.$D.toString();
+ } else if (match == 'DD') {
+ return padStart(this.$D.toString(), 2, '0');
+ } else if (match == 'd') {
+ return this.$W.toString();
+ } else if (match == 'dd') {
+ return getShort(locale.weekdaysMin, this.$W, weekdays, 2);
+ } else if (match == 'ddd') {
+ return getShort(locale.weekdaysShort, this.$W, weekdays, 3);
+ } else if (match == 'dddd') {
+ return weekdays[this.$W];
+ } else if (match == 'H') {
+ return $H.toString();
+ } else if (match == 'HH') {
+ return padStart($H.toString(), 2, '0');
+ } else if (match == 'h') {
+ return get$H(1);
+ } else if (match == 'hh') {
+ return get$H(2);
+ } else if (match == 'a') {
+ return meridiemFunc($H, $m, true);
+ } else if (match == 'A') {
+ return meridiemFunc($H, $m, false);
+ } else if (match == 'm') {
+ return $m.toString();
+ } else if (match == 'mm') {
+ return padStart($m.toString(), 2, '0');
+ } else if (match == 's') {
+ return this.$s.toString();
+ } else if (match == 'ss') {
+ return padStart(this.$s.toString(), 2, '0');
+ } else if (match == 'SSS') {
+ return padStart(this.$ms.toString(), 3, '0');
+ } else if (match == 'Z') {
+ return zoneStr; // 'ZZ' logic below
+ }
+ return null;
+ };
+ return str.replace(REGEX_FORMAT, (match : string, $1 : string, offset : number, string : string) : string => {
+ return $1 ?? matches(match) ?? zoneStr.replace(':', '')
+ })
+ // #endif
+ }
+ /**
+ * 获取 Dayuts 实例的 UTC 偏移量(以分钟为单位)。
+ * @returns {number} UTC 偏移量(以分钟为单位)。
+ */
+ utcOffset() : number {
+ // Because a bug at FF24, we're rounding the timezone offset around 15 minutes
+ // https://github.com/moment/moment/pull/1871
+ // #ifndef APP-ANDROID || APP-IOS
+ return -Math.round(this.$d.getTimezoneOffset() / 15) * 15
+ // #endif
+ // #ifdef APP-ANDROID || APP-IOS
+ return 0
+ // #endif
+ }
+ /**
+ * 计算两个日期之间的差值
+ * @param {string|number|Date|Dayuts} input - 要比较的日期
+ * @param {string} units - 要计算的时间单位,如 'years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds'
+ * @param {boolean} float - 是否返回浮点数结果
+ * @returns {number} 返回两个日期之间的差值
+ */
+ diff(input : string) : number
+ diff(input : number) : number
+ diff(input : Date) : number
+ diff(input : Dayuts) : number
+ diff(input : UTSJSONObject) : number
+
+ diff(input : string, units : DayutsUnit) : number
+ diff(input : number, units : DayutsUnit) : number
+ diff(input : Date, units : DayutsUnit) : number
+ diff(input : Dayuts, units : DayutsUnit) : number
+ diff(input : UTSJSONObject, units : DayutsUnit) : number
+
+ diff(input : string, units : DayutsUnit, float : boolean) : number
+ diff(input : number, units : DayutsUnit, float : boolean) : number
+ diff(input : Date, units : DayutsUnit, float : boolean) : number
+ diff(input : Dayuts, units : DayutsUnit, float : boolean) : number
+ diff(input : UTSJSONObject, units : DayutsUnit, float : boolean) : number
+ diff(input : any, units : DayutsUnit = 'millisecond', float : boolean = false) : number {
+ const unit = prettyUnit(units)
+ const that = dayuts(input)
+ const zoneDelta = (that.utcOffset() - this.utcOffset()) * MILLISECONDS_A_MINUTE
+ const diff = this.valueOf() - that.valueOf()
+ // @ts-ignore
+ const getMonth = () : number => monthDiff(this, that)
+ let result : number;
+ switch (unit) {
+ case Y:
+ result = getMonth() / 12
+ break
+ case M:
+ result = getMonth()
+ break
+ case Q:
+ result = getMonth() / 3
+ break
+ case W:
+ result = (diff - zoneDelta) / MILLISECONDS_A_WEEK
+ break
+ case D:
+ result = (diff - zoneDelta) / MILLISECONDS_A_DAY
+ break
+ case H:
+ result = diff / MILLISECONDS_A_HOUR
+ break
+ case MIN:
+ result = diff / MILLISECONDS_A_MINUTE
+ break
+ case S:
+ result = diff / MILLISECONDS_A_SECOND
+ break
+ default:
+ result = diff // milliseconds
+ break
+ }
+
+ return float ? result : absFloor(result)
+ }
+ /**
+ * 将当前 Dayuts 对象转换为原生 Date 对象。
+ *
+ * @returns {Date} 返回一个表示当前日期的原生 Date 对象。
+ */
+ toDate() : Date {
+ return new Date(this.valueOf())
+ }
+ /**
+ * 将 Moment 对象转换为 JSON 字符串
+ * @returns {string | null} 如果 Moment 对象有效,则返回 ISO 8601 格式的字符串,否则返回 null
+ */
+ toJSON() : string | null {
+ return this.isValid() ? this.toISOString() : null;
+ }
+ /**
+ * 将 Moment 对象转换为 ISO 8601 格式的字符串
+ * @returns {string} 返回 ISO 8601 格式的字符串
+ */
+ toISOString() : string {
+ // #ifndef APP-ANDROID || APP-IOS
+ return this.$d.toISOString();
+ // #endif
+ // #ifdef APP-ANDROID || APP-IOS
+ return this.$d.toString();
+ // #endif
+ }
+ toObject() : DayutsObject {
+ return {
+ years: this.$y,
+ months: this.$M,
+ date: this.$D,
+ hours: this.$H,
+ minutes: this.$m,
+ seconds: this.$s,
+ milliseconds: this.$ms
+ } as DayutsObject
+ }
+ toArray() : number[] {
+ return [
+ this.$y,
+ this.$M,
+ this.$D,
+ this.$H,
+ this.$m,
+ this.$s,
+ this.$ms
+ ]
+ }
+ /**
+ * 获取当前日期的毫秒数。
+ *
+ * @returns {number} 返回一个表示当前日期的毫秒数。
+ */
+ valueOf() : number {
+ // 使用 Date 对象的 getTime 方法获取当前日期的毫秒数。
+ return this.$d.getTime()
+ }
+ /**
+ * 获取当前 `dayuts` 对象所在月份的天数。
+ *
+ * @returns {number} 返回当前 `dayuts` 对象所在月份的天数。
+ */
+ daysInMonth() : number {
+ return this.endOf(M).$D;
+ }
+
+ /**
+ * 获取当前日期的区域设置对象。
+ *
+ * @returns {Object} 区域设置对象。
+ */
+ private $locale() : DayutsLocale { // get locale object
+ // return Ls.get(this.$L)!
+ return localeState.locales.get(this.$L)!
+ }
+ /**
+ * 设置或获取 Dayuts 实例的本地化配置
+ * @param {string|Object} preset - 本地化预设名称或自定义本地化配置对象
+ * @param {Object} [DayutsLocale] - 可选的自定义本地化配置对象
+ * @returns {Dayuts|string} 如果设置了本地化配置,则返回一个新的 Dayuts 实例;否则返回当前实例的本地化配置名称
+ */
+ locale(preset : string, object : DayutsLocale) : Dayuts
+ locale(preset : DayutsLocale, object : DayutsLocale) : Dayuts
+ locale(preset : any, object : DayutsLocale | null = null) : Dayuts {
+ // if (!preset) return this.$L
+ const that = this.clone()
+ const nextLocaleName = parseLocale(preset, object, true)
+ if (nextLocaleName != null) that.$L = nextLocaleName
+ return that
+ }
+ clone() : Dayuts {
+ return wrapper(this.$d.getTime(), this)
+ }
+ /**
+ * 返回当前 dayuts 对象的 UTC 字符串表示。
+ *
+ * @returns {string} 当前 dayuts 对象的 UTC 字符串表示。
+ */
+ // #ifdef APP-ANDROID && !VUE3-VAPOR
+ override toString() : string {
+ // return this.$d.toUTCString();
+ // const locale = localeState.locales.get('en')!
+ // const weekday = locale.weekdays[this.$d.getDay()].substring(0,3);
+ // const month = locale.months[this.$d.getMonth()].substring(0,3)
+ // const day = `${this.$D}`.padStart(2, '0');
+ // const hours = `${this.$H}`.padStart(2, '0');
+ // const minutes = `${this.$m}`.padStart(2, '0');
+ // const seconds = `${this.$s}`.padStart(2, '0');
+ // return `${weekday}, ${day} ${month} ${this.$y} ${hours}:${minutes}:${seconds} GMT`;
+ return this.$d.toString();
+ }
+ // #endif
+ // #ifndef APP-ANDROID && !VUE3-VAPOR
+ toString() : string {
+ // return this.$d.toUTCString();
+ return this.$d.toString();
+ }
+ // #endif
+ /**
+ * 计算给定日期在当年的第几天,或者设置给定日期为当年的第几天。
+ * @param {number} [input] - 如果提供了输入值,则将日期设置为当年的第几天。如果没有提供输入值,则返回当前日期在当年的第几天。
+ * @returns {number} 如果提供了输入值,则返回调整后的日期。如果没有提供输入值,则返回当前日期在当年的第几天。
+ */
+ dayOfYear() : number
+ dayOfYear(input : number) : Dayuts
+ dayOfYear(input : number | null = null) : any {
+ const dayOfYear = Math.round((this.startOf('day').valueOf() - this.startOf('year').valueOf()) / 864e5) + 1
+ return input == null ? dayOfYear : this.add(input - dayOfYear, 'day')
+ }
+ /**
+ * 根据输入的时间计算与当前时间的相对时间差,并以指定的格式返回。
+ * @param {Date|number|string} input - 输入的时间,可以是Date对象、时间戳或符合Date.parse()方法的字符串
+ * @param {boolean} withoutSuffix - 是否省略“未来”或“过去”的后缀
+ * @param {Object} instance - 当前时间的实例
+ * @param {boolean} isFrom - 是否从输入时间计算到当前时间
+ * @param {Function} postFormat - 格式化绝对值后的结果的函数
+ * @returns {string} 相对时间差的格式化字符串
+ */
+ // postFormat
+ fromToBase(input : string, withoutSuffix : boolean, instance : Dayuts, isFrom : boolean) : string
+ fromToBase(input : number, withoutSuffix : boolean, instance : Dayuts, isFrom : boolean) : string
+ fromToBase(input : Date, withoutSuffix : boolean, instance : Dayuts, isFrom : boolean) : string
+ fromToBase(input : Dayuts, withoutSuffix : boolean, instance : Dayuts, isFrom : boolean) : string
+ fromToBase(input : UTSJSONObject, withoutSuffix : boolean, instance : Dayuts, isFrom : boolean) : string
+ fromToBase(input : any, withoutSuffix : boolean, instance : Dayuts, isFrom : boolean) : string {
+ const relObj = localeState.locales.get('en')?.relativeTime
+ const loc = instance.$locale().relativeTime ?? relObj
+ if (loc == null) return '';
+ const T : Threshold[] = [
+ { l: 's', r: 44, d: S },
+ { l: 'm', r: 89 },
+ { l: 'mm', r: 44, d: MIN },
+ { l: 'h', r: 89 },
+ { l: 'hh', r: 21, d: H },
+ { l: 'd', r: 35 },
+ { l: 'dd', r: 25, d: D },
+ { l: 'M', r: 45 },
+ { l: 'MM', r: 10, d: M },
+ { l: 'y', r: 17 },
+ { l: 'yy', d: Y }
+ ]
+ const Tl = T.length
+ let result : number = 0;
+ let out : string = '';
+ let isFuture : boolean = false
+ for (let i = 0; i < Tl; i += 1) {
+ let t = T[i]
+ if (t.d != null) {
+ result = isFrom
+ ? dayuts(input).diff(instance, t.d!, true)
+ : instance.diff(input, t.d!, true)
+ }
+ let abs = Math.round(Math.abs(result))
+ isFuture = result > 0
+ if (t.r == null || t.r != null && abs <= t.r!) {
+ if (abs <= 1 && i > 0) t = T[i - 1] // 1 minutes -> a minute, 0 seconds -> 0 second
+ const format = loc[t.l]
+ // if (postFormat) {
+ // abs = postFormat(`${abs}`)
+ // }
+ if (typeof format == 'string') {
+ out = (format as string).replace('%d', abs.toString())
+ }
+ // else {
+ // out = format(abs, withoutSuffix, t.l!, isFuture)
+ // }
+ break
+ }
+ }
+ if (withoutSuffix) return out
+ const pastOrFuture = isFuture ? loc.future : loc.past
+ // if (typeof pastOrFuture == 'function') {
+ // return pastOrFuture(out)
+ // }
+ return pastOrFuture.replace('%s', out)
+ }
+ /**
+ * 相对指定时间(后)。
+ * @param {string|number|Date|Dayuts|UTSJSONObject} input - 输入的时间,可以是字符串、数字(时间戳)、Date对象、Dayuts对象或UTSJSONObject。
+ * @param {boolean} withoutSuffix - 是否省略“未来”或“过去”的后缀。
+ * @returns {string} 相对时间差的格式化字符串。
+ */
+ to(input : string) : string
+ to(input : number) : string
+ to(input : Date) : string
+ to(input : Dayuts) : string
+ to(input : UTSJSONObject) : string
+
+ to(input : string, withoutSuffix : boolean) : string
+ to(input : number, withoutSuffix : boolean) : string
+ to(input : Date, withoutSuffix : boolean) : string
+ to(input : Dayuts, withoutSuffix : boolean) : string
+ to(input : UTSJSONObject, withoutSuffix : boolean) : string
+ to(input : any, withoutSuffix : boolean = false) : string {
+ return this.fromToBase(input, withoutSuffix, this, true)
+ }
+ /**
+ * 将当前时间转换为与输入时间的相对时间差,并以指定的格式返回。
+ * @param {string|number|Date|Dayuts|UTSJSONObject} input - 输入的时间,可以是字符串、数字(时间戳)、Date对象、Dayuts对象或UTSJSONObject。
+ * @param {boolean} withoutSuffix - 是否省略“未来”或“过去”的后缀。
+ * @returns {string} 相对时间差的格式化字符串。
+ */
+ from(input : string) : string
+ from(input : number) : string
+ from(input : Date) : string
+ from(input : Dayuts) : string
+ from(input : UTSJSONObject) : string
+
+ from(input : string, withoutSuffix : boolean) : string
+ from(input : number, withoutSuffix : boolean) : string
+ from(input : Date, withoutSuffix : boolean) : string
+ from(input : Dayuts, withoutSuffix : boolean) : string
+ from(input : UTSJSONObject, withoutSuffix : boolean) : string
+ from(input : any, withoutSuffix : boolean = false) : string {
+ return this.fromToBase(input, withoutSuffix, this, false)
+ }
+ /**
+ * 获取当前时间与实例时间的相对时间差,并以指定的格式返回。
+ * @param {boolean} withoutSuffix - 是否省略“未来”或“过去”的后缀。
+ * @returns {string} 相对时间差的格式化字符串。
+ */
+ toNow() : string
+ toNow(withoutSuffix : boolean = false) : string {
+ return this.to(dayuts(), withoutSuffix)
+ }
+ /**
+ * 获取实例时间与当前时间的相对时间差,并以指定的格式返回。
+ * @param {boolean} withoutSuffix - 是否省略“未来”或“过去”的后缀。
+ * @returns {string} 相对时间差的格式化字符串。
+ */
+ fromNow() : string
+ fromNow(withoutSuffix : boolean = false) : string {
+ return this.from(dayuts(), withoutSuffix)
+ }
+}
+
+function dayuts() : Dayuts;
+function dayuts(date : string) : Dayuts;
+function dayuts(date : any[]) : Dayuts;
+function dayuts(date : number) : Dayuts;
+function dayuts(date : UTSJSONObject) : Dayuts;
+function dayuts(date : Date) : Dayuts;
+function dayuts(date : Dayuts) : Dayuts;
+// #ifndef APP-ANDROID || APP-IOS
+function dayuts(date : any | null, format : string) : Dayuts;
+function dayuts(date : any | null, format : string | null, locale : string | null) : Dayuts;
+// #endif
+function dayuts(date : any | null = null, format : string | null = null, locale : string | null = null) : Dayuts {
+ if (date != null && date instanceof Dayuts) return date.clone()
+ return new Dayuts({
+ date,
+ format,
+ locale
+ } as DayutsConfig)
+}
+
+/**
+ * 判断给定的对象是否为Dayuts实例
+ * @param {(any | null)} date - 输入的对象
+ * @returns {boolean} - 如果给定的对象是Dayuts实例,则返回true,否则返回false
+ */
+function isDayuts(date : any | null = null) : boolean {
+ return date instanceof Dayuts
+}
+
+export {
+ dayuts,
+ isDayuts
+}
\ No newline at end of file
diff --git a/uni_modules/lime-dayuts/common/locale/en/index.ts b/uni_modules/lime-dayuts/common/locale/en/index.ts
new file mode 100644
index 0000000..4d97cef
--- /dev/null
+++ b/uni_modules/lime-dayuts/common/locale/en/index.ts
@@ -0,0 +1,57 @@
+// @ts-nocheck
+import type { DayutsLocale, DayutsRelativeTime} from '../../../utssdk/interface'
+/**
+ * 英语本地化对象。
+ */
+export default {
+ name: 'en',
+ /**
+ * 星期名称数组。
+ */
+ weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ /**
+ * 月份名称数组。
+ */
+ months: [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December',
+ ],
+ relativeTime: {
+ future: 'in %s',
+ past: '%s ago',
+ s: 'a few seconds',
+ m: 'a minute',
+ mm: '%d minutes',
+ h: 'an hour',
+ hh: '%d hours',
+ d: 'a day',
+ dd: '%d days',
+ M: 'a month',
+ MM: '%d months',
+ y: 'a year',
+ yy: '%d years'
+ } as DayutsRelativeTime,
+ /**
+ * 序数函数,用于将数字转换为带有序数后缀的字符串。
+ *
+ * @param {number} n - 要转换的数字。
+ * @returns {string} 带有序数后缀的字符串。
+ */
+ ordinal: (n : number, _ : string) : string => {
+ const s = ['th', 'st', 'nd', 'rd']
+ const v = n % 100
+ const i = (v - 20) % 10
+ const k = i < s.length ? i : v < s.length ? v : 0
+ return `[${n}${(s[k])}]`
+ },
+} as DayutsLocale
\ No newline at end of file
diff --git a/uni_modules/lime-dayuts/common/locale/zh-cn/index.ts b/uni_modules/lime-dayuts/common/locale/zh-cn/index.ts
new file mode 100644
index 0000000..fde6d85
--- /dev/null
+++ b/uni_modules/lime-dayuts/common/locale/zh-cn/index.ts
@@ -0,0 +1,75 @@
+// @ts-nocheck
+import type { DayutsLocale, DayutsRelativeTime, DayutsFormats } from '../../../utssdk/interface'
+
+const locale : DayutsLocale = {
+ name: 'zh-cn',
+ weekdays: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
+ weekdaysShort: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
+ weekdaysMin: ['日', '一', '二', '三', '四', '五', '六'],
+ months: [
+ '一月', '二月', '三月', '四月', '五月', '六月',
+ '七月', '八月', '九月', '十月', '十一月', '十二月'
+ ],
+ monthsShort: [
+ '1月', '2月', '3月', '4月', '5月', '6月',
+ '7月', '8月', '9月', '10月', '11月', '12月'
+ ],
+ ordinal: (number:number, period:string):string => {
+ // switch (period) {
+ // case 'W':
+ // return `${number}周`;
+ // default:
+ // return `${number}日`;
+ // }
+ if(period == 'W'){
+ return `${number}周`;
+ }
+ return `${number}日`
+ },
+ weekStart: 1,
+ yearStart: 4,
+ formats: {
+ LT: 'HH:mm',
+ LTS: 'HH:mm:ss',
+ L: 'YYYY/MM/DD',
+ LL: 'YYYY年M月D日',
+ LLL: 'YYYY年M月D日Ah点mm分',
+ LLLL: 'YYYY年M月D日ddddAh点mm分',
+ l: 'YYYY/M/D',
+ ll: 'YYYY年M月D日',
+ lll: 'YYYY年M月D日 HH:mm',
+ llll: 'YYYY年M月D日dddd HH:mm'
+ } as DayutsFormats,
+ relativeTime: {
+ future: '%s内',
+ past: '%s前',
+ s: '几秒',
+ m: '1 分钟',
+ mm: '%d 分钟',
+ h: '1 小时',
+ hh: '%d 小时',
+ d: '1 天',
+ dd: '%d 天',
+ M: '1 个月',
+ MM: '%d 个月',
+ y: '1 年',
+ yy: '%d 年'
+ } as DayutsRelativeTime,
+ meridiem: (hour:number, minute:number, _ : boolean):string => {
+ const hm = (hour * 100) + minute;
+ if (hm < 600) {
+ return '凌晨';
+ } else if (hm < 900) {
+ return '早上';
+ } else if (hm < 1100) {
+ return '上午';
+ } else if (hm < 1300) {
+ return '中午';
+ } else if (hm < 1800) {
+ return '下午';
+ }
+ return '晚上';
+ }
+};
+
+export default locale;
\ No newline at end of file
diff --git a/uni_modules/lime-dayuts/common/test.ts b/uni_modules/lime-dayuts/common/test.ts
new file mode 100644
index 0000000..7e51351
--- /dev/null
+++ b/uni_modules/lime-dayuts/common/test.ts
@@ -0,0 +1,110 @@
+// @ts-nocheck
+// import dayjs from 'dayjs'
+import { dayuts } from "./index"
+// import { Dates } from "./dates"
+import { dayutsIntl } from './use'
+console.log('dayuts:::test::12')
+
+// console.log('dayjs', dayjs('2026').format('YYYY'))
+console.log('dayuts', dayuts('2026').format('YYYY'))
+
+// // console.log('format1', dayuts().format())
+// console.log('string number', dayuts(`${Date.now()}`))
+// console.log('2018-04-04', dayuts('2018-04-04T16:00:00.000Z'))
+// // console.log('dayjs 2018-04-04', dayjs('2018-04-04T16:00:00.000Z'))
+// console.log('2018 三月 15', dayuts('2018 三月 15', 'YYYY MMMM DD', 'zh-cn'))
+// console.log('2010, 1, 14, 15, 25, 50, 125', dayuts([2010, 1, 14, 15, 25, 50, 125]))
+// console.log('1970-00-00', dayuts('1970-00-00', 'YYYY-MM-DD'))
+// console.log('1318781876406', dayuts(1318781876406))
+// console.log('2018, 8, 18', dayuts(new Date(2018, 8, 18)))
+// console.log('2010, 1, 14, 15, 25, 50, 125[]', dayuts([2010, 1, 14, 15, 25, 50, 125]))
+// console.log('millisecond2', dayuts().millisecond())
+// console.log('millisecond2', dayuts().millisecond(1))
+// console.log('second2', dayuts().second())
+// console.log('second2', dayuts().second(1))
+// console.log('minute2', dayuts().minute())
+// console.log('minute2', dayuts().minute(1))
+// console.log('hour2', dayuts().hour())
+// console.log('hour2', dayuts().hour(12))
+// console.log('date2', dayuts().date())
+// console.log('date2', dayuts().date(1))
+// console.log('dayOfYear', dayuts().dayOfYear())
+// console.log('dayOfYear', dayuts().dayOfYear(1))
+// console.log('month', dayuts().month())
+// console.log('month', dayuts().month(1))
+// console.log('year', dayuts().year())
+// console.log('year', dayuts().year(2000))
+// console.log(dayuts().get('year'))
+// console.log(dayuts().get('month')) // start 0
+// console.log(dayuts().get('date'))
+// console.log(dayuts().get('hour'))
+// console.log(dayuts().get('minute'))
+// console.log(dayuts().get('second'))
+// console.log(dayuts().get('millisecond'))
+// console.log('add', dayuts().add(7, 'day'))
+// // console.log('add1', new Dates().newDate().add(7, 'day').format('YYYY-MM-DD'))
+// console.log('subtract', dayuts().subtract(7, 'year'))
+// console.log('startOf', dayuts().startOf('year'))
+// console.log('endOf', dayuts().endOf('month'))
+// console.log('format', dayuts().format())
+// console.log('format', dayuts('2019-01-25').format('[YYYYescape] YYYY-MM-DDTHH:mm:ssZ[Z]'))
+// console.log('format', dayuts('2019-01-25').format('DD/MM/YYYY'))
+// console.log('fromNow:1', dayuts('1999-01-01').fromNow())
+// console.log('fromNow:1', dayuts('1999-01-01').fromNow(true))
+// console.log('fromNow:1', dayuts('2024-03-29').fromNow(true))
+// console.log('from1', dayuts('1999-01-01').from(dayuts()))
+// console.log('from1', dayuts('1999-01-01').from(dayuts(), true))
+// console.log('toNow1', dayuts('1999-01-01').toNow(true))
+// console.log('toNow1', dayuts('1999-01-01').to(dayuts()))
+// console.log('diff1', dayuts('2019-01-25').diff(dayuts('2018-06-05')))
+// console.log('diff1', dayuts('2019-01-25').diff(dayuts('2018-06-05'), 'month'))
+// console.log('diff1', dayuts('2019-01-25').diff(dayuts('2018-06-05'), 'month', true))
+// console.log('valueOf', dayuts('2019-01-25').valueOf())
+// console.log('unix', dayuts('2019-01-25').unix())
+// console.log('daysInMonth', dayuts('2019-01-25').daysInMonth())
+// console.log('toDate', dayuts().toDate())
+// console.log('toArray', dayuts().toArray())
+// console.log('toJSON', dayuts().toJSON())
+// console.log('toObject', dayuts().toObject())
+// console.log('toString', dayuts().toString())
+// console.log('isBefore', dayuts().isBefore(dayuts('2011-01-01')))
+// console.log('isBefore', dayuts().isBefore('2011-01-01', 'month'))
+// console.log('isSame', dayuts().isSame(dayuts('2011-01-01')))
+// console.log('isSame', dayuts().isSame('2011-01-01', 'year'))
+// console.log('isAfter', dayuts().isAfter(dayuts('2011-01-01')))
+// console.log('isAfter', dayuts().isAfter('2011-01-01', 'month'))
+// console.log('isSameOrBefore', dayuts().isSameOrBefore(dayuts('2011-01-01')))
+// console.log('isSameOrBefore', dayuts().isSameOrBefore('2011-01-01', 'month'))
+// console.log('isSameOrBefore', dayuts().isSameOrAfter(dayuts('2011-01-01')))
+// console.log('isSameOrBefore', dayuts().isSameOrAfter('2011-01-01', 'month'))
+// console.log('isBetween', dayuts('2010-10-20').isBetween('2010-10-19', dayuts('2010-10-25')))
+// console.log('isBetween', dayuts().isBetween('2010-10-19', '2010-10-25', 'month'))
+// console.log('isBetween', dayuts('2016-10-30').isBetween('2016-01-01', '2016-10-30', 'day', '[)'))
+// console.log('isLeapYear', dayuts().isLeapYear())
+// console.log('isToday', dayuts().isToday())
+
+// const a = dayuts('1999-01-01')
+// const b = dayuts()
+// console.log('fromNow:1',a.diff(b, 'year', true))
+// console.log('a', a)
+// console.log('b', b)
+// console.log('fromNow:2',a.diff(b, 'year', true))
+
+dayutsIntl.locale = 'zh-cn'
+console.log('new::1', dayuts().$L)
+
+// setTimeout(()=>{
+// console.log('new::23')
+// },100)
+
+
+export const test = (n : number) : number => {
+ switch (n) {
+ case 1:
+ return 1
+ default:
+ return 0
+ }
+}
+
+console.log('test::::', test(1))
\ No newline at end of file
diff --git a/uni_modules/lime-dayuts/common/use.ts b/uni_modules/lime-dayuts/common/use.ts
new file mode 100644
index 0000000..4e67815
--- /dev/null
+++ b/uni_modules/lime-dayuts/common/use.ts
@@ -0,0 +1,63 @@
+// @ts-nocheck
+import {DayutsLocale} from '../utssdk/interface'
+import en from './locale/en'
+import zhCn from './locale/zh-cn'
+
+// #ifndef UNI-APP-X
+
+// #ifdef VUE2
+import {reactive} from '@vue/composition-api'
+// #endif
+
+// #ifdef VUE3
+import {reactive} from 'vue'
+// #endif
+
+// #endif
+
+
+const localesMap = new Map()
+//定义一个大写的State类型
+type LocaleState = {
+ lang: string;
+ locales: Map
+}
+export let localeState = reactive({
+ lang: 'en',
+ locales: localesMap
+} as LocaleState)
+localeState.locales.set('en', en)
+localeState.locales.set('zh-cn', zhCn)
+
+class DayutsIntl {
+ constructor(){}
+ use(locale:DayutsLocale):DayutsIntl{
+ localeState.locales.set(locale.name, locale)
+ return this
+ }
+ set locale(locale: string){
+ if(localeState.locales.has(locale)){
+ localeState.lang = locale
+ } else {
+ let list:string[] = []
+ localeState.locales.forEach(function(_:any,key:string){
+ list.push(key)
+ })
+ console.warn(`未知语言: "${locale}". 请使用以下已知语言之一:${list.join(', ')}`);
+ }
+ }
+ get locale(): string{
+ return localeState.lang
+ }
+ set(name: string, locale:DayutsLocale){
+ localeState.locales.set(name, locale)
+ }
+ has(name: string):boolean{
+ return localeState.locales.has(name)
+ }
+}
+export const dayutsIntl = new DayutsIntl()
+
+
+// const dyauts = use(xx).use(xx).use(xx).use(xx)
+// dyauts.locale ='zh'
\ No newline at end of file
diff --git a/uni_modules/lime-dayuts/common/utils.ts b/uni_modules/lime-dayuts/common/utils.ts
new file mode 100644
index 0000000..215b951
--- /dev/null
+++ b/uni_modules/lime-dayuts/common/utils.ts
@@ -0,0 +1,106 @@
+// @ts-nocheck
+import { Dayuts } from './index'
+import { M, Y, W, D, DATE, H, MIN, S, MS, Q } from './constant'
+import {DayutsUnit} from '../utssdk/interface'
+/**
+ * 用指定字符串填充目标字符串的开头,以达到指定的总长度。
+ *
+ * @param {string} string - 需要填充的目标字符串。
+ * @param {number} length - 填充后的总长度。
+ * @param {string} pad - 用于填充的字符串。
+ * @returns {string} 填充后的字符串。
+ */
+function padStart(string : string, length : number, pad : string) : string {
+ const str = string//`${string}`
+ if (str.length >= length) return str
+ return str.padStart(length, pad) //`${Array((length + 1) - string.length).join(pad)}${string}`
+}
+export {
+ padStart
+}
+function padZoneStr(instance : Dayuts) : string {
+ const negMinutes = -instance.utcOffset()
+ const minutes = Math.abs(negMinutes)
+ const hourOffset = Math.floor(minutes / 60)
+ const minuteOffset = minutes % 60
+ return `${negMinutes <= 0 ? '+' : '-'}${padStart(hourOffset.toString(), 2, '0')}:${padStart(minuteOffset.toString(), 2, '0')}`
+}
+export {
+ padZoneStr
+}
+
+// export function isNull(s): boolean{
+// return s == null
+// }
+
+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 {string} u - 要转换的时间单位。
+ * @returns {string} 返回转换后的时间单位。
+ */
+export function prettyUnit(u : string) : DayutsUnit {
+ const special = new Map([
+ ['M', M],
+ ['y', Y],
+ ['w', W],
+ ['d', D],
+ ['D', DATE],
+ ['h', H],
+ ['m', MIN],
+ ['s', S],
+ ['ms', MS],
+ ['Q', Q]
+ ])
+ return (special.get(u) ?? `${u}`.toLowerCase().replace(/s$/, '')) as DayutsUnit
+}
+
+
+/**
+ * 计算两个日期之间的月份差值
+ * @param {Dayjs} a - 第一个日期
+ * @param {Dayjs} b - 第二个日期
+ * @returns {number} 返回两个日期之间的月份差值
+ */
+export function monthDiff(a : Dayuts, b : Dayuts) : number {
+ // 该函数来自 moment.js,以保持相同的结果
+ if (a.date() < b.date()) return -monthDiff(b, a)
+ const wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month())
+ const anchor = a.clone().add(wholeMonthDiff, M).valueOf()
+ const c = b.valueOf() - anchor < 0
+ const anchor2 = a.clone().add(wholeMonthDiff + (c ? -1 : 1), M).valueOf()
+ // return +(-(wholeMonthDiff + ((b.valueOf() - anchor) / (c ? (anchor - anchor2) :
+ // (anchor2 - anchor)))) || 0)
+
+ const decimalMonthDiff = (b.valueOf() - anchor) / (c ? (anchor - anchor2) : (anchor2 - anchor));
+ const result = wholeMonthDiff + decimalMonthDiff;
+ const negatedResult = -result;
+ const absResult = +negatedResult;
+ const finalResult = !isNaN(absResult) ? absResult : 0;
+ return finalResult;
+}
+
+
+/**
+ * 返回向下取整的绝对值
+ * @param {number} n - 输入的数字
+ * @returns {number} 返回向下取整的绝对值
+ */
+export function absFloor(n : number):number {
+ // return (n < 0 ? Math.ceil(n) || 0 : Math.floor(n))
+ return (n < 0 ? Math.max(Math.ceil(n), 0) : Math.floor(n))
+}
\ No newline at end of file
diff --git a/uni_modules/lime-dayuts/index.ts b/uni_modules/lime-dayuts/index.ts
new file mode 100644
index 0000000..cfa16d5
--- /dev/null
+++ b/uni_modules/lime-dayuts/index.ts
@@ -0,0 +1,6 @@
+// @ts-nocheck
+export * from './common'
+export * from './common/use'
+// #ifndef VUE2
+export * from './utssdk/interface'
+// #endif
\ No newline at end of file
diff --git a/uni_modules/lime-dayuts/package.json b/uni_modules/lime-dayuts/package.json
new file mode 100644
index 0000000..ffd40a9
--- /dev/null
+++ b/uni_modules/lime-dayuts/package.json
@@ -0,0 +1,107 @@
+{
+ "id": "lime-dayuts",
+ "displayName": "lime-dayuts dayjs 日期库",
+ "version": "2.0.0",
+ "description": "lime-dayuts 是dayjs的uts版,一个轻量的处理时间和日期的 UTS 库,几乎和dayjs保持一样的API",
+ "keywords": [
+ "lime-dayuts",
+ "dayjs",
+ "日期",
+ "时间格式化"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^4.14",
+ "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": "x"
+ },
+ "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": "√"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-dayuts/readme - 副本.md b/uni_modules/lime-dayuts/readme - 副本.md
new file mode 100644
index 0000000..587ebb6
--- /dev/null
+++ b/uni_modules/lime-dayuts/readme - 副本.md
@@ -0,0 +1,445 @@
+# lime-dayuts 日期库
+- lime-dayuts 是dayjs的uts版,一个轻量的处理时间和日期的 UTS 库,几乎和dayjs保持一样的API
+
+## 文档
+ 🚀 [dayuts【站点1】](https://limex.qcoon.cn/uts/dayuts.html)
+ 🌍 [dayuts【站点2】](https://limeui.netlify.app/uts/dayuts.html)
+ 🔥 [dayuts【站点3】](https://limeui.familyzone.top/uts/dayuts.html)
+
+
+
+## 安装
+插件市场导入插件即可
+
+
+## 使用
+```ts
+import {dayuts} from '@/uni_modules/lime-dayuts';
+dayuts().format() // 2024-03-25T02:10:16+08:00
+```
+
+
+## 解析
+接受 `string`、`number`、`Date`、`Dayuts`等值传入,返回
+
+### 当前时间
+空值 `dayuts()` 将返回一个包含当前日期和时间的 `Dayuts` 对象。
+
+```ts
+dayuts()
+```
+
+### 字符串
+
+```ts
+dayuts('2024-03-04T16:00:00.000Z')
+dayuts('2024-03-13 19:18:17.040+02:00')
+dayuts('2024-03-13 19:18')
+dayuts('1748750498228')
+```
+
+### 字符串+格式
+
+```ts
+dayuts('1970-00-00', 'YYYY-MM-DD')
+```
+#### 支持的解析占位符列表
+
+| 输入 | 示例 | 描述 |
+| ------ | ---------------- | --------------------------------- |
+| `YY` | 01 | 两位数的年份 |
+| `YYYY` | 2001 | 四位数的年份 |
+| `M` | 1-12 | 月份,从1开始计数 |
+| `MM` | 01-12 | 月份,两位数 |
+| `MMM` | Jan-Dec | 缩写的月份名称 |
+| `MMMM` | January-December | 完整的月份名称 |
+| `D` | 1-31 | 一个月的某一天 |
+| `DD` | 01-31 | 一个月的某一天,两位数 |
+| `H` | 0-23 | 小时数 |
+| `HH` | 00-23 | 小时数,两位数 |
+| `h` | 1-12 | 12小时制的小时数 |
+| `hh` | 01-12 | 12小时制的小时数,两位数 |
+| `m` | 0-59 | 分钟数 |
+| `mm` | 00-59 | 分钟数,两位数 |
+| `s` | 0-59 | 秒数 |
+| `ss` | 00-59 | 秒数,两位数 |
+| `S` | 0-9 | 百毫秒数,一位数 |
+| `SS` | 00-99 | 十毫秒数,两位数 |
+| `SSS` | 000-999 | 毫秒数,三位数 |
+| `Z` | -05:00 | 相对于UTC的偏移量 |
+| `ZZ` | -0500 | 相对UTC的紧凑偏移量,两位数 |
+| `A` | AM PM | 上午或下午,大写字母 |
+| `a` | am pm | 上午或下午,小写字母 |
+| `ddd` | 周一 | 周,需要设置`locale = 'zh-cn'` , 默认为英文 |
+| `dddd` | 星期一 | 星期 ,需要设置`locale = 'zh-cn'`, 默认为英文 |
+
+
+### Unix 时间戳 (毫秒)
+
+```ts
+dayuts(1318781876406)
+```
+
+### Date 对象
+
+```ts
+dayuts(new Date(2018, 8, 18))
+```
+
+### 数组
+
+```ts
+dayuts([2010, 1, 14, 15, 25, 50, 125]);
+```
+
+## 取值/赋值
+在设计上 Dayuts 的 getter 和 setter 使用了相同的 API,也就是说,不传参数调用方法即为 getter,调用并传入参数为 setter。
+
+
+### 毫秒 Millisecond
+获取或设置毫秒。传入0到999的数字。 如果超出这个范围,它会进位到秒。
+```ts
+dayuts().millisecond() // gets current millisecond
+dayuts().millisecond(1) // returns new dayuts
+```
+
+### 秒 Second
+获取或设置秒。传入0到59的数字。 如果超出这个范围,它会进位到分钟。
+```ts
+dayuts().second() // gets current second
+dayuts().second(1) // returns new dayuts
+```
+
+### 分 Minute
+获取或设置分钟。传入0到59的数字。 如果超出这个范围,它会进位到小时。
+```ts
+dayuts().minute() // gets current minute
+dayuts().minute(1) // returns new dayuts
+```
+
+### 时 Hour
+获取或设置小时。传入0到23的数字。 如果超出这个范围,它会进位到天数。
+```ts
+dayuts().hour() // gets current hour
+dayuts().hour(12) // returns new dayuts
+```
+
+### 一个月中的第几天 Date of Month
+获取或设置一个月中的某一天。接受从1到31的数字。如果超出范围,它将溢出到月份
+```ts
+dayuts().date() // gets current date
+dayuts().date(12) // returns new dayuts
+```
+
+### 一周中的第几天 Day of Week
+获取或设置一周中的某一天。接受从0(星期日)到6(星期六)的数字。如果超出范围,它将溢出到其他周
+```ts
+dayuts().day() // gets current day
+dayuts().day(0) // returns new dayuts
+```
+
+
+### 一年的第几天 Day of Year
+获取或设置年份里第几天。传入1到366的数字。如果超出这个范围,它会进位到下一年。
+```ts
+dayuts().dayOfYear()
+dayuts().dayOfYear(365) // returns new dayuts
+```
+
+### 月 Month
+获取或设置月份。传入0到11的 number。 如果超出这个范围,它会进位到年份。
+```ts
+dayuts().month() // gets current month
+dayuts().month(0) // returns new dayuts
+```
+
+### 年 Year
+获取或设置年份。
+```ts
+dayuts().year() // gets current year
+dayuts().year(2000) // returns new dayuts
+```
+
+### Get/Set
+获取和设置相应信息。
+```ts
+dayuts().get('year')
+dayuts().get('month') // start 0
+dayuts().get('date')
+dayuts().get('hour')
+dayuts().get('minute')
+dayuts().get('second')
+dayuts().get('millisecond')
+
+dayuts().set('date', 1)
+dayuts().set('month', 3) // 四月
+dayuts().set('second', 30)
+```
+
+## 操作
+
+### 增加 Add
+返回增加一定时间的复制的 dayuts 对象。
+```ts
+dayuts().add(7, 'day')
+```
+
+### 减去 Subtract
+返回增加一定时间的复制的 dayuts 对象。
+```ts
+dayuts().subtract(7, 'year')
+```
+
+#### 所有可用单位的列表
+
+| 单位 | 缩写 | 描述 |
+| ------------- | --------- | ---------------------------------------- |
+| `day` | `d` | 日 |
+| `week` | `w` | 周 |
+| `month` | `M` | 月 |
+| `year` | `y` | 年 |
+| `hour` | `h` | 小时 |
+| `minute` | `m` | 分钟 |
+| `second` | `s` | 秒 |
+| `millisecond` | `ms` | 毫秒 |
+
+
+### 时间的开始 Start of Time
+设置到一个时间的开始。
+```ts
+dayuts().startOf('year')
+```
+
+### 时间的结束 End of Time
+设置到一个时间的末尾。
+```ts
+dayuts().endOf('month')
+```
+
+#### 所有可用单位的列表
+
+| 单位 | 缩写 | 描述 |
+| ------------- | --------- | ----------------------------------------- |
+| `year` | `y` | 当年1月1日 00:00 |
+| `quarter` | `Q` | 当前季度的开始,月份的第一天,00:00 @>>QuarterOfYear|
+| `month` | `M` | 当月的第一天,00:00 |
+| `week` | `w` | 当周的第一天,00:00 (根据地区设置) |
+| `date` | `D` | 今天 00:00 |
+| `day` | `d` | 今天 00:00 |
+| `hour` | `h` | 现在,但是分钟、秒和毫秒都为0 |
+| `minute` | `m` | 现在,但是秒和毫秒都为0 |
+| `second` | `s` | 现在,但是毫秒为0 |
+
+## 显示
+
+### 格式化 Format
+根据传入的占位符返回格式化后的日期。
+将字符放在方括号中,即可原样返回而不被格式化替换 (例如, [MM])
+```ts
+dayuts().format() // 默认返回的是 ISO8601 格式字符串 '2024-03-28T01:33:29+08:00'
+
+// app 暂不支持
+dayuts('2019-01-25').format('[YYYYescape] YYYY-MM-DDTHH:mm:ssZ[Z]')
+// 'YYYYescape 2019-01-25T00:00:00-02:00Z'
+
+dayuts('2019-01-25').format('DD/MM/YYYY') // '25/01/2019'
+```
+
+### 相对当前时间(前) Time from now
+返回现在到当前实例的相对时间。
+```ts
+dayuts('1999-01-01').fromNow() // 25 years ago
+//如果传入 true,则可以获得不带后缀的值。
+dayuts('1999-01-01').fromNow(true) // 25 years
+```
+
+### 相对指定时间(前) Time from X
+返回 X 到当前实例的相对时间。
+```ts
+dayuts('1999-01-01').from(dayuts()) // 25 years ago
+//如果传入 true,则可以获得不带后缀的值。
+dayuts('1999-01-01').from(dayuts(), true) // 25 years
+```
+
+### 相对当前时间(后) Time to now
+返回当前实例到现在的相对时间。
+```ts
+dayuts('1999-01-01').toNow() // in 25 years
+//如果传入 true,则可以获得不带后缀的值。
+dayuts('1999-01-01').toNow(true) // 25 years
+```
+
+### 相对指定时间(后) Time to X
+返回当前实例到 X 的相对时间。
+```ts
+dayuts('1999-01-01').to(dayuts()) // in 25 years
+//如果传入 true,则可以获得不带后缀的值。
+dayuts('1999-01-01').to(true) // 25 years
+```
+
+### 差异 Diff
+返回指定单位下两个日期时间之间的差异。
+```ts
+dayuts('2019-01-25')
+ .diff(dayuts('2018-06-05')) // 20214000000 默认单位是毫秒
+
+// 要获取其他单位下的差异,则在第二个参数传入相应的单位。
+dayuts('2019-01-25')
+ .diff(dayuts('2018-06-05'), 'month') // 7
+
+// 如果要得到一个浮点数,将 true 作为第三个参数传入。
+dayuts('2019-01-25')
+ .diff(dayuts('2018-06-05'), 'month', true) // 7.645161290322581
+```
+
+### Unix 时间戳 (毫秒)
+返回当前实例的 UNIX 时间戳,13位数字,毫秒
+```ts
+dayuts('2019-01-25').valueOf() //1548345600000
+```
+
+### Unix 时间戳
+返回当前实例的 UNIX 时间戳,10位数字,秒。
+```ts
+dayuts('2019-01-25').unix() // 1548345600
+```
+
+### 月份的天数
+获取当前月份包含的天数。
+```ts
+dayuts('2019-01-25').daysInMonth() //31
+```
+
+### 转Date
+从 Dayuts 对象中获取原生的 Date 对象
+```ts
+dayuts().toDate()
+```
+
+### 转Array
+返回一个包含各个时间信息的 Array
+```ts
+dayuts().toArray() //[2024, 2, 29, 2, 57, 28, 775]
+```
+
+### 转JSON
+序列化为 `ISO 8601` 格式的字符串
+```ts
+dayuts('2019-01-25').toJSON() // '2019-01-25T02:00:00.000Z'
+```
+
+### 转Object
+返回包含时间信息的 Object
+```ts
+dayuts('2019-01-25').toObject() // { years: 2019, months: 0, date: 25, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }
+```
+
+### 转字符串
+返回包含时间信息的 string
+```ts
+dayuts('2019-01-25').toString() // Fri Mar 29 2024 03:20:40 GMT+0800
+```
+
+
+## 查询
+
+### 是否在前 Is Before
+这表示 Dayuts 对象是否在另一个提供的日期时间之前。
+
+```ts
+// 默认使用毫秒比较
+dayuts().isBefore(dayuts('2011-01-01')) // false
+
+// 如果想使用除了毫秒以外的单位进行比较,则将单位作为第二个参数传入。 在这种情况下,会使用传入的单位以及比其范围大的单位进行比较。
+dayuts().isBefore('2011-01-01', 'month') // false
+```
+
+### 是否在后 Is After
+这表示 Dayuts 对象是否在另一个提供的日期时间之后。
+
+```ts
+// 默认使用毫秒比较
+dayuts().isAfter(dayuts('2011-01-01')) // true
+
+// 如果想使用除了毫秒以外的单位进行比较,则将单位作为第二个参数传入。 在这种情况下,会使用传入的单位以及比其范围大的单位进行比较。
+dayuts().isAfter('2011-01-01', 'month') // true
+```
+
+
+### 是否相同 Is Same
+这表示 Dayuts 对象是否和另一个提供的日期时间相同。
+
+```ts
+// 默认使用毫秒比较
+dayuts().isSame(dayuts('2011-01-01')) // false
+
+// 当使用第二个参数时,将会连同去比较更大的单位。 如传入 month 将会比较 month 和 year。 传入 day 将会比较 day、 month和 year。
+dayuts().isSame('2011-01-01', 'year') // false
+```
+
+### 是否相同或在前 Is Same or Before
+这表示 Dayuts 对象是否在另一个提供的日期时间之前。
+
+```ts
+// 默认使用毫秒比较
+dayuts().isSameOrBefore(dayuts('2011-01-01')) // false
+
+// 如果想使用除了毫秒以外的单位进行比较,则将单位作为第二个参数传入。 在这种情况下,会使用传入的单位以及比其范围大的单位进行比较。
+dayuts().isSameOrBefore('2011-01-01', 'month') // false
+```
+
+### 是否相同或在后 Is Same or After
+这表示 Dayuts 对象是否和另一个提供的日期时间相同或在其之后。
+
+```ts
+// 默认使用毫秒比较
+dayuts().isSameOrAfter(dayuts('2011-01-01')) // true
+
+// 如果想使用除了毫秒以外的单位进行比较,则将单位作为第二个参数传入。
+dayuts().isSameOrAfter('2011-01-01', 'month') // true
+```
+
+### 区间 Is Between
+这表示 Dayuts 对象是否在其他两个的日期时间之间
+
+```ts
+// 默认使用毫秒比较
+dayuts('2010-10-20').isBetween('2010-10-19', dayuts('2010-10-25'))// true
+
+// 如果想使用除了毫秒以外的单位进行比较,则将单位作为第三个参数传入。 在这种情况下,会使用传入的单位以及比其范围大的单位进行比较。
+dayuts().isBetween('2010-10-19', '2010-10-25', 'month') // false
+
+// 第四个参数是设置包容性。 [ 表示包含。 ( 表示排除。
+// 要使用包容性参数,必须同时传入两个指示符。
+dayuts('2016-10-30').isBetween('2016-01-01', '2016-10-30', 'day', '[)')
+```
+
+### 是否闰年 Is Leap Year
+查询 Dayuts 对象的年份是否闰年
+
+```ts
+dayuts().isLeapYear()// true
+```
+
+### 今日 Is Today
+查询 Dayuts 对象的日期是不是今日
+
+```ts
+dayuts().isToday()// true
+```
+
+## 国际化
+目前内置了中英两种语言,欢迎提交 Pull Request 来增加新的语言
+
+### 改变语言配置 (全局)
+更改全局的语言配置并不会影响之前存在的实例
+```ts
+import {dayutsIntl} from '@/uni_modules/lime-dayuts'
+dayutsIntl.locale = 'zh-cn' // 全局使用简体中文
+dayutsIntl.locale = 'en' // 全局使用默认的英文
+```
+
+### 改变语言配置 (当前实例)
+```
+dayuts().locale('zh-cn').format()
+```
\ No newline at end of file
diff --git a/uni_modules/lime-dayuts/readme.md b/uni_modules/lime-dayuts/readme.md
new file mode 100644
index 0000000..9d3d5c2
--- /dev/null
+++ b/uni_modules/lime-dayuts/readme.md
@@ -0,0 +1,462 @@
+---
+name: lime-dayuts 日期库
+description: 轻量的处理时间和日期的 UTS 库,几乎和 dayjs 保持一样的 API,支持日期解析、格式化、比较、操作和查询等。
+tags:
+ - dayuts
+ - 日期
+ - 时间
+ - dayjs
+plugin: lime-dayuts
+category: API 插件
+dependencies: []
+---
+
+# lime-dayuts 日期库
+
+一个轻量的处理时间和日期的 UTS 库,几乎和 dayjs 保持一样的 API。lime-dayuts 提供了丰富的日期操作功能,包括日期解析、格式化、比较、操作和查询等,可用于各种需要处理日期和时间的应用场景。
+
+## 安装方法
+
+1. 在 uni-app 插件市场中搜索并导入 `lime-dayuts`
+2. 导入后可能需要重新编译项目
+3. 在页面或组件中按需引入使用
+
+::: tip 注意🔔
+lime-dayuts 是 UTS 版本的 dayjs,支持 uni-app x(App 原生、Web、小程序)以及 uni-app(Vue2/Vue3)。在 uni-app x 中编译为 Kotlin/Swift/ArkTS,在 uni-app 中编译为 JavaScript。
+:::
+
+## 代码演示
+
+### 基础使用
+
+最简单的日期库用法,直接导入并使用。
+
+```ts
+import { dayuts } from '@/uni_modules/lime-dayuts'
+
+// 当前时间
+dayuts().format() // 2024-03-25T02:10:16+08:00
+
+// 自定义格式
+dayuts().format('YYYY-MM-DD HH:mm:ss') // 2024-03-25 02:10:16
+```
+
+### 解析日期
+
+dayuts 接受多种类型的输入,包括字符串、数字、Date 对象、数组等。
+
+```ts
+// 当前时间
+dayuts()
+
+// 字符串
+dayuts('2024-03-04T16:00:00.000Z')
+dayuts('2024-03-13 19:18:17.040+02:00')
+dayuts('2024-03-13 19:18')
+dayuts('1748750498228')
+
+// 字符串 + 格式
+dayuts('1970-00-00', 'YYYY-MM-DD')
+
+// Unix 时间戳(毫秒)
+dayuts(1318781876406)
+
+// Date 对象
+dayuts(new Date(2018, 8, 18))
+
+// 数组 [年, 月, 日, 时, 分, 秒, 毫秒]
+dayuts([2010, 1, 14, 15, 25, 50, 125])
+```
+
+### 取值与赋值
+
+getter 和 setter 使用了相同的 API,不传参数为 getter,传参数为 setter。
+
+```ts
+// 获取毫秒
+dayuts().millisecond() // 0-999
+
+// 设置毫秒(返回新的 dayuts 对象)
+dayuts().millisecond(1)
+
+// 获取秒
+dayuts().second() // 0-59
+dayuts().second(1)
+
+// 获取分钟
+dayuts().minute() // 0-59
+dayuts().minute(1)
+
+// 获取小时
+dayuts().hour() // 0-23
+dayuts().hour(12)
+
+// 获取日期
+dayuts().date() // 1-31
+dayuts().date(12)
+
+// 获取月份(0-11)
+dayuts().month() // 0-11
+dayuts().month(0)
+
+// 获取年份
+dayuts().year()
+dayuts().year(2000)
+
+// 获取星期几(0-6,0 表示周日)
+dayuts().day()
+
+// 通用 get/set
+dayuts().get('year')
+dayuts().set('month', 5)
+```
+
+### 日期操作
+
+```ts
+// 增加时间
+dayuts().add(7, 'day')
+dayuts().add(1, 'month')
+dayuts().add(2, 'year')
+
+// 减去时间
+dayuts().subtract(7, 'year')
+dayuts().subtract(1, 'day')
+
+// 设置到时间开始
+dayuts().startOf('year')
+dayuts().startOf('month')
+dayuts().startOf('day')
+
+// 设置到时间末尾
+dayuts().endOf('year')
+dayuts().endOf('month')
+dayuts().endOf('day')
+```
+
+### 日期格式化
+
+```ts
+// 默认格式(ISO8601)
+dayuts().format() // 2024-03-28T01:33:29+08:00
+
+// 自定义格式
+dayuts('2019-01-25').format('DD/MM/YYYY') // 25/01/2019
+dayuts('2019-01-25').format('YYYY-MM-DD HH:mm:ss') // 2019-01-25 00:00:00
+
+// 转义字符(用方括号包裹)
+dayuts('2019-01-25').format('[YYYYescape] YYYY-MM-DD') // YYYYescape 2019-01-25
+```
+
+### 日期比较
+
+```ts
+// 是否在前
+dayuts().isBefore(dayuts('2011-01-01')) // false
+dayuts().isBefore('2011-01-01', 'month')
+
+// 是否在后
+dayuts().isAfter(dayuts('2011-01-01')) // true
+dayuts().isAfter('2011-01-01', 'year')
+
+// 是否相同
+dayuts().isSame('2011-01-01', 'year') // false
+dayuts().isSame(dayuts(), 'day') // true
+
+// 是否相同或在前
+dayuts().isSameOrBefore('2011-01-01', 'month')
+
+// 是否相同或在后
+dayuts().isSameOrAfter('2011-01-01', 'month')
+
+// 是否在区间内
+dayuts('2010-10-20').isBetween('2010-10-19', dayuts('2010-10-25')) // true
+dayuts('2010-10-20').isBetween('2010-10-19', '2010-10-25', 'day', '()') // true
+dayuts('2010-10-20').isBetween('2010-10-19', '2010-10-25', 'day', '[)') // true(包含开始)
+```
+
+### 相对时间
+
+```ts
+// 相对当前时间(前)
+dayuts('1999-01-01').fromNow() // 26 年前
+dayuts('1999-01-01').fromNow(true) // 26 年(不带后缀)
+
+// 相对指定时间(前)
+dayuts('1999-01-01').from(dayuts())
+
+// 相对当前时间(后)
+dayuts('1999-01-01').toNow()
+dayuts('1999-01-01').to(dayuts())
+
+// 计算时间差
+dayuts('2019-01-25').diff(dayuts('2018-06-05')) // 毫秒差
+dayuts('2019-01-25').diff(dayuts('2018-06-05'), 'month') // 月份差(整数)
+dayuts('2019-01-25').diff(dayuts('2018-06-05'), 'month', true) // 月份差(浮点数)
+```
+
+### 国际化
+
+```ts
+import { dayuts, dayutsIntl } from '@/uni_modules/lime-dayuts'
+
+// 全局设置语言
+dayutsIntl.locale = 'zh-cn'
+
+// 实例级设置语言
+dayuts().locale('zh-cn').format('dddd') // 星期一
+
+// 注册自定义语言
+dayutsIntl.use({
+ name: 'ja',
+ weekdays: ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'],
+ months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
+ ordinal: (n: number, _: string): string => `${n}日`,
+ // ... 其他配置
+})
+dayutsIntl.locale = 'ja'
+```
+
+### 其他常用方法
+
+```ts
+// 是否为闰年
+dayuts().isLeapYear()
+
+// 是否为今天
+dayuts('2024-03-25').isToday()
+
+// 获取时间戳(毫秒)
+dayuts().valueOf()
+
+// 获取 Unix 时间戳(秒)
+dayuts().unix()
+
+// 获取月份天数
+dayuts('2024-02-01').daysInMonth() // 29(闰年)
+
+// 获取一年中的第几天
+dayuts('2024-03-01').dayOfYear() // 61
+
+// 转换方法
+dayuts().toDate() // Date 对象
+dayuts().toArray() // [年, 月, 日, 时, 分, 秒, 毫秒]
+dayuts().toJSON() // ISO 8601 字符串
+dayuts().toObject() // { years, months, date, hours, minutes, seconds, milliseconds }
+dayuts().toString() // 字符串表示
+
+// 克隆
+const d = dayuts('2024-03-25')
+const d2 = d.clone()
+
+// 是否有效
+dayuts('invalid').isValid() // false
+
+// UTC 偏移量(分钟)
+dayuts().utcOffset()
+```
+
+## API 文档
+
+### 解析方法
+
+| 方法 | 说明 | 参数 | 返回值 |
+| --- | --- | --- | --- |
+| `dayuts()` | 创建当前时间的 Dayuts 对象 | - | Dayuts 对象 |
+| `dayuts(string)` | 从字符串创建 Dayuts 对象 | 日期字符串 | Dayuts 对象 |
+| `dayuts(string, format)` | 从指定格式的字符串创建 Dayuts 对象 | 日期字符串, 格式字符串 | Dayuts 对象 |
+| `dayuts(number)` | 从时间戳创建 Dayuts 对象 | 时间戳(毫秒) | Dayuts 对象 |
+| `dayuts(Date)` | 从 Date 对象创建 Dayuts 对象 | Date 对象 | Dayuts 对象 |
+| `dayuts(array)` | 从数组创建 Dayuts 对象 | `[年, 月, 日, 时, 分, 秒, 毫秒]` | Dayuts 对象 |
+| `dayuts(Dayuts)` | 克隆 Dayuts 对象 | Dayuts 对象 | Dayuts 对象 |
+| `isDayuts(date)` | 判断是否为 Dayuts 实例 | 任意值 | 布尔值 |
+
+### 取值/赋值方法
+
+| 方法 | 说明 | 参数 | 返回值 |
+| --- | --- | --- | --- |
+| `millisecond()` | 获取毫秒 | - | 0-999 |
+| `millisecond(value)` | 设置毫秒 | 0-999 | Dayuts 对象 |
+| `second()` | 获取秒 | - | 0-59 |
+| `second(value)` | 设置秒 | 0-59 | Dayuts 对象 |
+| `minute()` | 获取分钟 | - | 0-59 |
+| `minute(value)` | 设置分钟 | 0-59 | Dayuts 对象 |
+| `hour()` | 获取小时 | - | 0-23 |
+| `hour(value)` | 设置小时 | 0-23 | Dayuts 对象 |
+| `date()` | 获取日期 | - | 1-31 |
+| `date(value)` | 设置日期 | 1-31 | Dayuts 对象 |
+| `day()` | 获取星期(0=周日) | - | 0-6 |
+| `day(value)` | 设置星期 | 0-6 | Dayuts 对象 |
+| `month()` | 获取月份(0=一月) | - | 0-11 |
+| `month(value)` | 设置月份 | 0-11 | Dayuts 对象 |
+| `year()` | 获取年份 | - | 年份数字 |
+| `year(value)` | 设置年份 | 年份数字 | Dayuts 对象 |
+| `get(unit)` | 获取指定单位的值 | 时间单位 | 数值 |
+| `set(unit, value)` | 设置指定单位的值 | 时间单位, 值 | Dayuts 对象 |
+
+### 操作方法
+
+| 方法 | 说明 | 参数 | 返回值 |
+| --- | --- | --- | --- |
+| `add(value, unit)` | 增加时间 | 数值, 单位 | Dayuts 对象 |
+| `subtract(value, unit)` | 减少时间 | 数值, 单位 | Dayuts 对象 |
+| `startOf(unit)` | 设置为时间单位的开始 | 单位 | Dayuts 对象 |
+| `endOf(unit)` | 设置为时间单位的结束 | 单位 | Dayuts 对象 |
+| `dayOfYear()` | 获取一年中的第几天 | - | 数值 |
+| `dayOfYear(value)` | 设置为一年中的第几天 | 数值 | Dayuts 对象 |
+| `clone()` | 克隆当前对象 | - | Dayuts 对象 |
+| `locale(name)` | 设置实例语言 | 语言名称 | Dayuts 对象 |
+
+### 显示方法
+
+| 方法 | 说明 | 参数 | 返回值 |
+| --- | --- | --- | --- |
+| `format(template)` | 格式化日期 | 格式模板 | 字符串 |
+| `fromNow(withoutSuffix)` | 相对当前时间(前) | 是否不带后缀 | 字符串 |
+| `from(compared, withoutSuffix)` | 相对指定时间(前) | 比较时间, 是否不带后缀 | 字符串 |
+| `toNow(withoutSuffix)` | 相对当前时间(后) | 是否不带后缀 | 字符串 |
+| `to(compared, withoutSuffix)` | 相对指定时间(后) | 比较时间, 是否不带后缀 | 字符串 |
+| `diff(compared, unit, float)` | 计算时间差 | 比较时间, 单位, 是否返回浮点数 | 数值 |
+| `valueOf()` | 获取时间戳(毫秒) | - | 数值 |
+| `unix()` | 获取时间戳(秒) | - | 数值 |
+| `daysInMonth()` | 获取月份的天数 | - | 数值 |
+| `utcOffset()` | 获取 UTC 偏移量(分钟) | - | 数值 |
+| `toDate()` | 转换为 Date 对象 | - | Date 对象 |
+| `toArray()` | 转换为数组 | - | `number[]` |
+| `toJSON()` | 转换为 JSON 字符串 | - | 字符串或 null |
+| `toISOString()` | 转换为 ISO 8601 字符串 | - | 字符串 |
+| `toObject()` | 转换为对象 | - | DayutsObject |
+| `toString()` | 转换为字符串 | - | 字符串 |
+
+### 查询方法
+
+| 方法 | 说明 | 参数 | 返回值 |
+| --- | --- | --- | --- |
+| `isBefore(compared, unit)` | 是否在指定时间之前 | 比较时间, 单位 | 布尔值 |
+| `isAfter(compared, unit)` | 是否在指定时间之后 | 比较时间, 单位 | 布尔值 |
+| `isSame(compared, unit)` | 是否与指定时间相同 | 比较时间, 单位 | 布尔值 |
+| `isSameOrBefore(compared, unit)` | 是否相同或之前 | 比较时间, 单位 | 布尔值 |
+| `isSameOrAfter(compared, unit)` | 是否相同或之后 | 比较时间, 单位 | 布尔值 |
+| `isBetween(from, to, unit, inclusivity)` | 是否在区间内 | 开始, 结束, 单位, 包含性 | 布尔值 |
+| `isLeapYear()` | 是否为闰年 | - | 布尔值 |
+| `isToday()` | 是否为今天 | - | 布尔值 |
+| `isValid()` | 日期是否有效 | - | 布尔值 |
+
+### 国际化 API
+
+| 方法 | 说明 | 参数 | 返回值 |
+| --- | --- | --- | --- |
+| `dayutsIntl.locale = 'zh-cn'` | 全局设置语言 | 语言名称 | - |
+| `dayutsIntl.locale` | 获取当前语言 | - | 语言名称 |
+| `dayutsIntl.use(locale)` | 注册语言 | DayutsLocale 对象 | DayutsIntl |
+| `dayutsIntl.set(name, locale)` | 设置语言配置 | 名称, DayutsLocale | - |
+| `dayutsIntl.has(name)` | 是否已注册语言 | 名称 | 布尔值 |
+| `instance.locale(name)` | 设置实例语言 | 语言名称 | Dayuts 对象 |
+
+### 格式化占位符
+
+| 占位符 | 输出 | 描述 |
+| --- | --- | --- |
+| `YY` | 01 | 两位数的年份 |
+| `YYYY` | 2001 | 四位数的年份 |
+| `M` | 1-12 | 月份,从 1 开始计数 |
+| `MM` | 01-12 | 月份,两位数 |
+| `MMM` | Jan-Dec | 缩写的月份名称 |
+| `MMMM` | January-December | 完整的月份名称 |
+| `D` | 1-31 | 一个月的某一天 |
+| `DD` | 01-31 | 一个月的某一天,两位数 |
+| `d` | 0-6 | 一周的某一天(0=周日) |
+| `dd` | Su-Sa | 最小缩写的周名 |
+| `ddd` | Sun-Sat | 缩写的周名 |
+| `dddd` | Sunday-Saturday | 完整的周名 |
+| `H` | 0-23 | 小时数(24 小时制) |
+| `HH` | 00-23 | 小时数,两位数 |
+| `h` | 1-12 | 小时数(12 小时制) |
+| `hh` | 01-12 | 小时数,两位数 |
+| `m` | 0-59 | 分钟数 |
+| `mm` | 00-59 | 分钟数,两位数 |
+| `s` | 0-59 | 秒数 |
+| `ss` | 00-59 | 秒数,两位数 |
+| `S` | 0-9 | 百毫秒数,一位数 |
+| `SS` | 00-99 | 十毫秒数,两位数 |
+| `SSS` | 000-999 | 毫秒数,三位数 |
+| `Z` | -05:00 | 相对于 UTC 的偏移量 |
+| `ZZ` | -0500 | 相对 UTC 的紧凑偏移量 |
+| `A` | AM/PM | 上午或下午,大写 |
+| `a` | am/pm | 上午或下午,小写 |
+| `[...]` | 原样输出 | 转义字符,方括号内的内容原样输出 |
+
+### 时间单位
+
+| 单位 | 缩写 | 描述 |
+| --- | --- | --- |
+| `day` | `d` | 日 |
+| `week` | `w` | 周 |
+| `month` | `M` | 月 |
+| `year` | `y` | 年 |
+| `hour` | `h` | 小时 |
+| `minute` | `m` | 分钟 |
+| `second` | `s` | 秒 |
+| `millisecond` | `ms` | 毫秒 |
+| `date` | `D` | 日期(与 day 不同,date 表示一个月的某一天) |
+| `quarter` | `Q` | 季度(常量已定义,部分方法未完整实现) |
+
+## Vue2 使用说明
+
+lime-dayuts 在 Vue2 项目中使用了 `composition-api`(用于 locale 响应式状态),如需在 Vue2 项目中使用,请按照[官方教程](https://uniapp.dcloud.net.cn/tutorial/vue-composition-api.html)配置。
+
+关键配置代码(在 main.js 中添加):
+
+```js
+// vue2
+import Vue from 'vue'
+import VueCompositionAPI from '@vue/composition-api'
+Vue.use(VueCompositionAPI)
+```
+
+::: tip 注意🔔
+仅在 Vue2 项目中需要此配置,Vue3 和 uni-app x 项目无需配置。
+:::
+
+## 文档链接
+
+📚 组件详细文档请访问以下站点:
+- [日期库文档 - 站点1](https://limex.qcoon.cn/uts/dayuts.html)
+- [日期库文档 - 站点2](https://limeui.netlify.app/uts/dayuts.html)
+- [日期库文档 - 站点3](https://limeui.familyzone.top/uts/dayuts.html)
+
+## 注意事项
+
+### 与 dayjs 的差异
+
+lime-dayuts 虽然尽量保持与 dayjs 一致的 API,但受 UTS 强类型语言限制,存在以下差异:
+
+1. **不支持 `undefined`**:空值统一使用 `null`
+2. **不支持 truthy/falsy 判断**:必须显式比较,如 `if (value != null)`
+3. **插件机制**:lime-dayuts 暂未实现 `dayuts.extend(plugin)` 插件机制,所有功能内置
+4. **UTC 模式**:暂未实现 `dayuts.utc()`,`utcOffset()` 在 App 端(Android/iOS)始终返回 0
+5. **时区支持**:暂未实现 `dayuts.tz()` 时区转换
+6. **Duration**:暂未实现 `dayuts.duration()` 时间段对象
+
+### 平台差异
+
+1. **`toISOString()`**:在 App 端(Android/iOS)由于平台限制,返回 `toString()` 的结果而非标准 ISO 8601 格式
+2. **`utcOffset()`**:在 App 端(Android/iOS)始终返回 0,Web 端正常返回时区偏移
+3. **格式化实现**:Android 端使用链式 `replace` 实现,其他平台使用正则匹配,两者行为一致
+4. **纯数字字符串**:支持 `'1748750498228'` 形式的字符串时间戳解析
+
+### 内置语言
+
+lime-dayuts 内置以下语言:
+- `en` — 英语
+- `zh-cn` — 简体中文
+
+可通过 `dayutsIntl.use()` 方法注册其他语言。
+
+## 支持与赞赏
+
+如果你觉得本插件解决了你的问题,可以考虑支持作者:
+
+| 支付宝赞助 | 微信赞助 |
+|------------|------------|
+|  |  |
diff --git a/uni_modules/lime-dayuts/utssdk/app-js/config.json b/uni_modules/lime-dayuts/utssdk/app-js/config.json
new file mode 100644
index 0000000..721b81e
--- /dev/null
+++ b/uni_modules/lime-dayuts/utssdk/app-js/config.json
@@ -0,0 +1,3 @@
+{
+ "deploymentTarget": "9"
+}
\ No newline at end of file
diff --git a/uni_modules/lime-dayuts/utssdk/app-js/index.uts b/uni_modules/lime-dayuts/utssdk/app-js/index.uts
new file mode 100644
index 0000000..e538c34
--- /dev/null
+++ b/uni_modules/lime-dayuts/utssdk/app-js/index.uts
@@ -0,0 +1,3 @@
+export * from '../../common'
+export * from '../../common/use'
+export * from '../interface'
\ No newline at end of file
diff --git a/uni_modules/lime-dayuts/utssdk/interface.uts b/uni_modules/lime-dayuts/utssdk/interface.uts
new file mode 100644
index 0000000..6bd8d4b
--- /dev/null
+++ b/uni_modules/lime-dayuts/utssdk/interface.uts
@@ -0,0 +1,405 @@
+// @ts-nocheck
+export type DayutsConfig = {
+ date: any|null
+ format : string|null
+ locale : string|null
+}
+
+export type DayutsUnit = 'year' | 'month' | 'day' | 'week' | 'date' | 'hour' | 'minute' | 'second' | 'millisecond' | 'quarter'
+
+export type DayutsFormats = {
+ /**
+ * 小时和分钟的格式化字符串。
+ */
+ LT : string;
+
+ /**
+ * 小时、分钟和秒的格式化字符串。
+ */
+ LTS : string;
+
+ /**
+ * 年份、月份和日期的格式化字符串。
+ */
+ L : string;
+
+ /**
+ * 年份、月份、日期和星期的格式化字符串。
+ */
+ LL : string;
+
+ /**
+ * 年份、月份、日期、星期和小时的格式化字符串。
+ */
+ LLL : string;
+
+ /**
+ * 年份、月份、日期、星期、小时和分钟的格式化字符串。
+ */
+ LLLL : string;
+
+ /**
+ * 缩小的年份、月份和日期的格式化字符串。
+ */
+ l : string;
+
+ /**
+ * 缩小的年份、月份、日期和星期的格式化字符串。
+ */
+ ll : string;
+
+ /**
+ * 缩小的年份、月份、日期、星期和小时的格式化字符串。
+ */
+ lll : string;
+
+ /**
+ * 缩小的年份、月份、日期、星期、小时和分钟的格式化字符串。
+ */
+ llll : string;
+}
+export type DayutsRelativeTime = {
+ /**
+ * 时间单位之后的格式化字符串。
+ */
+ future : string;
+
+ /**
+ * 时间单位之前的格式化字符串。
+ */
+ past : string;
+
+ /**
+ * 秒的格式化字符串。
+ */
+ s : string;
+
+ /**
+ * 分钟的格式化字符串。
+ */
+ m : string;
+
+ /**
+ * 分钟(带前缀)的格式化字符串。
+ */
+ mm : string;
+
+ /**
+ * 小时的格式化字符串。
+ */
+ h : string;
+
+ /**
+ * 小时(带前缀)的格式化字符串。
+ */
+ hh : string;
+
+ /**
+ * 天的格式化字符串。
+ */
+ d : string;
+
+ /**
+ * 天(带前缀)的格式化字符串。
+ */
+ dd : string;
+
+ /**
+ * 月的格式化字符串。
+ */
+ M : string;
+
+ /**
+ * 月(带前缀)的格式化字符串。
+ */
+ MM : string;
+
+ /**
+ * 年的格式化字符串。
+ */
+ y : string;
+
+ /**
+ * 年(带前缀)的格式化字符串。
+ */
+ yy : string;
+}
+
+/**
+ * 本地化对象接口。
+ */
+export type DayutsLocale = {
+ /**
+ * 区域设置名称。
+ */
+ name : string;
+
+ /**
+ * 一周中每天的完整名称。
+ */
+ weekdays : string[];
+
+ /**
+ * 一周中每天的缩写名称。
+ */
+ weekdaysShort ?: string[];
+
+ /**
+ * 一周中每天的最小缩写名称。
+ */
+ weekdaysMin ?: string[];
+
+ /**
+ * 一年中的每个月份的名称。
+ */
+ months : string[];
+
+ /**
+ * 一年中的每个月份的缩写名称。
+ */
+ monthsShort ?: string[];
+
+ /**
+ * 返回序数词的函数。
+ *
+ * @param number - 要格式化的数字。
+ * @param period - 周期类型(如 "W" 表示周)。
+ * @returns 返回格式化后的序数词字符串。
+ */
+ // ordinal : (number : number) => string;
+ ordinal : (number : number, period : string) => string;
+
+ /**
+ * 一周的开始日期(星期几)。
+ */
+ weekStart ?: number;
+
+ /**
+ * 年的开始月份。
+ */
+ yearStart ?: number;
+
+ /**
+ * 日期和时间格式化选项。
+ */
+ formats ?: DayutsFormats;
+
+ /**
+ * 相对时间格式化选项。
+ */
+ relativeTime ?: DayutsRelativeTime;
+
+ /**
+ * 根据小时和分钟返回上午或下午的函数。
+ *
+ * @param hour - 小时数(0-23)。
+ * @param minute - 分钟数(0-59)。
+ * @returns 返回上午或下午的字符串。
+ */
+ meridiem ?: (hour : number, minute : number, isLowercase : boolean) => string;
+}
+
+
+export type DayutsObject = {
+ years : number;
+ months : number;
+ date : number;
+ hours : number;
+ minutes : number;
+ seconds : number;
+ milliseconds : number;
+}
+
+
+// 主 Dayuts 类类型定义
+// export interface Dayuts {
+// // 公共属性
+// $L: string;
+
+// // 私有属性
+// $d: Date;
+// $y: number;
+// $M: number;
+// $D: number;
+// $W: number;
+// $H: number;
+// $m: number;
+// $s: number;
+// $ms: number;
+// $u: boolean;
+
+// // 构造函数
+// // constructor(cfg: DayutsConfig);
+
+// // 解析配置并初始化日期
+// // private parse(cfg: DayutsConfig): void;
+
+// // 初始化日期的各个部分
+// // private init(): void;
+
+// // 检查日期是否有效
+// isValid(): boolean;
+
+// // 判断当前日期是否与给定日期在指定时间单位内相同
+// // isSame(input: string, units?: DayutsUnit): boolean;
+// // isSame(input: number, units?: DayutsUnit): boolean;
+// // isSame(input: Date, units?: DayutsUnit): boolean;
+// // isSame(input: Dayuts, units?: DayutsUnit): boolean;
+// // isSame(input: UTSJSONObject, units?: DayutsUnit): boolean;
+// // isSame(input : string) : boolean
+// // isSame(input : number) : boolean
+// // isSame(input : Date) : boolean
+// // isSame(input : Dayuts) : boolean
+// // isSame(input : UTSJSONObject) : boolean
+
+// // isSame(input : string, units : DayutsUnit) : boolean
+// // isSame(input : number, units : DayutsUnit) : boolean
+// // isSame(input : Date, units : DayutsUnit) : boolean
+// // isSame(input : Dayuts, units : DayutsUnit) : boolean
+// // isSame(input : UTSJSONObject, units : DayutsUnit) : boolean
+// // isSame(input : any, units : DayutsUnit) : boolean
+
+// // 判断给定日期是否在当前日期指定时间单位之后
+// // isAfter(input: string, units?: DayutsUnit): boolean;
+// // isAfter(input: number, units?: DayutsUnit): boolean;
+// // isAfter(input: Date, units?: DayutsUnit): boolean;
+// // isAfter(input: Dayuts, units?: DayutsUnit): boolean;
+// // isAfter(input: UTSJSONObject, units?: DayutsUnit): boolean;
+// // isAfter(input : string) : boolean
+// // isAfter(input : number) : boolean
+// // isAfter(input : Date) : boolean
+// // isAfter(input : Dayuts) : boolean
+// // isAfter(input : UTSJSONObject) : boolean
+
+// // isAfter(input : string, units : DayutsUnit) : boolean
+// // isAfter(input : number, units : DayutsUnit) : boolean
+// // isAfter(input : Date, units : DayutsUnit) : boolean
+// // isAfter(input : Dayuts, units : DayutsUnit) : boolean
+// // isAfter(input : UTSJSONObject, units : DayutsUnit) : boolean
+// // isAfter(input : any, units : DayutsUnit) : boolean
+
+// // 判断给定日期是否在当前日期指定时间单位之前
+// // isBefore(input: string, units?: DayutsUnit): boolean;
+// // isBefore(input: number, units?: DayutsUnit): boolean;
+// // isBefore(input: Date, units?: DayutsUnit): boolean;
+// // isBefore(input: Dayuts, units?: DayutsUnit): boolean;
+// // isBefore(input: UTSJSONObject, units?: DayutsUnit): boolean;
+
+// // 判断当前日期是否与给定日期在指定时间单位内相同或之前
+// // isSameOrBefore(input: string, units?: DayutsUnit): boolean;
+// // isSameOrBefore(input: number, units?: DayutsUnit): boolean;
+// // isSameOrBefore(input: Date, units?: DayutsUnit): boolean;
+// // isSameOrBefore(input: Dayuts, units?: DayutsUnit): boolean;
+// // isSameOrBefore(input: UTSJSONObject, units?: DayutsUnit): boolean;
+
+// // 判断当前日期是否与给定日期在指定时间单位内相同或之后
+// // isSameOrAfter(input: string, units?: DayutsUnit): boolean;
+// // isSameOrAfter(input: number, units?: DayutsUnit): boolean;
+// // isSameOrAfter(input: Date, units?: DayutsUnit): boolean;
+// // isSameOrAfter(input: Dayuts, units?: DayutsUnit): boolean;
+// // isSameOrAfter(input: UTSJSONObject, units?: DayutsUnit): boolean;
+
+// // 判断当前日期是否在两个给定日期之间
+// // isBetween(input: any, input2: any): boolean;
+// // isBetween(input: any, input2: any, units?: DayutsUnit, interval?: string): boolean;
+
+// // 判断当前年份是否为闰年
+// isLeapYear(): boolean;
+
+// // 判断当前日期是否为今天
+// isToday(): boolean;
+
+// // 获取 Unix 时间戳(秒)
+// unix(): number;
+
+// // 设置为指定时间单位的开始或结束
+// // startOf(units: DayutsUnit): Dayuts;
+// // startOf(units: DayutsUnit, startOf?: boolean): Dayuts;
+// endOf(units: DayutsUnit): Dayuts;
+
+// // 设置指定时间单位的值
+// set(units: DayutsUnit, int: number): Dayuts;
+
+// // 获取指定时间单位的值
+// get(units: DayutsUnit): number;
+
+// // 年、月、日、时、分、秒、毫秒的 getter 和 setter 方法
+// year(): number;
+// year(input?: number): Dayuts;
+// month(): number
+// month(input?: number): Dayuts;
+// // day(): number;
+// day(input?: number): Dayuts;
+// date(): number;
+// date(input?: number): Dayuts;
+// // hour(): number;
+// hour(input?: number): Dayuts;
+// // minute(): number;
+// minute(input?: number): Dayuts;
+// // second(): number;
+// second(input?: number): Dayuts;
+// // millisecond(): number;
+// millisecond(input?: number): Dayuts;
+
+// // 添加时间
+// add(number: number, units: DayutsUnit): Dayuts;
+
+// // 减去时间
+// subtract(number: number, units: DayutsUnit): Dayuts;
+
+// // 格式化日期
+// format(formatStr?: string): string;
+
+// // 获取 UTC 偏移量(分钟)
+// utcOffset(): number;
+
+// // 计算两个日期之间的差值
+// diff(input: string, units?: DayutsUnit, float?: boolean): number;
+// diff(input: number, units?: DayutsUnit, float?: boolean): number;
+// diff(input: Date, units?: DayutsUnit, float?: boolean): number;
+// diff(input: Dayuts, units?: DayutsUnit, float?: boolean): number;
+// diff(input: UTSJSONObject, units?: DayutsUnit, float?: boolean): number;
+
+// // 转换为原生 Date 对象
+// toDate(): Date;
+
+// // 转换为 ISO 8601 字符串
+// toJSON(): string | null;
+// toISOString(): string;
+
+// // 转换为对象
+// toObject(): DayutsObject;
+
+// // 转换为数组
+// toArray(): number[];
+
+// // 获取时间戳(毫秒)
+// valueOf(): number;
+
+// // 获取月份的天数
+// daysInMonth(): number;
+
+// // 获取本地化对象
+// // private $locale(): DayutsLocale;
+
+// // 设置或获取本地化配置
+// locale(preset: string, object?: DayutsLocale): Dayuts;
+// locale(preset: DayutsLocale, object?: DayutsLocale): Dayuts;
+
+// // 克隆当前实例
+// clone(): Dayuts;
+
+// // 转换为字符串
+// // toString(): string;
+
+// // 获取或设置一年中的第几天
+// dayOfYear(): number;
+// dayOfYear(input?: number): Dayuts;
+
+// // 相对时间方法
+// fromToBase(input: any, withoutSuffix: boolean, instance: Dayuts, isFrom: boolean): string;
+// to(input: any, withoutSuffix?: boolean): string;
+// from(input: any, withoutSuffix?: boolean): string;
+// toNow(withoutSuffix?: boolean): string;
+// fromNow(withoutSuffix?: boolean): string;
+// }
\ No newline at end of file
diff --git a/uni_modules/lime-dayuts/utssdk/unierror.uts b/uni_modules/lime-dayuts/utssdk/unierror.uts
new file mode 100644
index 0000000..8428bbd
--- /dev/null
+++ b/uni_modules/lime-dayuts/utssdk/unierror.uts
@@ -0,0 +1,39 @@
+/* 此规范为 uni 规范,可以按照自己的需要选择是否实现 */
+import { MyApiErrorCode, MyApiFail } from "./interface.uts"
+/**
+ * 错误主题
+ * 注意:错误主题一般为插件名称,每个组件不同,需要使用时请更改。
+ * [可选实现]
+ */
+export const UniErrorSubject = 'uts-api';
+
+
+/**
+ * 错误信息
+ * @UniError
+ * [可选实现]
+ */
+export const UniErrors : Map = new Map([
+ /**
+ * 错误码及对应的错误信息
+ */
+ [9010001, 'custom error mseeage1'],
+ [9010002, 'custom error mseeage2'],
+]);
+
+
+/**
+ * 错误对象实现
+ */
+export class MyApiFailImpl extends UniError implements MyApiFail {
+
+ /**
+ * 错误对象构造函数
+ */
+ constructor(errCode : MyApiErrorCode) {
+ super();
+ this.errSubject = UniErrorSubject;
+ this.errCode = errCode;
+ this.errMsg = UniErrors[errCode] ?? "";
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-loading/changelog.md b/uni_modules/lime-loading/changelog.md
new file mode 100644
index 0000000..a7f1991
--- /dev/null
+++ b/uni_modules/lime-loading/changelog.md
@@ -0,0 +1,44 @@
+## 0.2.2(2025-12-09)
+- chore: 删除多余文件
+## 0.2.1(2025-12-09)
+- fix: 修复类型问题
+## 0.2.0(2025-10-21)
+- fix: 修复uniappx ios 在弹窗下会导致弹窗无内容。
+## 0.1.9(2025-08-31)
+- chore: 更新文档
+## 0.1.8(2025-05-30)
+- feat: 重构,针对uniapp x app增加`mode`属性,用于是使用计时器还是元素的animate实现
+## 0.1.7(2025-05-16)
+- fix: 修复 uniappx ios 初始化尺寸为0导致不生效的问题
+## 0.1.6(2025-04-21)
+- fix: uniappx 鸿蒙next 尺寸从0变化时无法渲染
+## 0.1.5(2025-04-21)
+- feat: 兼容uniappx 鸿蒙next
+## 0.1.4(2025-04-10)
+- feat: 增加暂停
+## 0.1.3(2025-03-12)
+- feat: uniappx app使用`requestAnimationFrame`
+## 0.1.2(2025-02-13)
+- fix: 修复因uniapp x ios监听元素不生效导致不生效问题
+## 0.1.1(2025-02-09)
+- chore: 去掉多余console
+## 0.1.0(2025-02-09)
+- feat: 重构useLoading
+## 0.0.9(2025-01-14)
+- feat: useLoading 增加color ref
+## 0.0.8(2024-12-24)
+- feat: 改用监听尺寸方式
+## 0.0.7(2024-12-18)
+- fix: 修复vue2 微信小程序不显示加载图标的问题
+## 0.0.6(2024-12-18)
+- chore: 更新文档
+## 0.0.5(2024-09-30)
+- fix: vue2 app 使用渐变
+## 0.0.4(2024-09-29)
+- chore: 非uvue app size使用css变量
+## 0.0.3(2024-09-25)
+- fix: useLoading
+## 0.0.2(2024-04-05)
+- feat: 支持 uniapp x ios(app-js)
+## 0.0.1(2023-10-13)
+- 首次上传
diff --git a/uni_modules/lime-loading/components/l-loading/index-u.scss b/uni_modules/lime-loading/components/l-loading/index-u.scss
new file mode 100644
index 0000000..4437509
--- /dev/null
+++ b/uni_modules/lime-loading/components/l-loading/index-u.scss
@@ -0,0 +1,252 @@
+@import '@/uni_modules/lime-style/index.scss';
+
+$use-css-var: true;
+
+$loading-color: create-var(loading-color, $primary-color);
+$loading-size: create-var(loading-size, 20px);
+$loading-text-color: create-var(loading-text-color, $text-color-3);
+$loading-font-size: create-var(loading-font-size, $font-size);
+
+/* #ifndef UNI-APP-X && APP || APP-NVUE */
+/* #ifndef MP-ALIPAY */
+$loading-duration: var(--l-loading-duration, 2s);
+
+@property --l-loading-start {
+ syntax: '';
+ initial-value: 1%;
+ inherits: false;
+}
+@property --l-loading-end {
+ syntax: '';
+ initial-value: 1%;
+ inherits: false;
+}
+@property --l-left {
+ syntax: '';
+ initial-value: 1%;
+ inherits: false;
+}
+@property --l-loadding-ball-size {
+ syntax: ' | ';
+ // initial-value: 1%;
+ inherits: false;
+}
+
+/* #endif */
+/* #ifdef MP-ALIPAY */
+$loading-duration: var(--l-loading-duration, 1s);
+/* #endif */
+
+
+/* #endif */
+
+
+
+.l-loading {
+ display: flex;
+ position: relative;
+ flex-direction: row;
+ align-items: center;
+ // align-self: flex-start;
+ /* #ifndef UNI-APP-X && APP || APP-NVUE */
+ color: $loading-color;
+ &--ball{
+ display: inline-flex;
+ align-items: center;
+
+ .l-loading {
+ &__ball {
+ position: relative;
+ perspective: calc(var(--l-loadding-ball-size) * 4);
+ transform-style: preserve-3d;
+ // border: 1px solid;
+
+ &:before{
+ background-color: $primary-color;
+ left: 0%;
+ // mix-blend-mode: darken;
+ animation-name: l-ball-before;
+ }
+ &:after{
+ right: 0;
+ background-color: red;
+ // mix-blend-mode: darken;
+ animation-name: l-ball-after;
+ }
+ &:before,&:after{
+ top: 0;
+ content: '';
+ position: absolute;
+ // width: 100%;
+ height: 100%;
+ aspect-ratio: 1/1;
+ border-radius: 50%;
+ animation-iteration-count: infinite;
+ animation-delay: -100ms;
+ animation-duration: 900ms;
+ mix-blend-mode: darken;
+ animation-play-state: var(--l-play-state, running);
+ }
+ }
+ }
+ }
+ &--circular {
+ .l-loading {
+ &__circular {
+ display: inline-block;
+ position: relative;
+ animation: l-rotate $loading-duration linear infinite;
+ animation-play-state: var(--l-play-state, running);
+ vertical-align: middle;
+ &:before {
+ content: '';
+ display: block;
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ /* #ifndef MP-ALIPAY */
+ background: conic-gradient(
+ transparent 0%,
+ transparent var(--l-loading-start, 0%), var(--l-loading-color-1, currentColor) var(--l-loading-start, 0%),
+ var(--l-loading-color-2, currentColor) var(--l-loading-end, 0%), transparent var(--l-loading-end, 0%),
+ transparent 100%);
+ /* #endif */
+ /* #ifdef MP-ALIPAY */
+ background: conic-gradient(
+ var(--l-loading-color-1, transparent) 0%,
+ var(--l-loading-color-2, currentColor) 100%);
+ /* #endif */
+ mask: radial-gradient(closest-side, transparent calc(80% - 1px), #fff 80%);
+ -webkit-mask: radial-gradient(closest-side, transparent calc(80% - 1px), #fff 80%);
+ animation: l-circular 3s ease-in-out infinite;
+ animation-play-state: var(--l-play-state, running);
+ transform: rotate(90deg);
+ }
+ }
+ }
+ }
+ &--spinner {
+ .l-loading {
+ &__spinner {
+ position: relative;
+ box-sizing: border-box;
+ // width: 100%;
+ // height: 100%;
+ // max-width: 100%;
+ // max-height: 100%;
+ animation-timing-function: steps(12);
+ animation: l-rotate 1.5s linear infinite;
+ animation-play-state: var(--l-play-state, running)
+ }
+ &__dot {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ transform: rotate(calc(var(--l-loading-dot, 1) * 30deg));
+ opacity: calc(var(--l-loading-dot, 1) / 12);
+ &::before {
+ display: block;
+ width: 5rpx;
+ height: 25%;
+ margin: 0 auto;
+ background-color: currentColor;
+ border-radius: 40%;
+ content: ' ';
+ }
+ }
+ }
+ }
+ /* #endif */
+ /* #ifdef UNI-APP-X && APP || APP-NVUE */
+ &__view{
+ // background-color: aqua;
+ // background-color: #1677ff;
+ // transition-duration: 1.5s;
+ // transition-property: transform;
+ // transition-timing-function: linear;
+ }
+ /* #endif */
+ &__text {
+ margin-left: $spacer-xs;
+ color: $loading-text-color;
+ font-size: $loading-font-size;
+ }
+
+
+ &.is-vertical {
+ flex-direction: column;
+ .l-loading__text {
+ margin: $spacer-tn 0 0;
+ }
+ }
+ &__ball,&__circular,&__spinner {
+ width: $loading-size;
+ height: $loading-size;
+ }
+}
+
+
+/* #ifndef UNI-APP-X && APP || APP-NVUE */
+@keyframes l-circular {
+ 0% {
+ --l-loading-start: 0%;
+ --l-loading-end: 0%;
+ }
+ 50% {
+ --l-loading-start: 0%;
+ --l-loading-end: 100%;
+ }
+ 100% {
+ --l-loading-start: 100%;
+ --l-loading-end: 100%;
+ }
+}
+@keyframes l-rotate {
+ to {
+ transform: rotate(1turn)
+ }
+}
+
+@keyframes l-ball-before {
+ 0%{
+ animation-timing-function: ease-in;
+ }
+ 25% {
+ animation-timing-function: ease-out;
+ --l-left: calc((var(--l-loadding-ball-size,100%) * 2.1 - var(--l-loadding-ball-size,100%)) / 2);
+ transform: translate3d(var(--l-left), 0, var(--l-loadding-ball-size));
+ }
+ 50% {
+ --l-left: calc((var(--l-loadding-ball-size,100%) * 2.1 - var(--l-loadding-ball-size,100%)));
+ animation-timing-function:ease-in;
+ transform: translate3d(var(--l-left), 0, 0);
+ }
+ 75% {
+ animation-timing-function: ease-out;
+ --l-left: calc((var(--l-loadding-ball-size,100%) * 2.1 - var(--l-loadding-ball-size,100%)) / 2);
+ transform: translate3d(var(--l-left), 0, calc(var(--l-loadding-ball-size) * -1));
+ }
+}
+@keyframes l-ball-after {
+ 0%{
+ animation-timing-function: ease-in;
+ }
+ 25% {
+ animation-timing-function: ease-out;
+ --l-left: calc((var(--l-loadding-ball-size,100%) * 2.1 - var(--l-loadding-ball-size,100%)) / 2 * -1);
+ transform: translate3d(var(--l-left), 0, calc(var(--l-loadding-ball-size) * -1));
+ }
+ 50% {
+ animation-timing-function:ease-in;
+ --l-left: calc((var(--l-loadding-ball-size,100%) * 2.1 - var(--l-loadding-ball-size,100%)) * -1);
+ transform: translate3d(var(--l-left), 0, 0);
+ }
+ 75% {
+ animation-timing-function: ease-out;
+ --l-left: calc((var(--l-loadding-ball-size,100%) * 2.1 - var(--l-loadding-ball-size,100%)) / 2 * -1);
+ transform: translate3d(var(--l-left), 0, var(--l-loadding-ball-size));
+ }
+}
+/* #endif */
\ No newline at end of file
diff --git a/uni_modules/lime-loading/components/l-loading/index.scss b/uni_modules/lime-loading/components/l-loading/index.scss
new file mode 100644
index 0000000..17644ec
--- /dev/null
+++ b/uni_modules/lime-loading/components/l-loading/index.scss
@@ -0,0 +1,246 @@
+@import '@/uni_modules/lime-style/index.scss';
+
+$loading-color: create-var(loading-color, $primary-color);
+$loading-size: create-var(loading-size, 20px);
+$loading-text-color: create-var(loading-text-color, $text-color-3);
+$loading-font-size: create-var(loading-font-size, $font-size);
+
+/* #ifndef MP-ALIPAY */
+$loading-duration: create-var(loading-duration, 2s);
+/* #endif */
+/* #ifdef MP-ALIPAY */
+$loading-duration: create-var(loading-duration, 1s);
+/* #endif */
+
+/* #ifndef APP-NVUE */
+
+
+
+/* #ifndef MP-ALIPAY */
+@property --l-loading-start {
+ syntax: '';
+ initial-value: 1%;
+ inherits: false;
+}
+@property --l-loading-end {
+ syntax: '';
+ initial-value: 1%;
+ inherits: false;
+}
+@property --l-left {
+ syntax: '';
+ initial-value: 1%;
+ inherits: false;
+}
+@property --l-loadding-ball-size {
+ syntax: ' | ';
+ // initial-value: 1%;
+ inherits: false;
+}
+/* #endif */
+
+:host {
+ display: inline-flex;
+}
+/* #endif */
+
+
+.l-loading {
+ position: relative;
+ // color: #c8c9cc;
+ color: $loading-color;
+ font-size: 0;
+ vertical-align: middle;
+ &--ball{
+ display: inline-flex;
+ align-items: center;
+ .l-loading {
+ &__ball {
+ position: relative;
+ perspective: calc(var(--l-loadding-ball-size) * 4);
+ transform-style: preserve-3d;
+ // border: 1px solid;
+
+ &:before{
+ background-color: $primary-color;
+ left: 0%;
+ // mix-blend-mode: darken;
+ animation-name: l-ball-before;
+ }
+ &:after{
+ right: 0;
+ background-color: red;
+ // mix-blend-mode: darken;
+ animation-name: l-ball-after;
+ }
+ &:before,&:after{
+ top: 0;
+ content: '';
+ position: absolute;
+ // width: 100%;
+ height: 100%;
+ aspect-ratio: 1/1;
+ border-radius: 50%;
+ animation-iteration-count: infinite;
+ animation-delay: -100ms;
+ animation-duration: 900ms;
+ mix-blend-mode: darken;
+ animation-play-state: var(--l-play-state, running);
+ }
+ }
+ }
+ }
+ &--circular {
+ .l-loading {
+ &__circular {
+ display: inline-block;
+ position: relative;
+ /* #ifndef APP-NVUE */
+ animation: l-rotate $loading-duration linear infinite;
+ vertical-align: middle;
+ animation-play-state: var(--l-play-state, running);
+ &:before {
+ content: '';
+ display: block;
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ /* #ifndef MP-ALIPAY || APP-VUE */
+ background-image: conic-gradient(
+ transparent 0%,
+ transparent var(--l-loading-start, 0%), var(--l-loading-color-1, currentColor) var(--l-loading-start, 0%),
+ var(--l-loading-color-2, currentColor) var(--l-loading-end, 0%), transparent var(--l-loading-end, 0%),
+ transparent 100%);
+ /* #endif */
+ /* #ifdef MP-ALIPAY || APP-VUE */
+ background-image: conic-gradient(
+ var(--l-loading-color-1, transparent) 0%,
+ var(--l-loading-color-2, currentColor) 100%);
+ /* #endif */
+ mask: radial-gradient(closest-side, transparent calc(80% - 1px), #fff 80%);
+ -webkit-mask: radial-gradient(closest-side, transparent calc(80% - 1px), #fff 80%);
+ animation: l-circular 2.5s ease-in-out infinite;
+ transform: rotate(90deg);
+ animation-play-state: var(--l-play-state, running);
+ }
+ /* #endif */
+ }
+ }
+ }
+ &--spinner {
+ .l-loading {
+ &__spinner {
+ position: relative;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ max-width: 100%;
+ max-height: 100%;
+ animation-timing-function: steps(12);
+ animation: l-rotate 0.8s linear infinite;
+ animation-play-state: var(--l-play-state, running);
+ }
+ &__dot {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ transform: rotate(calc(var(--l-loading-dot, 1) * 30deg));
+ opacity: calc(var(--l-loading-dot, 1) / 12);
+ &::before {
+ display: block;
+ width: 5rpx;
+ height: 25%;
+ margin: 0 auto;
+ background-color: currentColor;
+ border-radius: 40%;
+ content: ' ';
+ }
+ }
+ }
+
+ }
+ &__text{
+ display: inline-block;
+ margin-left: $spacer-xs;
+ color: $loading-text-color;
+ font-size: $loading-font-size;
+ vertical-align: middle;
+ }
+ &.is-vertical {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+
+ .l-loading__text {
+ margin: $spacer-tn 0 0;
+ }
+ }
+ &__ball,&__circular,&__spinner {
+ width: $loading-size;
+ height: $loading-size;
+ }
+}
+/* #ifndef APP-NVUE */
+@keyframes l-circular {
+ 0% {
+ --l-loading-start: 0%;
+ --l-loading-end: 0%;
+ }
+ 50% {
+ --l-loading-start: 0%;
+ --l-loading-end: 100%;
+ }
+ 100% {
+ --l-loading-start: 100%;
+ --l-loading-end: 100%;
+ }
+}
+@keyframes l-rotate {
+ to {
+ transform: rotate(1turn)
+ }
+}
+
+@keyframes l-ball-before {
+ 0%{
+ animation-timing-function: ease-in;
+ }
+ 25% {
+ animation-timing-function: ease-out;
+ --l-left: calc((var(--l-loadding-ball-size,100%) * 2.1 - var(--l-loadding-ball-size,100%)) / 2);
+ transform: translate3d(var(--l-left), 0, var(--l-loadding-ball-size));
+ }
+ 50% {
+ --l-left: calc((var(--l-loadding-ball-size,100%) * 2.1 - var(--l-loadding-ball-size,100%)));
+ animation-timing-function:ease-in;
+ transform: translate3d(var(--l-left), 0, 0);
+ }
+ 75% {
+ animation-timing-function: ease-out;
+ --l-left: calc((var(--l-loadding-ball-size,100%) * 2.1 - var(--l-loadding-ball-size,100%)) / 2);
+ transform: translate3d(var(--l-left), 0, calc(var(--l-loadding-ball-size) * -1));
+ }
+}
+@keyframes l-ball-after {
+ 0%{
+ animation-timing-function: ease-in;
+ }
+ 25% {
+ animation-timing-function: ease-out;
+ --l-left: calc((var(--l-loadding-ball-size,100%) * 2.1 - var(--l-loadding-ball-size,100%)) / 2 * -1);
+ transform: translate3d(var(--l-left), 0, calc(var(--l-loadding-ball-size) * -1));
+ }
+ 50% {
+ animation-timing-function:ease-in;
+ --l-left: calc((var(--l-loadding-ball-size,100%) * 2.1 - var(--l-loadding-ball-size,100%)) * -1);
+ transform: translate3d(var(--l-left), 0, 0);
+ }
+ 75% {
+ animation-timing-function: ease-out;
+ --l-left: calc((var(--l-loadding-ball-size,100%) * 2.1 - var(--l-loadding-ball-size,100%)) / 2 * -1);
+ transform: translate3d(var(--l-left), 0, var(--l-loadding-ball-size));
+ }
+}
+/* #endif */
\ No newline at end of file
diff --git a/uni_modules/lime-loading/components/l-loading/l-loading.uvue b/uni_modules/lime-loading/components/l-loading/l-loading.uvue
new file mode 100644
index 0000000..5894814
--- /dev/null
+++ b/uni_modules/lime-loading/components/l-loading/l-loading.uvue
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{text}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-loading/components/l-loading/l-loading.vue b/uni_modules/lime-loading/components/l-loading/l-loading.vue
new file mode 100644
index 0000000..5e7467e
--- /dev/null
+++ b/uni_modules/lime-loading/components/l-loading/l-loading.vue
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{text}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-loading/components/l-loading/props.ts b/uni_modules/lime-loading/components/l-loading/props.ts
new file mode 100644
index 0000000..c953a54
--- /dev/null
+++ b/uni_modules/lime-loading/components/l-loading/props.ts
@@ -0,0 +1,28 @@
+// import {PropType} from 'vue'
+export default {
+ color: {
+ type: String,
+ // default: '#c9c9c9'
+ },
+ type: {
+ type: String, //as PropType<'circular'|'spinner'>,
+ default: 'circular'
+ },
+ size: {
+ type: String,
+ // #ifdef APP-NVUE
+ default: '40rpx'
+ // #endif
+ },
+ text: String,
+ textColor: String,
+ textSize: {
+ type: [String, Number]
+ },
+ vertical: Boolean,
+ inheritColor: Boolean,
+ animated: {
+ type: Boolean,
+ default: true
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-loading/components/l-loading/type.ts b/uni_modules/lime-loading/components/l-loading/type.ts
new file mode 100644
index 0000000..7d586c0
--- /dev/null
+++ b/uni_modules/lime-loading/components/l-loading/type.ts
@@ -0,0 +1,18 @@
+// @ts-nocheck
+// 完整类型定义
+export interface LoadingProps {
+ color?: string;
+ type: 'circular' | 'spinner' | 'failed';
+ // #ifndef APP
+ size?: string;
+ // #endif
+ // #ifdef APP
+ size: string;
+ // #endif
+ text?: string;
+ textColor?: string;
+ textSize?: string|number;
+ mode: 'raf' | 'animate';
+ vertical: boolean;
+ animated: boolean;
+}
diff --git a/uni_modules/lime-loading/components/lime-loading/lime-loading.uvue b/uni_modules/lime-loading/components/lime-loading/lime-loading.uvue
new file mode 100644
index 0000000..c18631d
--- /dev/null
+++ b/uni_modules/lime-loading/components/lime-loading/lime-loading.uvue
@@ -0,0 +1,132 @@
+
+
+ 加载
+ 用于表示加载中的过渡状态。
+
+
+
+ 基础
+
+
+
+
+
+
+
+
+
+ 文字
+
+ 加载中…
+
+
+
+
+
+ 垂直
+
+ 加载中…
+
+
+
+
+
+ 尺寸
+
+
+
+
+
+
+
+
+
+ 颜色
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-loading/components/lime-loading/lime-loading.vue b/uni_modules/lime-loading/components/lime-loading/lime-loading.vue
new file mode 100644
index 0000000..5ccceff
--- /dev/null
+++ b/uni_modules/lime-loading/components/lime-loading/lime-loading.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-loading/index.uts b/uni_modules/lime-loading/index.uts
new file mode 100644
index 0000000..504f76e
--- /dev/null
+++ b/uni_modules/lime-loading/index.uts
@@ -0,0 +1,439 @@
+// @ts-nocheck
+// #ifndef UNI-APP-X
+type UTSJSONObject = Record
+// #endif
+// 引入颜色处理库
+import { tinyColor } from '@/uni_modules/lime-color';
+
+
+/**
+ * 操作类型
+ * play: 开始动画
+ * failed: 显示失败状态
+ * clear: 清除动画
+ * destroy: 销毁实例
+ */
+export type TickType = 'play' | 'failed' | 'clear' | 'destroy' | 'pause'
+/**
+ * 加载动画类型
+ * circular: 环形加载动画
+ * spinner: 旋转器加载动画
+ * failed: 失败状态动画
+ */
+export type LoadingType = 'circular' | 'spinner' | 'failed';
+/**
+ * 加载组件返回接口
+ */
+export type UseLoadingReturn = {
+ ratio : 1;
+ type : LoadingType;
+ mode : 'raf' | 'animate'; //
+ color : string;//Ref;
+ play : () => void;
+ failed : () => void;
+ clear : () => void;
+ destroy : () => void;
+ pause : () => void;
+}
+
+
+/**
+ * 计算圆周上指定角度的点的坐标
+ * @param centerX 圆心的 X 坐标
+ * @param centerY 圆心的 Y 坐标
+ * @param radius 圆的半径
+ * @param angleDegrees 角度(以度为单位)
+ * @returns 包含 X 和 Y 坐标的对象
+ */
+function getPointOnCircle(
+ centerX : number,
+ centerY : number,
+ radius : number,
+ angleDegrees : number
+) : number[] {
+ // 将角度转换为弧度
+ const angleRadians = (angleDegrees * Math.PI) / 180;
+
+ // 计算点的 X 和 Y 坐标
+ const x = centerX + radius * Math.cos(angleRadians);
+ const y = centerY + radius * Math.sin(angleRadians);
+
+ return [x, y]
+}
+
+export function useLoading(element : Ref) : UseLoadingReturn {
+ let isPlaying = false
+ let canvasWidth = ref(0)
+ let canvasHeight = ref(0)
+ let canvasSize = ref(0)
+
+ let animationFrameId = -1
+ let animation : UniAnimation | null = null
+
+ let drawFrame : (() => void) | null = null
+
+ const tick = ref('pause')
+ const context = shallowRef(null);
+ const state = reactive({
+ color: '#000',
+ type: 'circular',
+ ratio: 1,
+ mode: 'raf',
+ play: () => {
+ tick.value = 'play'
+ },
+ failed: () => {
+ tick.value = 'failed'
+ },
+ clear: () => {
+ tick.value = 'clear'
+ },
+ destroy: () => {
+ tick.value = 'destroy'
+ cancelAnimationFrame(animationFrameId)
+ animation?.pause()
+ animation?.cancel()
+ context.value?.reset();
+ context.value?.update();
+ context.value = null
+ animation = null
+ isPlaying = false
+ },
+ pause: () => {
+ tick.value = 'pause'
+ }
+ })
+
+
+ // let ctx:DrawableContext|null = null
+
+ // let rotation = 0
+
+ const size = computed(() : number => state.ratio > 1 ? state.ratio : canvasSize.value * state.ratio)
+ // 绘制圆形加载
+ const drawCircular = () => {
+ let startAngle = 0; // 起始角度
+ let endAngle = 0; // 结束角度
+ let rotate = 0; // 旋转角度
+
+ // const ctx = context.value!
+ // 动画参数配置
+ const MIN_ANGLE = 5; // 最小保持角度
+ const ARC_LENGTH = 359.5 // 最大弧长(避免闭合)
+ const PI = Math.PI / 180 // 角度转弧度系数
+ const SPEED = 0.018 / 4 // 动画速度
+ const ROTATE_INTERVAL = 0.09 / 4 // 旋转增量
+
+ const lineWidth = size.value / 10; // 线宽计算
+ const x = canvasWidth.value / 2 // 中心点X
+ const y = canvasHeight.value / 2 // 中心点Y
+ const radius = size.value / 2 - lineWidth // 实际绘制半径
+ try {
+ drawFrame = () => {
+ if (context.value == null || !isPlaying) return
+ let ctx = context.value!
+
+
+ // console.log('radius', radius, size.value)
+ ctx.reset();
+
+ // 绘制圆弧
+ ctx.beginPath();
+ ctx.arc(
+ x,
+ y,
+ radius,
+ startAngle * PI + rotate,
+ endAngle * PI + rotate
+ );
+ ctx.lineWidth = lineWidth;
+ ctx.strokeStyle = state.color;
+ ctx.stroke();
+
+ // 角度更新逻辑
+ if (endAngle < ARC_LENGTH) {
+ endAngle = Math.min(ARC_LENGTH, endAngle + (ARC_LENGTH - MIN_ANGLE) * SPEED);
+ } else if (startAngle < ARC_LENGTH) {
+ startAngle = Math.min(ARC_LENGTH, startAngle + (ARC_LENGTH - MIN_ANGLE) * SPEED);
+ } else {
+ // 重置时保留最小可见角度
+ startAngle = 0;
+ endAngle = MIN_ANGLE;
+ }
+
+ ctx.update()
+
+
+
+ if (state.mode == 'raf') {
+ rotate = (rotate + ROTATE_INTERVAL) % 360; // 持续旋转并限制范围
+ if (isPlaying && drawFrame != null) {
+ animationFrameId = requestAnimationFrame(drawFrame!)
+ }
+ }
+ }
+ } catch(err) {
+
+ }
+ }
+
+ let lastTime = Date.now();
+ const drawSpinner = () => {
+ const steps = 12; // 旋转线条数量
+ // const size = state.ratio > 1 ? state.ratio : canvasSize.value
+ const lineWidth = size.value / 10; // 线宽
+ const x = canvasWidth.value / 2 // 中心坐标
+ const y = canvasHeight.value / 2
+
+ let step = 0; // 当前步数
+ // #ifdef APP-HARMONY
+ const length = size.value / 3.4 - lineWidth; // 线长
+ // #endif
+ // #ifndef APP-HARMONY
+ const length = size.value / 3.6 - lineWidth; // 线长
+ // #endif
+ const offset = size.value / 4; // 距中心偏移
+
+ /** 生成颜色渐变数组 */
+ function generateColorGradient(hex : string, steps : number) : string[] {
+ const colors : string[] = []
+ const _color = tinyColor(hex)
+
+ for (let i = 1; i <= steps; i++) {
+ _color.setAlpha(i / steps);
+ colors.push(_color.toRgbString());
+ }
+ return colors
+ }
+
+ // 计算颜色渐变
+ let colors = computed(() : string[] => generateColorGradient(state.color, steps))
+
+ /** 帧绘制函数 */
+ drawFrame = () => {
+ if (context.value == null || !isPlaying) return
+ const delta = Date.now() - lastTime;
+
+
+ if (delta >= 1000 / 10) {
+ lastTime = Date.now();
+ let ctx = context.value!
+ ctx.reset();
+ for (let i = 0; i < steps; i++) {
+ const stepAngle = 360 / steps; // 单步角度
+ const angle = stepAngle * i; // 当前角度
+ const index = (steps + i - step) % steps // 颜色索引
+ // 计算线段坐标
+ const radian = angle * Math.PI / 180;
+ const cos = Math.cos(radian);
+ const sin = Math.sin(radian);
+
+ // 绘制线段
+ ctx.beginPath();
+ ctx.moveTo(x + offset * cos, y + offset * sin);
+ ctx.lineTo(x + (offset + length) * cos, y + (offset + length) * sin);
+ ctx.lineWidth = lineWidth;
+ ctx.lineCap = 'round';
+ ctx.strokeStyle = colors.value[index];
+ ctx.stroke();
+ }
+
+ ctx.update()
+ if(state.mode == 'raf') {
+ // step += 1
+ step = (step + 1) % steps; // 限制step范围
+ }
+ }
+
+
+ if (state.mode == 'raf') {
+ if (isPlaying && drawFrame != null) {
+ animationFrameId = requestAnimationFrame(drawFrame!)
+ }
+ }
+ }
+ }
+
+ const drwaFailed = () => {
+ if (context.value == null) return
+ let ctx = context.value!
+
+ // const size = state.ratio > 1 ? state.ratio : canvasSize.value
+ const innerSize = size.value * 0.8 // 内圈尺寸
+ const lineWidth = innerSize / 10; // 线宽
+ const lineLength = (size.value - lineWidth) / 2 // X长度
+ const centerX = canvasWidth.value / 2;
+ const centerY = canvasHeight.value / 2;
+ const radius = (size.value - lineWidth) / 2
+
+
+
+ const angleRadians1 = 45 * Math.PI / 180
+ const angleRadians2 = (45 - 90) * Math.PI / 180
+
+ ctx.reset()
+ ctx.lineWidth = lineWidth;
+ ctx.strokeStyle = state.color;
+
+ // 绘制逐渐显示的圆
+ ctx.beginPath();
+ ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
+ ctx.lineWidth = lineWidth;
+ ctx.strokeStyle = state.color;
+ ctx.stroke();
+
+ const [startX1, startY] = getPointOnCircle(centerX, centerY, lineLength / 2, 180 + 45)
+ const [startX2] = getPointOnCircle(centerX, centerY, lineLength / 2, 180 + 90 + 45)
+
+ const x2 = Math.sin(angleRadians1) * lineLength + startX1
+ const y2 = Math.cos(angleRadians1) * lineLength + startY
+
+ ctx.beginPath();
+ ctx.moveTo(startX1, startY)
+ ctx.lineTo(x2, y2)
+ ctx.stroke();
+
+ const x3 = Math.sin(angleRadians2) * lineLength + startX2
+ const y3 = Math.cos(angleRadians2) * lineLength + startY
+
+ ctx.beginPath();
+ ctx.moveTo(startX2, startY)
+ ctx.lineTo(x3, y3)
+ ctx.stroke();
+
+
+ ctx.update()
+ }
+
+ let currentType : LoadingType | null = null
+ const useMode = () => {
+ if (state.mode != 'raf') {
+ const keyframes = [{ transform: 'rotate(0)' }, { transform: 'rotate(360)' }]
+ animation = element.value!.animate(keyframes, {
+ duration: 80000,
+ easing: 'linear',
+ // fill: 'forwards',
+ iterations: Infinity
+ })
+ }
+ }
+ const startAnimation = (type : string) => {
+ if (context.value == null || element.value == null) return
+ animation?.pause()
+
+ if (currentType == type) {
+ isPlaying = true
+ animation?.play()
+ drawFrame?.()
+ return
+ }
+
+ if (type == 'circular') {
+ currentType = 'circular'
+ drawCircular()
+ useMode()
+
+ }
+
+ if (type == 'spinner') {
+ currentType = 'spinner'
+ drawSpinner()
+ useMode()
+ }
+
+ isPlaying = true
+ drawFrame?.()
+ }
+
+ // 监听元素尺寸
+ let manualCheckTimer:number|null = null;
+ const getBoundingClientRect = () => {
+ if(manualCheckTimer != null){
+ clearTimeout(manualCheckTimer!);
+ }
+ requestAnimationFrame(()=> {
+ element.value?.getBoundingClientRectAsync()?.then(rect => {
+ if (rect.width == 0 || rect.height == 0) return
+ context.value = element.value!.getDrawableContext() as DrawableContext;
+ canvasWidth.value = rect.width;
+ canvasHeight.value = rect.height;
+ canvasSize.value = Math.min(rect.width, rect.height);
+ // startAnimation(state.type)
+ })
+ })
+ }
+
+ const resizeObserver : UniResizeObserver = new UniResizeObserver((_entries : UniResizeObserverEntry[]) => {
+ getBoundingClientRect()
+ });
+
+ watchEffect(() => {
+ if (element.value == null) return
+ resizeObserver.observe(element.value!);
+ // #ifdef APP-IOS
+ // nextTick(getBoundingClientRect)
+ manualCheckTimer = setTimeout(() => {
+ getBoundingClientRect();
+ }, 50);
+ // #endif
+ })
+
+
+
+ watchEffect(() => {
+ if (context.value == null) return
+ if (tick.value == 'play') {
+ animation?.pause()
+ isPlaying = false
+ cancelAnimationFrame(animationFrameId)
+ startAnimation(state.type)
+ }
+ if (tick.value == 'failed') {
+ cancelAnimationFrame(animationFrameId)
+ animation?.pause()
+ animation?.cancel()
+ drwaFailed()
+ return
+ }
+ if (tick.value == 'clear') {
+ cancelAnimationFrame(animationFrameId)
+ animation?.pause()
+ animation?.cancel()
+ context.value?.reset();
+ context.value?.update();
+ isPlaying = false
+ return
+ }
+ if (tick.value == 'destroy') {
+ cancelAnimationFrame(animationFrameId)
+ animation?.pause()
+ animation?.cancel()
+ context.value?.reset();
+ context.value?.update();
+ context.value = null
+ animation = null
+ isPlaying = false
+ return
+ }
+ if (tick.value == 'pause') {
+ // 首次需要绘制一帧
+ if(animation == null) {
+ startAnimation(state.type)
+ }
+ cancelAnimationFrame(animationFrameId)
+ isPlaying = false
+ animation?.pause()
+ return
+ }
+ })
+
+ watchEffect(()=>{
+ if(state.color == '') return
+ // #ifdef APP-HARMONY
+ isPlaying = false
+ cancelAnimationFrame(animationFrameId)
+ // #endif
+ })
+
+ return state
+}
\ No newline at end of file
diff --git a/uni_modules/lime-loading/package.json b/uni_modules/lime-loading/package.json
new file mode 100644
index 0000000..76ad84e
--- /dev/null
+++ b/uni_modules/lime-loading/package.json
@@ -0,0 +1,110 @@
+{
+ "id": "lime-loading",
+ "displayName": "lime-loading 加载",
+ "version": "0.2.2",
+ "description": "lime-loading 全端通用加载插件.用于表示页面或操作的加载状态,给予用户反馈的同时减缓等待的焦虑感。支持uniapp/uniappx",
+ "keywords": [
+ "loading",
+ "lime-loading",
+ "加载",
+ "uvue"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.92",
+ "uni-app": "^4.73",
+ "uni-app-x": "^4.76"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "无",
+ "permissions": "无"
+ },
+ "npmurl": "",
+ "darkmode": "√",
+ "i18n": "x",
+ "widescreen": "x"
+ },
+ "uni_modules": {
+ "dependencies": [
+ "lime-shared",
+ "lime-style",
+ "lime-color"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "√",
+ "aliyun": "√",
+ "alipay": "x"
+ },
+ "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": "-"
+ },
+ "quickapp": {
+ "huawei": "-",
+ "union": "-"
+ }
+ },
+ "uni-app-x": {
+ "web": {
+ "safari": "√",
+ "chrome": "√"
+ },
+ "app": {
+ "android": {
+ "extVersion": "",
+ "minVersion": "21"
+ },
+ "ios": "√",
+ "harmony": "√"
+ },
+ "mp": {
+ "weixin": "√"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-loading/readme.md b/uni_modules/lime-loading/readme.md
new file mode 100644
index 0000000..cf6cdf3
--- /dev/null
+++ b/uni_modules/lime-loading/readme.md
@@ -0,0 +1,114 @@
+# lime-loading 加载动画
+
+一个功能丰富的加载动画组件,提供多种加载效果,支持自定义颜色、大小和文本,适用于各种加载场景。
+
+> 插件依赖:`lime-shared`、`lime-style`
+
+## 文档链接
+📚 组件详细文档请访问以下站点:
+- [加载动画文档 - 站点1](https://limex.qcoon.cn/components/loading.html)
+- [加载动画文档 - 站点2](https://limeui.netlify.app/components/loading.html)
+- [加载动画文档 - 站点3](https://limeui.familyzone.top/components/loading.html)
+
+## 安装方法
+1. 在uni-app插件市场中搜索并导入`lime-loading`
+2. 导入后可能需要重新编译项目
+3. 在页面中使用`l-loading`组件
+
+## 代码演示
+
+### 基础使用
+最简单的加载动画使用方式。
+
+```html
+
+```
+
+### 不同类型
+通过 `type` 属性可以设置加载图标的类型,默认为 `circular`,可选值为 `spinner`。nvue只有`circular`,因为其使用的是原生组件。
+
+
+```html
+
+
+```
+
+### 自定义颜色和大小
+可以通过`color`和`size`属性自定义加载动画的颜色和大小。
+
+```html
+
+```
+
+### 带文本的加载动画
+通过设置`text`属性可以在加载动画下方显示文本。
+
+```html
+
+```
+
+### 垂直排列
+通过`vertical`属性可以设置加载动画和文本的排列方式。
+
+```html
+
+```
+
+### 自定义文本样式
+可以通过`textSize`和`textColor`属性自定义文本的大小和颜色。
+
+```html
+
+```
+
+## Vue2使用说明
+main.js中添加以下代码:
+```js
+// vue2项目中使用
+import Vue from 'vue'
+import VueCompositionAPI from '@vue/composition-api'
+Vue.use(VueCompositionAPI)
+```
+
+详细配置请参考官方文档:[Vue Composition API](https://uniapp.dcloud.net.cn/tutorial/vue-composition-api.html)
+
+## 插件标签说明
+`l-loading` 为组件标签
+`lime-loading` 为演示标签
+
+## API文档
+
+### Props 属性说明
+
+| 参数 | 说明 | 类型 | 默认值 |
+| ---------- | ----------------------------- | ------------------ | ---------- |
+| color | 颜色 | _string_ | `` |
+| type | 类型,可选值为 `spinner` | _string_ | `circular` |
+| size | 加载图标大小,默认单位为 `px`,uvue只支持string | _number \| string_ | `40rpx` |
+| text | 加载文本 | _string_ | `-` |
+| textColor | 文本颜色 | _string_ | `-` |
+| textSize | 文本大小 `px`,uvue只支持string | _number \| string_ | `-` |
+| vertical | 是否垂直排列图标和文字内容 | _boolean_ | `false` |
+| mode | 动画实现的模式.只针对APP,可选值有:`animate` | _string_ | `raf` |
+
+## 主题定制
+
+组件提供了丰富的CSS变量,可以通过自定义CSS变量来自定义组件样式:
+
+| 变量名 | 默认值 | 说明 |
+| ---- | ---- | ---- |
+| --l-loading-color | #1677ff | 加载动画颜色 |
+| --l-loading-size | 20px | 加载动画大小 |
+| --l-loading-text-color | `$text-color-3` | 文本颜色 |
+| --l-loading-text-size | `$font-size`| 文本大小 |
+
+由于`uniappx app`是基于canvas故暂时`uniappx app`无效
+
+
+## 支持与赞赏
+
+如果你觉得本插件解决了你的问题,可以考虑支持作者:
+
+| 支付宝赞助 | 微信赞助 |
+|------------|------------|
+|  |  |
\ No newline at end of file
diff --git a/uni_modules/lime-picker/changelog.md b/uni_modules/lime-picker/changelog.md
new file mode 100644
index 0000000..f854f3a
--- /dev/null
+++ b/uni_modules/lime-picker/changelog.md
@@ -0,0 +1,76 @@
+## 0.3.8(2026-05-11)
+- fix: 修复缺失watch
+## 0.3.7(2026-01-16)
+- fix: 修复uniapp vue2问题
+## 0.3.6(2026-01-16)
+- fix:修复uniapp vue2报错问题
+## 0.3.5(2025-12-13)
+- fix: 修复uniappx ios鸿蒙显示不全问题
+## 0.3.4(2025-12-12)
+- fix: 修复uniappx ios,鸿蒙显示问题
+## 0.3.3(2025-10-09)
+- fix: 修复uniapp报错的问题
+## 0.3.2(2025-09-28)
+- feat: 暴露`confirm`和`getSelectedOptions`方法
+## 0.3.1(2025-09-15)
+- fix: 修复uniappx 安卓无法设置选中项的颜色
+## 0.3.0(2025-09-08)
+- fix: 修复uniappx 鸿蒙 使用mask-class会导致选中项下边有阴影
+## 0.2.9(2025-09-01)
+- fix:修复uniapp 选项高度问题
+## 0.2.8(2025-06-10)
+- fix:鸿蒙当插件在弹窗里的时候,文本没有样式(估计为官方BUG)
+## 0.2.7(2025-05-30)
+- fix:修复ios报`error: Can't find variable: stop`
+## 0.2.6(2025-05-12)
+- fix:修复安卓尺寸的问题
+## 0.2.5(2025-05-06)
+- fix:修复安卓初始化-1报错的问题
+## 0.2.4(2025-04-24)
+- feat:字体由rpx转成px
+## 0.2.3(2025-04-23)
+- fix:安卓更新问题
+## 0.2.2(2025-04-23)
+- feat:优化边界
+## 0.2.1(2025-04-22)
+- feat:兼容uniappx 鸿蒙next
+## 0.2.0(2025-04-18)
+- fix:修复安卓字体无加粗的问题
+## 0.1.9(2025-04-18)
+- fix:修复安卓更新数据后绘制尺寸不对的问题
+## 0.1.8(2025-03-25)
+- fix:修复安卓设置日期大小不居中的问题
+## 0.1.7(2025-03-25)
+- fix:修复安卓单标题时不居中的问题
+## 0.1.6(2025-03-25)
+- fix:修复单标题时不居中的问题
+## 0.1.5(2025-02-25)
+- chore:更新文档
+## 0.1.4(2025-02-20)
+- fix:修复uniapp 级联缺少keys问题
+## 0.1.3(2025-02-13)
+- fix:修复ios 默认值问题
+## 0.1.2(2025-02-12)
+- fix:修复默认值问题
+## 0.1.1(2025-02-11)
+- fix:修复uniapp indicator样式问题
+## 0.1.0(2025-02-10)
+- fix:修复uniapp pickerItemInstanceArray缺少.value的问题
+## 0.0.9(2025-01-09)
+- feat:更新文档
+## 0.0.8(2025-01-09)
+- feat:增加级联`l-cascade`组件
+## 0.0.7(2024-12-31)
+- fix:修复缺少resetindex
+## 0.0.6(2024-12-27)
+- feat:更新样式
+## 0.0.5(2024-12-27)
+- feat:更新样式
+## 0.0.4(2024-12-27)
+- fix:优化列表数据无法更新的问题
+## 0.0.3(2024-12-24)
+- feat:去掉多余文本
+## 0.0.2(2024-12-24)
+- feat:兼容vue2
+## 0.0.1(2024-12-24)
+- init
diff --git a/uni_modules/lime-picker/components/l-cascade/l-cascade.uvue b/uni_modules/lime-picker/components/l-cascade/l-cascade.uvue
new file mode 100644
index 0000000..03b5617
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-cascade/l-cascade.uvue
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-cascade/l-cascade.vue b/uni_modules/lime-picker/components/l-cascade/l-cascade.vue
new file mode 100644
index 0000000..1edc780
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-cascade/l-cascade.vue
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-cascade/type.ts b/uni_modules/lime-picker/components/l-cascade/type.ts
new file mode 100644
index 0000000..257152c
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-cascade/type.ts
@@ -0,0 +1,58 @@
+// @ts-nocheck
+import {PickerColumn, PickerColumnItem ,PickerValue} from '../l-picker/type';
+export type KeysType = {
+ value: string;
+ label: string;
+ children:string;
+ disabled?: string;
+}
+export interface CascadeProps {
+ /**
+ * 取消按钮文字
+ */
+ cancelBtn ?: string;
+ cancelStyle ?: string;
+ /**
+ * 确定按钮文字
+ */
+ confirmBtn ?: string;
+ confirmStyle ?: string;
+ /**
+ * 标题
+ */
+ title ?: string;
+ titleStyle ?: string;
+ /**
+ * 用来定义 value / label 在 `options` 中对应的字段别名
+ */
+ keys?: UTSJSONObject;
+ /**
+ * 配置每一列的选项
+ */
+ columns : UTSJSONObject[];
+ /**
+ * 选中值
+ */
+ modelValue ?: PickerValue[];
+ defaultValue ?: PickerValue[];
+ value ?: PickerValue[];
+ /**
+ * 是否为加载状态
+ */
+ loading: boolean;
+ loadingColor?: string;
+ loadingMaskColor ?: string;
+ loadingSize: string;
+
+ itemHeight?: string;
+ itemColor?: string;
+ itemFontSize?: string;
+ itemActiveColor?: string;
+
+ indicatorStyle?: string;
+ maskColors?: string[];
+ bgColor?: string;
+ groupHeight?: string;
+ radius?: string;
+ resetIndex: boolean
+}
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-cascade/utils.uts b/uni_modules/lime-picker/components/l-cascade/utils.uts
new file mode 100644
index 0000000..8959d97
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-cascade/utils.uts
@@ -0,0 +1,123 @@
+// @ts-nocheck
+import { clamp } from '@/uni_modules/lime-shared/clamp'
+import { PickerValue, PickerColumn, PickerColumnItem, PickerPickEvent } from '../l-picker/type';
+import { KeysType } from './type';
+// #ifndef UNI-APP-X
+import type { Ref } from '@/uni_modules/lime-shared/vue'
+type UTSJSONObject = Record
+// #endif
+
+/**
+ * 解析键配置对象,生成用于访问选项属性的键映射。
+ *
+ * @param {UTSJSONObject|null} keys - 配置对象,包含 label、value 和 children 的键名。
+ * @returns {KeysType} 包含 label、value 和 children 键名的映射对象。
+ */
+export function parseKeys(keys : UTSJSONObject | null) : KeysType {
+ const _labelKey = `${keys?.['label'] ?? 'label'}`
+ const _valueKey = `${keys?.['value'] ?? 'value'}`
+ const _childrenKey = `${keys?.['children'] ?? 'children'}`
+
+ return {
+ label: _labelKey,
+ value: _valueKey,
+ children: _childrenKey,
+ } as KeysType
+}
+
+
+/**
+ * 获取选项数组中第一个未被禁用的选项。
+ *
+ * @param {UTSJSONObject[]} options - 选项对象数组。
+ * @returns {UTSJSONObject|null} 第一个未被禁用的选项对象,如果所有选项都被禁用则返回第一个选项。
+ */
+export const getFirstEnabledOption = (
+ options : UTSJSONObject[],
+) : UTSJSONObject | null =>
+ options.find((option) : boolean => option['disabled'] != true) ?? options[0];
+
+/**
+ * 在选项数组中查找指定索引之后或之前的第一个未被禁用的选项的索引。
+ *
+ * @param {UTSJSONObject[]} options - 选项对象数组。
+ * @param {number} index - 当前索引位置。
+ * @returns {number} 第一个未被禁用的选项的索引,如果不存在则返回 0。
+ */
+export function findIndexOfEnabledOption(
+ options : UTSJSONObject[],
+ index : number,
+) {
+ index = clamp(index, 0, options.length);
+
+ // for (let i = index; i < options.length; i++) {
+ // if (options[i]['disabled'] != true) return i;
+ // }
+ // for (let i = index - 1; i >= 0; i--) {
+ // if (options[i]['disabled'] != true) return i;
+ // }
+
+ // return 0;
+ return index
+}
+
+/**
+ * 根据指定的值在选项数组中查找对应的选项对象。
+ *
+ * @param {UTSJSONObject[]} options - 选项对象数组。
+ * @param {PickerValue} value - 要查找的值。
+ * @param {KeysType} fields - 包含 label、value 和 children 键名的映射对象。
+ * @returns {UTSJSONObject|null} 找到的选项对象,如果未找到则返回 null。
+ */
+export function findOptionByValue(
+ options : UTSJSONObject[],
+ value : PickerValue,
+ fields : KeysType,
+) : UTSJSONObject | null {
+ if(options.length == 0) return null
+ const index = options.findIndex((option) : boolean => option[fields.value] == value);
+ const enabledIndex = findIndexOfEnabledOption(options, index);
+ return options[enabledIndex];
+}
+
+/**
+ * 格式化级联选择器的列数据。
+ *
+ * @param {UTSJSONObject[]} columns - 初始的列数据数组。
+ * @param {KeysType} fields - 包含 label、value 和 children 键名的映射对象。
+ * @param {Ref} selectedValues - 当前选中的值数组。
+ * @returns {PickerColumn[]} 格式化后的级联列数据数组。
+ */
+export function formatCascadeColumns(
+ columns : UTSJSONObject[],
+ fields : KeysType,
+ selectedValues : Ref
+) {
+ const formatted : PickerColumn[] = [];
+
+ let cursor : UTSJSONObject | null = {};
+ cursor![fields.children] = columns;
+ let columnIndex = 0;
+
+ while (cursor != null && cursor[fields.children] != null) {
+ const options : UTSJSONObject[] = cursor[fields.children]! as UTSJSONObject[];
+ const value = selectedValues.value.length > columnIndex ? selectedValues.value[columnIndex] : null;
+ cursor = value != null
+ ? findOptionByValue(options, value, fields)
+ : null;
+
+ if (cursor == null && options.length > 0) {
+ const firstValue = getFirstEnabledOption(options)![fields.value]!;
+ cursor = findOptionByValue(options, firstValue, fields);
+ }
+
+ columnIndex++;
+ formatted.push(
+ options.map((option : UTSJSONObject) : PickerColumnItem => ({
+ label: `${option['label']}`,
+ value: `${option['value']}`,
+ } as PickerColumnItem))
+ );
+ }
+ return formatted;
+}
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker-item/PickerRenderer.uts b/uni_modules/lime-picker/components/l-picker-item/PickerRenderer.uts
new file mode 100644
index 0000000..fb0a5d5
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker-item/PickerRenderer.uts
@@ -0,0 +1,187 @@
+// @ts-nocheck
+import { unitConvert } from '@/uni_modules/lime-shared/unitConvert'
+import type { PickerColumnItem, PickerValue } from '../l-picker/type';
+// #ifdef APP-ANDROID || APP-IOS
+type PickerCanvasElement = UniElement
+type PickerDrawableContext = DrawableContext
+// #endif
+// #ifdef APP-HARMONY
+type PickerCanvasElement = UniElement//UniCanvasElement
+type PickerDrawableContext = DrawableContext//CanvasRenderingContext2D
+// #endif
+
+
+export type PickerCanvasConfig = {
+ itemHeight : number;
+ itemFontSize : number;
+ itemActiveFontWeight ?: number;
+ itemColor ?: string;
+ itemActiveColor ?: string;
+ canvasWidth : number;
+ canvasHeight : number;
+}
+
+export class PickerCanvasRenderer {
+ // private canvas : PickerCanvasElement | null = null;
+ private ctx : PickerDrawableContext | null = null;
+ private dpr : number = 1;
+ private isInitialized : boolean = false;
+ private canvas : PickerCanvasElement
+ private lastWidth : number = 0;
+ private lastHeight : number = 0;
+ constructor(itemRef : PickerCanvasElement) {
+ this.canvas = itemRef
+ this.initCanvas();
+ }
+
+ private initCanvas() {
+ // #ifdef APP-ANDROID||APP-IOS || APP-HARMONY
+ // this.ctx = this.canvas.getDrawableContext();
+ this.isInitialized = true;
+ return
+ // #endif
+
+ // #ifdef APP-HARMONY
+ const rect = this.canvas.getBoundingClientRect()
+ // this.ctx = this.canvas.getContext('2d');
+ this.ctx = this.canvas.getDrawableContext();
+ this.dpr = uni.getDeviceInfo().devicePixelRatio ?? 1;
+ this.isInitialized = true;
+ // this.canvas.height = rect.height * this.dpr;
+ // this.canvas.width = rect.width * this.dpr;
+ // this.ctx.scale(this.dpr, this.dpr);
+ this.lastWidth = rect.width;
+ this.lastHeight = rect.height;
+ // #endif
+ }
+
+ private setupCanvas(width : number, height : number) {
+ // if (!this.isInitialized) return;
+ // #ifdef APP-ANDROID || APP-IOS || APP-HARMONY
+ // this.canvas.style.setProperty('height', `${height}px`);
+ // this.ctx?.reset();
+ return
+ // #endif
+
+ // #ifdef APP-HARMONY
+ if (this.lastWidth == width && this.lastHeight == height) return;
+
+ this.lastWidth = width;
+ this.lastHeight = height;
+ // this.canvas.height = height * this.dpr;
+ // this.canvas.width = width * this.dpr;
+ // this.ctx?.setTransform(1, 0, 0, 1, 0, 0);
+ // this.ctx?.scale(this.dpr, this.dpr);
+ // #endif
+ }
+ clearRect(width : number = 1000, height : number = 100000) {
+ // #ifdef APP-ANDROID || APP-IOS || APP-HARMONY
+ this.ctx?.reset();
+ return
+ // #endif
+ // #ifdef APP-HARMONY
+ this.ctx?.clearRect(0, 0, width, height);
+ // #endif
+ }
+ render(options : PickerColumnItem[], curIndex : number, config : PickerCanvasConfig, isDarkMode : boolean) {
+ // 安卓不能把ctx存在实例中?
+ // if(this.ctx == null) return
+
+ const ctx = this.canvas.getDrawableContext()!
+ const itemHeight = config.itemHeight;
+ const fontSize = config.itemFontSize;
+ const canvasWidth = config.canvasWidth
+ const canvasHeight = config.canvasHeight
+ // this.ctx = ctx
+ // 设置容器和Canvas尺寸
+ this.setupCanvas(canvasWidth, canvasHeight);
+ ctx.reset();
+ const x = canvasWidth / 2;
+ const itemActiveFontWeight = config?.itemActiveFontWeight ?? 700;
+
+ const color = config?.itemColor ?? (isDarkMode ? `rgba(255,255,255,0.88)` : 'rgba(0,0,0,0.88)');
+ const itemActiveColor = config?.itemActiveColor ?? (isDarkMode ? `rgba(255,255,255,0.88)` : 'rgba(0,0,0,0.88)');
+
+
+ // 设置绘图上下文
+ // #ifndef APP-HARMONY
+ ctx.font = `${fontSize}px`;
+ // #endif
+ // #ifdef APP-HARMONY
+ ctx.font = `${fontSize}px`;// sans-serif
+ // #endif
+ ctx.textAlign = 'center';
+ // ctx.textBaseline = 'middle'; // 使用中间基线实现垂直居中
+ ctx.lineWidth = 0.5;
+
+ // 清空画布
+ this.clearRect(canvasWidth, canvasHeight);
+
+ // 绘制所有选项
+ options.forEach((item, index) => {
+ // const y = itemHeight * index + (itemHeight + fontSize) / 2;
+ let offset = 0.4
+ // #ifdef APP-IOS
+ offset = 0.55
+ // #endif
+ const y = itemHeight * index + fontSize + (itemHeight - fontSize) * offset;
+ // const y = itemHeight * index + itemHeight / 2; // 垂直居中
+
+ const isActive = index == curIndex && itemActiveFontWeight > 600;
+ ctx.fillStyle = isActive ? itemActiveColor : color;
+ ctx.strokeStyle = isActive ? itemActiveColor : color;
+
+ ctx.fillText(item.label, x, y);
+
+ if (isActive) {
+ // #ifdef APP-IOS || APP-HARMONY
+ // ctx.strokeText(item.label, x, y);
+ ctx.fillText(item.label, x - 0.2, y);
+ ctx.fillText(item.label, x + 0.2, y);
+ ctx.fillText(item.label, x, y - 0.2);
+ ctx.fillText(item.label, x, y + 0.2);
+ // #endif
+ // #ifndef APP-IOS|| APP-HARMONY
+ ctx.strokeText(item.label, x, y);
+ // #endif
+ }
+ });
+
+ // #ifdef APP-ANDROID || APP-IOS || APP-HARMONY
+ ctx.update();
+ // #endif
+ }
+
+ destroy() {
+ // this.canvas = null;
+ this.ctx = null;
+ this.isInitialized = false;
+ }
+}
+
+// // 在组件中使用
+// const canvasRenderer = ref(null);
+
+// const updateItemStyle = () => {
+// if (!itemRef.value) return;
+
+// // 初始化渲染器(只初始化一次)
+// if (!canvasRenderer.value) {
+// canvasRenderer.value = new PickerCanvasRenderer(
+// itemRef.value,
+// isDarkMode.value,
+// picker
+// );
+// }
+
+// // 执行渲染
+// canvasRenderer.value.render(props.options, curIndex.value);
+// };
+
+// // 在组件卸载时清理
+// onBeforeUnmount(() => {
+// if (canvasRenderer.value) {
+// canvasRenderer.value.destroy();
+// canvasRenderer.value = null;
+// }
+// });
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker-item/index.scss b/uni_modules/lime-picker/components/l-picker-item/index.scss
new file mode 100644
index 0000000..6bf8613
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker-item/index.scss
@@ -0,0 +1,82 @@
+@import '~@/uni_modules/lime-style/index.scss';
+/* #ifdef uniVersion >= 4.75 */
+$use-css-var: true;
+/* #endif */
+
+$item: #{$prefix}-picker-item;
+
+$picker-group-height: create-var(picker-group-height, 200px);
+$picker-indicator-bg-color: create-var(picker-indicator-bg-color, $fill-4);
+$picker-indicator-border-radius: create-var(picker-indicator-border-radius, 6px);
+
+$picker-item-height: create-var(picker-item-height, 50px);
+$picker-item-active-color: create-var(picker-item-active-color, $text-color-1);
+$picker-item-active-font-weight: create-var(picker-item-active-font-weight, 700);
+$picker-item-color: create-var(picker-item-color, $text-color-1);
+$picker-item-font-size: create-var(picker-item-font-size, $font-size-md);
+$picker-mask-top-color: create-var(picker-mask-top-color, white);
+$picker-mask-bottom-color: create-var(picker-mask-bottom-color, rgba(36, 36, 36, 0));
+
+
+/* #ifndef UNI-APP-X && APP */
+:host {
+ flex: 1;
+ display: flex;
+}
+/* #endif */
+
+
+.#{$item} {
+ /* #ifndef UNI-APP-X && APP */
+ // uniapp x鸿蒙这个设置导致选中项下边黑影
+ &__mask {
+ background-image:
+ linear-gradient(180deg, $picker-mask-top-color, $picker-mask-bottom-color),
+ linear-gradient(0deg, $picker-mask-top-color, $picker-mask-bottom-color)
+ }
+ /* #endif */
+ &__group {
+ /* #ifdef WEB */
+ :deep(.uni-picker-view-content) {
+ // 300ms
+ transition-duration: var(--picker-duration, 0);
+ }
+ /* #endif */
+ flex: 1;
+
+ // height: $picker-group-height;
+ &-item {
+ height: $picker-item-height;
+ line-height: $picker-item-height;
+ text-align: center;
+ /* #ifndef UNI-APP-X && APP */
+ display: flex;
+ justify-content: center;
+ /* #endif */
+ transition-duration: 100ms;
+ transition-property: font-weight, color;
+ transition-timing-function: linear;
+ font-weight: 400;
+ color: $picker-item-color;
+ font-size: $picker-item-font-size;
+ white-space: nowrap;
+ &--active {
+ color: $picker-item-active-color;
+ font-weight: $picker-item-active-font-weight;
+ }
+ }
+ }
+ &__wrapper {
+ width: 100%;
+ // background-color: red;
+ }
+ &__indicator {
+ /* #ifndef UNI-APP-X && APP */
+ height: $picker-item-height;
+ &:after,&:before{
+ display: none;
+ }
+ /* #endif */
+ }
+}
+
diff --git a/uni_modules/lime-picker/components/l-picker-item/l-picker-item-old.uvue b/uni_modules/lime-picker/components/l-picker-item/l-picker-item-old.uvue
new file mode 100644
index 0000000..ff4fd36
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker-item/l-picker-item-old.uvue
@@ -0,0 +1,324 @@
+
+
+
+
+
+
+
+ {{item.label}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker-item/l-picker-item.uvue b/uni_modules/lime-picker/components/l-picker-item/l-picker-item.uvue
new file mode 100644
index 0000000..973c3e0
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker-item/l-picker-item.uvue
@@ -0,0 +1,297 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.label}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker-item/l-picker-item.vue b/uni_modules/lime-picker/components/l-picker-item/l-picker-item.vue
new file mode 100644
index 0000000..f5ab756
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker-item/l-picker-item.vue
@@ -0,0 +1,212 @@
+
+
+
+ {{item.label}}
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker-item/props.ts b/uni_modules/lime-picker/components/l-picker-item/props.ts
new file mode 100644
index 0000000..3e8cc96
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker-item/props.ts
@@ -0,0 +1,15 @@
+// @ts-nocheck
+export default {
+ options: {
+ type: Array,
+ default: () => []
+ },
+ value: {
+ type: [String, Number],
+ default: null
+ },
+ column: {
+ type: Number,
+ default: -1
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker-item/type.ts b/uni_modules/lime-picker/components/l-picker-item/type.ts
new file mode 100644
index 0000000..7125295
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker-item/type.ts
@@ -0,0 +1,13 @@
+// @ts-nocheck
+import type{ PickerColumnItem, PickerValue } from '../l-picker/type';
+
+export type ManageChildInList = (child: LPickerItemComponentPublicInstance, shouldAdd: boolean) => void;
+export type OnPick = (value: PickerValue, index:number, column: number) => void;
+export type UpdateItems = (value: PickerValue, index:number, column: number) => void;
+
+export interface PickerItemProps {
+ options: PickerColumnItem[]
+ value?: PickerValue;
+ column: number
+ name?: string|number
+}
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker-item/usePickerMask.ts b/uni_modules/lime-picker/components/l-picker-item/usePickerMask.ts
new file mode 100644
index 0000000..dc2c846
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker-item/usePickerMask.ts
@@ -0,0 +1,86 @@
+// @ts-nocheck
+// #ifndef UNI-APP-X
+import { computed, ComputedRef, Ref } from '@/uni_modules/lime-shared/vue';
+// #endif
+export type MaskConfig = {
+ maskStartColor : string;
+ maskEndColor : string;
+};
+
+export type PlatformMaskStyles = {
+ common : string;
+ top : string;
+ bottom : string;
+};
+export type UsePickerMaskReturn = {
+ maskConfig : ComputedRef;
+ platformMaskStyles : ComputedRef;
+}
+
+/**
+ * 生成 Picker 组件顶部与底部的渐变遮罩样式(完全响应式)
+ *
+ * @param backgroundColorRef - 背景色 ref 或 computed(可为 null)
+ * @param isDarkModeRef - 是否暗色模式(响应式)
+ * @param maskColorsRef - 自定义遮罩颜色 [start, end](响应式)
+ * @param isInitializedRef - 是否已初始化(响应式)
+ */
+export const usePickerMask = (
+ backgroundColorRef : ComputedRef,
+ isDarkModeRef :ComputedRef,
+ maskColorsRef :ComputedRef,
+ isInitializedRef : Ref
+) : UsePickerMaskReturn => {
+ const maskConfig = computed(() => {
+ const bgColor = backgroundColorRef.value;
+ const isDark = isDarkModeRef.value;
+ const maskColors = maskColorsRef?.value;
+
+ // 优先使用用户提供的 maskColors
+ if (maskColors != null && maskColors.length >= 1) {
+ const maskStartColor = maskColors![0]
+ const maskEndColor = maskColors!.length > 1 ? maskColors[1] : 'rgba(0,0,0,0)'
+ return {
+ maskStartColor,
+ maskEndColor
+ } as MaskConfig
+ }
+
+ const bg = bgColor ?? (isDark ? '#242424' : '#ffffff');
+ const endColor = isDark
+ ? 'rgba(36, 36, 36, 0)'
+ : 'rgba(255, 255, 255, 0)';
+
+ return {
+ maskStartColor: bg,
+ maskEndColor: endColor
+ } as MaskConfig
+ });
+
+ const platformMaskStyles = computed(() => {
+ const { maskStartColor, maskEndColor } = maskConfig.value;
+
+ const clean = (str : string) => str.replace(/\s+/g, ' ').trim();
+
+ return {
+ common: backgroundColorRef.value == null && !isDarkModeRef.value
+ ? clean(`background-image:
+ linear-gradient(180deg, ${maskStartColor}, ${maskEndColor}),
+ linear-gradient(0deg, ${maskStartColor}, ${maskEndColor})`)
+ : '',
+ // #ifndef APP-IOS
+ top: `background-image: linear-gradient(to bottom, ${maskStartColor}, ${maskEndColor})`,
+ bottom: `background-image: linear-gradient(to top, ${maskStartColor}, ${maskEndColor})`,
+ // #endif
+ // #ifdef APP-IOS
+ top: isInitializedRef.value ? 'background-color: rgba(0,0,0,0)' : '',
+ bottom: isInitializedRef.value ? 'background-color: rgba(0,0,0,0)' : ''
+ // #endif
+ } as PlatformMaskStyles
+ });
+
+ return {
+ maskConfig,
+ platformMaskStyles
+ } as UsePickerMaskReturn
+};
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker-view/index.scss b/uni_modules/lime-picker/components/l-picker-view/index.scss
new file mode 100644
index 0000000..3f0c17d
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker-view/index.scss
@@ -0,0 +1,191 @@
+@import '~@/uni_modules/lime-style/index.scss';
+
+/* #ifdef uniVersion >= 4.75 */
+$use-css-var: true;
+/* #endif */
+
+$picker: #{$prefix}-picker-view;
+
+
+$picker-border-radius: create-var(picker-border-radius, $border-radius-xl);
+$picker-bg-color: create-var(picker-bg-color, $bg-color-container);
+$picker-toolbar-height: create-var(picker-toolbar-height, 58px);
+
+$picker-cancel-color: create-var(picker-cancel-color, $text-color-2);
+$picker-confirm-color: create-var(picker-confirm-color, $primary-color);
+$picker-button-font-size: create-var(picker-button-font-size, $font-size-md);
+
+$picker-title-font-size: create-var(picker-title-font-size, 18px);
+$picker-title-font-weight: create-var(picker-title-font-weight, 700);
+$picker-title-line-height: create-var(picker-title-line-height, 26px);
+$picker-title-color: create-var(picker-title-color, $text-color-1);
+
+
+$picker-group-height: create-var(picker-group-height, 200px);
+$picker-indicator-bg-color: create-var(picker-indicator-bg-color, $fill-3);
+$picker-indicator-border-radius: create-var(picker-indicator-border-radius, $border-radius);
+$picker-indicator-margin: create-var(picker-indicator-margin, 10px);
+
+$picker-item-height: create-var(picker-item-height, 50px);
+$picker-item-active-color: create-var(picker-item-active-color, $text-color-1);
+
+
+$picker-loading-mask-color: create-var(picker-loading-mask-color, rgba(255,255,255,.9));
+$picker-loading-color: create-var(picker-loading-color, $primary-color);
+
+
+// $item: #{$prefix}-picker-item;
+$picker-item-active-font-weight: create-var(picker-item-active-font-weight, 700);
+$picker-item-color: create-var(picker-item-color, $text-color-1);
+$picker-item-font-size: create-var(picker-item-font-size, $font-size-md);
+$picker-mask-top-color: create-var(picker-mask-top-color, white);
+$picker-mask-bottom-color: create-var(picker-mask-bottom-color, rgba(36, 36, 36, 0));
+
+
+.#{$picker} {
+ position: relative;
+ background-color: $picker-bg-color;
+ border-top-left-radius: $picker-border-radius;
+ border-top-right-radius: $picker-border-radius;
+
+ &__toolbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ // overflow: hidden;
+ height: $picker-toolbar-height;
+ flex-direction: row;
+ position: relative;
+ }
+ &__title {
+ /* #ifdef UNI-APP-X && APP */
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translateX(-50%) translateY(-50%);
+ /* #endif */
+ text-align: center;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ color: $picker-title-color;
+ // line-height: $picker-toolbar-height;
+ line-height: $picker-title-line-height;
+ font-weight: $picker-title-font-weight;
+ font-size: $picker-title-font-size;
+ }
+
+ &__cancel,
+ &__confirm {
+ white-space: nowrap;
+ // position: absolute;
+ /* #ifndef UNI-APP-X && APP */
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ user-select: none;
+ /* #endif */
+ font-size: $picker-button-font-size;
+ line-height: $picker-toolbar-height;
+ height: 100%;
+ // padding: 0 $spacer;
+ @include padding(0 $spacer);
+ }
+
+ &__cancel {
+ // left: 0;
+ margin-right: auto;
+ color: $picker-cancel-color;
+ }
+
+ &__confirm {
+ margin-left: auto;
+ color: $picker-confirm-color;
+ // right: 0
+ }
+
+
+ &__main {
+ position: relative;
+ display: flex;
+ height: $picker-group-height;
+ flex-direction: row;
+ z-index: 2;
+ // padding: 0 $spacer-xs;
+ @include padding(0 $spacer-xs);
+ }
+ &__mask {
+
+ }
+ &__empty {
+ pointer-events: none;
+ justify-content: center;
+ align-items: center;
+ display: flex;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 3;
+ }
+ &__loading {
+ z-index: 3;
+ // color: $picker-loading-color;
+ background-color: $picker-loading-mask-color;
+ // background-color: red;
+ justify-content: center;
+ align-items: center;
+ display: flex;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0
+ }
+ &__indicator{
+ position: absolute;
+ background-color: $picker-indicator-bg-color;
+ top: 50%;
+ left: $picker-indicator-margin;
+ right: $picker-indicator-margin;
+ height: $picker-item-height;
+ transform: translateY(-50%);
+ z-index: -1;
+ @include border-radius($picker-indicator-border-radius);
+ }
+
+ &__group {
+ flex: 1;
+ width: 100%;
+
+ &-item {
+ height: $picker-item-height;
+ line-height: $picker-item-height;
+ text-align: center;
+ /* #ifndef UNI-APP-X && APP */
+ display: flex;
+ justify-content: center;
+ /* #endif */
+ transition-duration: 100ms;
+ transition-property: font-weight, color;
+ transition-timing-function: linear;
+ font-weight: 400;
+ color: $picker-item-color;
+ font-size: $picker-item-font-size;
+ white-space: nowrap;
+ &--active {
+ color: $picker-item-active-color;
+ font-weight: $picker-item-active-font-weight;
+ }
+ }
+ }
+ &__indicator {
+ /* #ifndef UNI-APP-X && APP */
+ height: $picker-item-height;
+ &:after,&:before{
+ display: none;
+ }
+ /* #endif */
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker-view/l-picker-view.uvue b/uni_modules/lime-picker/components/l-picker-view/l-picker-view.uvue
new file mode 100644
index 0000000..891570a
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker-view/l-picker-view.uvue
@@ -0,0 +1,285 @@
+
+
+
+ {{cancelBtn}}
+ {{title}}
+ {{confirmBtn}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.label}}
+
+
+
+
+
+ {{item.label}}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker-view/l-picker-view.vue b/uni_modules/lime-picker/components/l-picker-view/l-picker-view.vue
new file mode 100644
index 0000000..7a230f4
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker-view/l-picker-view.vue
@@ -0,0 +1,173 @@
+
+
+
+ {{cancelBtn}}
+ {{title}}
+ {{confirmBtn}}
+
+
+
+
+
+
+
+
+
+ {{item.label}}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker-view/props.ts b/uni_modules/lime-picker/components/l-picker-view/props.ts
new file mode 100644
index 0000000..90b128c
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker-view/props.ts
@@ -0,0 +1,128 @@
+// @ts-nocheck
+export default {
+ /**
+ * 取消按钮文字
+ */
+ cancelBtn: {
+ type: String,
+ default: null
+ },
+ cancelStyle: {
+ type: [String, Object],
+ default: null
+ },
+ /**
+ * 确定按钮文字
+ */
+ confirmBtn: {
+ type: String,
+ default: null
+ },
+ confirmStyle: {
+ type: [String, Object],
+ default: null
+ },
+ keys: {
+ type: Object,
+ default: null
+ },
+ /**
+ * 标题
+ */
+ title: {
+ type: String,
+ default: null
+ },
+ titleStyle: {
+ type: [String, Object],
+ default: null
+ },
+ /**
+ * 配置每一列的选项
+ */
+ columns: {
+ type: Array,
+ default: () => []
+ },
+ /**
+ * 选中值
+ */
+ modelValue: {
+ type: Array,
+ default: null
+ },
+ defaultValue: {
+ type: Array,
+ default: null
+ },
+ value: {
+ type: Array,
+ default: null
+ },
+ /**
+ * 是否为加载状态
+ */
+ loading: {
+ type: Boolean,
+ default: false
+ },
+ loadingColor: {
+ type: String,
+ default: null
+ },
+ loadingMaskColor: {
+ type: String,
+ default: null
+ },
+ loadingSize: {
+ type: String,
+ default: '64rpx'
+ },
+
+ itemHeight: {
+ type: String
+ },
+ itemColor: {
+ type: String,
+ default: ''
+ },
+ itemFontSize: {
+ type: String,
+ default: ''
+ },
+ itemActiveColor: {
+ type: String,
+ default: ''
+ },
+ itemActiveFontWeight: {
+ type: Number,
+ default: null
+ },
+
+ indicatorStyle: {
+ type: String,
+ default: ''
+ },
+ bgColor: {
+ type: String,
+ default: null
+ },
+ groupHeight: {
+ type: String,
+ default: ''
+ },
+ radius: {
+ type: String,
+ default: null
+ },
+ /**
+ * 列表更新后,是否归0
+ */
+ resetIndex: {
+ type: Boolean,
+ default: false
+ },
+ maskColors: {
+ type: Array,
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker-view/type.ts b/uni_modules/lime-picker/components/l-picker-view/type.ts
new file mode 100644
index 0000000..efdb5dd
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker-view/type.ts
@@ -0,0 +1,10 @@
+// @ts-nocheck
+import type{ PickerProps, PickerColumn, PickerValue, PickerColumnItem, PickerConfirmEvent, PickerPickEvent, PickerChangeInfo } from '../l-picker/type';
+
+export type ManageChildInList = (child: LPickerItemComponentPublicInstance, shouldAdd: boolean) => void;
+export type OnPick = (value: PickerValue, index:number, column: number) => void;
+export type UpdateItems = (value: PickerValue, index:number, column: number) => void;
+
+export interface PickerViewProps extends PickerProps {
+ // 可以在这里添加l-picker-view特有的属性
+}
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker-view/usePickerView.ts b/uni_modules/lime-picker/components/l-picker-view/usePickerView.ts
new file mode 100644
index 0000000..ad9379c
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker-view/usePickerView.ts
@@ -0,0 +1,339 @@
+// @ts-nocheck
+import type { PickerProps, PickerValue, PickerColumnItem, PickerColumn, PickerConfirmEvent, PickerPickEvent } from '../l-picker/type';
+import { arrayEqual } from '@/uni_modules/lime-shared/arrayEqual';
+import { clamp } from '@/uni_modules/lime-shared/clamp';
+import { assignAtIndex } from '@/uni_modules/lime-shared/assignAtIndex'
+// #ifndef UNI-APP-X
+import { computed, ref, watch, onMounted, nextTick, onBeforeUnmount, toRaw, WritableComputedOptions, Ref, ComputedRef } from '@/uni_modules/lime-shared/vue';
+// #endif
+// #ifdef UNI-APP-X
+import type { ComputedRef } from 'vue'
+// #endif
+
+// 定义 usePickerView 钩子的返回类型接口
+export type UsePickerViewReturn = {
+ // 状态
+ defaultValue : Ref;
+ curIndex : Ref;
+ curValue : Ref;
+ curItem : Ref;
+
+ // 计算属性
+ modelValue : ComputedRef;
+ isEmpty : ComputedRef;
+ innerIndex : ComputedRef;
+
+ // 方法
+ getIndexByValue : (val : PickerValue | null, columnIndex : number) => number;
+ setIndex : (indexes : number[]) => void;
+ setValue : (values : PickerValue[]) => void;
+ updatePickerItems : () => void;
+ handlePick : (e : UniPickerViewChangeEvent) => void;
+ onCancel : (e : UniPointerEvent) => void;
+ onConfirm : () => void;
+ getSelectedOptions : () => PickerConfirmEvent;
+}
+
+
+/**
+ * 检测变化的列索引
+ * @param newIndexes 新的索引数组
+ * @param oldIndexes 旧的索引数组
+ * @returns 变化的列索引,如果没有变化返回-1
+ */
+const findChangedColumnIndex = (
+ newIndexes : number[],
+ oldIndexes : number[]
+) : number => {
+ // 比较两个数组的长度,取较长的进行遍历
+ const maxLength = Math.max(newIndexes.length, oldIndexes.length);
+
+ for (let i = 0; i < maxLength; i++) {
+ const newIndex = i < newIndexes.length ? newIndexes[i] : null;
+ const oldIndex = i < oldIndexes.length ? oldIndexes[i] : null;
+
+ // 如果索引不相等,说明该列发生了变化
+ if (newIndex != oldIndex) {
+ return i;
+ }
+ }
+
+ return -1; // 没有变化
+};
+
+
+export function usePickerView(props : PickerProps) : UsePickerViewReturn {
+ // 内部状态管理
+ const defaultValue = ref(props.value ?? props.modelValue ?? props.defaultValue ?? []);
+ const curIndex = ref([]);
+ const curValue = ref([]);
+ const curItem = ref([]);
+ // 添加初始化标志
+ let isInitialized = false;
+ let isInternalChange = false;
+
+ const instance = getCurrentInstance()?.proxy
+ // 计算属性
+ const modelValue = computed({
+ set(value : PickerValue[]) {
+ if (arrayEqual(value, defaultValue.value)) return;
+ defaultValue.value = value;
+ instance?.$emit('update:modelValue', value);
+ instance?.$emit('change', value);
+ },
+ get() : PickerValue[] {
+ return props.value ?? props.modelValue ?? defaultValue.value;
+ }
+ } as WritableComputedOptions);
+
+ const isEmpty = computed(() : boolean => {
+ return props.columns.length == 0 || props.columns.every(column => column.length == 0);
+ });
+
+ const innerIndex = computed(() : number[] => {
+ return curIndex.value;
+ });
+
+ // 辅助方法
+ const getIndexByValue = (val : PickerValue | null, columnIndex : number) : number => {
+ let defaultIndex = 0;
+ if (val != null && props.columns.length > columnIndex) {
+ defaultIndex = props.columns[columnIndex].findIndex((item) => item.value == val);
+
+ }
+
+ return defaultIndex < 0 ? 0 : defaultIndex;
+ };
+
+ const setIndex = (indexes : number[]) => {
+ const oldIndex = [...curIndex.value]
+ indexes.forEach((index, columnIndex) => {
+ if (props.columns.length < columnIndex + 1) return;
+ // let lastIndex = curIndex.value[columnIndex] || 0;
+ let _index = clamp(index, 0, props.columns[columnIndex].length - 1);
+ if (props.columns[columnIndex].length > _index) {
+ assignAtIndex(curIndex.value, columnIndex, _index)
+ assignAtIndex(curValue.value, columnIndex, props.columns[columnIndex][_index].value)
+ assignAtIndex(curItem.value, columnIndex, props.columns[columnIndex][_index])
+ }
+ });
+ // 只有创建新的数组 watch才能监听到
+ curIndex.value = [...curIndex.value]
+ curValue.value = [...curValue.value]
+ curItem.value = [...curItem.value]
+ };
+
+ const setValue = (values : PickerValue[]) => {
+ if (arrayEqual(values, curValue.value)) return;
+
+ const indexes : number[] = [];
+ values.forEach((value, columnIndex) => {
+ const index = getIndexByValue(value, columnIndex);
+ indexes.push(index);
+ });
+ // _changedColumn = -1
+ setIndex(indexes);
+ };
+
+ const updatePickerItems = () => {
+ const _indexs : number[] = [];
+ const _values : PickerValue[] = [];
+ const _oldIndexs = curIndex.value
+ props.columns.forEach((column, columnIndex) => {
+ if (column.length == 0) return;
+ const value = curValue.value.length > columnIndex ? curValue.value[columnIndex] : null;
+ const index = value == null ? 0 : getIndexByValue(value, columnIndex);
+ const item = column[index];
+
+ _indexs.push(index);
+ _values.push(item.value);
+ assignAtIndex(curItem.value, columnIndex, item)
+
+ });
+
+ if (arrayEqual(curValue.value, _values) && arrayEqual(curIndex.value, _indexs)) return;
+ curIndex.value = _indexs;
+ curValue.value = _values;
+ modelValue.value = [...curValue.value];
+
+ // #ifdef APP-IOS
+
+ if (_oldIndexs.length != _indexs.length) return
+ // uniappx ios可能无法滚动到指定项
+ // 所以先找到变化的列 让列里的项先前滚动再滚回
+ // 找出变化的列
+ const changedColumn = findChangedColumnIndex(_indexs, _oldIndexs)
+ isInternalChange = true
+ requestAnimationFrame(() => {
+ const value = curIndex.value[changedColumn]
+ curIndex.value[changedColumn] = value - 1
+ requestAnimationFrame(() => {
+ curIndex.value[changedColumn] = value
+ requestAnimationFrame(() => {
+ isInternalChange = false
+ })
+ })
+ })
+ // #endif
+
+
+ };
+
+ // 事件处理
+ const handlePick = (e : UniPickerViewChangeEvent) => {
+ // 如果是初始化阶段,不处理
+ if (!isInitialized || isInternalChange) return;
+ const indexes = [...e.detail.value];
+ if (arrayEqual(indexes, curIndex.value)) return;
+ // 处理索引越界问题
+ indexes.forEach((v, i) => {
+ if (i < props.columns.length) {
+ const n = props.columns[i].length;
+
+ if (v >= n) {
+ indexes[i] = n - 1;
+ }
+ }
+ });
+ // 找出变化的列
+ const changedColumn = findChangedColumnIndex(indexes, curIndex.value)
+ // let changedColumn = -1;
+ // for (let i = 0; i < indexes.length; i++) {
+
+ // if (i < curIndex.value.length) {
+ // if (indexes[i] != curIndex.value[i]) {
+ // changedColumn = i;
+ // break;
+ // }
+ // } else {
+ // // 如果当前索引不存在,说明是新增的列
+ // changedColumn = i;
+ // break;
+ // }
+ // }
+ setIndex(indexes);
+ if (changedColumn != -1) {
+ const column = props.columns[changedColumn];
+ const index = indexes[changedColumn];
+ const item = column[index];
+
+ const obj : PickerPickEvent = {
+ values: curValue.value,
+ column: changedColumn,
+ index
+ };
+
+ modelValue.value = [...curValue.value];
+ instance?.$emit('pick', obj);
+ }
+ };
+
+ const onCancel = (e : UniPointerEvent) => {
+ updatePickerItems();
+ instance?.$emit('cancel', e);
+ };
+
+ const onConfirm = () => {
+ const values = [...curValue.value];
+ const indexs = [...curIndex.value];
+ const items = curItem.value.map((item) : PickerColumnItem => toRaw(item));
+
+ if (!arrayEqual(modelValue.value, values)) {
+ modelValue.value = values;
+ }
+
+ const obj : PickerConfirmEvent = {
+ values,
+ indexs,
+ items
+ };
+
+ instance?.$emit('confirm', obj);
+ };
+
+ const getSelectedOptions = () : PickerConfirmEvent => {
+ const values = [...curValue.value];
+ const indexs = [...curIndex.value];
+ const items = curItem.value.map((item) : PickerColumnItem => toRaw(item));
+
+ if (!arrayEqual(modelValue.value, values)) {
+ modelValue.value = values;
+ }
+
+ const obj : PickerConfirmEvent = {
+ values,
+ indexs,
+ items
+ };
+
+ return obj;
+ };
+
+ // 生命周期管理
+ const stopModelValue = watch(modelValue, () => {
+ // 如果是初始化阶段,不处理
+ if (!isInitialized) return;
+ if (arrayEqual(modelValue.value, curValue.value)) return;
+ curValue.value = modelValue.value.map((item : PickerValue) => item);
+ updatePickerItems();
+ });
+
+ // const stopColumns = watch(() : PickerColumn[] => props.columns, () => {
+ // updatePickerItems();
+ // }, { deep: true });
+
+ const stopColumns = watchEffect(() => {
+ const currentColumns = props.columns;
+ if (!isInitialized) return
+ nextTick(() => {
+ // 可能会跟change发生冲突
+ updatePickerItems();
+ })
+ })
+
+ onMounted(() => {
+ if (
+ !arrayEqual(modelValue.value, curValue.value) &&
+ modelValue.value.length > 0) {
+ isInternalChange = true; // 标记为内部变化
+ curValue.value = [...modelValue.value];
+ }
+ updatePickerItems();
+ // 延迟设置初始化完成标志
+ nextTick(() => {
+ setTimeout(() => {
+ isInitialized = true;
+ isInternalChange = false;
+ }, 300)
+ });
+ });
+
+ onBeforeUnmount(() => {
+ stopModelValue();
+ stopColumns();
+ });
+
+ // 返回所有需要在模板中使用的状态和方法
+ return {
+ // 状态
+ defaultValue,
+ curIndex,
+ curValue,
+ curItem,
+
+ // 计算属性
+ modelValue,
+ isEmpty,
+ innerIndex,
+
+ // 方法
+ getIndexByValue,
+ setIndex,
+ setValue,
+ updatePickerItems,
+ handlePick,
+ onCancel,
+ onConfirm,
+ getSelectedOptions
+ } as UsePickerViewReturn;
+}
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker/index.scss b/uni_modules/lime-picker/components/l-picker/index.scss
new file mode 100644
index 0000000..687094c
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker/index.scss
@@ -0,0 +1,149 @@
+@import '~@/uni_modules/lime-style/index.scss';
+
+/* #ifdef uniVersion >= 4.75 */
+$use-css-var: true;
+/* #endif */
+
+$picker: #{$prefix}-picker;
+
+
+$picker-border-radius: create-var(picker-border-radius, $border-radius-xl);
+$picker-bg-color: create-var(picker-bg-color, $bg-color-container);
+$picker-toolbar-height: create-var(picker-toolbar-height, 58px);
+
+$picker-cancel-color: create-var(picker-cancel-color, $text-color-2);
+$picker-confirm-color: create-var(picker-confirm-color, $primary-color);
+$picker-button-font-size: create-var(picker-button-font-size, $font-size-md);
+
+$picker-title-font-size: create-var(picker-title-font-size, 18px);
+$picker-title-font-weight: create-var(picker-title-font-weight, 700);
+$picker-title-line-height: create-var(picker-title-line-height, 26px);
+$picker-title-color: create-var(picker-title-color, $text-color-1);
+
+
+$picker-group-height: create-var(picker-group-height, 200px);
+$picker-indicator-bg-color: create-var(picker-indicator-bg-color, $fill-3);
+$picker-indicator-border-radius: create-var(picker-indicator-border-radius, $border-radius);
+$picker-indicator-margin: create-var(picker-indicator-margin, 10px);
+
+$picker-item-height: create-var(picker-item-height, 50px);
+$picker-item-active-color: create-var(picker-item-active-color, $text-color-1);
+
+
+$picker-loading-mask-color: create-var(picker-loading-mask-color, rgba(255,255,255,.9));
+$picker-loading-color: create-var(picker-loading-color, $primary-color);
+
+.#{$picker} {
+ position: relative;
+ background-color: $picker-bg-color;
+ border-top-left-radius: $picker-border-radius;
+ border-top-right-radius: $picker-border-radius;
+
+ &__toolbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ // overflow: hidden;
+ height: $picker-toolbar-height;
+ flex-direction: row;
+ position: relative;
+ }
+ &__title {
+ /* #ifdef APP-ANDROID || APP-IOS */
+ // 鸿蒙使用定位在弹窗里会导致样式不生效
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translateX(-50%) translateY(-50%);
+ /* #endif */
+ text-align: center;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ color: $picker-title-color;
+ // line-height: $picker-toolbar-height;
+ line-height: $picker-title-line-height;
+ font-weight: $picker-title-font-weight;
+ font-size: $picker-title-font-size;
+ }
+
+ &__cancel,
+ &__confirm {
+ white-space: nowrap;
+ // position: absolute;
+ /* #ifndef UNI-APP-X && APP */
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ user-select: none;
+ /* #endif */
+ font-size: $picker-button-font-size;
+ line-height: $picker-toolbar-height;
+ height: 100%;
+ // padding: 0 $spacer;
+ @include padding(0 $spacer);
+ }
+
+ &__cancel {
+ // left: 0;
+ margin-right: auto;
+ color: $picker-cancel-color;
+ }
+
+ &__confirm {
+ margin-left: auto;
+ color: $picker-confirm-color;
+ // right: 0
+ }
+
+
+ &__main {
+ position: relative;
+ display: flex;
+ height: $picker-group-height;
+ flex-direction: row;
+ z-index: 2;
+ // padding: 0 $spacer-xs;
+ @include padding(0 $spacer-xs);
+ }
+ &__mask {
+
+ }
+ &__empty {
+ pointer-events: none;
+ justify-content: center;
+ align-items: center;
+ display: flex;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 3;
+ }
+ &__loading {
+ z-index: 3;
+ // color: $picker-loading-color;
+ background-color: $picker-loading-mask-color;
+ // background-color: red;
+ justify-content: center;
+ align-items: center;
+ display: flex;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0
+ }
+ &__indicator{
+ position: absolute;
+ background-color: $picker-indicator-bg-color;
+ top: 50%;
+ left: $picker-indicator-margin;
+ right: $picker-indicator-margin;
+ height: $picker-item-height;
+ transform: translateY(-50%);
+ z-index: -1;
+ @include border-radius($picker-indicator-border-radius);
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker/l-picker.uvue b/uni_modules/lime-picker/components/l-picker/l-picker.uvue
new file mode 100644
index 0000000..f77c044
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker/l-picker.uvue
@@ -0,0 +1,269 @@
+
+
+
+ {{cancelBtn}}
+
+
+
+ {{title}}
+ {{confirmBtn}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker/l-picker.vue b/uni_modules/lime-picker/components/l-picker/l-picker.vue
new file mode 100644
index 0000000..b129e29
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker/l-picker.vue
@@ -0,0 +1,271 @@
+
+
+
+ {{cancelBtn}}
+ {{title}}
+ {{confirmBtn}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker/props.ts b/uni_modules/lime-picker/components/l-picker/props.ts
new file mode 100644
index 0000000..b920383
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker/props.ts
@@ -0,0 +1,126 @@
+// @ts-nocheck
+export default {
+ /**
+ * 取消按钮文字
+ */
+ cancelBtn: {
+ type: String,
+ default: null
+ },
+ cancelStyle: {
+ type: [String, Object],
+ default: null
+ },
+ /**
+ * 确定按钮文字
+ */
+ confirmBtn: {
+ type: String,
+ default: null
+ },
+ confirmStyle: {
+ type: [String, Object],
+ default: null
+ },
+ keys: {
+ type: Object,
+ default: null
+ },
+ /**
+ * 标题
+ */
+ title: {
+ type: String,
+ default: null
+ },
+ titleStyle: {
+ type: [String, Object],
+ default: null
+ },
+ /**
+ * 配置每一列的选项
+ */
+ columns: {
+ type: Array,
+ default: () => []
+ },
+ /**
+ * 选中值
+ */
+ modelValue: {
+ type: Array,
+ default: null
+ },
+ defaultValue: {
+ type: Array,
+ default: null
+ },
+ value: {
+ type: Array,
+ default: null
+ },
+ /**
+ * 是否为加载状态
+ */
+ loading: {
+ type: Boolean,
+ default: false
+ },
+ loadingColor: {
+ type: String,
+ default: null
+ },
+ loadingMaskColor: {
+ type: String,
+ default: null
+ },
+ loadingSize: {
+ type: String,
+ default: '64rpx'
+ },
+ itemHeight: {
+ type: String
+ },
+ itemColor: {
+ type: String,
+ default: ''
+ },
+ itemFontSize: {
+ type: String,
+ default: ''
+ },
+ itemActiveColor: {
+ type: String,
+ default: ''
+ },
+ itemActiveFontWeight: {
+ type: Number,
+ default: null
+ },
+ indicatorStyle: {
+ type: String,
+ default: ''
+ },
+ bgColor: {
+ type: String,
+ default: null
+ },
+ groupHeight: {
+ type: String,
+ default: ''
+ },
+ radius: {
+ type: String,
+ default: null
+ },
+ /**
+ * 列表更新后,是否归0
+ */
+ resetIndex: {
+ type: Boolean,
+ default: false
+ },
+ maskColors: {
+ type: Array,
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker/type.ts b/uni_modules/lime-picker/components/l-picker/type.ts
new file mode 100644
index 0000000..0325033
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker/type.ts
@@ -0,0 +1,87 @@
+// @ts-nocheck
+export type PickerValue = string | number;
+export type LimePickerValue = string | number;
+export type PickerColumnItem = {
+ id: any|null;
+ label : string;
+ disabled: boolean | null;
+ value : string;//string | number;
+ children : PickerColumn | null
+}
+export type PickerColumn = PickerColumnItem[];
+export type PickerPickEvent= {
+ values: PickerValue[];
+ column : number;
+ index : number;
+}
+export type PickerConfirmEvent = {
+ values: PickerValue[]
+ indexs: number[]
+ items: PickerColumnItem[]
+}
+/**
+ * 定义比较数组时返回的变化对象类型。
+ */
+export type PickerChangeInfo = {
+ column: number; // 变化的列索引
+ direction: 1 | -1 | 0; // 变化方向:1 表示增加,-1 表示减少, 0表示无变化
+ index: number; // 变化后的新值,在列表中表示下标
+}
+
+
+export interface PickerProps {
+ /**
+ * 取消按钮文字
+ */
+ cancelBtn ?: string;
+ cancelStyle ?: string | UTSJSONObject;
+ /**
+ * 确定按钮文字
+ */
+ confirmBtn ?: string;
+ confirmStyle ?: string | UTSJSONObject;
+ /**
+ * 标题
+ */
+ title ?: string;
+ titleStyle ?: string | UTSJSONObject;
+ /**
+ * 用来定义 value / label 在 `options` 中对应的字段别名
+ */
+ keys?: UTSJSONObject;
+ /**
+ * 配置每一列的选项
+ */
+ columns : PickerColumn[];
+ /**
+ * 选中值
+ */
+ modelValue ?: PickerValue[];
+ defaultValue ?: PickerValue[];
+ value ?: PickerValue[];
+ /**
+ * 是否为加载状态
+ */
+ loading: boolean;
+ loadingColor?: string;
+ loadingMaskColor?: string;
+ loadingSize: string;
+
+ itemHeight?: string;
+ itemColor?: string;
+ itemFontSize?: string;
+ itemActiveColor?: string;
+ itemActiveFontWeight?: number;
+
+ indicatorStyle?: string;
+ /**
+ * 遮罩渐变颜色,格式为 [起始颜色, 结束颜色]
+ * 例如:['#ffffff', 'rgba(255,255,255,0)']
+ * 若未提供,则根据主题(明/暗)自动计算
+ */
+ maskColors?: string[];
+ bgColor?:string;
+ groupHeight?:string;
+ radius?:string;
+ resetIndex: boolean
+}
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/l-picker/utils.ts b/uni_modules/lime-picker/components/l-picker/utils.ts
new file mode 100644
index 0000000..00bdab8
--- /dev/null
+++ b/uni_modules/lime-picker/components/l-picker/utils.ts
@@ -0,0 +1,36 @@
+// @ts-nocheck
+/**
+ * 在数组的指定位置插入或更新值。
+ * 如果指定的索引小于数组的长度,则更新该位置的值。
+ * 如果指定的索引大于或等于数组的长度,则将值添加到数组的末尾。
+ *
+ * @param {number[]} arr - 要操作的数字数组。
+ * @param {number} index - 要插入或更新值的索引位置。
+ * @param {number} value - 要插入或更新的值。
+ */
+export function pushAt(arr : T[], index : number, value : T) {
+ // #ifdef APP-ANDROID
+ if (index < arr.length) {
+ arr[index] = value;
+ } else {
+ arr.push(value);
+ }
+ // #endif
+ // #ifndef APP-ANDROID
+ arr[index] = value;
+ // #endif
+};
+
+
+/**
+ * 精确比较两个数组是否相等(浅比较)
+ * @param arr1 数组1
+ * @param arr2 数组2
+ * @returns 是否相等
+ */
+export function arraysEqual(arr1 : T[], arr2 : T[]) : boolean {
+ return (
+ arr1.length == arr2.length &&
+ arr1.every((val, i):boolean => val == arr2[i])
+ )
+}
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/lime-picker/lime-picker.uvue b/uni_modules/lime-picker/components/lime-picker/lime-picker.uvue
new file mode 100644
index 0000000..f2108a3
--- /dev/null
+++ b/uni_modules/lime-picker/components/lime-picker/lime-picker.uvue
@@ -0,0 +1,411 @@
+
+
+ Picker 选择器
+ 用于一组预设数据中的选择。
+
+
+ 基础用法
+
+
+
+
+
+
+
+
+ 搭配弹出层使用
+
+
+
+
+
+
+
+
+ 双向绑定: {{citys.join(',')}}
+
+
+
+
+
+
+
+ 多列
+
+
+
+
+
+
+ 多列联动
+
+
+
+
+
+
+
+
+
+ 级联选择
+
+
+
+
+
+
+ 加载状态
+
+
+
+
+
+
+ 空状态
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-picker/components/lime-picker/lime-picker.vue b/uni_modules/lime-picker/components/lime-picker/lime-picker.vue
new file mode 100644
index 0000000..978b1b4
--- /dev/null
+++ b/uni_modules/lime-picker/components/lime-picker/lime-picker.vue
@@ -0,0 +1,398 @@
+
+
+ Picker 选择器
+ 用于一组预设数据中的选择。
+
+
+ 基础用法
+
+
+
+
+
+
+
+
+ 配合弹窗插件
+
+
+
+
+
+
+
+
+ 双向绑定: {{citys.join(',')}}
+
+
+
+
+
+
+
+ 多列
+
+
+
+
+
+
+ 多列联动
+
+
+
+
+
+
+
+
+
+ 级联选择
+
+
+
+
+
+
+ 加载状态
+
+
+
+
+
+
+ 空状态
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-picker/index.uts b/uni_modules/lime-picker/index.uts
new file mode 100644
index 0000000..7cfb56a
--- /dev/null
+++ b/uni_modules/lime-picker/index.uts
@@ -0,0 +1,6 @@
+// @ts-nocheck
+export * from './components/l-picker/type'
+// export { LimePickerValue } from './components/l-picker/type'
+
+// export type PickerValue = string | number;
+// export type LimePickerValue = PickerValue;
\ No newline at end of file
diff --git a/uni_modules/lime-picker/package.json b/uni_modules/lime-picker/package.json
new file mode 100644
index 0000000..0436ffe
--- /dev/null
+++ b/uni_modules/lime-picker/package.json
@@ -0,0 +1,110 @@
+{
+ "id": "lime-picker",
+ "displayName": "lime-picker 选择器",
+ "version": "0.3.8",
+ "description": "lime-picker 用于一组预设数据中的选择。支持单列选择、多列选择,兼容uniapp/uniappx",
+ "keywords": [
+ "lime-picker",
+ "picker",
+ "选择器"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^4.36",
+ "uni-app": "^4.51",
+ "uni-app-x": "^4.45"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "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": [
+ "lime-style",
+ "lime-shared",
+ "lime-loading"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "x",
+ "aliyun": "x",
+ "alipay": "x"
+ },
+ "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": "√"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-picker/readme.md b/uni_modules/lime-picker/readme.md
new file mode 100644
index 0000000..81c499e
--- /dev/null
+++ b/uni_modules/lime-picker/readme.md
@@ -0,0 +1,573 @@
+# lime-picker 选择器
+一个功能丰富的选择器组件,用于在一组预设数据中进行选择,支持单列/多列选择、级联选择、多列联动、自定义样式、加载状态和空状态等功能。
+
+> 插件依赖:`lime-style`、`lime-shared`、`lime-loading`
+
+## 文档链接
+📚 组件详细文档请访问以下站点:
+- [选择器文档 - 站点1](https://limex.qcoon.cn/components/picker.html)
+- [选择器文档 - 站点2](https://limeui.netlify.app/components/picker.html)
+- [选择器文档 - 站点3](https://limeui.familyzone.top/components/picker.html)
+
+## 安装方法
+1. 在uni-app插件市场中搜索并导入`lime-picker`
+2. 首次导入可能需要重新编译
+3. 在页面中使用`l-picker`组件(组件)或`lime-picker`(演示)
+
+
+## 代码演示
+
+### 基础用法
+
+Picker 组件可通过 `columns` 属性配置选项数据,或子组件`l-picker-item`的`options`配置数据,二选一。
+顶部栏包含标题、确认按钮和取消按钮,点击确认按钮触发 `confirm` 事件,点击取消按钮触发 `cancel` 事件。
+```html
+
+
+
+
+
+
+```
+```js
+// 非TS项目不需要引入类型
+import type { PickerColumn, PickerConfirmEvent } from '@/uni_modules/lime-picker';
+
+// 通过`l-picker-item`子组件时,数据结构为单数组
+// PickerColumn
+const cityOptions = [
+ {
+ label: '北京市',
+ value: '北京市',
+ },
+ {
+ label: '上海市',
+ value: '上海市',
+ }
+] as PickerColumn
+
+// 通过 Picker 的 columns 属性的数组结构为[PickerColumn, PickerColumn]
+
+const columns = [
+ // 第一列
+ cityOptions,
+]
+
+const onConfirm = ({values} : PickerConfirmEvent) => {
+ showToast(`当前值: ${values.join(',')}`);
+};
+const onChange = ({values, column, index} : PickerPickEvent) => {
+ showToast(`当前值: ${values.join(',')}`);
+};
+const onCancel = () => {console.log('取消')};
+```
+
+
+
+
+### 搭配弹出层使用
+
+在实际场景中,Picker 通常作为用于辅助表单填写,可以搭配 [Popup](https://ext.dcloud.net.cn/plugin?id=20769)。
+
+```html
+
+
+
+
+
+```
+```js
+import type { PickerColumn } from '@/uni_modules/lime-picker';
+const showPicker = ref(false)
+const cityOptions = [
+ [
+ {
+ label: '北京市',
+ value: '北京市',
+ },
+ {
+ label: '上海市',
+ value: '上海市',
+ }
+ ]
+] as PickerColumn[]
+```
+
+
+### 多列
+`columns` 属性可以通过二维数组的形式配置多列选择。也可以使用`l-picker-item`子组件实现多列。
+```html
+
+
+
+
+
+
+```
+```js
+import type { PickerColumn } from '@/uni_modules/lime-picker';
+const showPicker = ref(false)
+const years = [
+ {
+ label: '2024',
+ value: '2024',
+ },
+ {
+ label: '2025',
+ value: '2025',
+ }
+] as PickerColumn
+const seasonOptions = [
+ {
+ label: '春',
+ value: '春',
+ },
+ {
+ label: '夏',
+ value: '夏',
+ },
+ {
+ label: '秋',
+ value: '秋',
+ },
+ {
+ label: '冬',
+ value: '冬',
+ },
+] as PickerColumn
+
+const seasonColumns = [years, seasonOptions]
+
+```
+
+### 双向绑定
+
+通过 `v-model` 可以绑定当前选中项的 `values`,修改 `v-model` 绑定的值时,`Picker` 的选中状态也会随之改变。
+
+`v-model` 的值是一个数组,数组的第一位对应第一列选中项的 `value`,第二位对应第二列选中项的 `value`,以此类推。
+
+```html
+
+
+
+```
+```js
+import type { PickerColumn } from '@/uni_modules/lime-picker';
+const citys = ref(['上海市'])
+const cityOptions = [
+ {
+ label: '北京市',
+ value: '北京市',
+ },
+ {
+ label: '上海市',
+ value: '上海市',
+ },
+ {
+ label: '广州市',
+ value: '广州市',
+ },
+ {
+ label: '深圳市',
+ value: '深圳市',
+ },
+] as PickerColumn
+```
+
+
+### 加载状态
+
+若选择器数据是异步获取的,可以通过 `loading` 属性显示加载提示。
+```html
+
+```
+```js
+import type { PickerColumn } from '@/uni_modules/lime-picker';
+const cityOptions = [
+ [
+ {
+ label: '北京市',
+ value: '北京市',
+ },
+ {
+ label: '上海市',
+ value: '上海市',
+ },
+ {
+ label: '广州市',
+ value: '广州市',
+ },
+ {
+ label: '深圳市',
+ value: '深圳市',
+ },
+ ]
+] as PickerColumn[]
+```
+
+### 空状态
+当数据为空时,可以使用 `empty` 插槽自定义空状态内容。[empty插件](https://ext.dcloud.net.cn/plugin?id=19563)
+```html
+
+
+
+
+
+```
+```js
+import type { PickerColumn } from '@/uni_modules/lime-picker';
+const cityOptions = [] as PickerColumn[]
+```
+
+
+### 多列联动
+通过`pick`事件完成联动操作。
+```html
+
+
+
+
+
+```
+```js
+import type { PickerColumn, PickerColumnItem, PickerConfirmEvent, PickerPickEvent } from '@/uni_modules/lime-picker';
+const areaList = {
+ provinces: {
+ '110000': '北京市',
+ '440000': '广东省',
+ },
+ cities: {
+ '110100': '北京市',
+ '440100': '广州市',
+ '440200': '韶关市',
+ '440300': '深圳市',
+ '440400': '珠海市',
+ '440500': '汕头市',
+ '440600': '佛山市',
+ },
+ counties: {
+ '110101': '东城区',
+ '110102': '西城区',
+ '110105': '朝阳区',
+ '110106': '丰台区',
+ '110107': '石景山区',
+ '110108': '海淀区',
+ '110109': '门头沟区',
+ '110111': '房山区',
+ '110112': '通州区',
+ '110113': '顺义区',
+ '110114': '昌平区',
+ '110115': '大兴区',
+ '110116': '怀柔区',
+ '110117': '平谷区',
+ '110118': '密云区',
+ '110119': '延庆区',
+ '440103': '荔湾区',
+ '440104': '越秀区',
+ '440105': '海珠区',
+ '440106': '天河区',
+ '440111': '白云区',
+ '440112': '黄埔区',
+ '440113': '番禺区',
+ '440114': '花都区',
+ '440115': '南沙区',
+ '440117': '从化区',
+ '440118': '增城区',
+ '440203': '武江区',
+ '440204': '浈江区',
+ '440205': '曲江区',
+ '440222': '始兴县',
+ '440224': '仁化县',
+ '440229': '翁源县',
+ '440232': '乳源瑶族自治县',
+ '440233': '新丰县',
+ '440281': '乐昌市',
+ '440282': '南雄市',
+ '440303': '罗湖区',
+ '440304': '福田区',
+ '440305': '南山区',
+ '440306': '宝安区',
+ '440307': '龙岗区',
+ '440308': '盐田区',
+ '440309': '龙华区',
+ '440310': '坪山区',
+ '440311': '光明区',
+ '440402': '香洲区',
+ '440403': '斗门区',
+ '440404': '金湾区',
+ '440507': '龙湖区',
+ '440511': '金平区',
+ '440512': '濠江区',
+ '440513': '潮阳区',
+ '440514': '潮南区',
+ '440515': '澄海区',
+ '440523': '南澳县',
+ '440604': '禅城区',
+ '440605': '南海区',
+ '440606': '顺德区',
+ '440607': '三水区',
+ '440608': '高明区',
+ },
+};
+
+// UTSJSONObject 为uniapp x的 object,非uniapp 可使用Object
+const getOptions = (obj : UTSJSONObject, filter ?: ((value : PickerColumnItem) => boolean) | null) : PickerColumn => {
+ const res = UTSJSONObject.keys(obj).map((key) : PickerColumnItem => {
+ return {
+ // id: key,
+ value: `${key}`,
+ label: `${obj[key]}`
+ } as PickerColumnItem
+ })
+ if (filter != null) {
+ return res.filter(filter)
+ }
+ return res
+}
+const match = (v1 : string, v2 : string, size : number) : boolean => {
+ return v1.slice(0, size) == v2.slice(0, size)
+};
+const getCounties = (cityValue : string) : PickerColumn => {
+ return getOptions(areaList.counties as UTSJSONObject, (county : PickerColumnItem) : boolean => match(county.value, cityValue, 4));
+};
+const getCities = (provinceValue : string) : PickerColumn[] => {
+ const cities = getOptions(areaList.cities as UTSJSONObject, (city : PickerColumnItem) : boolean => match(city.value, provinceValue, 2));
+ const counties = getCounties(cities[0].value);
+ return [cities, counties];
+};
+// 省
+const provinces = ref([]);
+provinces.value = getOptions(areaList.provinces as UTSJSONObject, null)
+const data = getCities(provinces.value[0].value);
+// 市
+const cities = ref(data[0]);
+// 区
+const counties = ref(data[1]);
+
+const onConfirm = (context : PickerConfirmEvent) => {
+ console.log('context', context)
+}
+const onChange = ({values, column, index} : PickerPickEvent) => {
+ // column 表示第几列
+ // index 表示列数组里的下标
+ if (column == 0) {
+ // 更改省份
+ const data = getCities(provinces.value[index].value);
+ cities.value = data[0];
+ counties.value = data[1];
+ }
+
+ if (column == 1) {
+ // 更改城市
+ counties.value = getCounties(cities.value[index].value);
+ }
+
+ if (column == 2) {
+ // 更改区县
+ }
+}
+```
+
+### 级联选择
+使用 `l-cascade` 组件 通过`columns` 的 `children` 字段可以实现选项级联的效果。如果级联层级较多,推荐使用 [Cascader 级联选项组件](https://ext.dcloud.net.cn/plugin?id=20880)。
+
+```html
+
+```
+```js
+ const columns = [
+ {
+ lable: '浙江',
+ value: 'Zhejiang',
+ children: [
+ {
+ lable: '杭州',
+ value: 'Hangzhou',
+ children: [
+ { lable: '西湖区', value: 'Xihu' },
+ { lable: '余杭区', value: 'Yuhang' },
+ ],
+ },
+ {
+ lable: '温州',
+ value: 'Wenzhou',
+ children: [
+ { lable: '鹿城区', value: 'Lucheng' },
+ { lable: '瓯海区', value: 'Ouhai' },
+ ],
+ },
+ ],
+ },
+ {
+ lable: '福建',
+ value: 'Fujian',
+ children: [
+ {
+ lable: '福州',
+ value: 'Fuzhou',
+ children: [
+ { lable: '鼓楼区', value: 'Gulou' },
+ { lable: '台江区', value: 'Taijiang' },
+ ],
+ },
+ {
+ lable: '厦门',
+ value: 'Xiamen',
+ children: [
+ { lable: '思明区', value: 'Siming' },
+ { lable: '海沧区', value: 'Haicang' },
+ ],
+ },
+ ],
+ },
+ ];
+```
+
+## 快速预览
+导入后直接使用这个标签查看演示效果
+
+```html
+
+
+```
+
+## 插件标签说明
+- `l-picker`: 组件标签
+- `lime-picker`: 演示标签
+
+
+## Vue2使用说明
+本插件使用了`composition-api`,请按照[官方教程](https://uniapp.dcloud.net.cn/tutorial/vue-composition-api.html)配置。
+
+```js
+// main.js
+import Vue from 'vue'
+import VueCompositionAPI from '@vue/composition-api'
+Vue.use(VueCompositionAPI)
+```
+
+## API 文档
+
+### Picker Props
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| v-model | 当前选中项对应的值 | _string\|number[]_ | - |
+| value | 当前选中项对应的值 | _string\|number[]_ | - |
+| columns | 对象数组,配置每一列显示的数据 | _PickerColumn[]_ | `false` |
+| cancelBtn | 取消按钮文字 | _string_ | `` |
+| cancelStyle | 取消按钮样式 | _string_ | `` |
+| confirmBtn | 确定按钮文字 | _string_ | `` |
+| confirmStyle | 确定按钮样式 | _string_ | `` |
+| title | 标题 | _string_ | `` |
+| titleStyle | 标题样式 | _string_ | `` |
+| loading | 是否显示加载状态 | _boolean_ | `false` |
+| loadingColor | 加载图标颜色 | _string_ | `` |
+| loadingMaskColor | 加载遮罩背景颜色 | _string_ | `` |
+| loadingSize | 加载图标大小 | _string_ | `` |
+| itemHeight | 每项高度 | _string_ | `50px` |
+| itemColor | 每项文本颜色 | _string_ | `` |
+| itemFontSize | 每项文本字体大小 | _string_ | `` |
+| itemActiveColor | 每项文本选中颜色 | _string_ | `` |
+| indicatorStyle | 选择器中间选中框的样式 | _string_ | `` |
+| maskColors | 选择器遮罩颜色样式,`[开始颜色, 结束颜色]` | _string[]_ | `` |
+| bgColor | 选择器背景色 | _string_ | `` |
+| groupHeight | 选项组高度 | _string_ | `400rpx` |
+| radius | 圆角 | _string_ | |
+
+### PickerItem Props
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| options | 对象数组,配置列显示的数据 | _PickerColumnItem[]_ | `false` |
+| value | 值 | _string_ | |
+| column | 当前为第几列 | _number_ | |
+
+
+### Events
+| 事件名 | 说明 | 回调参数 |
+| ------ | ------------------------ | ---------------------- |
+| cancel | 点击取消按钮时触发 | __ |
+| change | 选中变化时候触发 | _value: string\|number[]_ |
+| confirm | 点击确认按钮时触发 | _{values: PickerValue[], indexs:number[], items:PickerColumnItem[]}:PickerConfirmEvent_ |
+| pick | 任何一列选中都会触发,不同的列参数不同,column 表示第几列变化,index 表示变化那一列的选中项下标 | _{values: PickerValue[], column:number,index:number}:PickerPickEvent_ |
+
+### Picker Slots
+| 插槽名 | 说明 | 回调参数 |
+| ------ | ------------------------ | ---------------------- |
+| default | | __ |
+| header | 头部 | __ |
+| footer | 底部 | __ |
+| empty | 空数据插槽 | __ |
+
+### 方法
+通过 ref 可以获取到 Picker 实例并调用实例方法:
+
+| 方法名 | 说明 | 返回值 |
+| ------ | ------------------------ | ---------------------- |
+| confirm | 停止惯性滚动并触发 `confirm` 事件 | __ |
+| getSelectedOptions | 获取当前选中的选项 | PickerConfirmEvent[] |
+
+```js
+// cascade 组件类型为 LCascadeComponentPublicInstance
+// picker 组件类型为 LPickerComponentPublicInstance
+const pickerRef = ref(null)
+pickerRef.value?.confirm()
+const res = pickerRef.value?.getSelectedOptions()
+```
+
+## 主题定制
+
+组件提供了下列 CSS 变量,可用于自定义样式。
+
+| 变量名 | 默认值 | 说明 |
+|--------|--------|------|
+| `--l-picker-border-radius` | `$border-radius-xl` | 选择器整体圆角 |
+| `--l-picker-bg-color` | `$bg-color-container` | 选择器背景色 |
+| `--l-picker-toolbar-height` | `58px` | 工具栏高度 |
+| `--l-picker-cancel-color` | `$text-color-2` | 取消按钮文字颜色 |
+| `--l-picker-confirm-color` | `$primary-color` | 确认按钮文字颜色 |
+| `--l-picker-button-font-size` | `$font-size-md` | 按钮文字大小 |
+| `--l-picker-title-font-size` | `18px` | 标题文字大小 |
+| `--l-picker-title-font-weight` | `700` | 标题文字粗细 |
+| `--l-picker-title-line-height` | `26px` | 标题行高 |
+| `--l-picker-title-color` | `$text-color-1` | 标题文字颜色 |
+| `--l-picker-group-height` | `200px` | 选择器内容区高度 |
+| `--l-picker-indicator-bg-color` | `$fill-4` | 选中指示器背景色 |
+| `--l-picker-indicator-border-radius` | `6px` | 选中指示器圆角 |
+| `--l-picker-indicator-margin` | `10px` | 选中指示器边距 |
+| `--l-picker-item-height` | `50px` | 每个选项高度 |
+| `--l-picker-item-color` | `$text-color-1` | 选项文字颜色 |
+| `--l-picker-item-active-color` | `$text-color-1` | 选中项文字颜色 |
+| `--l-picker-mask-top-color` | `white` | 遮罩开始颜色 |
+| `--l-picker-mask-bottom-color` | `white` | 遮罩结束颜色 |
+| `--l-picker-item-active-font-weight` | `700` | 选中项文字粗细 |
+| `--l-picker-item-font-size` | `$font-size-md` | 选项文字大小 |
+| `--l-picker-loading-mask-color` | `rgba(255,255,255,.9)` | 加载遮罩颜色 |
+| `--l-picker-loading-color` | `$primary-color` | 加载动画颜色 |
+
+
+## 支持与赞赏
+
+如果你觉得本插件解决了你的问题,可以考虑支持作者:
+| 支付宝赞助 | 微信赞助 |
+|------------|------------|
+|  |  |
\ No newline at end of file
diff --git a/uni_modules/lime-picker/readme_old.md b/uni_modules/lime-picker/readme_old.md
new file mode 100644
index 0000000..f4d762b
--- /dev/null
+++ b/uni_modules/lime-picker/readme_old.md
@@ -0,0 +1,555 @@
+# lime-picker 选择器
+用于一组预设数据中的选择。通常与弹出层组件配合使用。
+插件依赖`lime-style`,`lime-shared`,`lime-loading`不喜勿下
+
+## 文档
+ 🚀 [picker【站点1】](https://limex.qcoon.cn/components/picker.html)
+ 🌍 [picker【站点2】](https://limeui.netlify.app/components/picker.html)
+ 🔥 [picker【站点3】](https://limeui.familyzone.top/components/picker.html)
+
+
+## 安装
+插件市场导入即可。首次导入可能需要重新编译
+
+
+## 代码演示
+
+### 基础用法
+
+Picker 组件可通过 `columns` 属性配置选项数据,或子组件`l-picker-item`的`options`配置数据,二选一。
+顶部栏包含标题、确认按钮和取消按钮,点击确认按钮触发 `confirm` 事件,点击取消按钮触发 `cancel` 事件。
+```html
+
+
+
+
+
+
+```
+```js
+// 非TS项目不需要引入类型
+import type { PickerColumn, PickerConfirmEvent } from '@/uni_modules/lime-picker';
+
+// 通过`l-picker-item`子组件时,数据结构为单数组
+// PickerColumn
+const cityOptions = [
+ {
+ label: '北京市',
+ value: '北京市',
+ },
+ {
+ label: '上海市',
+ value: '上海市',
+ }
+] as PickerColumn
+
+// 通过 Picker 的 columns 属性的数组结构为[PickerColumn, PickerColumn]
+
+const columns = [
+ // 第一列
+ cityOptions,
+]
+
+const onConfirm = ({values} : PickerConfirmEvent) => {
+ showToast(`当前值: ${values.join(',')}`);
+};
+const onChange = ({values, column, index} : PickerPickEvent) => {
+ showToast(`当前值: ${values.join(',')}`);
+};
+const onCancel = () => {console.log('取消')};
+```
+
+
+
+
+### 搭配弹出层使用
+
+在实际场景中,Picker 通常作为用于辅助表单填写,可以搭配 [Popup](https://ext.dcloud.net.cn/plugin?id=20769)。
+
+```html
+
+
+
+
+
+```
+```js
+import { PickerColumn } from '@/uni_modules/lime-picker';
+const showPicker = ref(false)
+const cityOptions = [
+ [
+ {
+ label: '北京市',
+ value: '北京市',
+ },
+ {
+ label: '上海市',
+ value: '上海市',
+ }
+ ]
+] as PickerColumn[]
+```
+
+
+### 多列
+`columns` 属性可以通过二维数组的形式配置多列选择。也可以使用`l-picker-item`子组件实现多列。
+```html
+
+
+
+
+
+
+```
+```js
+import { PickerColumn } from '@/uni_modules/lime-picker';
+const showPicker = ref(false)
+const years = [
+ {
+ label: '2024',
+ value: '2024',
+ },
+ {
+ label: '2025',
+ value: '2025',
+ }
+] as PickerColumn
+const seasonOptions = [
+ {
+ label: '春',
+ value: '春',
+ },
+ {
+ label: '夏',
+ value: '夏',
+ },
+ {
+ label: '秋',
+ value: '秋',
+ },
+ {
+ label: '冬',
+ value: '冬',
+ },
+] as PickerColumn
+
+const seasonColumns = [years, seasonOptions]
+
+```
+
+### 双向绑定
+
+通过 `v-model` 可以绑定当前选中项的 `values`,修改 `v-model` 绑定的值时,`Picker` 的选中状态也会随之改变。
+
+`v-model` 的值是一个数组,数组的第一位对应第一列选中项的 `value`,第二位对应第二列选中项的 `value`,以此类推。
+
+```html
+
+
+
+```
+```js
+import { PickerColumn } from '@/uni_modules/lime-picker';
+const citys = ref(['上海市'])
+const cityOptions = [
+ {
+ label: '北京市',
+ value: '北京市',
+ },
+ {
+ label: '上海市',
+ value: '上海市',
+ },
+ {
+ label: '广州市',
+ value: '广州市',
+ },
+ {
+ label: '深圳市',
+ value: '深圳市',
+ },
+] as PickerColumn
+```
+
+
+### 加载状态
+
+若选择器数据是异步获取的,可以通过 `loading` 属性显示加载提示。
+```html
+
+```
+```js
+import { PickerColumn } from '@/uni_modules/lime-picker';
+const cityOptions = [
+ [
+ {
+ label: '北京市',
+ value: '北京市',
+ },
+ {
+ label: '上海市',
+ value: '上海市',
+ },
+ {
+ label: '广州市',
+ value: '广州市',
+ },
+ {
+ label: '深圳市',
+ value: '深圳市',
+ },
+ ]
+] as PickerColumn[]
+```
+
+### 空状态
+当数据为空时,可以使用 `empty` 插槽自定义空状态内容。[empty插件](https://ext.dcloud.net.cn/plugin?id=19563)
+```html
+
+
+
+
+
+```
+```js
+import { PickerColumn } from '@/uni_modules/lime-picker';
+const cityOptions = [] as PickerColumn[]
+```
+
+
+### 多列联动
+通过`pick`事件完成联动操作。
+```html
+
+
+
+
+
+```
+```js
+import { PickerColumn, PickerColumnItem, PickerConfirmEvent, PickerPickEvent } from '@/uni_modules/lime-picker';
+const areaList = {
+ provinces: {
+ '110000': '北京市',
+ '440000': '广东省',
+ },
+ cities: {
+ '110100': '北京市',
+ '440100': '广州市',
+ '440200': '韶关市',
+ '440300': '深圳市',
+ '440400': '珠海市',
+ '440500': '汕头市',
+ '440600': '佛山市',
+ },
+ counties: {
+ '110101': '东城区',
+ '110102': '西城区',
+ '110105': '朝阳区',
+ '110106': '丰台区',
+ '110107': '石景山区',
+ '110108': '海淀区',
+ '110109': '门头沟区',
+ '110111': '房山区',
+ '110112': '通州区',
+ '110113': '顺义区',
+ '110114': '昌平区',
+ '110115': '大兴区',
+ '110116': '怀柔区',
+ '110117': '平谷区',
+ '110118': '密云区',
+ '110119': '延庆区',
+ '440103': '荔湾区',
+ '440104': '越秀区',
+ '440105': '海珠区',
+ '440106': '天河区',
+ '440111': '白云区',
+ '440112': '黄埔区',
+ '440113': '番禺区',
+ '440114': '花都区',
+ '440115': '南沙区',
+ '440117': '从化区',
+ '440118': '增城区',
+ '440203': '武江区',
+ '440204': '浈江区',
+ '440205': '曲江区',
+ '440222': '始兴县',
+ '440224': '仁化县',
+ '440229': '翁源县',
+ '440232': '乳源瑶族自治县',
+ '440233': '新丰县',
+ '440281': '乐昌市',
+ '440282': '南雄市',
+ '440303': '罗湖区',
+ '440304': '福田区',
+ '440305': '南山区',
+ '440306': '宝安区',
+ '440307': '龙岗区',
+ '440308': '盐田区',
+ '440309': '龙华区',
+ '440310': '坪山区',
+ '440311': '光明区',
+ '440402': '香洲区',
+ '440403': '斗门区',
+ '440404': '金湾区',
+ '440507': '龙湖区',
+ '440511': '金平区',
+ '440512': '濠江区',
+ '440513': '潮阳区',
+ '440514': '潮南区',
+ '440515': '澄海区',
+ '440523': '南澳县',
+ '440604': '禅城区',
+ '440605': '南海区',
+ '440606': '顺德区',
+ '440607': '三水区',
+ '440608': '高明区',
+ },
+};
+
+// UTSJSONObject 为uniapp x的 object,非uniapp 可使用Object
+const getOptions = (obj : UTSJSONObject, filter ?: ((value : PickerColumnItem) => boolean) | null) : PickerColumn => {
+ const res = UTSJSONObject.keys(obj).map((key) : PickerColumnItem => {
+ return {
+ // id: key,
+ value: `${key}`,
+ label: `${obj[key]}`
+ } as PickerColumnItem
+ })
+ if (filter != null) {
+ return res.filter(filter)
+ }
+ return res
+}
+const match = (v1 : string, v2 : string, size : number) : boolean => {
+ return v1.slice(0, size) == v2.slice(0, size)
+};
+const getCounties = (cityValue : string) : PickerColumn => {
+ return getOptions(areaList.counties as UTSJSONObject, (county : PickerColumnItem) : boolean => match(county.value, cityValue, 4));
+};
+const getCities = (provinceValue : string) : PickerColumn[] => {
+ const cities = getOptions(areaList.cities as UTSJSONObject, (city : PickerColumnItem) : boolean => match(city.value, provinceValue, 2));
+ const counties = getCounties(cities[0].value);
+ return [cities, counties];
+};
+// 省
+const provinces = ref([]);
+provinces.value = getOptions(areaList.provinces as UTSJSONObject, null)
+const data = getCities(provinces.value[0].value);
+// 市
+const cities = ref(data[0]);
+// 区
+const counties = ref(data[1]);
+
+const onConfirm = (context : PickerConfirmEvent) => {
+ console.log('context', context)
+}
+const onChange = ({values, column, index} : PickerPickEvent) => {
+ // column 表示第几列
+ // index 表示列数组里的下标
+ if (column == 0) {
+ // 更改省份
+ const data = getCities(provinces.value[index].value);
+ cities.value = data[0];
+ counties.value = data[1];
+ }
+
+ if (column == 1) {
+ // 更改城市
+ counties.value = getCounties(cities.value[index].value);
+ }
+
+ if (column == 2) {
+ // 更改区县
+ }
+}
+```
+
+### 级联选择
+使用 `l-cascade` 组件 通过`columns` 的 `children` 字段可以实现选项级联的效果。如果级联层级较多,推荐使用 [Cascader 级联选项组件](https://ext.dcloud.net.cn/plugin?id=20880)。
+
+```html
+
+```
+```js
+ const columns = [
+ {
+ lable: '浙江',
+ value: 'Zhejiang',
+ children: [
+ {
+ lable: '杭州',
+ value: 'Hangzhou',
+ children: [
+ { lable: '西湖区', value: 'Xihu' },
+ { lable: '余杭区', value: 'Yuhang' },
+ ],
+ },
+ {
+ lable: '温州',
+ value: 'Wenzhou',
+ children: [
+ { lable: '鹿城区', value: 'Lucheng' },
+ { lable: '瓯海区', value: 'Ouhai' },
+ ],
+ },
+ ],
+ },
+ {
+ lable: '福建',
+ value: 'Fujian',
+ children: [
+ {
+ lable: '福州',
+ value: 'Fuzhou',
+ children: [
+ { lable: '鼓楼区', value: 'Gulou' },
+ { lable: '台江区', value: 'Taijiang' },
+ ],
+ },
+ {
+ lable: '厦门',
+ value: 'Xiamen',
+ children: [
+ { lable: '思明区', value: 'Siming' },
+ { lable: '海沧区', value: 'Haicang' },
+ ],
+ },
+ ],
+ },
+ ];
+```
+
+### 查看示例
+导入后直接使用这个标签查看演示效果
+
+```html
+
+
+```
+
+
+### 插件标签
+`l-picker` 为 component
+`lime-picker` 为 demo
+
+### Vue2使用
+插件使用了`composition-api`, 如果你希望在vue2中使用请按官方的教程[vue-composition-api](https://uniapp.dcloud.net.cn/tutorial/vue-composition-api.html)配置
+关键代码是: 在main.js中 在vue2部分加上这一段即可
+
+```js
+// vue2
+import Vue from 'vue'
+import VueCompositionAPI from '@vue/composition-api'
+Vue.use(VueCompositionAPI)
+```
+
+
+
+## API
+
+### Picker Props
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| v-model | 当前选中项对应的值 | _string\|number[]_ | - |
+| value | 当前选中项对应的值 | _string\|number[]_ | - |
+| columns | 对象数组,配置每一列显示的数据 | _PickerColumn[]_ | `false` |
+| cancelBtn | 取消按钮文字 | _string_ | `` |
+| cancelStyle | 取消按钮样式 | _string_ | `` |
+| confirmBtn | 确定按钮文字 | _string_ | `` |
+| confirmStyle | 确定按钮样式 | _string_ | `` |
+| title | 标题 | _string_ | `` |
+| titleStyle | 标题样式 | _string_ | `` |
+| loading | 是否显示加载状态 | _boolean_ | `false` |
+| loadingColor | 加载图标颜色 | _string_ | `` |
+| loadingMaskColor | 加载遮罩背景颜色 | _string_ | `` |
+| loadingSize | 加载图标大小 | _string_ | `` |
+| itemHeight | 每项高度 | _string_ | `50px` |
+| itemColor | 每项文本颜色 | _string_ | `` |
+| itemFontSize | 每项文本字体大小 | _string_ | `` |
+| itemActiveColor | 每项文本选中颜色 | _string_ | `` |
+| indicatorStyle | 选择器中间选中框的样式 | _string_ | `` |
+| bgColor | 选择器背景色 | _string_ | `` |
+| groupHeight | 选项组高度 | _string_ | `400rpx` |
+| radius | 圆角 | _string_ | |
+
+### PickerItem Props
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| options | 对象数组,配置列显示的数据 | _PickerColumnItem[]_ | `false` |
+| value | 值 | _string_ | |
+| column | 当前为第几列 | _number_ | |
+
+
+### Events
+| 事件名 | 说明 | 回调参数 |
+| ------ | ------------------------ | ---------------------- |
+| cancel | 点击取消按钮时触发 | __ |
+| change | 选中变化时候触发 | _value: string\|number[]_ |
+| confirm | 点击确认按钮时触发 | _{values: PickerValue[], indexs:number[], items:PickerColumnItem[]}:PickerConfirmEvent_ |
+| pick | 任何一列选中都会触发,不同的列参数不同,column 表示第几列变化,index 表示变化那一列的选中项下标 | _{values: PickerValue[], column:number,index:number}:PickerPickEvent_ |
+
+### Picker Slots
+| 插槽名 | 说明 | 回调参数 |
+| ------ | ------------------------ | ---------------------- |
+| default | | __ |
+| header | 头部 | __ |
+| footer | 底部 | __ |
+| empty | 空数据插槽 | __ |
+
+
+
+## 主题定制
+
+### 样式变量
+
+组件提供了下列 CSS 变量,可用于自定义样式,uvue app无效。
+
+| 名称 | 默认值 | 描述 |
+| ------------------------ | -------------------- | ---- |
+| --l-picker-border-radius | _24rpx_ | - |
+| --l-picker-bg-color | _$bg-color-container_ | - |
+| --l-picker-toolbar-height | _116rpx_ | - |
+| --l-picker-cancel-color | _text-color-2_ | - |
+| --l-picker-confirm-color | _$primary-color_ | - |
+| --l-picker-button-font-size | _32rpx_ | - |
+| --l-picker-title-font-size | _36rpx_ | - |
+| --l-picker-title-font-weight | _700_ | - |
+| --l-picker-title-line-height | _52rpx_ | - |
+| --l-picker-title-color | _$text-color-1_ | - |
+| --l-picker-group-height | _400rpx_ | - |
+| --l-picker-indicator-bg-color | _$fill-4_ | - |
+| --l-picker-indicator-border-radius | _12rpx_ | - |
+| --l-picker-item-height | _50px_ | - |
+| --l-picker-item-color | _$text-color-1_ | - |
+| --l-picker-item-active-color | _$text-color-1_ | - |
+| --l-picker-item-active-color | _$text-color-1_ | - |
+| --l-picker-loading-mask-color | _rgba(255,255,255,.9)_ | - |
+| --l-picker-item-font-size | _$font-size-md_ | - |
+
+
+## 打赏
+
+如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-shared/addSeparator/index.ts b/uni_modules/lime-shared/addSeparator/index.ts
new file mode 100644
index 0000000..e2df12b
--- /dev/null
+++ b/uni_modules/lime-shared/addSeparator/index.ts
@@ -0,0 +1,10 @@
+// @ts-nocheck
+/**
+ * 每n位数字添加一个分隔符
+ */
+export function addSeparator(num : number | string, separator = ',', digit = 3) {
+ return `${num}`.replace(
+ new RegExp(`\\B(?=(\\d{${digit}})+(?!\\d))`, 'g'),
+ separator,
+ )
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/addUnit/index.ts b/uni_modules/lime-shared/addUnit/index.ts
new file mode 100644
index 0000000..19a1393
--- /dev/null
+++ b/uni_modules/lime-shared/addUnit/index.ts
@@ -0,0 +1,41 @@
+// @ts-nocheck
+import {isNumeric} from '../isNumeric'
+import {isDef} from '../isDef'
+/**
+ * 给一个值添加单位(像素 px)
+ * @param value 要添加单位的值,可以是字符串或数字
+ * @param unit 单位,默认为 'px'
+ * @returns 添加了单位的值,如果值为 null 则返回 null
+ */
+
+// #ifndef UNI-APP-X && APP
+export function addUnit(value?: string | number, unit: string = 'px'): string | null {
+ if (!isDef(value)) {
+ return null;
+ }
+ value = String(value); // 将值转换为字符串
+ // 如果值是数字,则在后面添加单位 "px",否则保持原始值
+ return isNumeric(value) ? `${value}${unit}` : value;
+}
+// #endif
+
+
+// #ifdef UNI-APP-X && APP
+function addUnit(value: string|number|null, unit: string = 'px'): string|null {
+ if (!isDef(value)) {
+ return null;
+ }
+ value = `${value}` //value.toString(); // 将值转换为字符串
+
+ // 如果值是数字,则在后面添加单位 "px",否则保持原始值
+ return isNumeric(value) ? `${value}${unit}` : value;
+}
+export {addUnit}
+// #endif
+
+
+// console.log(addUnit(100)); // 输出: "100px"
+// console.log(addUnit("200")); // 输出: "200px"
+// console.log(addUnit("300px")); // 输出: "300px"(已经包含单位)
+// console.log(addUnit()); // 输出: undefined(值为 undefined)
+// console.log(addUnit(null)); // 输出: undefined(值为 null)
\ No newline at end of file
diff --git a/uni_modules/lime-shared/animation/bezier.ts b/uni_modules/lime-shared/animation/bezier.ts
new file mode 100644
index 0000000..ca9190f
--- /dev/null
+++ b/uni_modules/lime-shared/animation/bezier.ts
@@ -0,0 +1,83 @@
+// @ts-nocheck
+export function cubicBezier(p1x : number, p1y : number, p2x : number, p2y : number):(x: number)=> number {
+ const ZERO_LIMIT = 1e-6;
+ // Calculate the polynomial coefficients,
+ // implicit first and last control points are (0,0) and (1,1).
+ const ax = 3 * p1x - 3 * p2x + 1;
+ const bx = 3 * p2x - 6 * p1x;
+ const cx = 3 * p1x;
+
+ const ay = 3 * p1y - 3 * p2y + 1;
+ const by = 3 * p2y - 6 * p1y;
+ const cy = 3 * p1y;
+
+ function sampleCurveDerivativeX(t : number) : number {
+ // `ax t^3 + bx t^2 + cx t` expanded using Horner's rule
+ return (3 * ax * t + 2 * bx) * t + cx;
+ }
+
+ function sampleCurveX(t : number) : number {
+ return ((ax * t + bx) * t + cx) * t;
+ }
+
+ function sampleCurveY(t : number) : number {
+ return ((ay * t + by) * t + cy) * t;
+ }
+
+ // Given an x value, find a parametric value it came from.
+ function solveCurveX(x : number) : number {
+ let t2 = x;
+ let derivative : number;
+ let x2 : number;
+
+ // https://trac.webkit.org/browser/trunk/Source/WebCore/platform/animation
+ // first try a few iterations of Newton's method -- normally very fast.
+ // http://en.wikipedia.org/wikiNewton's_method
+ for (let i = 0; i < 8; i++) {
+ // f(t) - x = 0
+ x2 = sampleCurveX(t2) - x;
+ if (Math.abs(x2) < ZERO_LIMIT) {
+ return t2;
+ }
+ derivative = sampleCurveDerivativeX(t2);
+ // == 0, failure
+ /* istanbul ignore if */
+ if (Math.abs(derivative) < ZERO_LIMIT) {
+ break;
+ }
+ t2 -= x2 / derivative;
+ }
+
+ // Fall back to the bisection method for reliability.
+ // bisection
+ // http://en.wikipedia.org/wiki/Bisection_method
+ let t1 = 1;
+ /* istanbul ignore next */
+ let t0 = 0;
+
+ /* istanbul ignore next */
+ t2 = x;
+ /* istanbul ignore next */
+ while (t1 > t0) {
+ x2 = sampleCurveX(t2) - x;
+ if (Math.abs(x2) < ZERO_LIMIT) {
+ return t2;
+ }
+ if (x2 > 0) {
+ t1 = t2;
+ } else {
+ t0 = t2;
+ }
+ t2 = (t1 + t0) / 2;
+ }
+
+ // Failure
+ return t2;
+ }
+
+ return function (x : number) : number {
+ return sampleCurveY(solveCurveX(x));
+ }
+
+ // return solve;
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/animation/ease.ts b/uni_modules/lime-shared/animation/ease.ts
new file mode 100644
index 0000000..49f066b
--- /dev/null
+++ b/uni_modules/lime-shared/animation/ease.ts
@@ -0,0 +1,4 @@
+// @ts-nocheck
+import {cubicBezier} from './bezier';
+export let ease = cubicBezier(0.25, 0.1, 0.25, 1);
+export let linear = cubicBezier(0,0,1,1);
\ No newline at end of file
diff --git a/uni_modules/lime-shared/animation/index.ts b/uni_modules/lime-shared/animation/index.ts
new file mode 100644
index 0000000..60e56b9
--- /dev/null
+++ b/uni_modules/lime-shared/animation/index.ts
@@ -0,0 +1,10 @@
+// @ts-nocheck
+// #ifdef UNI-APP-X && APP
+export { Timeline, Animation } from './uvue'
+// #endif
+
+
+
+// #ifndef UNI-APP-X && APP
+export { Timeline, Animation } from './vue'
+// #endif
diff --git a/uni_modules/lime-shared/animation/useTransition.ts b/uni_modules/lime-shared/animation/useTransition.ts
new file mode 100644
index 0000000..57120b8
--- /dev/null
+++ b/uni_modules/lime-shared/animation/useTransition.ts
@@ -0,0 +1,110 @@
+// @ts-nocheck
+import type { ComponentPublicInstance, Ref } from 'vue'
+import { ease, linear } from './ease';
+import { Timeline, Animation } from './';
+export type UseTransitionOptions = {
+ duration ?: number
+ immediate ?: boolean
+ context ?: ComponentPublicInstance
+ onChange?: (v: number) => void
+}
+// #ifndef UNI-APP-X && APP
+import { ref, watch } from '@/uni_modules/lime-shared/vue'
+
+export function useTransition(percent : Ref|(() => number), options : UseTransitionOptions) : Ref {
+ const current = ref(0)
+ const { immediate, duration = 300, onChange } = options
+ let tl:Timeline|null = null;
+ let timer = -1
+ const isFunction = typeof percent === 'function'
+ watch(isFunction ? percent : () => percent.value, (v) => {
+ if(tl == null){
+ tl = new Timeline()
+ }
+ tl.start();
+ tl.add(
+ new Animation(
+ current.value,
+ v,
+ duration,
+ 0,
+ ease,
+ nowValue => {
+ current.value = nowValue
+ onChange?.(nowValue)
+ clearTimeout(timer)
+ if(current.value == v){
+ timer = setTimeout(()=>{
+ tl?.pause();
+ tl = null
+ }, duration)
+ }
+ }
+ )
+ );
+ }, { immediate })
+
+ return current
+}
+
+// #endif
+
+// #ifdef UNI-APP-X && APP
+type UseTransitionReturnType = Ref
+export function useTransition(source : any, options : UseTransitionOptions) : UseTransitionReturnType {
+ const outputRef : Ref = ref(0)
+ const immediate = options.immediate ?? false
+ const duration = options.duration ?? 300
+ const context = options.context //as ComponentPublicInstance | null
+ let tl:Timeline|null = null;
+ let timer = -1
+ const watchFunc = (v : number) => {
+ if(tl == null){
+ tl = new Timeline()
+ }
+ tl!.start();
+ tl!.add(
+ new Animation(
+ outputRef.value,
+ v,
+ duration,
+ 0,
+ ease,
+ nowValue => {
+ outputRef.value = nowValue
+ clearTimeout(timer)
+ if(outputRef.value == v){
+ timer = setTimeout(()=>{
+ tl?.pause();
+ tl = null
+ }, duration)
+ }
+ }
+ ),
+ null
+ );
+ }
+
+ if (context != null && typeof source == 'string') {
+ context.$watch(source, watchFunc, { immediate } as WatchOptions)
+ } else if(typeof source == 'function'){
+ watch(source, watchFunc, { immediate })
+ }
+ // #ifdef APP-ANDROID
+ else if(isRef(source) && source instanceof Ref) {
+ watch(source as Ref, watchFunc, { immediate })
+ }
+ // #endif
+ // #ifndef APP-ANDROID
+ else if(isRef(source) && typeof source.value == 'number') {
+ watch(source, watchFunc, { immediate })
+ }
+ // #endif
+
+ const stop = ()=>{
+
+ }
+ return outputRef //as UseTransitionReturnType
+}
+
+// #endif
\ No newline at end of file
diff --git a/uni_modules/lime-shared/animation/uvue.ts b/uni_modules/lime-shared/animation/uvue.ts
new file mode 100644
index 0000000..45a7a23
--- /dev/null
+++ b/uni_modules/lime-shared/animation/uvue.ts
@@ -0,0 +1,119 @@
+// @ts-nocheck
+import { raf, cancelRaf} from '../raf'
+export class Timeline {
+ state : string
+ animations : Set = new Set()
+ delAnimations : Animation[] = []
+ startTimes : Map = new Map()
+ pauseTime : number = 0
+ pauseStart : number = Date.now()
+ tickHandler : number = 0
+ tickHandlers : number[] = []
+ tick : (() => void) | null = null
+ constructor() {
+ this.state = 'Initiated';
+ }
+ start() {
+ if (!(this.state == 'Initiated')) return;
+ this.state = 'Started';
+
+ let startTime = Date.now();
+ this.pauseTime = 0;
+ this.tick = () => {
+ let now = Date.now();
+ this.animations.forEach((animation : Animation) => {
+ let t:number;
+ const ani = this.startTimes.get(animation)
+ if (ani == null) return
+ if (ani < startTime) {
+ t = now - startTime - animation.delay - this.pauseTime;
+ } else {
+ t = now - ani - animation.delay - this.pauseTime;
+ }
+ if (t > animation.duration) {
+ this.delAnimations.push(animation)
+ // 不能在 foreach 里面 对 集合进行删除操作
+ // this.animations.delete(animation);
+ t = animation.duration;
+ }
+ if (t > 0) animation.run(t);
+ })
+ // 不能在 foreach 里面 对 集合进行删除操作
+ while (this.delAnimations.length > 0) {
+ const animation = this.delAnimations.pop();
+ if (animation == null) return
+ this.animations.delete(animation);
+ }
+ // cancelAnimationFrame(this.tickHandler);
+ if (this.state != 'Started') return
+
+ this.tickHandler = raf(()=>{
+ this.tick!()
+ })
+
+ this.tickHandlers.push(this.tickHandler)
+ }
+ if(this.tick != null) {
+ this.tick!()
+ }
+
+ }
+ pause() {
+ if (!(this.state === 'Started')) return;
+ this.state = 'Paused';
+ this.pauseStart = Date.now();
+ cancelRaf(this.tickHandler);
+ // cancelRaf(this.tickHandler);
+ }
+ resume() {
+ if (!(this.state === 'Paused')) return;
+ this.state = 'Started';
+ this.pauseTime += Date.now() - this.pauseStart;
+ this.tick!();
+ }
+ reset() {
+ this.pause();
+ this.state = 'Initiated';
+ this.pauseTime = 0;
+ this.pauseStart = 0;
+ this.animations.clear()
+ this.delAnimations.clear()
+ this.startTimes.clear()
+ this.tickHandler = 0;
+ }
+ add(animation : Animation, startTime ?: number | null) {
+ if (startTime == null) startTime = Date.now();
+ this.animations.add(animation);
+ this.startTimes.set(animation, startTime);
+ }
+}
+
+export class Animation {
+ startValue : number
+ endValue : number
+ duration : number
+ timingFunction : (t : number) => number
+ delay : number
+ template : (t : number) => void
+ constructor(
+ startValue : number,
+ endValue : number,
+ duration : number,
+ delay : number,
+ timingFunction : (t : number) => number,
+ template : (v : number) => void) {
+ this.startValue = startValue;
+ this.endValue = endValue;
+ this.duration = duration;
+ this.timingFunction = timingFunction;
+ this.delay = delay;
+ this.template = template;
+ }
+
+ run(time : number) {
+ let range = this.endValue - this.startValue;
+ let progress = time / this.duration
+ if(progress != 1) progress = this.timingFunction(progress)
+ this.template(this.startValue + range * progress)
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/animation/uvue.uts b/uni_modules/lime-shared/animation/uvue.uts
new file mode 100644
index 0000000..45a7a23
--- /dev/null
+++ b/uni_modules/lime-shared/animation/uvue.uts
@@ -0,0 +1,119 @@
+// @ts-nocheck
+import { raf, cancelRaf} from '../raf'
+export class Timeline {
+ state : string
+ animations : Set = new Set()
+ delAnimations : Animation[] = []
+ startTimes : Map = new Map()
+ pauseTime : number = 0
+ pauseStart : number = Date.now()
+ tickHandler : number = 0
+ tickHandlers : number[] = []
+ tick : (() => void) | null = null
+ constructor() {
+ this.state = 'Initiated';
+ }
+ start() {
+ if (!(this.state == 'Initiated')) return;
+ this.state = 'Started';
+
+ let startTime = Date.now();
+ this.pauseTime = 0;
+ this.tick = () => {
+ let now = Date.now();
+ this.animations.forEach((animation : Animation) => {
+ let t:number;
+ const ani = this.startTimes.get(animation)
+ if (ani == null) return
+ if (ani < startTime) {
+ t = now - startTime - animation.delay - this.pauseTime;
+ } else {
+ t = now - ani - animation.delay - this.pauseTime;
+ }
+ if (t > animation.duration) {
+ this.delAnimations.push(animation)
+ // 不能在 foreach 里面 对 集合进行删除操作
+ // this.animations.delete(animation);
+ t = animation.duration;
+ }
+ if (t > 0) animation.run(t);
+ })
+ // 不能在 foreach 里面 对 集合进行删除操作
+ while (this.delAnimations.length > 0) {
+ const animation = this.delAnimations.pop();
+ if (animation == null) return
+ this.animations.delete(animation);
+ }
+ // cancelAnimationFrame(this.tickHandler);
+ if (this.state != 'Started') return
+
+ this.tickHandler = raf(()=>{
+ this.tick!()
+ })
+
+ this.tickHandlers.push(this.tickHandler)
+ }
+ if(this.tick != null) {
+ this.tick!()
+ }
+
+ }
+ pause() {
+ if (!(this.state === 'Started')) return;
+ this.state = 'Paused';
+ this.pauseStart = Date.now();
+ cancelRaf(this.tickHandler);
+ // cancelRaf(this.tickHandler);
+ }
+ resume() {
+ if (!(this.state === 'Paused')) return;
+ this.state = 'Started';
+ this.pauseTime += Date.now() - this.pauseStart;
+ this.tick!();
+ }
+ reset() {
+ this.pause();
+ this.state = 'Initiated';
+ this.pauseTime = 0;
+ this.pauseStart = 0;
+ this.animations.clear()
+ this.delAnimations.clear()
+ this.startTimes.clear()
+ this.tickHandler = 0;
+ }
+ add(animation : Animation, startTime ?: number | null) {
+ if (startTime == null) startTime = Date.now();
+ this.animations.add(animation);
+ this.startTimes.set(animation, startTime);
+ }
+}
+
+export class Animation {
+ startValue : number
+ endValue : number
+ duration : number
+ timingFunction : (t : number) => number
+ delay : number
+ template : (t : number) => void
+ constructor(
+ startValue : number,
+ endValue : number,
+ duration : number,
+ delay : number,
+ timingFunction : (t : number) => number,
+ template : (v : number) => void) {
+ this.startValue = startValue;
+ this.endValue = endValue;
+ this.duration = duration;
+ this.timingFunction = timingFunction;
+ this.delay = delay;
+ this.template = template;
+ }
+
+ run(time : number) {
+ let range = this.endValue - this.startValue;
+ let progress = time / this.duration
+ if(progress != 1) progress = this.timingFunction(progress)
+ this.template(this.startValue + range * progress)
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/animation/vue.ts b/uni_modules/lime-shared/animation/vue.ts
new file mode 100644
index 0000000..30f89e5
--- /dev/null
+++ b/uni_modules/lime-shared/animation/vue.ts
@@ -0,0 +1,123 @@
+// @ts-nocheck
+const TICK = Symbol('tick');
+const TICK_HANDLER = Symbol('tick-handler');
+const ANIMATIONS = Symbol('animations');
+const START_TIMES = Symbol('start-times');
+const PAUSE_START = Symbol('pause-start');
+const PAUSE_TIME = Symbol('pause-time');
+const _raf = typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame : function(cb: Function) {return setTimeout(cb, 1000/60)}
+const _caf = typeof cancelAnimationFrame !== 'undefined' ? cancelAnimationFrame: function(id: any) {clearTimeout(id)}
+
+// const TICK = 'tick';
+// const TICK_HANDLER = 'tick-handler';
+// const ANIMATIONS = 'animations';
+// const START_TIMES = 'start-times';
+// const PAUSE_START = 'pause-start';
+// const PAUSE_TIME = 'pause-time';
+// const _raf = function(callback):number|null {return setTimeout(callback, 1000/60)}
+// const _caf = function(id: number):void {clearTimeout(id)}
+
+export class Timeline {
+ state: string
+ constructor() {
+ this.state = 'Initiated';
+ this[ANIMATIONS] = new Set();
+ this[START_TIMES] = new Map();
+ }
+ start() {
+ if (!(this.state === 'Initiated')) return;
+ this.state = 'Started';
+
+ let startTime = Date.now();
+ this[PAUSE_TIME] = 0;
+ this[TICK] = () => {
+ let now = Date.now();
+ this[ANIMATIONS].forEach((animation) => {
+ let t: number;
+ if (this[START_TIMES].get(animation) < startTime) {
+ t = now - startTime - animation.delay - this[PAUSE_TIME];
+ } else {
+ t = now - this[START_TIMES].get(animation) - animation.delay - this[PAUSE_TIME];
+ }
+
+ if (t > animation.duration) {
+ this[ANIMATIONS].delete(animation);
+ t = animation.duration;
+ }
+ if (t > 0) animation.run(t);
+ })
+ // for (let animation of this[ANIMATIONS]) {
+ // let t: number;
+ // console.log('animation', animation)
+ // if (this[START_TIMES].get(animation) < startTime) {
+ // t = now - startTime - animation.delay - this[PAUSE_TIME];
+ // } else {
+ // t = now - this[START_TIMES].get(animation) - animation.delay - this[PAUSE_TIME];
+ // }
+
+ // if (t > animation.duration) {
+ // this[ANIMATIONS].delete(animation);
+ // t = animation.duration;
+ // }
+ // if (t > 0) animation.run(t);
+ // }
+ this[TICK_HANDLER] = _raf(this[TICK]);
+ };
+ this[TICK]();
+ }
+ pause() {
+ if (!(this.state === 'Started')) return;
+ this.state = 'Paused';
+
+ this[PAUSE_START] = Date.now();
+ _caf(this[TICK_HANDLER]);
+ }
+ resume() {
+ if (!(this.state === 'Paused')) return;
+ this.state = 'Started';
+
+ this[PAUSE_TIME] += Date.now() - this[PAUSE_START];
+ this[TICK]();
+ }
+ reset() {
+ this.pause();
+ this.state = 'Initiated';
+ this[PAUSE_TIME] = 0;
+ this[PAUSE_START] = 0;
+ this[ANIMATIONS] = new Set();
+ this[START_TIMES] = new Map();
+ this[TICK_HANDLER] = null;
+ }
+ add(animation: any, startTime?: number) {
+ if (arguments.length < 2) startTime = Date.now();
+ this[ANIMATIONS].add(animation);
+ this[START_TIMES].set(animation, startTime);
+ }
+}
+
+export class Animation {
+ startValue: number
+ endValue: number
+ duration: number
+ timingFunction: (t: number) => number
+ delay: number
+ template: (t: number) => void
+ constructor(startValue: number, endValue: number, duration: number, delay: number, timingFunction: (t: number) => number, template: (v: number) => void) {
+ timingFunction = timingFunction || (v => v);
+ template = template || (v => v);
+
+ this.startValue = startValue;
+ this.endValue = endValue;
+ this.duration = duration;
+ this.timingFunction = timingFunction;
+ this.delay = delay;
+ this.template = template;
+ }
+
+ run(time: number) {
+ let range = this.endValue - this.startValue;
+ let progress = time / this.duration
+ if(progress != 1) progress = this.timingFunction(progress)
+ this.template(this.startValue + range * progress)
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/areaData/city-china.json b/uni_modules/lime-shared/areaData/city-china.json
new file mode 100644
index 0000000..d9872a0
--- /dev/null
+++ b/uni_modules/lime-shared/areaData/city-china.json
@@ -0,0 +1,3888 @@
+{
+ "province_list": {
+ "110000": "北京市",
+ "120000": "天津市",
+ "130000": "河北省",
+ "140000": "山西省",
+ "150000": "内蒙古自治区",
+ "210000": "辽宁省",
+ "220000": "吉林省",
+ "230000": "黑龙江省",
+ "310000": "上海市",
+ "320000": "江苏省",
+ "330000": "浙江省",
+ "340000": "安徽省",
+ "350000": "福建省",
+ "360000": "江西省",
+ "370000": "山东省",
+ "410000": "河南省",
+ "420000": "湖北省",
+ "430000": "湖南省",
+ "440000": "广东省",
+ "450000": "广西壮族自治区",
+ "460000": "海南省",
+ "500000": "重庆市",
+ "510000": "四川省",
+ "520000": "贵州省",
+ "530000": "云南省",
+ "540000": "西藏自治区",
+ "610000": "陕西省",
+ "620000": "甘肃省",
+ "630000": "青海省",
+ "640000": "宁夏回族自治区",
+ "650000": "新疆维吾尔自治区",
+ "710000": "台湾省",
+ "810000": "香港特别行政区",
+ "820000": "澳门特别行政区"
+ },
+ "city_list": {
+ "110100": "北京市",
+ "120100": "天津市",
+ "130100": "石家庄市",
+ "130200": "唐山市",
+ "130300": "秦皇岛市",
+ "130400": "邯郸市",
+ "130500": "邢台市",
+ "130600": "保定市",
+ "130700": "张家口市",
+ "130800": "承德市",
+ "130900": "沧州市",
+ "131000": "廊坊市",
+ "131100": "衡水市",
+ "140100": "太原市",
+ "140200": "大同市",
+ "140300": "阳泉市",
+ "140400": "长治市",
+ "140500": "晋城市",
+ "140600": "朔州市",
+ "140700": "晋中市",
+ "140800": "运城市",
+ "140900": "忻州市",
+ "141000": "临汾市",
+ "141100": "吕梁市",
+ "150100": "呼和浩特市",
+ "150200": "包头市",
+ "150300": "乌海市",
+ "150400": "赤峰市",
+ "150500": "通辽市",
+ "150600": "鄂尔多斯市",
+ "150700": "呼伦贝尔市",
+ "150800": "巴彦淖尔市",
+ "150900": "乌兰察布市",
+ "152200": "兴安盟",
+ "152500": "锡林郭勒盟",
+ "152900": "阿拉善盟",
+ "210100": "沈阳市",
+ "210200": "大连市",
+ "210300": "鞍山市",
+ "210400": "抚顺市",
+ "210500": "本溪市",
+ "210600": "丹东市",
+ "210700": "锦州市",
+ "210800": "营口市",
+ "210900": "阜新市",
+ "211000": "辽阳市",
+ "211100": "盘锦市",
+ "211200": "铁岭市",
+ "211300": "朝阳市",
+ "211400": "葫芦岛市",
+ "220100": "长春市",
+ "220200": "吉林市",
+ "220300": "四平市",
+ "220400": "辽源市",
+ "220500": "通化市",
+ "220600": "白山市",
+ "220700": "松原市",
+ "220800": "白城市",
+ "222400": "延边朝鲜族自治州",
+ "230100": "哈尔滨市",
+ "230200": "齐齐哈尔市",
+ "230300": "鸡西市",
+ "230400": "鹤岗市",
+ "230500": "双鸭山市",
+ "230600": "大庆市",
+ "230700": "伊春市",
+ "230800": "佳木斯市",
+ "230900": "七台河市",
+ "231000": "牡丹江市",
+ "231100": "黑河市",
+ "231200": "绥化市",
+ "232700": "大兴安岭地区",
+ "310100": "上海市",
+ "320100": "南京市",
+ "320200": "无锡市",
+ "320300": "徐州市",
+ "320400": "常州市",
+ "320500": "苏州市",
+ "320600": "南通市",
+ "320700": "连云港市",
+ "320800": "淮安市",
+ "320900": "盐城市",
+ "321000": "扬州市",
+ "321100": "镇江市",
+ "321200": "泰州市",
+ "321300": "宿迁市",
+ "330100": "杭州市",
+ "330200": "宁波市",
+ "330300": "温州市",
+ "330400": "嘉兴市",
+ "330500": "湖州市",
+ "330600": "绍兴市",
+ "330700": "金华市",
+ "330800": "衢州市",
+ "330900": "舟山市",
+ "331000": "台州市",
+ "331100": "丽水市",
+ "340100": "合肥市",
+ "340200": "芜湖市",
+ "340300": "蚌埠市",
+ "340400": "淮南市",
+ "340500": "马鞍山市",
+ "340600": "淮北市",
+ "340700": "铜陵市",
+ "340800": "安庆市",
+ "341000": "黄山市",
+ "341100": "滁州市",
+ "341200": "阜阳市",
+ "341300": "宿州市",
+ "341500": "六安市",
+ "341600": "亳州市",
+ "341700": "池州市",
+ "341800": "宣城市",
+ "350100": "福州市",
+ "350200": "厦门市",
+ "350300": "莆田市",
+ "350400": "三明市",
+ "350500": "泉州市",
+ "350600": "漳州市",
+ "350700": "南平市",
+ "350800": "龙岩市",
+ "350900": "宁德市",
+ "360100": "南昌市",
+ "360200": "景德镇市",
+ "360300": "萍乡市",
+ "360400": "九江市",
+ "360500": "新余市",
+ "360600": "鹰潭市",
+ "360700": "赣州市",
+ "360800": "吉安市",
+ "360900": "宜春市",
+ "361000": "抚州市",
+ "361100": "上饶市",
+ "370100": "济南市",
+ "370200": "青岛市",
+ "370300": "淄博市",
+ "370400": "枣庄市",
+ "370500": "东营市",
+ "370600": "烟台市",
+ "370700": "潍坊市",
+ "370800": "济宁市",
+ "370900": "泰安市",
+ "371000": "威海市",
+ "371100": "日照市",
+ "371300": "临沂市",
+ "371400": "德州市",
+ "371500": "聊城市",
+ "371600": "滨州市",
+ "371700": "菏泽市",
+ "410100": "郑州市",
+ "410200": "开封市",
+ "410300": "洛阳市",
+ "410400": "平顶山市",
+ "410500": "安阳市",
+ "410600": "鹤壁市",
+ "410700": "新乡市",
+ "410800": "焦作市",
+ "410900": "濮阳市",
+ "411000": "许昌市",
+ "411100": "漯河市",
+ "411200": "三门峡市",
+ "411300": "南阳市",
+ "411400": "商丘市",
+ "411500": "信阳市",
+ "411600": "周口市",
+ "411700": "驻马店市",
+ "419000": "省直辖县",
+ "420100": "武汉市",
+ "420200": "黄石市",
+ "420300": "十堰市",
+ "420500": "宜昌市",
+ "420600": "襄阳市",
+ "420700": "鄂州市",
+ "420800": "荆门市",
+ "420900": "孝感市",
+ "421000": "荆州市",
+ "421100": "黄冈市",
+ "421200": "咸宁市",
+ "421300": "随州市",
+ "422800": "恩施土家族苗族自治州",
+ "429000": "省直辖县",
+ "430100": "长沙市",
+ "430200": "株洲市",
+ "430300": "湘潭市",
+ "430400": "衡阳市",
+ "430500": "邵阳市",
+ "430600": "岳阳市",
+ "430700": "常德市",
+ "430800": "张家界市",
+ "430900": "益阳市",
+ "431000": "郴州市",
+ "431100": "永州市",
+ "431200": "怀化市",
+ "431300": "娄底市",
+ "433100": "湘西土家族苗族自治州",
+ "440100": "广州市",
+ "440200": "韶关市",
+ "440300": "深圳市",
+ "440400": "珠海市",
+ "440500": "汕头市",
+ "440600": "佛山市",
+ "440700": "江门市",
+ "440800": "湛江市",
+ "440900": "茂名市",
+ "441200": "肇庆市",
+ "441300": "惠州市",
+ "441400": "梅州市",
+ "441500": "汕尾市",
+ "441600": "河源市",
+ "441700": "阳江市",
+ "441800": "清远市",
+ "441900": "东莞市",
+ "442000": "中山市",
+ "445100": "潮州市",
+ "445200": "揭阳市",
+ "445300": "云浮市",
+ "450100": "南宁市",
+ "450200": "柳州市",
+ "450300": "桂林市",
+ "450400": "梧州市",
+ "450500": "北海市",
+ "450600": "防城港市",
+ "450700": "钦州市",
+ "450800": "贵港市",
+ "450900": "玉林市",
+ "451000": "百色市",
+ "451100": "贺州市",
+ "451200": "河池市",
+ "451300": "来宾市",
+ "451400": "崇左市",
+ "460100": "海口市",
+ "460200": "三亚市",
+ "460300": "三沙市",
+ "460400": "儋州市",
+ "469000": "省直辖县",
+ "500100": "重庆市",
+ "500200": "县",
+ "510100": "成都市",
+ "510300": "自贡市",
+ "510400": "攀枝花市",
+ "510500": "泸州市",
+ "510600": "德阳市",
+ "510700": "绵阳市",
+ "510800": "广元市",
+ "510900": "遂宁市",
+ "511000": "内江市",
+ "511100": "乐山市",
+ "511300": "南充市",
+ "511400": "眉山市",
+ "511500": "宜宾市",
+ "511600": "广安市",
+ "511700": "达州市",
+ "511800": "雅安市",
+ "511900": "巴中市",
+ "512000": "资阳市",
+ "513200": "阿坝藏族羌族自治州",
+ "513300": "甘孜藏族自治州",
+ "513400": "凉山彝族自治州",
+ "520100": "贵阳市",
+ "520200": "六盘水市",
+ "520300": "遵义市",
+ "520400": "安顺市",
+ "520500": "毕节市",
+ "520600": "铜仁市",
+ "522300": "黔西南布依族苗族自治州",
+ "522600": "黔东南苗族侗族自治州",
+ "522700": "黔南布依族苗族自治州",
+ "530100": "昆明市",
+ "530300": "曲靖市",
+ "530400": "玉溪市",
+ "530500": "保山市",
+ "530600": "昭通市",
+ "530700": "丽江市",
+ "530800": "普洱市",
+ "530900": "临沧市",
+ "532300": "楚雄彝族自治州",
+ "532500": "红河哈尼族彝族自治州",
+ "532600": "文山壮族苗族自治州",
+ "532800": "西双版纳傣族自治州",
+ "532900": "大理白族自治州",
+ "533100": "德宏傣族景颇族自治州",
+ "533300": "怒江傈僳族自治州",
+ "533400": "迪庆藏族自治州",
+ "540100": "拉萨市",
+ "540200": "日喀则市",
+ "540300": "昌都市",
+ "540400": "林芝市",
+ "540500": "山南市",
+ "540600": "那曲市",
+ "542500": "阿里地区",
+ "610100": "西安市",
+ "610200": "铜川市",
+ "610300": "宝鸡市",
+ "610400": "咸阳市",
+ "610500": "渭南市",
+ "610600": "延安市",
+ "610700": "汉中市",
+ "610800": "榆林市",
+ "610900": "安康市",
+ "611000": "商洛市",
+ "620100": "兰州市",
+ "620200": "嘉峪关市",
+ "620300": "金昌市",
+ "620400": "白银市",
+ "620500": "天水市",
+ "620600": "武威市",
+ "620700": "张掖市",
+ "620800": "平凉市",
+ "620900": "酒泉市",
+ "621000": "庆阳市",
+ "621100": "定西市",
+ "621200": "陇南市",
+ "622900": "临夏回族自治州",
+ "623000": "甘南藏族自治州",
+ "630100": "西宁市",
+ "630200": "海东市",
+ "632200": "海北藏族自治州",
+ "632300": "黄南藏族自治州",
+ "632500": "海南藏族自治州",
+ "632600": "果洛藏族自治州",
+ "632700": "玉树藏族自治州",
+ "632800": "海西蒙古族藏族自治州",
+ "640100": "银川市",
+ "640200": "石嘴山市",
+ "640300": "吴忠市",
+ "640400": "固原市",
+ "640500": "中卫市",
+ "650100": "乌鲁木齐市",
+ "650200": "克拉玛依市",
+ "650400": "吐鲁番市",
+ "650500": "哈密市",
+ "652300": "昌吉回族自治州",
+ "652700": "博尔塔拉蒙古自治州",
+ "652800": "巴音郭楞蒙古自治州",
+ "652900": "阿克苏地区",
+ "653000": "克孜勒苏柯尔克孜自治州",
+ "653100": "喀什地区",
+ "653200": "和田地区",
+ "654000": "伊犁哈萨克自治州",
+ "654200": "塔城地区",
+ "654300": "阿勒泰地区",
+ "659000": "自治区直辖县级行政区划",
+ "710100": "台北市",
+ "710200": "高雄市",
+ "710300": "台南市",
+ "710400": "台中市",
+ "710500": "金门县",
+ "710600": "南投县",
+ "710700": "基隆市",
+ "710800": "新竹市",
+ "710900": "嘉义市",
+ "711100": "新北市",
+ "711200": "宜兰县",
+ "711300": "新竹县",
+ "711400": "桃园市",
+ "711500": "苗栗县",
+ "711700": "彰化县",
+ "711900": "嘉义县",
+ "712100": "云林县",
+ "712400": "屏东县",
+ "712500": "台东县",
+ "712600": "花莲县",
+ "712700": "澎湖县",
+ "712800": "连江县",
+ "810100": "香港岛",
+ "810200": "九龙",
+ "810300": "新界",
+ "820100": "澳门半岛",
+ "820200": "离岛"
+ },
+ "county_list": {
+ "110101": "东城区",
+ "110102": "西城区",
+ "110105": "朝阳区",
+ "110106": "丰台区",
+ "110107": "石景山区",
+ "110108": "海淀区",
+ "110109": "门头沟区",
+ "110111": "房山区",
+ "110112": "通州区",
+ "110113": "顺义区",
+ "110114": "昌平区",
+ "110115": "大兴区",
+ "110116": "怀柔区",
+ "110117": "平谷区",
+ "110118": "密云区",
+ "110119": "延庆区",
+ "120101": "和平区",
+ "120102": "河东区",
+ "120103": "河西区",
+ "120104": "南开区",
+ "120105": "河北区",
+ "120106": "红桥区",
+ "120110": "东丽区",
+ "120111": "西青区",
+ "120112": "津南区",
+ "120113": "北辰区",
+ "120114": "武清区",
+ "120115": "宝坻区",
+ "120116": "滨海新区",
+ "120117": "宁河区",
+ "120118": "静海区",
+ "120119": "蓟州区",
+ "130102": "长安区",
+ "130104": "桥西区",
+ "130105": "新华区",
+ "130107": "井陉矿区",
+ "130108": "裕华区",
+ "130109": "藁城区",
+ "130110": "鹿泉区",
+ "130111": "栾城区",
+ "130121": "井陉县",
+ "130123": "正定县",
+ "130125": "行唐县",
+ "130126": "灵寿县",
+ "130127": "高邑县",
+ "130128": "深泽县",
+ "130129": "赞皇县",
+ "130130": "无极县",
+ "130131": "平山县",
+ "130132": "元氏县",
+ "130133": "赵县",
+ "130171": "石家庄高新技术产业开发区",
+ "130172": "石家庄循环化工园区",
+ "130181": "辛集市",
+ "130183": "晋州市",
+ "130184": "新乐市",
+ "130202": "路南区",
+ "130203": "路北区",
+ "130204": "古冶区",
+ "130205": "开平区",
+ "130207": "丰南区",
+ "130208": "丰润区",
+ "130209": "曹妃甸区",
+ "130224": "滦南县",
+ "130225": "乐亭县",
+ "130227": "迁西县",
+ "130229": "玉田县",
+ "130273": "唐山高新技术产业开发区",
+ "130274": "河北唐山海港经济开发区",
+ "130281": "遵化市",
+ "130283": "迁安市",
+ "130284": "滦州市",
+ "130302": "海港区",
+ "130303": "山海关区",
+ "130304": "北戴河区",
+ "130306": "抚宁区",
+ "130321": "青龙满族自治县",
+ "130322": "昌黎县",
+ "130324": "卢龙县",
+ "130371": "秦皇岛市经济技术开发区",
+ "130372": "北戴河新区",
+ "130390": "经济技术开发区",
+ "130402": "邯山区",
+ "130403": "丛台区",
+ "130404": "复兴区",
+ "130406": "峰峰矿区",
+ "130407": "肥乡区",
+ "130408": "永年区",
+ "130423": "临漳县",
+ "130424": "成安县",
+ "130425": "大名县",
+ "130426": "涉县",
+ "130427": "磁县",
+ "130430": "邱县",
+ "130431": "鸡泽县",
+ "130432": "广平县",
+ "130433": "馆陶县",
+ "130434": "魏县",
+ "130435": "曲周县",
+ "130471": "邯郸经济技术开发区",
+ "130473": "邯郸冀南新区",
+ "130481": "武安市",
+ "130502": "襄都区",
+ "130503": "信都区",
+ "130505": "任泽区",
+ "130506": "南和区",
+ "130522": "临城县",
+ "130523": "内丘县",
+ "130524": "柏乡县",
+ "130525": "隆尧县",
+ "130528": "宁晋县",
+ "130529": "巨鹿县",
+ "130530": "新河县",
+ "130531": "广宗县",
+ "130532": "平乡县",
+ "130533": "威县",
+ "130534": "清河县",
+ "130535": "临西县",
+ "130571": "河北邢台经济开发区",
+ "130581": "南宫市",
+ "130582": "沙河市",
+ "130602": "竞秀区",
+ "130606": "莲池区",
+ "130607": "满城区",
+ "130608": "清苑区",
+ "130609": "徐水区",
+ "130623": "涞水县",
+ "130624": "阜平县",
+ "130626": "定兴县",
+ "130627": "唐县",
+ "130628": "高阳县",
+ "130629": "容城县",
+ "130630": "涞源县",
+ "130631": "望都县",
+ "130632": "安新县",
+ "130633": "易县",
+ "130634": "曲阳县",
+ "130635": "蠡县",
+ "130636": "顺平县",
+ "130637": "博野县",
+ "130638": "雄县",
+ "130671": "保定高新技术产业开发区",
+ "130672": "保定白沟新城",
+ "130681": "涿州市",
+ "130682": "定州市",
+ "130683": "安国市",
+ "130684": "高碑店市",
+ "130702": "桥东区",
+ "130703": "桥西区",
+ "130705": "宣化区",
+ "130706": "下花园区",
+ "130708": "万全区",
+ "130709": "崇礼区",
+ "130722": "张北县",
+ "130723": "康保县",
+ "130724": "沽源县",
+ "130725": "尚义县",
+ "130726": "蔚县",
+ "130727": "阳原县",
+ "130728": "怀安县",
+ "130730": "怀来县",
+ "130731": "涿鹿县",
+ "130732": "赤城县",
+ "130772": "张家口市察北管理区",
+ "130802": "双桥区",
+ "130803": "双滦区",
+ "130804": "鹰手营子矿区",
+ "130821": "承德县",
+ "130822": "兴隆县",
+ "130824": "滦平县",
+ "130825": "隆化县",
+ "130826": "丰宁满族自治县",
+ "130827": "宽城满族自治县",
+ "130828": "围场满族蒙古族自治县",
+ "130871": "承德高新技术产业开发区",
+ "130881": "平泉市",
+ "130902": "新华区",
+ "130903": "运河区",
+ "130921": "沧县",
+ "130922": "青县",
+ "130923": "东光县",
+ "130924": "海兴县",
+ "130925": "盐山县",
+ "130926": "肃宁县",
+ "130927": "南皮县",
+ "130928": "吴桥县",
+ "130929": "献县",
+ "130930": "孟村回族自治县",
+ "130971": "河北沧州经济开发区",
+ "130972": "沧州高新技术产业开发区",
+ "130973": "沧州渤海新区",
+ "130981": "泊头市",
+ "130982": "任丘市",
+ "130983": "黄骅市",
+ "130984": "河间市",
+ "131002": "安次区",
+ "131003": "广阳区",
+ "131022": "固安县",
+ "131023": "永清县",
+ "131024": "香河县",
+ "131025": "大城县",
+ "131026": "文安县",
+ "131028": "大厂回族自治县",
+ "131071": "廊坊经济技术开发区",
+ "131081": "霸州市",
+ "131082": "三河市",
+ "131090": "开发区",
+ "131102": "桃城区",
+ "131103": "冀州区",
+ "131121": "枣强县",
+ "131122": "武邑县",
+ "131123": "武强县",
+ "131124": "饶阳县",
+ "131125": "安平县",
+ "131126": "故城县",
+ "131127": "景县",
+ "131128": "阜城县",
+ "131171": "河北衡水经济开发区",
+ "131172": "衡水滨湖新区",
+ "131182": "深州市",
+ "140105": "小店区",
+ "140106": "迎泽区",
+ "140107": "杏花岭区",
+ "140108": "尖草坪区",
+ "140109": "万柏林区",
+ "140110": "晋源区",
+ "140121": "清徐县",
+ "140122": "阳曲县",
+ "140123": "娄烦县",
+ "140181": "古交市",
+ "140212": "新荣区",
+ "140213": "平城区",
+ "140214": "云冈区",
+ "140215": "云州区",
+ "140221": "阳高县",
+ "140222": "天镇县",
+ "140223": "广灵县",
+ "140224": "灵丘县",
+ "140225": "浑源县",
+ "140226": "左云县",
+ "140271": "山西大同经济开发区",
+ "140302": "城区",
+ "140303": "矿区",
+ "140311": "郊区",
+ "140321": "平定县",
+ "140322": "盂县",
+ "140403": "潞州区",
+ "140404": "上党区",
+ "140405": "屯留区",
+ "140406": "潞城区",
+ "140423": "襄垣县",
+ "140425": "平顺县",
+ "140426": "黎城县",
+ "140427": "壶关县",
+ "140428": "长子县",
+ "140429": "武乡县",
+ "140430": "沁县",
+ "140431": "沁源县",
+ "140471": "山西长治高新技术产业园区",
+ "140502": "城区",
+ "140521": "沁水县",
+ "140522": "阳城县",
+ "140524": "陵川县",
+ "140525": "泽州县",
+ "140581": "高平市",
+ "140602": "朔城区",
+ "140603": "平鲁区",
+ "140621": "山阴县",
+ "140622": "应县",
+ "140623": "右玉县",
+ "140671": "山西朔州经济开发区",
+ "140681": "怀仁市",
+ "140702": "榆次区",
+ "140703": "太谷区",
+ "140721": "榆社县",
+ "140722": "左权县",
+ "140723": "和顺县",
+ "140724": "昔阳县",
+ "140725": "寿阳县",
+ "140727": "祁县",
+ "140728": "平遥县",
+ "140729": "灵石县",
+ "140781": "介休市",
+ "140802": "盐湖区",
+ "140821": "临猗县",
+ "140822": "万荣县",
+ "140823": "闻喜县",
+ "140824": "稷山县",
+ "140825": "新绛县",
+ "140826": "绛县",
+ "140827": "垣曲县",
+ "140828": "夏县",
+ "140829": "平陆县",
+ "140830": "芮城县",
+ "140881": "永济市",
+ "140882": "河津市",
+ "140902": "忻府区",
+ "140921": "定襄县",
+ "140922": "五台县",
+ "140923": "代县",
+ "140924": "繁峙县",
+ "140925": "宁武县",
+ "140926": "静乐县",
+ "140927": "神池县",
+ "140928": "五寨县",
+ "140929": "岢岚县",
+ "140930": "河曲县",
+ "140931": "保德县",
+ "140932": "偏关县",
+ "140971": "五台山风景名胜区",
+ "140981": "原平市",
+ "141002": "尧都区",
+ "141021": "曲沃县",
+ "141022": "翼城县",
+ "141023": "襄汾县",
+ "141024": "洪洞县",
+ "141025": "古县",
+ "141026": "安泽县",
+ "141027": "浮山县",
+ "141028": "吉县",
+ "141029": "乡宁县",
+ "141030": "大宁县",
+ "141031": "隰县",
+ "141032": "永和县",
+ "141033": "蒲县",
+ "141034": "汾西县",
+ "141081": "侯马市",
+ "141082": "霍州市",
+ "141102": "离石区",
+ "141121": "文水县",
+ "141122": "交城县",
+ "141123": "兴县",
+ "141124": "临县",
+ "141125": "柳林县",
+ "141126": "石楼县",
+ "141127": "岚县",
+ "141128": "方山县",
+ "141129": "中阳县",
+ "141130": "交口县",
+ "141181": "孝义市",
+ "141182": "汾阳市",
+ "150102": "新城区",
+ "150103": "回民区",
+ "150104": "玉泉区",
+ "150105": "赛罕区",
+ "150121": "土默特左旗",
+ "150122": "托克托县",
+ "150123": "和林格尔县",
+ "150124": "清水河县",
+ "150125": "武川县",
+ "150172": "呼和浩特经济技术开发区",
+ "150202": "东河区",
+ "150203": "昆都仑区",
+ "150204": "青山区",
+ "150205": "石拐区",
+ "150206": "白云鄂博矿区",
+ "150207": "九原区",
+ "150221": "土默特右旗",
+ "150222": "固阳县",
+ "150223": "达尔罕茂明安联合旗",
+ "150271": "包头稀土高新技术产业开发区",
+ "150302": "海勃湾区",
+ "150303": "海南区",
+ "150304": "乌达区",
+ "150402": "红山区",
+ "150403": "元宝山区",
+ "150404": "松山区",
+ "150421": "阿鲁科尔沁旗",
+ "150422": "巴林左旗",
+ "150423": "巴林右旗",
+ "150424": "林西县",
+ "150425": "克什克腾旗",
+ "150426": "翁牛特旗",
+ "150428": "喀喇沁旗",
+ "150429": "宁城县",
+ "150430": "敖汉旗",
+ "150502": "科尔沁区",
+ "150521": "科尔沁左翼中旗",
+ "150522": "科尔沁左翼后旗",
+ "150523": "开鲁县",
+ "150524": "库伦旗",
+ "150525": "奈曼旗",
+ "150526": "扎鲁特旗",
+ "150571": "通辽经济技术开发区",
+ "150581": "霍林郭勒市",
+ "150602": "东胜区",
+ "150603": "康巴什区",
+ "150621": "达拉特旗",
+ "150622": "准格尔旗",
+ "150623": "鄂托克前旗",
+ "150624": "鄂托克旗",
+ "150625": "杭锦旗",
+ "150626": "乌审旗",
+ "150627": "伊金霍洛旗",
+ "150702": "海拉尔区",
+ "150703": "扎赉诺尔区",
+ "150721": "阿荣旗",
+ "150722": "莫力达瓦达斡尔族自治旗",
+ "150723": "鄂伦春自治旗",
+ "150724": "鄂温克族自治旗",
+ "150725": "陈巴尔虎旗",
+ "150726": "新巴尔虎左旗",
+ "150727": "新巴尔虎右旗",
+ "150781": "满洲里市",
+ "150782": "牙克石市",
+ "150783": "扎兰屯市",
+ "150784": "额尔古纳市",
+ "150785": "根河市",
+ "150802": "临河区",
+ "150821": "五原县",
+ "150822": "磴口县",
+ "150823": "乌拉特前旗",
+ "150824": "乌拉特中旗",
+ "150825": "乌拉特后旗",
+ "150826": "杭锦后旗",
+ "150902": "集宁区",
+ "150921": "卓资县",
+ "150922": "化德县",
+ "150923": "商都县",
+ "150924": "兴和县",
+ "150925": "凉城县",
+ "150926": "察哈尔右翼前旗",
+ "150927": "察哈尔右翼中旗",
+ "150928": "察哈尔右翼后旗",
+ "150929": "四子王旗",
+ "150981": "丰镇市",
+ "152201": "乌兰浩特市",
+ "152202": "阿尔山市",
+ "152221": "科尔沁右翼前旗",
+ "152222": "科尔沁右翼中旗",
+ "152223": "扎赉特旗",
+ "152224": "突泉县",
+ "152501": "二连浩特市",
+ "152502": "锡林浩特市",
+ "152522": "阿巴嘎旗",
+ "152523": "苏尼特左旗",
+ "152524": "苏尼特右旗",
+ "152525": "东乌珠穆沁旗",
+ "152526": "西乌珠穆沁旗",
+ "152527": "太仆寺旗",
+ "152528": "镶黄旗",
+ "152529": "正镶白旗",
+ "152530": "正蓝旗",
+ "152531": "多伦县",
+ "152571": "乌拉盖管委会",
+ "152921": "阿拉善左旗",
+ "152922": "阿拉善右旗",
+ "152923": "额济纳旗",
+ "152971": "内蒙古阿拉善经济开发区",
+ "210102": "和平区",
+ "210103": "沈河区",
+ "210104": "大东区",
+ "210105": "皇姑区",
+ "210106": "铁西区",
+ "210111": "苏家屯区",
+ "210112": "浑南区",
+ "210113": "沈北新区",
+ "210114": "于洪区",
+ "210115": "辽中区",
+ "210123": "康平县",
+ "210124": "法库县",
+ "210181": "新民市",
+ "210190": "经济技术开发区",
+ "210202": "中山区",
+ "210203": "西岗区",
+ "210204": "沙河口区",
+ "210211": "甘井子区",
+ "210212": "旅顺口区",
+ "210213": "金州区",
+ "210214": "普兰店区",
+ "210224": "长海县",
+ "210281": "瓦房店市",
+ "210283": "庄河市",
+ "210302": "铁东区",
+ "210303": "铁西区",
+ "210304": "立山区",
+ "210311": "千山区",
+ "210321": "台安县",
+ "210323": "岫岩满族自治县",
+ "210381": "海城市",
+ "210390": "高新区",
+ "210402": "新抚区",
+ "210403": "东洲区",
+ "210404": "望花区",
+ "210411": "顺城区",
+ "210421": "抚顺县",
+ "210422": "新宾满族自治县",
+ "210423": "清原满族自治县",
+ "210502": "平山区",
+ "210503": "溪湖区",
+ "210504": "明山区",
+ "210505": "南芬区",
+ "210521": "本溪满族自治县",
+ "210522": "桓仁满族自治县",
+ "210602": "元宝区",
+ "210603": "振兴区",
+ "210604": "振安区",
+ "210624": "宽甸满族自治县",
+ "210681": "东港市",
+ "210682": "凤城市",
+ "210702": "古塔区",
+ "210703": "凌河区",
+ "210711": "太和区",
+ "210726": "黑山县",
+ "210727": "义县",
+ "210781": "凌海市",
+ "210782": "北镇市",
+ "210793": "经济技术开发区",
+ "210802": "站前区",
+ "210803": "西市区",
+ "210804": "鲅鱼圈区",
+ "210811": "老边区",
+ "210881": "盖州市",
+ "210882": "大石桥市",
+ "210902": "海州区",
+ "210903": "新邱区",
+ "210904": "太平区",
+ "210905": "清河门区",
+ "210911": "细河区",
+ "210921": "阜新蒙古族自治县",
+ "210922": "彰武县",
+ "211002": "白塔区",
+ "211003": "文圣区",
+ "211004": "宏伟区",
+ "211005": "弓长岭区",
+ "211011": "太子河区",
+ "211021": "辽阳县",
+ "211081": "灯塔市",
+ "211102": "双台子区",
+ "211103": "兴隆台区",
+ "211104": "大洼区",
+ "211122": "盘山县",
+ "211202": "银州区",
+ "211204": "清河区",
+ "211221": "铁岭县",
+ "211223": "西丰县",
+ "211224": "昌图县",
+ "211281": "调兵山市",
+ "211282": "开原市",
+ "211302": "双塔区",
+ "211303": "龙城区",
+ "211321": "朝阳县",
+ "211322": "建平县",
+ "211324": "喀喇沁左翼蒙古族自治县",
+ "211381": "北票市",
+ "211382": "凌源市",
+ "211402": "连山区",
+ "211403": "龙港区",
+ "211404": "南票区",
+ "211421": "绥中县",
+ "211422": "建昌县",
+ "211481": "兴城市",
+ "220102": "南关区",
+ "220103": "宽城区",
+ "220104": "朝阳区",
+ "220105": "二道区",
+ "220106": "绿园区",
+ "220112": "双阳区",
+ "220113": "九台区",
+ "220122": "农安县",
+ "220171": "长春经济技术开发区",
+ "220172": "长春净月高新技术产业开发区",
+ "220173": "长春高新技术产业开发区",
+ "220174": "长春汽车经济技术开发区",
+ "220182": "榆树市",
+ "220183": "德惠市",
+ "220184": "公主岭市",
+ "220192": "经济技术开发区",
+ "220202": "昌邑区",
+ "220203": "龙潭区",
+ "220204": "船营区",
+ "220211": "丰满区",
+ "220221": "永吉县",
+ "220271": "吉林经济开发区",
+ "220272": "吉林高新技术产业开发区",
+ "220281": "蛟河市",
+ "220282": "桦甸市",
+ "220283": "舒兰市",
+ "220284": "磐石市",
+ "220302": "铁西区",
+ "220303": "铁东区",
+ "220322": "梨树县",
+ "220323": "伊通满族自治县",
+ "220382": "双辽市",
+ "220402": "龙山区",
+ "220403": "西安区",
+ "220421": "东丰县",
+ "220422": "东辽县",
+ "220502": "东昌区",
+ "220503": "二道江区",
+ "220521": "通化县",
+ "220523": "辉南县",
+ "220524": "柳河县",
+ "220581": "梅河口市",
+ "220582": "集安市",
+ "220602": "浑江区",
+ "220605": "江源区",
+ "220621": "抚松县",
+ "220622": "靖宇县",
+ "220623": "长白朝鲜族自治县",
+ "220681": "临江市",
+ "220702": "宁江区",
+ "220721": "前郭尔罗斯蒙古族自治县",
+ "220722": "长岭县",
+ "220723": "乾安县",
+ "220771": "吉林松原经济开发区",
+ "220781": "扶余市",
+ "220802": "洮北区",
+ "220821": "镇赉县",
+ "220822": "通榆县",
+ "220871": "吉林白城经济开发区",
+ "220881": "洮南市",
+ "220882": "大安市",
+ "222401": "延吉市",
+ "222402": "图们市",
+ "222403": "敦化市",
+ "222404": "珲春市",
+ "222405": "龙井市",
+ "222406": "和龙市",
+ "222424": "汪清县",
+ "222426": "安图县",
+ "230102": "道里区",
+ "230103": "南岗区",
+ "230104": "道外区",
+ "230108": "平房区",
+ "230109": "松北区",
+ "230110": "香坊区",
+ "230111": "呼兰区",
+ "230112": "阿城区",
+ "230113": "双城区",
+ "230123": "依兰县",
+ "230124": "方正县",
+ "230125": "宾县",
+ "230126": "巴彦县",
+ "230127": "木兰县",
+ "230128": "通河县",
+ "230129": "延寿县",
+ "230183": "尚志市",
+ "230184": "五常市",
+ "230202": "龙沙区",
+ "230203": "建华区",
+ "230204": "铁锋区",
+ "230205": "昂昂溪区",
+ "230206": "富拉尔基区",
+ "230207": "碾子山区",
+ "230208": "梅里斯达斡尔族区",
+ "230221": "龙江县",
+ "230223": "依安县",
+ "230224": "泰来县",
+ "230225": "甘南县",
+ "230227": "富裕县",
+ "230229": "克山县",
+ "230230": "克东县",
+ "230231": "拜泉县",
+ "230281": "讷河市",
+ "230302": "鸡冠区",
+ "230303": "恒山区",
+ "230304": "滴道区",
+ "230305": "梨树区",
+ "230306": "城子河区",
+ "230307": "麻山区",
+ "230321": "鸡东县",
+ "230381": "虎林市",
+ "230382": "密山市",
+ "230402": "向阳区",
+ "230403": "工农区",
+ "230404": "南山区",
+ "230405": "兴安区",
+ "230406": "东山区",
+ "230407": "兴山区",
+ "230421": "萝北县",
+ "230422": "绥滨县",
+ "230502": "尖山区",
+ "230503": "岭东区",
+ "230505": "四方台区",
+ "230506": "宝山区",
+ "230521": "集贤县",
+ "230522": "友谊县",
+ "230523": "宝清县",
+ "230524": "饶河县",
+ "230602": "萨尔图区",
+ "230603": "龙凤区",
+ "230604": "让胡路区",
+ "230605": "红岗区",
+ "230606": "大同区",
+ "230621": "肇州县",
+ "230622": "肇源县",
+ "230623": "林甸县",
+ "230624": "杜尔伯特蒙古族自治县",
+ "230671": "大庆高新技术产业开发区",
+ "230717": "伊美区",
+ "230718": "乌翠区",
+ "230719": "友好区",
+ "230722": "嘉荫县",
+ "230723": "汤旺县",
+ "230724": "丰林县",
+ "230725": "大箐山县",
+ "230726": "南岔县",
+ "230751": "金林区",
+ "230781": "铁力市",
+ "230803": "向阳区",
+ "230804": "前进区",
+ "230805": "东风区",
+ "230811": "郊区",
+ "230822": "桦南县",
+ "230826": "桦川县",
+ "230828": "汤原县",
+ "230881": "同江市",
+ "230882": "富锦市",
+ "230883": "抚远市",
+ "230902": "新兴区",
+ "230903": "桃山区",
+ "230904": "茄子河区",
+ "230921": "勃利县",
+ "231002": "东安区",
+ "231003": "阳明区",
+ "231004": "爱民区",
+ "231005": "西安区",
+ "231025": "林口县",
+ "231081": "绥芬河市",
+ "231083": "海林市",
+ "231084": "宁安市",
+ "231085": "穆棱市",
+ "231086": "东宁市",
+ "231102": "爱辉区",
+ "231123": "逊克县",
+ "231124": "孙吴县",
+ "231181": "北安市",
+ "231182": "五大连池市",
+ "231183": "嫩江市",
+ "231202": "北林区",
+ "231221": "望奎县",
+ "231222": "兰西县",
+ "231223": "青冈县",
+ "231224": "庆安县",
+ "231225": "明水县",
+ "231226": "绥棱县",
+ "231281": "安达市",
+ "231282": "肇东市",
+ "231283": "海伦市",
+ "232701": "漠河市",
+ "232721": "呼玛县",
+ "232722": "塔河县",
+ "232761": "加格达奇区",
+ "232762": "松岭区",
+ "232763": "新林区",
+ "232764": "呼中区",
+ "310101": "黄浦区",
+ "310104": "徐汇区",
+ "310105": "长宁区",
+ "310106": "静安区",
+ "310107": "普陀区",
+ "310109": "虹口区",
+ "310110": "杨浦区",
+ "310112": "闵行区",
+ "310113": "宝山区",
+ "310114": "嘉定区",
+ "310115": "浦东新区",
+ "310116": "金山区",
+ "310117": "松江区",
+ "310118": "青浦区",
+ "310120": "奉贤区",
+ "310151": "崇明区",
+ "320102": "玄武区",
+ "320104": "秦淮区",
+ "320105": "建邺区",
+ "320106": "鼓楼区",
+ "320111": "浦口区",
+ "320112": "江北新区",
+ "320113": "栖霞区",
+ "320114": "雨花台区",
+ "320115": "江宁区",
+ "320116": "六合区",
+ "320117": "溧水区",
+ "320118": "高淳区",
+ "320205": "锡山区",
+ "320206": "惠山区",
+ "320211": "滨湖区",
+ "320213": "梁溪区",
+ "320214": "新吴区",
+ "320281": "江阴市",
+ "320282": "宜兴市",
+ "320302": "鼓楼区",
+ "320303": "云龙区",
+ "320305": "贾汪区",
+ "320311": "泉山区",
+ "320312": "铜山区",
+ "320321": "丰县",
+ "320322": "沛县",
+ "320324": "睢宁县",
+ "320371": "徐州经济技术开发区",
+ "320381": "新沂市",
+ "320382": "邳州市",
+ "320391": "工业园区",
+ "320402": "天宁区",
+ "320404": "钟楼区",
+ "320411": "新北区",
+ "320412": "武进区",
+ "320413": "金坛区",
+ "320481": "溧阳市",
+ "320505": "虎丘区",
+ "320506": "吴中区",
+ "320507": "相城区",
+ "320508": "姑苏区",
+ "320509": "吴江区",
+ "320571": "苏州工业园区",
+ "320581": "常熟市",
+ "320582": "张家港市",
+ "320583": "昆山市",
+ "320585": "太仓市",
+ "320590": "工业园区",
+ "320591": "高新区",
+ "320611": "港闸区",
+ "320612": "通州区",
+ "320613": "崇川区",
+ "320614": "海门区",
+ "320623": "如东县",
+ "320681": "启东市",
+ "320682": "如皋市",
+ "320685": "海安市",
+ "320691": "高新区",
+ "320703": "连云区",
+ "320706": "海州区",
+ "320707": "赣榆区",
+ "320722": "东海县",
+ "320723": "灌云县",
+ "320724": "灌南县",
+ "320771": "连云港经济技术开发区",
+ "320803": "淮安区",
+ "320804": "淮阴区",
+ "320812": "清江浦区",
+ "320813": "洪泽区",
+ "320826": "涟水县",
+ "320830": "盱眙县",
+ "320831": "金湖县",
+ "320871": "淮安经济技术开发区",
+ "320890": "经济开发区",
+ "320902": "亭湖区",
+ "320903": "盐都区",
+ "320904": "大丰区",
+ "320921": "响水县",
+ "320922": "滨海县",
+ "320923": "阜宁县",
+ "320924": "射阳县",
+ "320925": "建湖县",
+ "320971": "盐城经济技术开发区",
+ "320981": "东台市",
+ "321002": "广陵区",
+ "321003": "邗江区",
+ "321012": "江都区",
+ "321023": "宝应县",
+ "321071": "扬州经济技术开发区",
+ "321081": "仪征市",
+ "321084": "高邮市",
+ "321090": "经济开发区",
+ "321102": "京口区",
+ "321111": "润州区",
+ "321112": "丹徒区",
+ "321150": "镇江新区",
+ "321181": "丹阳市",
+ "321182": "扬中市",
+ "321183": "句容市",
+ "321202": "海陵区",
+ "321203": "高港区",
+ "321204": "姜堰区",
+ "321271": "泰州医药高新技术产业开发区",
+ "321281": "兴化市",
+ "321282": "靖江市",
+ "321283": "泰兴市",
+ "321302": "宿城区",
+ "321311": "宿豫区",
+ "321322": "沭阳县",
+ "321323": "泗阳县",
+ "321324": "泗洪县",
+ "321371": "宿迁经济技术开发区",
+ "330102": "上城区",
+ "330105": "拱墅区",
+ "330106": "西湖区",
+ "330108": "滨江区",
+ "330109": "萧山区",
+ "330110": "余杭区",
+ "330111": "富阳区",
+ "330112": "临安区",
+ "330113": "临平区",
+ "330114": "钱塘区",
+ "330122": "桐庐县",
+ "330127": "淳安县",
+ "330182": "建德市",
+ "330203": "海曙区",
+ "330205": "江北区",
+ "330206": "北仑区",
+ "330211": "镇海区",
+ "330212": "鄞州区",
+ "330213": "奉化区",
+ "330225": "象山县",
+ "330226": "宁海县",
+ "330281": "余姚市",
+ "330282": "慈溪市",
+ "330302": "鹿城区",
+ "330303": "龙湾区",
+ "330304": "瓯海区",
+ "330305": "洞头区",
+ "330324": "永嘉县",
+ "330326": "平阳县",
+ "330327": "苍南县",
+ "330328": "文成县",
+ "330329": "泰顺县",
+ "330381": "瑞安市",
+ "330382": "乐清市",
+ "330383": "龙港市",
+ "330402": "南湖区",
+ "330411": "秀洲区",
+ "330421": "嘉善县",
+ "330424": "海盐县",
+ "330481": "海宁市",
+ "330482": "平湖市",
+ "330483": "桐乡市",
+ "330502": "吴兴区",
+ "330503": "南浔区",
+ "330521": "德清县",
+ "330522": "长兴县",
+ "330523": "安吉县",
+ "330602": "越城区",
+ "330603": "柯桥区",
+ "330604": "上虞区",
+ "330624": "新昌县",
+ "330681": "诸暨市",
+ "330683": "嵊州市",
+ "330702": "婺城区",
+ "330703": "金东区",
+ "330723": "武义县",
+ "330726": "浦江县",
+ "330727": "磐安县",
+ "330781": "兰溪市",
+ "330782": "义乌市",
+ "330783": "东阳市",
+ "330784": "永康市",
+ "330802": "柯城区",
+ "330803": "衢江区",
+ "330822": "常山县",
+ "330824": "开化县",
+ "330825": "龙游县",
+ "330881": "江山市",
+ "330902": "定海区",
+ "330903": "普陀区",
+ "330921": "岱山县",
+ "330922": "嵊泗县",
+ "331002": "椒江区",
+ "331003": "黄岩区",
+ "331004": "路桥区",
+ "331022": "三门县",
+ "331023": "天台县",
+ "331024": "仙居县",
+ "331081": "温岭市",
+ "331082": "临海市",
+ "331083": "玉环市",
+ "331102": "莲都区",
+ "331121": "青田县",
+ "331122": "缙云县",
+ "331123": "遂昌县",
+ "331124": "松阳县",
+ "331125": "云和县",
+ "331126": "庆元县",
+ "331127": "景宁畲族自治县",
+ "331181": "龙泉市",
+ "340102": "瑶海区",
+ "340103": "庐阳区",
+ "340104": "蜀山区",
+ "340111": "包河区",
+ "340121": "长丰县",
+ "340122": "肥东县",
+ "340123": "肥西县",
+ "340124": "庐江县",
+ "340171": "合肥高新技术产业开发区",
+ "340172": "合肥经济技术开发区",
+ "340173": "合肥新站高新技术产业开发区",
+ "340181": "巢湖市",
+ "340190": "高新技术开发区",
+ "340191": "经济技术开发区",
+ "340202": "镜湖区",
+ "340207": "鸠江区",
+ "340209": "弋江区",
+ "340210": "湾沚区",
+ "340212": "繁昌区",
+ "340223": "南陵县",
+ "340281": "无为市",
+ "340302": "龙子湖区",
+ "340303": "蚌山区",
+ "340304": "禹会区",
+ "340311": "淮上区",
+ "340321": "怀远县",
+ "340322": "五河县",
+ "340323": "固镇县",
+ "340371": "蚌埠市高新技术开发区",
+ "340372": "蚌埠市经济开发区",
+ "340402": "大通区",
+ "340403": "田家庵区",
+ "340404": "谢家集区",
+ "340405": "八公山区",
+ "340406": "潘集区",
+ "340421": "凤台县",
+ "340422": "寿县",
+ "340503": "花山区",
+ "340504": "雨山区",
+ "340506": "博望区",
+ "340521": "当涂县",
+ "340522": "含山县",
+ "340523": "和县",
+ "340602": "杜集区",
+ "340603": "相山区",
+ "340604": "烈山区",
+ "340621": "濉溪县",
+ "340705": "铜官区",
+ "340706": "义安区",
+ "340711": "郊区",
+ "340722": "枞阳县",
+ "340802": "迎江区",
+ "340803": "大观区",
+ "340811": "宜秀区",
+ "340822": "怀宁县",
+ "340825": "太湖县",
+ "340826": "宿松县",
+ "340827": "望江县",
+ "340828": "岳西县",
+ "340881": "桐城市",
+ "340882": "潜山市",
+ "341002": "屯溪区",
+ "341003": "黄山区",
+ "341004": "徽州区",
+ "341021": "歙县",
+ "341022": "休宁县",
+ "341023": "黟县",
+ "341024": "祁门县",
+ "341102": "琅琊区",
+ "341103": "南谯区",
+ "341122": "来安县",
+ "341124": "全椒县",
+ "341125": "定远县",
+ "341126": "凤阳县",
+ "341181": "天长市",
+ "341182": "明光市",
+ "341202": "颍州区",
+ "341203": "颍东区",
+ "341204": "颍泉区",
+ "341221": "临泉县",
+ "341222": "太和县",
+ "341225": "阜南县",
+ "341226": "颍上县",
+ "341271": "阜阳合肥现代产业园区",
+ "341282": "界首市",
+ "341302": "埇桥区",
+ "341321": "砀山县",
+ "341322": "萧县",
+ "341323": "灵璧县",
+ "341324": "泗县",
+ "341371": "宿州马鞍山现代产业园区",
+ "341372": "宿州经济技术开发区",
+ "341390": "经济开发区",
+ "341502": "金安区",
+ "341503": "裕安区",
+ "341504": "叶集区",
+ "341522": "霍邱县",
+ "341523": "舒城县",
+ "341524": "金寨县",
+ "341525": "霍山县",
+ "341602": "谯城区",
+ "341621": "涡阳县",
+ "341622": "蒙城县",
+ "341623": "利辛县",
+ "341702": "贵池区",
+ "341721": "东至县",
+ "341722": "石台县",
+ "341723": "青阳县",
+ "341802": "宣州区",
+ "341821": "郎溪县",
+ "341823": "泾县",
+ "341824": "绩溪县",
+ "341825": "旌德县",
+ "341871": "宣城市经济开发区",
+ "341881": "宁国市",
+ "341882": "广德市",
+ "350102": "鼓楼区",
+ "350103": "台江区",
+ "350104": "仓山区",
+ "350105": "马尾区",
+ "350111": "晋安区",
+ "350112": "长乐区",
+ "350121": "闽侯县",
+ "350122": "连江县",
+ "350123": "罗源县",
+ "350124": "闽清县",
+ "350125": "永泰县",
+ "350128": "平潭县",
+ "350181": "福清市",
+ "350203": "思明区",
+ "350205": "海沧区",
+ "350206": "湖里区",
+ "350211": "集美区",
+ "350212": "同安区",
+ "350213": "翔安区",
+ "350302": "城厢区",
+ "350303": "涵江区",
+ "350304": "荔城区",
+ "350305": "秀屿区",
+ "350322": "仙游县",
+ "350402": "梅列区",
+ "350404": "三元区",
+ "350405": "沙县区",
+ "350421": "明溪县",
+ "350423": "清流县",
+ "350424": "宁化县",
+ "350425": "大田县",
+ "350426": "尤溪县",
+ "350428": "将乐县",
+ "350429": "泰宁县",
+ "350430": "建宁县",
+ "350481": "永安市",
+ "350502": "鲤城区",
+ "350503": "丰泽区",
+ "350504": "洛江区",
+ "350505": "泉港区",
+ "350521": "惠安县",
+ "350524": "安溪县",
+ "350525": "永春县",
+ "350526": "德化县",
+ "350527": "金门县",
+ "350581": "石狮市",
+ "350582": "晋江市",
+ "350583": "南安市",
+ "350602": "芗城区",
+ "350603": "龙文区",
+ "350604": "龙海区",
+ "350605": "长泰区",
+ "350622": "云霄县",
+ "350623": "漳浦县",
+ "350624": "诏安县",
+ "350626": "东山县",
+ "350627": "南靖县",
+ "350628": "平和县",
+ "350629": "华安县",
+ "350702": "延平区",
+ "350703": "建阳区",
+ "350721": "顺昌县",
+ "350722": "浦城县",
+ "350723": "光泽县",
+ "350724": "松溪县",
+ "350725": "政和县",
+ "350781": "邵武市",
+ "350782": "武夷山市",
+ "350783": "建瓯市",
+ "350802": "新罗区",
+ "350803": "永定区",
+ "350821": "长汀县",
+ "350823": "上杭县",
+ "350824": "武平县",
+ "350825": "连城县",
+ "350881": "漳平市",
+ "350902": "蕉城区",
+ "350921": "霞浦县",
+ "350922": "古田县",
+ "350923": "屏南县",
+ "350924": "寿宁县",
+ "350925": "周宁县",
+ "350926": "柘荣县",
+ "350981": "福安市",
+ "350982": "福鼎市",
+ "360102": "东湖区",
+ "360103": "西湖区",
+ "360104": "青云谱区",
+ "360111": "青山湖区",
+ "360112": "新建区",
+ "360113": "红谷滩区",
+ "360121": "南昌县",
+ "360123": "安义县",
+ "360124": "进贤县",
+ "360190": "经济技术开发区",
+ "360192": "高新区",
+ "360202": "昌江区",
+ "360203": "珠山区",
+ "360222": "浮梁县",
+ "360281": "乐平市",
+ "360302": "安源区",
+ "360313": "湘东区",
+ "360321": "莲花县",
+ "360322": "上栗县",
+ "360323": "芦溪县",
+ "360402": "濂溪区",
+ "360403": "浔阳区",
+ "360404": "柴桑区",
+ "360423": "武宁县",
+ "360424": "修水县",
+ "360425": "永修县",
+ "360426": "德安县",
+ "360428": "都昌县",
+ "360429": "湖口县",
+ "360430": "彭泽县",
+ "360481": "瑞昌市",
+ "360482": "共青城市",
+ "360483": "庐山市",
+ "360490": "经济技术开发区",
+ "360502": "渝水区",
+ "360521": "分宜县",
+ "360602": "月湖区",
+ "360603": "余江区",
+ "360681": "贵溪市",
+ "360702": "章贡区",
+ "360703": "南康区",
+ "360704": "赣县区",
+ "360722": "信丰县",
+ "360723": "大余县",
+ "360724": "上犹县",
+ "360725": "崇义县",
+ "360726": "安远县",
+ "360728": "定南县",
+ "360729": "全南县",
+ "360730": "宁都县",
+ "360731": "于都县",
+ "360732": "兴国县",
+ "360733": "会昌县",
+ "360734": "寻乌县",
+ "360735": "石城县",
+ "360781": "瑞金市",
+ "360783": "龙南市",
+ "360802": "吉州区",
+ "360803": "青原区",
+ "360821": "吉安县",
+ "360822": "吉水县",
+ "360823": "峡江县",
+ "360824": "新干县",
+ "360825": "永丰县",
+ "360826": "泰和县",
+ "360827": "遂川县",
+ "360828": "万安县",
+ "360829": "安福县",
+ "360830": "永新县",
+ "360881": "井冈山市",
+ "360902": "袁州区",
+ "360921": "奉新县",
+ "360922": "万载县",
+ "360923": "上高县",
+ "360924": "宜丰县",
+ "360925": "靖安县",
+ "360926": "铜鼓县",
+ "360981": "丰城市",
+ "360982": "樟树市",
+ "360983": "高安市",
+ "361002": "临川区",
+ "361003": "东乡区",
+ "361021": "南城县",
+ "361022": "黎川县",
+ "361023": "南丰县",
+ "361024": "崇仁县",
+ "361025": "乐安县",
+ "361026": "宜黄县",
+ "361027": "金溪县",
+ "361028": "资溪县",
+ "361030": "广昌县",
+ "361102": "信州区",
+ "361103": "广丰区",
+ "361104": "广信区",
+ "361123": "玉山县",
+ "361124": "铅山县",
+ "361125": "横峰县",
+ "361126": "弋阳县",
+ "361127": "余干县",
+ "361128": "鄱阳县",
+ "361129": "万年县",
+ "361130": "婺源县",
+ "361181": "德兴市",
+ "370102": "历下区",
+ "370103": "市中区",
+ "370104": "槐荫区",
+ "370105": "天桥区",
+ "370112": "历城区",
+ "370113": "长清区",
+ "370114": "章丘区",
+ "370115": "济阳区",
+ "370116": "莱芜区",
+ "370117": "钢城区",
+ "370124": "平阴县",
+ "370126": "商河县",
+ "370171": "济南高新技术产业开发区",
+ "370190": "高新区",
+ "370202": "市南区",
+ "370203": "市北区",
+ "370211": "黄岛区",
+ "370212": "崂山区",
+ "370213": "李沧区",
+ "370214": "城阳区",
+ "370215": "即墨区",
+ "370271": "青岛高新技术产业开发区",
+ "370281": "胶州市",
+ "370283": "平度市",
+ "370285": "莱西市",
+ "370290": "开发区",
+ "370302": "淄川区",
+ "370303": "张店区",
+ "370304": "博山区",
+ "370305": "临淄区",
+ "370306": "周村区",
+ "370321": "桓台县",
+ "370322": "高青县",
+ "370323": "沂源县",
+ "370402": "市中区",
+ "370403": "薛城区",
+ "370404": "峄城区",
+ "370405": "台儿庄区",
+ "370406": "山亭区",
+ "370481": "滕州市",
+ "370502": "东营区",
+ "370503": "河口区",
+ "370505": "垦利区",
+ "370522": "利津县",
+ "370523": "广饶县",
+ "370571": "东营经济技术开发区",
+ "370572": "东营港经济开发区",
+ "370602": "芝罘区",
+ "370611": "福山区",
+ "370612": "牟平区",
+ "370613": "莱山区",
+ "370614": "蓬莱区",
+ "370634": "长岛县",
+ "370671": "烟台高新技术产业开发区",
+ "370672": "烟台经济技术开发区",
+ "370681": "龙口市",
+ "370682": "莱阳市",
+ "370683": "莱州市",
+ "370685": "招远市",
+ "370686": "栖霞市",
+ "370687": "海阳市",
+ "370690": "开发区",
+ "370702": "潍城区",
+ "370703": "寒亭区",
+ "370704": "坊子区",
+ "370705": "奎文区",
+ "370724": "临朐县",
+ "370725": "昌乐县",
+ "370772": "潍坊滨海经济技术开发区",
+ "370781": "青州市",
+ "370782": "诸城市",
+ "370783": "寿光市",
+ "370784": "安丘市",
+ "370785": "高密市",
+ "370786": "昌邑市",
+ "370790": "开发区",
+ "370791": "高新区",
+ "370811": "任城区",
+ "370812": "兖州区",
+ "370826": "微山县",
+ "370827": "鱼台县",
+ "370828": "金乡县",
+ "370829": "嘉祥县",
+ "370830": "汶上县",
+ "370831": "泗水县",
+ "370832": "梁山县",
+ "370871": "济宁高新技术产业开发区",
+ "370881": "曲阜市",
+ "370883": "邹城市",
+ "370890": "高新区",
+ "370902": "泰山区",
+ "370911": "岱岳区",
+ "370921": "宁阳县",
+ "370923": "东平县",
+ "370982": "新泰市",
+ "370983": "肥城市",
+ "371002": "环翠区",
+ "371003": "文登区",
+ "371071": "威海火炬高技术产业开发区",
+ "371072": "威海经济技术开发区",
+ "371082": "荣成市",
+ "371083": "乳山市",
+ "371091": "经济技术开发区",
+ "371102": "东港区",
+ "371103": "岚山区",
+ "371121": "五莲县",
+ "371122": "莒县",
+ "371171": "日照经济技术开发区",
+ "371302": "兰山区",
+ "371311": "罗庄区",
+ "371312": "河东区",
+ "371321": "沂南县",
+ "371322": "郯城县",
+ "371323": "沂水县",
+ "371324": "兰陵县",
+ "371325": "费县",
+ "371326": "平邑县",
+ "371327": "莒南县",
+ "371328": "蒙阴县",
+ "371329": "临沭县",
+ "371371": "临沂高新技术产业开发区",
+ "371402": "德城区",
+ "371403": "陵城区",
+ "371422": "宁津县",
+ "371423": "庆云县",
+ "371424": "临邑县",
+ "371425": "齐河县",
+ "371426": "平原县",
+ "371427": "夏津县",
+ "371428": "武城县",
+ "371472": "德州运河经济开发区",
+ "371481": "乐陵市",
+ "371482": "禹城市",
+ "371502": "东昌府区",
+ "371503": "茌平区",
+ "371521": "阳谷县",
+ "371522": "莘县",
+ "371524": "东阿县",
+ "371525": "冠县",
+ "371526": "高唐县",
+ "371581": "临清市",
+ "371602": "滨城区",
+ "371603": "沾化区",
+ "371621": "惠民县",
+ "371622": "阳信县",
+ "371623": "无棣县",
+ "371625": "博兴县",
+ "371681": "邹平市",
+ "371702": "牡丹区",
+ "371703": "定陶区",
+ "371721": "曹县",
+ "371722": "单县",
+ "371723": "成武县",
+ "371724": "巨野县",
+ "371725": "郓城县",
+ "371726": "鄄城县",
+ "371728": "东明县",
+ "371771": "菏泽经济技术开发区",
+ "371772": "菏泽高新技术开发区",
+ "410102": "中原区",
+ "410103": "二七区",
+ "410104": "管城回族区",
+ "410105": "金水区",
+ "410106": "上街区",
+ "410108": "惠济区",
+ "410122": "中牟县",
+ "410171": "郑州经济技术开发区",
+ "410172": "郑州高新技术产业开发区",
+ "410173": "郑州航空港经济综合实验区",
+ "410181": "巩义市",
+ "410182": "荥阳市",
+ "410183": "新密市",
+ "410184": "新郑市",
+ "410185": "登封市",
+ "410190": "高新技术开发区",
+ "410191": "经济技术开发区",
+ "410202": "龙亭区",
+ "410203": "顺河回族区",
+ "410204": "鼓楼区",
+ "410205": "禹王台区",
+ "410212": "祥符区",
+ "410221": "杞县",
+ "410222": "通许县",
+ "410223": "尉氏县",
+ "410225": "兰考县",
+ "410302": "老城区",
+ "410303": "西工区",
+ "410304": "瀍河回族区",
+ "410305": "涧西区",
+ "410307": "偃师区",
+ "410308": "孟津区",
+ "410311": "洛龙区",
+ "410323": "新安县",
+ "410324": "栾川县",
+ "410325": "嵩县",
+ "410326": "汝阳县",
+ "410327": "宜阳县",
+ "410328": "洛宁县",
+ "410329": "伊川县",
+ "410402": "新华区",
+ "410403": "卫东区",
+ "410404": "石龙区",
+ "410411": "湛河区",
+ "410421": "宝丰县",
+ "410422": "叶县",
+ "410423": "鲁山县",
+ "410425": "郏县",
+ "410471": "平顶山高新技术产业开发区",
+ "410481": "舞钢市",
+ "410482": "汝州市",
+ "410502": "文峰区",
+ "410503": "北关区",
+ "410505": "殷都区",
+ "410506": "龙安区",
+ "410522": "安阳县",
+ "410523": "汤阴县",
+ "410526": "滑县",
+ "410527": "内黄县",
+ "410581": "林州市",
+ "410590": "开发区",
+ "410602": "鹤山区",
+ "410603": "山城区",
+ "410611": "淇滨区",
+ "410621": "浚县",
+ "410622": "淇县",
+ "410702": "红旗区",
+ "410703": "卫滨区",
+ "410704": "凤泉区",
+ "410711": "牧野区",
+ "410721": "新乡县",
+ "410724": "获嘉县",
+ "410725": "原阳县",
+ "410726": "延津县",
+ "410727": "封丘县",
+ "410771": "新乡高新技术产业开发区",
+ "410772": "新乡经济技术开发区",
+ "410781": "卫辉市",
+ "410782": "辉县市",
+ "410783": "长垣市",
+ "410802": "解放区",
+ "410803": "中站区",
+ "410804": "马村区",
+ "410811": "山阳区",
+ "410821": "修武县",
+ "410822": "博爱县",
+ "410823": "武陟县",
+ "410825": "温县",
+ "410871": "焦作城乡一体化示范区",
+ "410882": "沁阳市",
+ "410883": "孟州市",
+ "410902": "华龙区",
+ "410922": "清丰县",
+ "410923": "南乐县",
+ "410926": "范县",
+ "410927": "台前县",
+ "410928": "濮阳县",
+ "410971": "河南濮阳工业园区",
+ "411002": "魏都区",
+ "411003": "建安区",
+ "411024": "鄢陵县",
+ "411025": "襄城县",
+ "411071": "许昌经济技术开发区",
+ "411081": "禹州市",
+ "411082": "长葛市",
+ "411102": "源汇区",
+ "411103": "郾城区",
+ "411104": "召陵区",
+ "411121": "舞阳县",
+ "411122": "临颍县",
+ "411171": "漯河经济技术开发区",
+ "411202": "湖滨区",
+ "411203": "陕州区",
+ "411221": "渑池县",
+ "411224": "卢氏县",
+ "411271": "河南三门峡经济开发区",
+ "411281": "义马市",
+ "411282": "灵宝市",
+ "411302": "宛城区",
+ "411303": "卧龙区",
+ "411321": "南召县",
+ "411322": "方城县",
+ "411323": "西峡县",
+ "411324": "镇平县",
+ "411325": "内乡县",
+ "411326": "淅川县",
+ "411327": "社旗县",
+ "411328": "唐河县",
+ "411329": "新野县",
+ "411330": "桐柏县",
+ "411372": "南阳市城乡一体化示范区",
+ "411381": "邓州市",
+ "411402": "梁园区",
+ "411403": "睢阳区",
+ "411421": "民权县",
+ "411422": "睢县",
+ "411423": "宁陵县",
+ "411424": "柘城县",
+ "411425": "虞城县",
+ "411426": "夏邑县",
+ "411481": "永城市",
+ "411502": "浉河区",
+ "411503": "平桥区",
+ "411521": "罗山县",
+ "411522": "光山县",
+ "411523": "新县",
+ "411524": "商城县",
+ "411525": "固始县",
+ "411526": "潢川县",
+ "411527": "淮滨县",
+ "411528": "息县",
+ "411602": "川汇区",
+ "411603": "淮阳区",
+ "411621": "扶沟县",
+ "411622": "西华县",
+ "411623": "商水县",
+ "411624": "沈丘县",
+ "411625": "郸城县",
+ "411627": "太康县",
+ "411628": "鹿邑县",
+ "411671": "河南周口经济开发区",
+ "411681": "项城市",
+ "411690": "经济开发区",
+ "411702": "驿城区",
+ "411721": "西平县",
+ "411722": "上蔡县",
+ "411723": "平舆县",
+ "411724": "正阳县",
+ "411725": "确山县",
+ "411726": "泌阳县",
+ "411727": "汝南县",
+ "411728": "遂平县",
+ "411729": "新蔡县",
+ "419001": "济源市",
+ "420102": "江岸区",
+ "420103": "江汉区",
+ "420104": "硚口区",
+ "420105": "汉阳区",
+ "420106": "武昌区",
+ "420107": "青山区",
+ "420111": "洪山区",
+ "420112": "东西湖区",
+ "420113": "汉南区",
+ "420114": "蔡甸区",
+ "420115": "江夏区",
+ "420116": "黄陂区",
+ "420117": "新洲区",
+ "420202": "黄石港区",
+ "420203": "西塞山区",
+ "420204": "下陆区",
+ "420205": "铁山区",
+ "420222": "阳新县",
+ "420281": "大冶市",
+ "420302": "茅箭区",
+ "420303": "张湾区",
+ "420304": "郧阳区",
+ "420322": "郧西县",
+ "420323": "竹山县",
+ "420324": "竹溪县",
+ "420325": "房县",
+ "420381": "丹江口市",
+ "420502": "西陵区",
+ "420503": "伍家岗区",
+ "420504": "点军区",
+ "420505": "猇亭区",
+ "420506": "夷陵区",
+ "420525": "远安县",
+ "420526": "兴山县",
+ "420527": "秭归县",
+ "420528": "长阳土家族自治县",
+ "420529": "五峰土家族自治县",
+ "420581": "宜都市",
+ "420582": "当阳市",
+ "420583": "枝江市",
+ "420590": "经济开发区",
+ "420602": "襄城区",
+ "420606": "樊城区",
+ "420607": "襄州区",
+ "420624": "南漳县",
+ "420625": "谷城县",
+ "420626": "保康县",
+ "420682": "老河口市",
+ "420683": "枣阳市",
+ "420684": "宜城市",
+ "420702": "梁子湖区",
+ "420703": "华容区",
+ "420704": "鄂城区",
+ "420802": "东宝区",
+ "420804": "掇刀区",
+ "420822": "沙洋县",
+ "420881": "钟祥市",
+ "420882": "京山市",
+ "420902": "孝南区",
+ "420921": "孝昌县",
+ "420922": "大悟县",
+ "420923": "云梦县",
+ "420981": "应城市",
+ "420982": "安陆市",
+ "420984": "汉川市",
+ "421002": "沙市区",
+ "421003": "荆州区",
+ "421022": "公安县",
+ "421024": "江陵县",
+ "421081": "石首市",
+ "421083": "洪湖市",
+ "421087": "松滋市",
+ "421088": "监利市",
+ "421102": "黄州区",
+ "421121": "团风县",
+ "421122": "红安县",
+ "421123": "罗田县",
+ "421124": "英山县",
+ "421125": "浠水县",
+ "421126": "蕲春县",
+ "421127": "黄梅县",
+ "421171": "龙感湖管理区",
+ "421181": "麻城市",
+ "421182": "武穴市",
+ "421202": "咸安区",
+ "421221": "嘉鱼县",
+ "421222": "通城县",
+ "421223": "崇阳县",
+ "421224": "通山县",
+ "421281": "赤壁市",
+ "421303": "曾都区",
+ "421321": "随县",
+ "421381": "广水市",
+ "422801": "恩施市",
+ "422802": "利川市",
+ "422822": "建始县",
+ "422823": "巴东县",
+ "422825": "宣恩县",
+ "422826": "咸丰县",
+ "422827": "来凤县",
+ "422828": "鹤峰县",
+ "429004": "仙桃市",
+ "429005": "潜江市",
+ "429006": "天门市",
+ "429021": "神农架林区",
+ "430102": "芙蓉区",
+ "430103": "天心区",
+ "430104": "岳麓区",
+ "430105": "开福区",
+ "430111": "雨花区",
+ "430112": "望城区",
+ "430121": "长沙县",
+ "430181": "浏阳市",
+ "430182": "宁乡市",
+ "430202": "荷塘区",
+ "430203": "芦淞区",
+ "430204": "石峰区",
+ "430211": "天元区",
+ "430212": "渌口区",
+ "430223": "攸县",
+ "430224": "茶陵县",
+ "430225": "炎陵县",
+ "430271": "云龙示范区",
+ "430281": "醴陵市",
+ "430302": "雨湖区",
+ "430304": "岳塘区",
+ "430321": "湘潭县",
+ "430373": "湘潭九华示范区",
+ "430381": "湘乡市",
+ "430382": "韶山市",
+ "430405": "珠晖区",
+ "430406": "雁峰区",
+ "430407": "石鼓区",
+ "430408": "蒸湘区",
+ "430412": "南岳区",
+ "430421": "衡阳县",
+ "430422": "衡南县",
+ "430423": "衡山县",
+ "430424": "衡东县",
+ "430426": "祁东县",
+ "430481": "耒阳市",
+ "430482": "常宁市",
+ "430502": "双清区",
+ "430503": "大祥区",
+ "430511": "北塔区",
+ "430522": "新邵县",
+ "430523": "邵阳县",
+ "430524": "隆回县",
+ "430525": "洞口县",
+ "430527": "绥宁县",
+ "430528": "新宁县",
+ "430529": "城步苗族自治县",
+ "430581": "武冈市",
+ "430582": "邵东市",
+ "430602": "岳阳楼区",
+ "430603": "云溪区",
+ "430611": "君山区",
+ "430621": "岳阳县",
+ "430623": "华容县",
+ "430624": "湘阴县",
+ "430626": "平江县",
+ "430681": "汨罗市",
+ "430682": "临湘市",
+ "430702": "武陵区",
+ "430703": "鼎城区",
+ "430721": "安乡县",
+ "430722": "汉寿县",
+ "430723": "澧县",
+ "430724": "临澧县",
+ "430725": "桃源县",
+ "430726": "石门县",
+ "430781": "津市市",
+ "430802": "永定区",
+ "430811": "武陵源区",
+ "430821": "慈利县",
+ "430822": "桑植县",
+ "430902": "资阳区",
+ "430903": "赫山区",
+ "430921": "南县",
+ "430922": "桃江县",
+ "430923": "安化县",
+ "430971": "益阳市大通湖管理区",
+ "430981": "沅江市",
+ "431002": "北湖区",
+ "431003": "苏仙区",
+ "431021": "桂阳县",
+ "431022": "宜章县",
+ "431023": "永兴县",
+ "431024": "嘉禾县",
+ "431025": "临武县",
+ "431026": "汝城县",
+ "431027": "桂东县",
+ "431028": "安仁县",
+ "431081": "资兴市",
+ "431102": "零陵区",
+ "431103": "冷水滩区",
+ "431122": "东安县",
+ "431123": "双牌县",
+ "431124": "道县",
+ "431125": "江永县",
+ "431126": "宁远县",
+ "431127": "蓝山县",
+ "431128": "新田县",
+ "431129": "江华瑶族自治县",
+ "431181": "祁阳市",
+ "431202": "鹤城区",
+ "431221": "中方县",
+ "431222": "沅陵县",
+ "431223": "辰溪县",
+ "431224": "溆浦县",
+ "431225": "会同县",
+ "431226": "麻阳苗族自治县",
+ "431227": "新晃侗族自治县",
+ "431228": "芷江侗族自治县",
+ "431229": "靖州苗族侗族自治县",
+ "431230": "通道侗族自治县",
+ "431271": "怀化市洪江管理区",
+ "431281": "洪江市",
+ "431302": "娄星区",
+ "431321": "双峰县",
+ "431322": "新化县",
+ "431381": "冷水江市",
+ "431382": "涟源市",
+ "433101": "吉首市",
+ "433122": "泸溪县",
+ "433123": "凤凰县",
+ "433124": "花垣县",
+ "433125": "保靖县",
+ "433126": "古丈县",
+ "433127": "永顺县",
+ "433130": "龙山县",
+ "440103": "荔湾区",
+ "440104": "越秀区",
+ "440105": "海珠区",
+ "440106": "天河区",
+ "440111": "白云区",
+ "440112": "黄埔区",
+ "440113": "番禺区",
+ "440114": "花都区",
+ "440115": "南沙区",
+ "440117": "从化区",
+ "440118": "增城区",
+ "440203": "武江区",
+ "440204": "浈江区",
+ "440205": "曲江区",
+ "440222": "始兴县",
+ "440224": "仁化县",
+ "440229": "翁源县",
+ "440232": "乳源瑶族自治县",
+ "440233": "新丰县",
+ "440281": "乐昌市",
+ "440282": "南雄市",
+ "440303": "罗湖区",
+ "440304": "福田区",
+ "440305": "南山区",
+ "440306": "宝安区",
+ "440307": "龙岗区",
+ "440308": "盐田区",
+ "440309": "龙华区",
+ "440310": "坪山区",
+ "440311": "光明区",
+ "440402": "香洲区",
+ "440403": "斗门区",
+ "440404": "金湾区",
+ "440507": "龙湖区",
+ "440511": "金平区",
+ "440512": "濠江区",
+ "440513": "潮阳区",
+ "440514": "潮南区",
+ "440515": "澄海区",
+ "440523": "南澳县",
+ "440604": "禅城区",
+ "440605": "南海区",
+ "440606": "顺德区",
+ "440607": "三水区",
+ "440608": "高明区",
+ "440703": "蓬江区",
+ "440704": "江海区",
+ "440705": "新会区",
+ "440781": "台山市",
+ "440783": "开平市",
+ "440784": "鹤山市",
+ "440785": "恩平市",
+ "440802": "赤坎区",
+ "440803": "霞山区",
+ "440804": "坡头区",
+ "440811": "麻章区",
+ "440823": "遂溪县",
+ "440825": "徐闻县",
+ "440881": "廉江市",
+ "440882": "雷州市",
+ "440883": "吴川市",
+ "440890": "经济技术开发区",
+ "440902": "茂南区",
+ "440904": "电白区",
+ "440981": "高州市",
+ "440982": "化州市",
+ "440983": "信宜市",
+ "441202": "端州区",
+ "441203": "鼎湖区",
+ "441204": "高要区",
+ "441223": "广宁县",
+ "441224": "怀集县",
+ "441225": "封开县",
+ "441226": "德庆县",
+ "441284": "四会市",
+ "441302": "惠城区",
+ "441303": "惠阳区",
+ "441322": "博罗县",
+ "441323": "惠东县",
+ "441324": "龙门县",
+ "441402": "梅江区",
+ "441403": "梅县区",
+ "441422": "大埔县",
+ "441423": "丰顺县",
+ "441424": "五华县",
+ "441426": "平远县",
+ "441427": "蕉岭县",
+ "441481": "兴宁市",
+ "441502": "城区",
+ "441521": "海丰县",
+ "441523": "陆河县",
+ "441581": "陆丰市",
+ "441602": "源城区",
+ "441621": "紫金县",
+ "441622": "龙川县",
+ "441623": "连平县",
+ "441624": "和平县",
+ "441625": "东源县",
+ "441702": "江城区",
+ "441704": "阳东区",
+ "441721": "阳西县",
+ "441781": "阳春市",
+ "441802": "清城区",
+ "441803": "清新区",
+ "441821": "佛冈县",
+ "441823": "阳山县",
+ "441825": "连山壮族瑶族自治县",
+ "441826": "连南瑶族自治县",
+ "441881": "英德市",
+ "441882": "连州市",
+ "441901": "中堂镇",
+ "441903": "南城街道",
+ "441904": "长安镇",
+ "441905": "东坑镇",
+ "441906": "樟木头镇",
+ "441907": "莞城街道",
+ "441908": "石龙镇",
+ "441909": "桥头镇",
+ "441910": "万江街道",
+ "441911": "麻涌镇",
+ "441912": "虎门镇",
+ "441913": "谢岗镇",
+ "441914": "石碣镇",
+ "441915": "茶山镇",
+ "441916": "东城街道",
+ "441917": "洪梅镇",
+ "441918": "道滘镇",
+ "441919": "高埗镇",
+ "441920": "企石镇",
+ "441921": "凤岗镇",
+ "441922": "大岭山镇",
+ "441923": "松山湖",
+ "441924": "清溪镇",
+ "441925": "望牛墩镇",
+ "441926": "厚街镇",
+ "441927": "常平镇",
+ "441928": "寮步镇",
+ "441929": "石排镇",
+ "441930": "横沥镇",
+ "441931": "塘厦镇",
+ "441932": "黄江镇",
+ "441933": "大朗镇",
+ "441934": "东莞港",
+ "441935": "东莞生态园",
+ "441990": "沙田镇",
+ "442001": "南头镇",
+ "442002": "神湾镇",
+ "442003": "东凤镇",
+ "442004": "五桂山街道",
+ "442005": "黄圃镇",
+ "442006": "小榄镇",
+ "442007": "石岐街道",
+ "442008": "横栏镇",
+ "442009": "三角镇",
+ "442010": "三乡镇",
+ "442011": "港口镇",
+ "442012": "沙溪镇",
+ "442013": "板芙镇",
+ "442015": "东升镇",
+ "442016": "阜沙镇",
+ "442017": "民众镇",
+ "442018": "东区街道",
+ "442019": "火炬开发区街道办事处",
+ "442020": "西区街道",
+ "442021": "南区街道",
+ "442022": "古镇镇",
+ "442023": "坦洲镇",
+ "442024": "大涌镇",
+ "442025": "南朗镇",
+ "445102": "湘桥区",
+ "445103": "潮安区",
+ "445122": "饶平县",
+ "445202": "榕城区",
+ "445203": "揭东区",
+ "445222": "揭西县",
+ "445224": "惠来县",
+ "445281": "普宁市",
+ "445302": "云城区",
+ "445303": "云安区",
+ "445321": "新兴县",
+ "445322": "郁南县",
+ "445381": "罗定市",
+ "450102": "兴宁区",
+ "450103": "青秀区",
+ "450105": "江南区",
+ "450107": "西乡塘区",
+ "450108": "良庆区",
+ "450109": "邕宁区",
+ "450110": "武鸣区",
+ "450123": "隆安县",
+ "450124": "马山县",
+ "450125": "上林县",
+ "450126": "宾阳县",
+ "450181": "横州市",
+ "450202": "城中区",
+ "450203": "鱼峰区",
+ "450204": "柳南区",
+ "450205": "柳北区",
+ "450206": "柳江区",
+ "450222": "柳城县",
+ "450223": "鹿寨县",
+ "450224": "融安县",
+ "450225": "融水苗族自治县",
+ "450226": "三江侗族自治县",
+ "450302": "秀峰区",
+ "450303": "叠彩区",
+ "450304": "象山区",
+ "450305": "七星区",
+ "450311": "雁山区",
+ "450312": "临桂区",
+ "450321": "阳朔县",
+ "450323": "灵川县",
+ "450324": "全州县",
+ "450325": "兴安县",
+ "450326": "永福县",
+ "450327": "灌阳县",
+ "450328": "龙胜各族自治县",
+ "450329": "资源县",
+ "450330": "平乐县",
+ "450332": "恭城瑶族自治县",
+ "450381": "荔浦市",
+ "450403": "万秀区",
+ "450405": "长洲区",
+ "450406": "龙圩区",
+ "450421": "苍梧县",
+ "450422": "藤县",
+ "450423": "蒙山县",
+ "450481": "岑溪市",
+ "450502": "海城区",
+ "450503": "银海区",
+ "450512": "铁山港区",
+ "450521": "合浦县",
+ "450602": "港口区",
+ "450603": "防城区",
+ "450621": "上思县",
+ "450681": "东兴市",
+ "450702": "钦南区",
+ "450703": "钦北区",
+ "450721": "灵山县",
+ "450722": "浦北县",
+ "450802": "港北区",
+ "450803": "港南区",
+ "450804": "覃塘区",
+ "450821": "平南县",
+ "450881": "桂平市",
+ "450902": "玉州区",
+ "450903": "福绵区",
+ "450921": "容县",
+ "450922": "陆川县",
+ "450923": "博白县",
+ "450924": "兴业县",
+ "450981": "北流市",
+ "451002": "右江区",
+ "451003": "田阳区",
+ "451022": "田东县",
+ "451024": "德保县",
+ "451026": "那坡县",
+ "451027": "凌云县",
+ "451028": "乐业县",
+ "451029": "田林县",
+ "451030": "西林县",
+ "451031": "隆林各族自治县",
+ "451081": "靖西市",
+ "451082": "平果市",
+ "451102": "八步区",
+ "451103": "平桂区",
+ "451121": "昭平县",
+ "451122": "钟山县",
+ "451123": "富川瑶族自治县",
+ "451202": "金城江区",
+ "451203": "宜州区",
+ "451221": "南丹县",
+ "451222": "天峨县",
+ "451223": "凤山县",
+ "451224": "东兰县",
+ "451225": "罗城仫佬族自治县",
+ "451226": "环江毛南族自治县",
+ "451227": "巴马瑶族自治县",
+ "451228": "都安瑶族自治县",
+ "451229": "大化瑶族自治县",
+ "451302": "兴宾区",
+ "451321": "忻城县",
+ "451322": "象州县",
+ "451323": "武宣县",
+ "451324": "金秀瑶族自治县",
+ "451381": "合山市",
+ "451402": "江州区",
+ "451421": "扶绥县",
+ "451422": "宁明县",
+ "451423": "龙州县",
+ "451424": "大新县",
+ "451425": "天等县",
+ "451481": "凭祥市",
+ "460105": "秀英区",
+ "460106": "龙华区",
+ "460107": "琼山区",
+ "460108": "美兰区",
+ "460202": "海棠区",
+ "460203": "吉阳区",
+ "460204": "天涯区",
+ "460205": "崖州区",
+ "460321": "西沙区",
+ "460322": "南沙区",
+ "460401": "那大镇",
+ "460402": "和庆镇",
+ "460403": "南丰镇",
+ "460404": "大成镇",
+ "460405": "雅星镇",
+ "460406": "兰洋镇",
+ "460407": "光村镇",
+ "460408": "木棠镇",
+ "460409": "海头镇",
+ "460410": "峨蔓镇",
+ "460411": "王五镇",
+ "460412": "白马井镇",
+ "460413": "中和镇",
+ "460414": "排浦镇",
+ "460415": "东成镇",
+ "460416": "新州镇",
+ "460417": "洋浦经济开发区",
+ "460418": "华南热作学院",
+ "469001": "五指山市",
+ "469002": "琼海市",
+ "469005": "文昌市",
+ "469006": "万宁市",
+ "469007": "东方市",
+ "469021": "定安县",
+ "469022": "屯昌县",
+ "469023": "澄迈县",
+ "469024": "临高县",
+ "469025": "白沙黎族自治县",
+ "469026": "昌江黎族自治县",
+ "469027": "乐东黎族自治县",
+ "469028": "陵水黎族自治县",
+ "469029": "保亭黎族苗族自治县",
+ "469030": "琼中黎族苗族自治县",
+ "500101": "万州区",
+ "500102": "涪陵区",
+ "500103": "渝中区",
+ "500104": "大渡口区",
+ "500105": "江北区",
+ "500106": "沙坪坝区",
+ "500107": "九龙坡区",
+ "500108": "南岸区",
+ "500109": "北碚区",
+ "500110": "綦江区",
+ "500111": "大足区",
+ "500112": "渝北区",
+ "500113": "巴南区",
+ "500114": "黔江区",
+ "500115": "长寿区",
+ "500116": "江津区",
+ "500117": "合川区",
+ "500118": "永川区",
+ "500119": "南川区",
+ "500120": "璧山区",
+ "500151": "铜梁区",
+ "500152": "潼南区",
+ "500153": "荣昌区",
+ "500154": "开州区",
+ "500155": "梁平区",
+ "500156": "武隆区",
+ "500229": "城口县",
+ "500230": "丰都县",
+ "500231": "垫江县",
+ "500233": "忠县",
+ "500235": "云阳县",
+ "500236": "奉节县",
+ "500237": "巫山县",
+ "500238": "巫溪县",
+ "500240": "石柱土家族自治县",
+ "500241": "秀山土家族苗族自治县",
+ "500242": "酉阳土家族苗族自治县",
+ "500243": "彭水苗族土家族自治县",
+ "510104": "锦江区",
+ "510105": "青羊区",
+ "510106": "金牛区",
+ "510107": "武侯区",
+ "510108": "成华区",
+ "510112": "龙泉驿区",
+ "510113": "青白江区",
+ "510114": "新都区",
+ "510115": "温江区",
+ "510116": "双流区",
+ "510117": "郫都区",
+ "510118": "新津区",
+ "510121": "金堂县",
+ "510129": "大邑县",
+ "510131": "蒲江县",
+ "510181": "都江堰市",
+ "510182": "彭州市",
+ "510183": "邛崃市",
+ "510184": "崇州市",
+ "510185": "简阳市",
+ "510191": "高新区",
+ "510302": "自流井区",
+ "510303": "贡井区",
+ "510304": "大安区",
+ "510311": "沿滩区",
+ "510321": "荣县",
+ "510322": "富顺县",
+ "510402": "东区",
+ "510403": "西区",
+ "510411": "仁和区",
+ "510421": "米易县",
+ "510422": "盐边县",
+ "510502": "江阳区",
+ "510503": "纳溪区",
+ "510504": "龙马潭区",
+ "510521": "泸县",
+ "510522": "合江县",
+ "510524": "叙永县",
+ "510525": "古蔺县",
+ "510603": "旌阳区",
+ "510604": "罗江区",
+ "510623": "中江县",
+ "510681": "广汉市",
+ "510682": "什邡市",
+ "510683": "绵竹市",
+ "510703": "涪城区",
+ "510704": "游仙区",
+ "510705": "安州区",
+ "510722": "三台县",
+ "510723": "盐亭县",
+ "510725": "梓潼县",
+ "510726": "北川羌族自治县",
+ "510727": "平武县",
+ "510781": "江油市",
+ "510791": "高新区",
+ "510802": "利州区",
+ "510811": "昭化区",
+ "510812": "朝天区",
+ "510821": "旺苍县",
+ "510822": "青川县",
+ "510823": "剑阁县",
+ "510824": "苍溪县",
+ "510903": "船山区",
+ "510904": "安居区",
+ "510921": "蓬溪县",
+ "510923": "大英县",
+ "510981": "射洪市",
+ "511002": "市中区",
+ "511011": "东兴区",
+ "511024": "威远县",
+ "511025": "资中县",
+ "511083": "隆昌市",
+ "511102": "市中区",
+ "511111": "沙湾区",
+ "511112": "五通桥区",
+ "511113": "金口河区",
+ "511123": "犍为县",
+ "511124": "井研县",
+ "511126": "夹江县",
+ "511129": "沐川县",
+ "511132": "峨边彝族自治县",
+ "511133": "马边彝族自治县",
+ "511181": "峨眉山市",
+ "511302": "顺庆区",
+ "511303": "高坪区",
+ "511304": "嘉陵区",
+ "511321": "南部县",
+ "511322": "营山县",
+ "511323": "蓬安县",
+ "511324": "仪陇县",
+ "511325": "西充县",
+ "511381": "阆中市",
+ "511402": "东坡区",
+ "511403": "彭山区",
+ "511421": "仁寿县",
+ "511423": "洪雅县",
+ "511424": "丹棱县",
+ "511425": "青神县",
+ "511502": "翠屏区",
+ "511503": "南溪区",
+ "511504": "叙州区",
+ "511523": "江安县",
+ "511524": "长宁县",
+ "511525": "高县",
+ "511526": "珙县",
+ "511527": "筠连县",
+ "511528": "兴文县",
+ "511529": "屏山县",
+ "511602": "广安区",
+ "511603": "前锋区",
+ "511621": "岳池县",
+ "511622": "武胜县",
+ "511623": "邻水县",
+ "511681": "华蓥市",
+ "511702": "通川区",
+ "511703": "达川区",
+ "511722": "宣汉县",
+ "511723": "开江县",
+ "511724": "大竹县",
+ "511725": "渠县",
+ "511781": "万源市",
+ "511802": "雨城区",
+ "511803": "名山区",
+ "511822": "荥经县",
+ "511823": "汉源县",
+ "511824": "石棉县",
+ "511825": "天全县",
+ "511826": "芦山县",
+ "511827": "宝兴县",
+ "511902": "巴州区",
+ "511903": "恩阳区",
+ "511921": "通江县",
+ "511922": "南江县",
+ "511923": "平昌县",
+ "511971": "巴中经济开发区",
+ "512002": "雁江区",
+ "512021": "安岳县",
+ "512022": "乐至县",
+ "513201": "马尔康市",
+ "513221": "汶川县",
+ "513222": "理县",
+ "513223": "茂县",
+ "513224": "松潘县",
+ "513225": "九寨沟县",
+ "513226": "金川县",
+ "513227": "小金县",
+ "513228": "黑水县",
+ "513230": "壤塘县",
+ "513231": "阿坝县",
+ "513232": "若尔盖县",
+ "513233": "红原县",
+ "513301": "康定市",
+ "513322": "泸定县",
+ "513323": "丹巴县",
+ "513324": "九龙县",
+ "513325": "雅江县",
+ "513326": "道孚县",
+ "513327": "炉霍县",
+ "513328": "甘孜县",
+ "513329": "新龙县",
+ "513330": "德格县",
+ "513331": "白玉县",
+ "513332": "石渠县",
+ "513333": "色达县",
+ "513334": "理塘县",
+ "513335": "巴塘县",
+ "513336": "乡城县",
+ "513337": "稻城县",
+ "513338": "得荣县",
+ "513401": "西昌市",
+ "513402": "会理市",
+ "513422": "木里藏族自治县",
+ "513423": "盐源县",
+ "513424": "德昌县",
+ "513426": "会东县",
+ "513427": "宁南县",
+ "513428": "普格县",
+ "513429": "布拖县",
+ "513430": "金阳县",
+ "513431": "昭觉县",
+ "513432": "喜德县",
+ "513433": "冕宁县",
+ "513434": "越西县",
+ "513435": "甘洛县",
+ "513436": "美姑县",
+ "513437": "雷波县",
+ "520102": "南明区",
+ "520103": "云岩区",
+ "520111": "花溪区",
+ "520112": "乌当区",
+ "520113": "白云区",
+ "520115": "观山湖区",
+ "520121": "开阳县",
+ "520122": "息烽县",
+ "520123": "修文县",
+ "520181": "清镇市",
+ "520201": "钟山区",
+ "520203": "六枝特区",
+ "520204": "水城区",
+ "520281": "盘州市",
+ "520302": "红花岗区",
+ "520303": "汇川区",
+ "520304": "播州区",
+ "520322": "桐梓县",
+ "520323": "绥阳县",
+ "520324": "正安县",
+ "520325": "道真仡佬族苗族自治县",
+ "520326": "务川仡佬族苗族自治县",
+ "520327": "凤冈县",
+ "520328": "湄潭县",
+ "520329": "余庆县",
+ "520330": "习水县",
+ "520381": "赤水市",
+ "520382": "仁怀市",
+ "520402": "西秀区",
+ "520403": "平坝区",
+ "520422": "普定县",
+ "520423": "镇宁布依族苗族自治县",
+ "520424": "关岭布依族苗族自治县",
+ "520425": "紫云苗族布依族自治县",
+ "520502": "七星关区",
+ "520521": "大方县",
+ "520523": "金沙县",
+ "520524": "织金县",
+ "520525": "纳雍县",
+ "520526": "威宁彝族回族苗族自治县",
+ "520527": "赫章县",
+ "520581": "黔西市",
+ "520602": "碧江区",
+ "520603": "万山区",
+ "520621": "江口县",
+ "520622": "玉屏侗族自治县",
+ "520623": "石阡县",
+ "520624": "思南县",
+ "520625": "印江土家族苗族自治县",
+ "520626": "德江县",
+ "520627": "沿河土家族自治县",
+ "520628": "松桃苗族自治县",
+ "522301": "兴义市",
+ "522302": "兴仁市",
+ "522323": "普安县",
+ "522324": "晴隆县",
+ "522325": "贞丰县",
+ "522326": "望谟县",
+ "522327": "册亨县",
+ "522328": "安龙县",
+ "522601": "凯里市",
+ "522622": "黄平县",
+ "522623": "施秉县",
+ "522624": "三穗县",
+ "522625": "镇远县",
+ "522626": "岑巩县",
+ "522627": "天柱县",
+ "522628": "锦屏县",
+ "522629": "剑河县",
+ "522630": "台江县",
+ "522631": "黎平县",
+ "522632": "榕江县",
+ "522633": "从江县",
+ "522634": "雷山县",
+ "522635": "麻江县",
+ "522636": "丹寨县",
+ "522701": "都匀市",
+ "522702": "福泉市",
+ "522722": "荔波县",
+ "522723": "贵定县",
+ "522725": "瓮安县",
+ "522726": "独山县",
+ "522727": "平塘县",
+ "522728": "罗甸县",
+ "522729": "长顺县",
+ "522730": "龙里县",
+ "522731": "惠水县",
+ "522732": "三都水族自治县",
+ "530102": "五华区",
+ "530103": "盘龙区",
+ "530111": "官渡区",
+ "530112": "西山区",
+ "530113": "东川区",
+ "530114": "呈贡区",
+ "530115": "晋宁区",
+ "530124": "富民县",
+ "530125": "宜良县",
+ "530126": "石林彝族自治县",
+ "530127": "嵩明县",
+ "530128": "禄劝彝族苗族自治县",
+ "530129": "寻甸回族彝族自治县",
+ "530181": "安宁市",
+ "530302": "麒麟区",
+ "530303": "沾益区",
+ "530304": "马龙区",
+ "530322": "陆良县",
+ "530323": "师宗县",
+ "530324": "罗平县",
+ "530325": "富源县",
+ "530326": "会泽县",
+ "530381": "宣威市",
+ "530402": "红塔区",
+ "530403": "江川区",
+ "530423": "通海县",
+ "530424": "华宁县",
+ "530425": "易门县",
+ "530426": "峨山彝族自治县",
+ "530427": "新平彝族傣族自治县",
+ "530428": "元江哈尼族彝族傣族自治县",
+ "530481": "澄江市",
+ "530502": "隆阳区",
+ "530521": "施甸县",
+ "530523": "龙陵县",
+ "530524": "昌宁县",
+ "530581": "腾冲市",
+ "530602": "昭阳区",
+ "530621": "鲁甸县",
+ "530622": "巧家县",
+ "530623": "盐津县",
+ "530624": "大关县",
+ "530625": "永善县",
+ "530626": "绥江县",
+ "530627": "镇雄县",
+ "530628": "彝良县",
+ "530629": "威信县",
+ "530681": "水富市",
+ "530702": "古城区",
+ "530721": "玉龙纳西族自治县",
+ "530722": "永胜县",
+ "530723": "华坪县",
+ "530724": "宁蒗彝族自治县",
+ "530802": "思茅区",
+ "530821": "宁洱哈尼族彝族自治县",
+ "530822": "墨江哈尼族自治县",
+ "530823": "景东彝族自治县",
+ "530824": "景谷傣族彝族自治县",
+ "530825": "镇沅彝族哈尼族拉祜族自治县",
+ "530826": "江城哈尼族彝族自治县",
+ "530827": "孟连傣族拉祜族佤族自治县",
+ "530828": "澜沧拉祜族自治县",
+ "530829": "西盟佤族自治县",
+ "530902": "临翔区",
+ "530921": "凤庆县",
+ "530922": "云县",
+ "530923": "永德县",
+ "530924": "镇康县",
+ "530925": "双江拉祜族佤族布朗族傣族自治县",
+ "530926": "耿马傣族佤族自治县",
+ "530927": "沧源佤族自治县",
+ "532301": "楚雄市",
+ "532302": "禄丰市",
+ "532322": "双柏县",
+ "532323": "牟定县",
+ "532324": "南华县",
+ "532325": "姚安县",
+ "532326": "大姚县",
+ "532327": "永仁县",
+ "532328": "元谋县",
+ "532329": "武定县",
+ "532501": "个旧市",
+ "532502": "开远市",
+ "532503": "蒙自市",
+ "532504": "弥勒市",
+ "532523": "屏边苗族自治县",
+ "532524": "建水县",
+ "532525": "石屏县",
+ "532527": "泸西县",
+ "532528": "元阳县",
+ "532529": "红河县",
+ "532530": "金平苗族瑶族傣族自治县",
+ "532531": "绿春县",
+ "532532": "河口瑶族自治县",
+ "532601": "文山市",
+ "532622": "砚山县",
+ "532623": "西畴县",
+ "532624": "麻栗坡县",
+ "532625": "马关县",
+ "532626": "丘北县",
+ "532627": "广南县",
+ "532628": "富宁县",
+ "532801": "景洪市",
+ "532822": "勐海县",
+ "532823": "勐腊县",
+ "532901": "大理市",
+ "532922": "漾濞彝族自治县",
+ "532923": "祥云县",
+ "532924": "宾川县",
+ "532925": "弥渡县",
+ "532926": "南涧彝族自治县",
+ "532927": "巍山彝族回族自治县",
+ "532928": "永平县",
+ "532929": "云龙县",
+ "532930": "洱源县",
+ "532931": "剑川县",
+ "532932": "鹤庆县",
+ "533102": "瑞丽市",
+ "533103": "芒市",
+ "533122": "梁河县",
+ "533123": "盈江县",
+ "533124": "陇川县",
+ "533301": "泸水市",
+ "533323": "福贡县",
+ "533324": "贡山独龙族怒族自治县",
+ "533325": "兰坪白族普米族自治县",
+ "533401": "香格里拉市",
+ "533422": "德钦县",
+ "533423": "维西傈僳族自治县",
+ "540102": "城关区",
+ "540103": "堆龙德庆区",
+ "540104": "达孜区",
+ "540121": "林周县",
+ "540122": "当雄县",
+ "540123": "尼木县",
+ "540124": "曲水县",
+ "540127": "墨竹工卡县",
+ "540202": "桑珠孜区",
+ "540221": "南木林县",
+ "540222": "江孜县",
+ "540223": "定日县",
+ "540224": "萨迦县",
+ "540225": "拉孜县",
+ "540226": "昂仁县",
+ "540227": "谢通门县",
+ "540228": "白朗县",
+ "540229": "仁布县",
+ "540230": "康马县",
+ "540231": "定结县",
+ "540232": "仲巴县",
+ "540233": "亚东县",
+ "540234": "吉隆县",
+ "540235": "聂拉木县",
+ "540236": "萨嘎县",
+ "540237": "岗巴县",
+ "540302": "卡若区",
+ "540321": "江达县",
+ "540322": "贡觉县",
+ "540323": "类乌齐县",
+ "540324": "丁青县",
+ "540325": "察雅县",
+ "540326": "八宿县",
+ "540327": "左贡县",
+ "540328": "芒康县",
+ "540329": "洛隆县",
+ "540330": "边坝县",
+ "540402": "巴宜区",
+ "540421": "工布江达县",
+ "540423": "墨脱县",
+ "540424": "波密县",
+ "540425": "察隅县",
+ "540426": "朗县",
+ "540481": "米林市",
+ "540502": "乃东区",
+ "540521": "扎囊县",
+ "540522": "贡嘎县",
+ "540523": "桑日县",
+ "540524": "琼结县",
+ "540525": "曲松县",
+ "540526": "措美县",
+ "540527": "洛扎县",
+ "540528": "加查县",
+ "540529": "隆子县",
+ "540531": "浪卡子县",
+ "540581": "错那市",
+ "540602": "色尼区",
+ "540621": "嘉黎县",
+ "540622": "比如县",
+ "540623": "聂荣县",
+ "540624": "安多县",
+ "540625": "申扎县",
+ "540626": "索县",
+ "540627": "班戈县",
+ "540628": "巴青县",
+ "540629": "尼玛县",
+ "540630": "双湖县",
+ "542521": "普兰县",
+ "542522": "札达县",
+ "542523": "噶尔县",
+ "542524": "日土县",
+ "542525": "革吉县",
+ "542526": "改则县",
+ "542527": "措勤县",
+ "610102": "新城区",
+ "610103": "碑林区",
+ "610104": "莲湖区",
+ "610111": "灞桥区",
+ "610112": "未央区",
+ "610113": "雁塔区",
+ "610114": "阎良区",
+ "610115": "临潼区",
+ "610116": "长安区",
+ "610117": "高陵区",
+ "610118": "鄠邑区",
+ "610122": "蓝田县",
+ "610124": "周至县",
+ "610202": "王益区",
+ "610203": "印台区",
+ "610204": "耀州区",
+ "610222": "宜君县",
+ "610302": "渭滨区",
+ "610303": "金台区",
+ "610304": "陈仓区",
+ "610305": "凤翔区",
+ "610323": "岐山县",
+ "610324": "扶风县",
+ "610326": "眉县",
+ "610327": "陇县",
+ "610328": "千阳县",
+ "610329": "麟游县",
+ "610330": "凤县",
+ "610331": "太白县",
+ "610402": "秦都区",
+ "610403": "杨陵区",
+ "610404": "渭城区",
+ "610422": "三原县",
+ "610423": "泾阳县",
+ "610424": "乾县",
+ "610425": "礼泉县",
+ "610426": "永寿县",
+ "610428": "长武县",
+ "610429": "旬邑县",
+ "610430": "淳化县",
+ "610431": "武功县",
+ "610481": "兴平市",
+ "610482": "彬州市",
+ "610502": "临渭区",
+ "610503": "华州区",
+ "610522": "潼关县",
+ "610523": "大荔县",
+ "610524": "合阳县",
+ "610525": "澄城县",
+ "610526": "蒲城县",
+ "610527": "白水县",
+ "610528": "富平县",
+ "610581": "韩城市",
+ "610582": "华阴市",
+ "610602": "宝塔区",
+ "610603": "安塞区",
+ "610621": "延长县",
+ "610622": "延川县",
+ "610625": "志丹县",
+ "610626": "吴起县",
+ "610627": "甘泉县",
+ "610628": "富县",
+ "610629": "洛川县",
+ "610630": "宜川县",
+ "610631": "黄龙县",
+ "610632": "黄陵县",
+ "610681": "子长市",
+ "610702": "汉台区",
+ "610703": "南郑区",
+ "610722": "城固县",
+ "610723": "洋县",
+ "610724": "西乡县",
+ "610725": "勉县",
+ "610726": "宁强县",
+ "610727": "略阳县",
+ "610728": "镇巴县",
+ "610729": "留坝县",
+ "610730": "佛坪县",
+ "610802": "榆阳区",
+ "610803": "横山区",
+ "610822": "府谷县",
+ "610824": "靖边县",
+ "610825": "定边县",
+ "610826": "绥德县",
+ "610827": "米脂县",
+ "610828": "佳县",
+ "610829": "吴堡县",
+ "610830": "清涧县",
+ "610831": "子洲县",
+ "610881": "神木市",
+ "610902": "汉滨区",
+ "610921": "汉阴县",
+ "610922": "石泉县",
+ "610923": "宁陕县",
+ "610924": "紫阳县",
+ "610925": "岚皋县",
+ "610926": "平利县",
+ "610927": "镇坪县",
+ "610929": "白河县",
+ "610981": "旬阳市",
+ "611002": "商州区",
+ "611021": "洛南县",
+ "611022": "丹凤县",
+ "611023": "商南县",
+ "611024": "山阳县",
+ "611025": "镇安县",
+ "611026": "柞水县",
+ "620102": "城关区",
+ "620103": "七里河区",
+ "620104": "西固区",
+ "620105": "安宁区",
+ "620111": "红古区",
+ "620121": "永登县",
+ "620122": "皋兰县",
+ "620123": "榆中县",
+ "620171": "兰州新区",
+ "620201": "市辖区",
+ "620290": "雄关区",
+ "620291": "长城区",
+ "620292": "镜铁区",
+ "620293": "新城镇",
+ "620294": "峪泉镇",
+ "620295": "文殊镇",
+ "620302": "金川区",
+ "620321": "永昌县",
+ "620402": "白银区",
+ "620403": "平川区",
+ "620421": "靖远县",
+ "620422": "会宁县",
+ "620423": "景泰县",
+ "620502": "秦州区",
+ "620503": "麦积区",
+ "620521": "清水县",
+ "620522": "秦安县",
+ "620523": "甘谷县",
+ "620524": "武山县",
+ "620525": "张家川回族自治县",
+ "620602": "凉州区",
+ "620621": "民勤县",
+ "620622": "古浪县",
+ "620623": "天祝藏族自治县",
+ "620702": "甘州区",
+ "620721": "肃南裕固族自治县",
+ "620722": "民乐县",
+ "620723": "临泽县",
+ "620724": "高台县",
+ "620725": "山丹县",
+ "620802": "崆峒区",
+ "620821": "泾川县",
+ "620822": "灵台县",
+ "620823": "崇信县",
+ "620825": "庄浪县",
+ "620826": "静宁县",
+ "620881": "华亭市",
+ "620902": "肃州区",
+ "620921": "金塔县",
+ "620922": "瓜州县",
+ "620923": "肃北蒙古族自治县",
+ "620924": "阿克塞哈萨克族自治县",
+ "620981": "玉门市",
+ "620982": "敦煌市",
+ "621002": "西峰区",
+ "621021": "庆城县",
+ "621022": "环县",
+ "621023": "华池县",
+ "621024": "合水县",
+ "621025": "正宁县",
+ "621026": "宁县",
+ "621027": "镇原县",
+ "621102": "安定区",
+ "621121": "通渭县",
+ "621122": "陇西县",
+ "621123": "渭源县",
+ "621124": "临洮县",
+ "621125": "漳县",
+ "621126": "岷县",
+ "621202": "武都区",
+ "621221": "成县",
+ "621222": "文县",
+ "621223": "宕昌县",
+ "621224": "康县",
+ "621225": "西和县",
+ "621226": "礼县",
+ "621227": "徽县",
+ "621228": "两当县",
+ "622901": "临夏市",
+ "622921": "临夏县",
+ "622922": "康乐县",
+ "622923": "永靖县",
+ "622924": "广河县",
+ "622925": "和政县",
+ "622926": "东乡族自治县",
+ "622927": "积石山保安族东乡族撒拉族自治县",
+ "623001": "合作市",
+ "623021": "临潭县",
+ "623022": "卓尼县",
+ "623023": "舟曲县",
+ "623024": "迭部县",
+ "623025": "玛曲县",
+ "623026": "碌曲县",
+ "623027": "夏河县",
+ "630102": "城东区",
+ "630103": "城中区",
+ "630104": "城西区",
+ "630105": "城北区",
+ "630106": "湟中区",
+ "630121": "大通回族土族自治县",
+ "630123": "湟源县",
+ "630202": "乐都区",
+ "630203": "平安区",
+ "630222": "民和回族土族自治县",
+ "630223": "互助土族自治县",
+ "630224": "化隆回族自治县",
+ "630225": "循化撒拉族自治县",
+ "632221": "门源回族自治县",
+ "632222": "祁连县",
+ "632223": "海晏县",
+ "632224": "刚察县",
+ "632301": "同仁市",
+ "632322": "尖扎县",
+ "632323": "泽库县",
+ "632324": "河南蒙古族自治县",
+ "632521": "共和县",
+ "632522": "同德县",
+ "632523": "贵德县",
+ "632524": "兴海县",
+ "632525": "贵南县",
+ "632621": "玛沁县",
+ "632622": "班玛县",
+ "632623": "甘德县",
+ "632624": "达日县",
+ "632625": "久治县",
+ "632626": "玛多县",
+ "632701": "玉树市",
+ "632722": "杂多县",
+ "632723": "称多县",
+ "632724": "治多县",
+ "632725": "囊谦县",
+ "632726": "曲麻莱县",
+ "632801": "格尔木市",
+ "632802": "德令哈市",
+ "632803": "茫崖市",
+ "632821": "乌兰县",
+ "632822": "都兰县",
+ "632823": "天峻县",
+ "632857": "大柴旦行政委员会",
+ "640104": "兴庆区",
+ "640105": "西夏区",
+ "640106": "金凤区",
+ "640121": "永宁县",
+ "640122": "贺兰县",
+ "640181": "灵武市",
+ "640202": "大武口区",
+ "640205": "惠农区",
+ "640221": "平罗县",
+ "640302": "利通区",
+ "640303": "红寺堡区",
+ "640323": "盐池县",
+ "640324": "同心县",
+ "640381": "青铜峡市",
+ "640402": "原州区",
+ "640422": "西吉县",
+ "640423": "隆德县",
+ "640424": "泾源县",
+ "640425": "彭阳县",
+ "640502": "沙坡头区",
+ "640521": "中宁县",
+ "640522": "海原县",
+ "650102": "天山区",
+ "650103": "沙依巴克区",
+ "650104": "新市区",
+ "650105": "水磨沟区",
+ "650106": "头屯河区",
+ "650107": "达坂城区",
+ "650109": "米东区",
+ "650121": "乌鲁木齐县",
+ "650202": "独山子区",
+ "650203": "克拉玛依区",
+ "650204": "白碱滩区",
+ "650205": "乌尔禾区",
+ "650402": "高昌区",
+ "650421": "鄯善县",
+ "650422": "托克逊县",
+ "650502": "伊州区",
+ "650521": "巴里坤哈萨克自治县",
+ "650522": "伊吾县",
+ "652301": "昌吉市",
+ "652302": "阜康市",
+ "652323": "呼图壁县",
+ "652324": "玛纳斯县",
+ "652325": "奇台县",
+ "652327": "吉木萨尔县",
+ "652328": "木垒哈萨克自治县",
+ "652701": "博乐市",
+ "652702": "阿拉山口市",
+ "652722": "精河县",
+ "652723": "温泉县",
+ "652801": "库尔勒市",
+ "652822": "轮台县",
+ "652823": "尉犁县",
+ "652824": "若羌县",
+ "652825": "且末县",
+ "652826": "焉耆回族自治县",
+ "652827": "和静县",
+ "652828": "和硕县",
+ "652829": "博湖县",
+ "652901": "阿克苏市",
+ "652902": "库车市",
+ "652922": "温宿县",
+ "652924": "沙雅县",
+ "652925": "新和县",
+ "652926": "拜城县",
+ "652927": "乌什县",
+ "652928": "阿瓦提县",
+ "652929": "柯坪县",
+ "653001": "阿图什市",
+ "653022": "阿克陶县",
+ "653023": "阿合奇县",
+ "653024": "乌恰县",
+ "653101": "喀什市",
+ "653121": "疏附县",
+ "653122": "疏勒县",
+ "653123": "英吉沙县",
+ "653124": "泽普县",
+ "653125": "莎车县",
+ "653126": "叶城县",
+ "653127": "麦盖提县",
+ "653128": "岳普湖县",
+ "653129": "伽师县",
+ "653130": "巴楚县",
+ "653131": "塔什库尔干塔吉克自治县",
+ "653201": "和田市",
+ "653221": "和田县",
+ "653222": "墨玉县",
+ "653223": "皮山县",
+ "653224": "洛浦县",
+ "653225": "策勒县",
+ "653226": "于田县",
+ "653227": "民丰县",
+ "654002": "伊宁市",
+ "654003": "奎屯市",
+ "654004": "霍尔果斯市",
+ "654021": "伊宁县",
+ "654022": "察布查尔锡伯自治县",
+ "654023": "霍城县",
+ "654024": "巩留县",
+ "654025": "新源县",
+ "654026": "昭苏县",
+ "654027": "特克斯县",
+ "654028": "尼勒克县",
+ "654201": "塔城市",
+ "654202": "乌苏市",
+ "654203": "沙湾市",
+ "654221": "额敏县",
+ "654224": "托里县",
+ "654225": "裕民县",
+ "654226": "和布克赛尔蒙古自治县",
+ "654301": "阿勒泰市",
+ "654321": "布尔津县",
+ "654322": "富蕴县",
+ "654323": "福海县",
+ "654324": "哈巴河县",
+ "654325": "青河县",
+ "654326": "吉木乃县",
+ "659001": "石河子市",
+ "659002": "阿拉尔市",
+ "659003": "图木舒克市",
+ "659004": "五家渠市",
+ "659005": "北屯市",
+ "659006": "铁门关市",
+ "659007": "双河市",
+ "659008": "可克达拉市",
+ "659009": "昆玉市",
+ "659010": "胡杨河市",
+ "659011": "新星市",
+ "659012": "白杨市",
+ "710101": "中正区",
+ "710102": "大同区",
+ "710103": "中山区",
+ "710104": "松山区",
+ "710105": "大安区",
+ "710106": "万华区",
+ "710107": "信义区",
+ "710108": "士林区",
+ "710109": "北投区",
+ "710110": "内湖区",
+ "710111": "南港区",
+ "710112": "文山区",
+ "710199": "其它区",
+ "710201": "新兴区",
+ "710202": "前金区",
+ "710203": "芩雅区",
+ "710204": "盐埕区",
+ "710205": "鼓山区",
+ "710206": "旗津区",
+ "710207": "前镇区",
+ "710208": "三民区",
+ "710209": "左营区",
+ "710210": "楠梓区",
+ "710211": "小港区",
+ "710241": "苓雅区",
+ "710242": "仁武区",
+ "710243": "大社区",
+ "710244": "冈山区",
+ "710245": "路竹区",
+ "710246": "阿莲区",
+ "710247": "田寮区",
+ "710248": "燕巢区",
+ "710249": "桥头区",
+ "710250": "梓官区",
+ "710251": "弥陀区",
+ "710252": "永安区",
+ "710253": "湖内区",
+ "710254": "凤山区",
+ "710255": "大寮区",
+ "710256": "林园区",
+ "710257": "鸟松区",
+ "710258": "大树区",
+ "710259": "旗山区",
+ "710260": "美浓区",
+ "710261": "六龟区",
+ "710262": "内门区",
+ "710263": "杉林区",
+ "710264": "甲仙区",
+ "710265": "桃源区",
+ "710266": "那玛夏区",
+ "710267": "茂林区",
+ "710268": "茄萣区",
+ "710299": "其它区",
+ "710301": "中西区",
+ "710302": "东区",
+ "710303": "南区",
+ "710304": "北区",
+ "710305": "安平区",
+ "710306": "安南区",
+ "710339": "永康区",
+ "710340": "归仁区",
+ "710341": "新化区",
+ "710342": "左镇区",
+ "710343": "玉井区",
+ "710344": "楠西区",
+ "710345": "南化区",
+ "710346": "仁德区",
+ "710347": "关庙区",
+ "710348": "龙崎区",
+ "710349": "官田区",
+ "710350": "麻豆区",
+ "710351": "佳里区",
+ "710352": "西港区",
+ "710353": "七股区",
+ "710354": "将军区",
+ "710355": "学甲区",
+ "710356": "北门区",
+ "710357": "新营区",
+ "710358": "后壁区",
+ "710359": "白河区",
+ "710360": "东山区",
+ "710361": "六甲区",
+ "710362": "下营区",
+ "710363": "柳营区",
+ "710364": "盐水区",
+ "710365": "善化区",
+ "710366": "大内区",
+ "710367": "山上区",
+ "710368": "新市区",
+ "710369": "安定区",
+ "710399": "其它区",
+ "710401": "中区",
+ "710402": "东区",
+ "710403": "南区",
+ "710404": "西区",
+ "710405": "北区",
+ "710406": "北屯区",
+ "710407": "西屯区",
+ "710408": "南屯区",
+ "710431": "太平区",
+ "710432": "大里区",
+ "710433": "雾峰区",
+ "710434": "乌日区",
+ "710435": "丰原区",
+ "710436": "后里区",
+ "710437": "石冈区",
+ "710438": "东势区",
+ "710439": "和平区",
+ "710440": "新社区",
+ "710441": "潭子区",
+ "710442": "大雅区",
+ "710443": "神冈区",
+ "710444": "大肚区",
+ "710445": "沙鹿区",
+ "710446": "龙井区",
+ "710447": "梧栖区",
+ "710448": "清水区",
+ "710449": "大甲区",
+ "710450": "外埔区",
+ "710451": "大安区",
+ "710499": "其它区",
+ "710507": "金沙镇",
+ "710508": "金湖镇",
+ "710509": "金宁乡",
+ "710510": "金城镇",
+ "710511": "烈屿乡",
+ "710512": "乌坵乡",
+ "710614": "南投市",
+ "710615": "中寮乡",
+ "710616": "草屯镇",
+ "710617": "国姓乡",
+ "710618": "埔里镇",
+ "710619": "仁爱乡",
+ "710620": "名间乡",
+ "710621": "集集镇",
+ "710622": "水里乡",
+ "710623": "鱼池乡",
+ "710624": "信义乡",
+ "710625": "竹山镇",
+ "710626": "鹿谷乡",
+ "710701": "仁爱区",
+ "710702": "信义区",
+ "710703": "中正区",
+ "710704": "中山区",
+ "710705": "安乐区",
+ "710706": "暖暖区",
+ "710707": "七堵区",
+ "710799": "其它区",
+ "710801": "东区",
+ "710802": "北区",
+ "710803": "香山区",
+ "710899": "其它区",
+ "710901": "东区",
+ "710902": "西区",
+ "710999": "其它区",
+ "711130": "万里区",
+ "711132": "板桥区",
+ "711133": "汐止区",
+ "711134": "深坑区",
+ "711135": "石碇区",
+ "711136": "瑞芳区",
+ "711137": "平溪区",
+ "711138": "双溪区",
+ "711139": "贡寮区",
+ "711140": "新店区",
+ "711141": "坪林区",
+ "711142": "乌来区",
+ "711143": "永和区",
+ "711144": "中和区",
+ "711145": "土城区",
+ "711146": "三峡区",
+ "711147": "树林区",
+ "711148": "莺歌区",
+ "711149": "三重区",
+ "711150": "新庄区",
+ "711151": "泰山区",
+ "711152": "林口区",
+ "711153": "芦洲区",
+ "711154": "五股区",
+ "711155": "八里区",
+ "711156": "淡水区",
+ "711157": "三芝区",
+ "711158": "石门区",
+ "711287": "宜兰市",
+ "711288": "头城镇",
+ "711289": "礁溪乡",
+ "711290": "壮围乡",
+ "711291": "员山乡",
+ "711292": "罗东镇",
+ "711293": "三星乡",
+ "711294": "大同乡",
+ "711295": "五结乡",
+ "711296": "冬山乡",
+ "711297": "苏澳镇",
+ "711298": "南澳乡",
+ "711299": "钓鱼台",
+ "711387": "竹北市",
+ "711388": "湖口乡",
+ "711389": "新丰乡",
+ "711390": "新埔镇",
+ "711391": "关西镇",
+ "711392": "芎林乡",
+ "711393": "宝山乡",
+ "711394": "竹东镇",
+ "711395": "五峰乡",
+ "711396": "横山乡",
+ "711397": "尖石乡",
+ "711398": "北埔乡",
+ "711399": "峨眉乡",
+ "711414": "中坜区",
+ "711415": "平镇区",
+ "711417": "杨梅区",
+ "711418": "新屋区",
+ "711419": "观音区",
+ "711420": "桃园区",
+ "711421": "龟山区",
+ "711422": "八德区",
+ "711423": "大溪区",
+ "711425": "大园区",
+ "711426": "芦竹区",
+ "711487": "中坜市",
+ "711488": "平镇市",
+ "711489": "龙潭乡",
+ "711490": "杨梅市",
+ "711491": "新屋乡",
+ "711492": "观音乡",
+ "711493": "桃园市",
+ "711494": "龟山乡",
+ "711495": "八德市",
+ "711496": "大溪镇",
+ "711497": "复兴乡",
+ "711498": "大园乡",
+ "711499": "芦竹乡",
+ "711520": "头份市",
+ "711582": "竹南镇",
+ "711583": "头份镇",
+ "711584": "三湾乡",
+ "711585": "南庄乡",
+ "711586": "狮潭乡",
+ "711587": "后龙镇",
+ "711588": "通霄镇",
+ "711589": "苑里镇",
+ "711590": "苗栗市",
+ "711591": "造桥乡",
+ "711592": "头屋乡",
+ "711593": "公馆乡",
+ "711594": "大湖乡",
+ "711595": "泰安乡",
+ "711596": "铜锣乡",
+ "711597": "三义乡",
+ "711598": "西湖乡",
+ "711599": "卓兰镇",
+ "711736": "员林市",
+ "711774": "彰化市",
+ "711775": "芬园乡",
+ "711776": "花坛乡",
+ "711777": "秀水乡",
+ "711778": "鹿港镇",
+ "711779": "福兴乡",
+ "711780": "线西乡",
+ "711781": "和美镇",
+ "711782": "伸港乡",
+ "711783": "员林镇",
+ "711784": "社头乡",
+ "711785": "永靖乡",
+ "711786": "埔心乡",
+ "711787": "溪湖镇",
+ "711788": "大村乡",
+ "711789": "埔盐乡",
+ "711790": "田中镇",
+ "711791": "北斗镇",
+ "711792": "田尾乡",
+ "711793": "埤头乡",
+ "711794": "溪州乡",
+ "711795": "竹塘乡",
+ "711796": "二林镇",
+ "711797": "大城乡",
+ "711798": "芳苑乡",
+ "711799": "二水乡",
+ "711982": "番路乡",
+ "711983": "梅山乡",
+ "711984": "竹崎乡",
+ "711985": "阿里山乡",
+ "711986": "中埔乡",
+ "711987": "大埔乡",
+ "711988": "水上乡",
+ "711989": "鹿草乡",
+ "711990": "太保市",
+ "711991": "朴子市",
+ "711992": "东石乡",
+ "711993": "六脚乡",
+ "711994": "新港乡",
+ "711995": "民雄乡",
+ "711996": "大林镇",
+ "711997": "溪口乡",
+ "711998": "义竹乡",
+ "711999": "布袋镇",
+ "712180": "斗南镇",
+ "712181": "大埤乡",
+ "712182": "虎尾镇",
+ "712183": "土库镇",
+ "712184": "褒忠乡",
+ "712185": "东势乡",
+ "712186": "台西乡",
+ "712187": "仑背乡",
+ "712188": "麦寮乡",
+ "712189": "斗六市",
+ "712190": "林内乡",
+ "712191": "古坑乡",
+ "712192": "莿桐乡",
+ "712193": "西螺镇",
+ "712194": "二仑乡",
+ "712195": "北港镇",
+ "712196": "水林乡",
+ "712197": "口湖乡",
+ "712198": "四湖乡",
+ "712199": "元长乡",
+ "712451": "崁顶乡",
+ "712467": "屏东市",
+ "712468": "三地门乡",
+ "712469": "雾台乡",
+ "712470": "玛家乡",
+ "712471": "九如乡",
+ "712472": "里港乡",
+ "712473": "高树乡",
+ "712474": "盐埔乡",
+ "712475": "长治乡",
+ "712476": "麟洛乡",
+ "712477": "竹田乡",
+ "712478": "内埔乡",
+ "712479": "万丹乡",
+ "712480": "潮州镇",
+ "712481": "泰武乡",
+ "712482": "来义乡",
+ "712483": "万峦乡",
+ "712484": "莰顶乡",
+ "712485": "新埤乡",
+ "712486": "南州乡",
+ "712487": "林边乡",
+ "712488": "东港镇",
+ "712489": "琉球乡",
+ "712490": "佳冬乡",
+ "712491": "新园乡",
+ "712492": "枋寮乡",
+ "712493": "枋山乡",
+ "712494": "春日乡",
+ "712495": "狮子乡",
+ "712496": "车城乡",
+ "712497": "牡丹乡",
+ "712498": "恒春镇",
+ "712499": "满州乡",
+ "712584": "台东市",
+ "712585": "绿岛乡",
+ "712586": "兰屿乡",
+ "712587": "延平乡",
+ "712588": "卑南乡",
+ "712589": "鹿野乡",
+ "712590": "关山镇",
+ "712591": "海端乡",
+ "712592": "池上乡",
+ "712593": "东河乡",
+ "712594": "成功镇",
+ "712595": "长滨乡",
+ "712596": "金峰乡",
+ "712597": "大武乡",
+ "712598": "达仁乡",
+ "712599": "太麻里乡",
+ "712686": "花莲市",
+ "712687": "新城乡",
+ "712688": "太鲁阁",
+ "712689": "秀林乡",
+ "712690": "吉安乡",
+ "712691": "寿丰乡",
+ "712692": "凤林镇",
+ "712693": "光复乡",
+ "712694": "丰滨乡",
+ "712695": "瑞穗乡",
+ "712696": "万荣乡",
+ "712697": "玉里镇",
+ "712698": "卓溪乡",
+ "712699": "富里乡",
+ "712794": "马公市",
+ "712795": "西屿乡",
+ "712796": "望安乡",
+ "712797": "七美乡",
+ "712798": "白沙乡",
+ "712799": "湖西乡",
+ "712896": "南竿乡",
+ "712897": "北竿乡",
+ "712898": "东引乡",
+ "712899": "莒光乡",
+ "810101": "中西区",
+ "810102": "湾仔区",
+ "810103": "东区",
+ "810104": "南区",
+ "810201": "九龙城区",
+ "810202": "油尖旺区",
+ "810203": "深水埗区",
+ "810204": "黄大仙区",
+ "810205": "观塘区",
+ "810301": "北区",
+ "810302": "大埔区",
+ "810303": "沙田区",
+ "810304": "西贡区",
+ "810305": "元朗区",
+ "810306": "屯门区",
+ "810307": "荃湾区",
+ "810308": "葵青区",
+ "810309": "离岛区",
+ "820102": "花地玛堂区",
+ "820103": "花王堂区",
+ "820104": "望德堂区",
+ "820105": "大堂区",
+ "820106": "风顺堂区",
+ "820202": "嘉模堂区",
+ "820203": "路氹填海区",
+ "820204": "圣方济各堂区"
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/areaData/index.ts b/uni_modules/lime-shared/areaData/index.ts
new file mode 100644
index 0000000..401f77f
--- /dev/null
+++ b/uni_modules/lime-shared/areaData/index.ts
@@ -0,0 +1,71 @@
+// @ts-nocheck
+import _areaList from './city-china.json';
+export const areaList = _areaList
+// #ifndef UNI-APP-X
+type UTSJSONObject = Record
+// #endif
+// #ifdef UNI-APP-X
+type Object = UTSJSONObject
+// #endif
+type AreaList = {
+ province_list : Map;
+ city_list : Map;
+ county_list : Map;
+}
+// type CascaderOption = {
+// text : string;
+// value : string;
+// children ?: CascaderOption[];
+// };
+
+const makeOption = (
+ label : string,
+ value : string,
+ children ?: UTSJSONObject[],
+) : UTSJSONObject => ({
+ label,
+ value,
+ children,
+});
+
+
+
+export function useCascaderAreaData() : UTSJSONObject[] {
+ const city = areaList['city_list'] as UTSJSONObject
+ const county = areaList['county_list'] as UTSJSONObject
+ const province = areaList['province_list'] as UTSJSONObject
+ const provinceMap = new Map();
+ Object.keys(province).forEach((code) => {
+ provinceMap.set(code.slice(0, 2), makeOption(`${province[code]}`, code, []));
+ });
+
+ const cityMap = new Map();
+
+ Object.keys(city).forEach((code) => {
+ const option = makeOption(`${city[code]}`, code, []);
+ cityMap.set(code.slice(0, 4), option);
+
+ const _province = provinceMap.get(code.slice(0, 2));
+ if (_province != null) {
+ (_province['children'] as UTSJSONObject[]).push(option)
+ }
+ });
+
+ Object.keys(county).forEach((code) => {
+ const _city = cityMap.get(code.slice(0, 4));
+ if (_city != null) {
+ (_city['children'] as UTSJSONObject[]).push(makeOption(`${county[code]}`, code, null));
+ }
+ });
+
+ // #ifndef APP-ANDROID || APP-IOS
+ return Array.from(provinceMap.values());
+ // #endif
+ // #ifdef APP-ANDROID || APP-IOS
+ const obj : UTSJSONObject[] = []
+ provinceMap.forEach((value, code) => {
+ obj.push(value)
+ })
+ return obj
+ // #endif
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/arrayBufferToFile/index.ts b/uni_modules/lime-shared/arrayBufferToFile/index.ts
new file mode 100644
index 0000000..d40e0d0
--- /dev/null
+++ b/uni_modules/lime-shared/arrayBufferToFile/index.ts
@@ -0,0 +1,10 @@
+// @ts-nocheck
+// #ifndef UNI-APP-X && APP
+// export * from './vue.ts'
+export { arrayBufferToFile } from './vue'
+// #endif
+
+// #ifdef UNI-APP-X && APP
+// export * from './uvue.uts'
+export { arrayBufferToFile } from './uvue'
+// #endif
\ No newline at end of file
diff --git a/uni_modules/lime-shared/arrayBufferToFile/uvue.ts b/uni_modules/lime-shared/arrayBufferToFile/uvue.ts
new file mode 100644
index 0000000..65c7b14
--- /dev/null
+++ b/uni_modules/lime-shared/arrayBufferToFile/uvue.ts
@@ -0,0 +1,10 @@
+// @ts-nocheck
+// import {platform} from '../platform'
+/**
+ * buffer转路径
+ * @param {Object} buffer
+ */
+// @ts-nocheck
+export function arrayBufferToFile(buffer: ArrayBuffer, name?: string, format?:string):Promise<(File|string)> {
+ console.error('[arrayBufferToFile] 当前环境不支持')
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/arrayBufferToFile/uvue.uts b/uni_modules/lime-shared/arrayBufferToFile/uvue.uts
new file mode 100644
index 0000000..65c7b14
--- /dev/null
+++ b/uni_modules/lime-shared/arrayBufferToFile/uvue.uts
@@ -0,0 +1,10 @@
+// @ts-nocheck
+// import {platform} from '../platform'
+/**
+ * buffer转路径
+ * @param {Object} buffer
+ */
+// @ts-nocheck
+export function arrayBufferToFile(buffer: ArrayBuffer, name?: string, format?:string):Promise<(File|string)> {
+ console.error('[arrayBufferToFile] 当前环境不支持')
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/arrayBufferToFile/vue.ts b/uni_modules/lime-shared/arrayBufferToFile/vue.ts
new file mode 100644
index 0000000..9760b20
--- /dev/null
+++ b/uni_modules/lime-shared/arrayBufferToFile/vue.ts
@@ -0,0 +1,63 @@
+// @ts-nocheck
+import {platform} from '../platform'
+/**
+ * buffer转路径
+ * @param {Object} buffer
+ */
+// @ts-nocheck
+export function arrayBufferToFile(buffer: ArrayBuffer | Blob, name?: string, format?:string):Promise<(File|string)> {
+ return new Promise((resolve, reject) => {
+ // #ifdef MP
+ const fs = uni.getFileSystemManager()
+ //自定义文件名
+ if (!name && !format) {
+ reject(new Error('ERROR_NAME_PARSE'))
+ }
+ const fileName = `${name || new Date().getTime()}.${format.replace(/(.+)?\//,'')}`;
+ let pre = platform()
+ const filePath = `${pre.env.USER_DATA_PATH}/${fileName}`
+ fs.writeFile({
+ filePath,
+ data: buffer,
+ success() {
+ resolve(filePath)
+ },
+ fail(err) {
+ console.error(err)
+ reject(err)
+ }
+ })
+ // #endif
+
+ // #ifdef H5
+ const file = new File([buffer], name, {
+ type: format,
+ });
+ resolve(file)
+ // #endif
+
+ // #ifdef APP-PLUS
+ const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
+ const base64 = uni.arrayBufferToBase64(buffer)
+ bitmap.loadBase64Data(base64, () => {
+ if (!name && !format) {
+ reject(new Error('ERROR_NAME_PARSE'))
+ }
+ const fileNmae = `${name || new Date().getTime()}.${format.replace(/(.+)?\//,'')}`;
+ const filePath = `_doc/uniapp_temp/${fileNmae}`
+ bitmap.save(filePath, {},
+ () => {
+ bitmap.clear()
+ resolve(filePath)
+ },
+ (error) => {
+ bitmap.clear()
+ reject(error)
+ })
+ }, (error) => {
+ bitmap.clear()
+ reject(error)
+ })
+ // #endif
+ })
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/arrayEqual/index.ts b/uni_modules/lime-shared/arrayEqual/index.ts
new file mode 100644
index 0000000..58d7788
--- /dev/null
+++ b/uni_modules/lime-shared/arrayEqual/index.ts
@@ -0,0 +1,13 @@
+// @ts-nocheck
+/**
+ * 精确比较两个数组是否相等(浅比较)
+ * @param arr1 数组1
+ * @param arr2 数组2
+ * @returns 是否相等
+ */
+export function arrayEqual(arr1 : T[], arr2 : T[]) : boolean {
+ return (
+ arr1.length == arr2.length &&
+ arr1.every((val, i) : boolean => val == arr2[i])
+ )
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/assignAtIndex/index.ts b/uni_modules/lime-shared/assignAtIndex/index.ts
new file mode 100644
index 0000000..7de9b2b
--- /dev/null
+++ b/uni_modules/lime-shared/assignAtIndex/index.ts
@@ -0,0 +1,42 @@
+// @ts-nocheck
+/**
+ * 安全地在数组指定索引处赋值,兼容 Android 平台对稀疏数组的限制。
+ *
+ * 行为说明:
+ * - 非 Android:等同于 arr[index] = value(可能产生稀疏数组)
+ * - Android:若 index >= length,则先用 undefined 填充至 index,再赋值(紧凑数组,无空槽)
+ *
+ * @param arr - 目标数组(会被修改)
+ * @param index - 非负整数索引
+ * @param value - 要设置的值
+ * @throws {Error} 如果 index 不是非负整数
+ */
+export function assignAtIndex(arr : T[], index : number, value : T) : void {
+
+ // 输入校验
+ if (index < 0) {
+ throw new Error(`Index must be a non-negative integer, got ${index}`);
+ }
+
+ // #ifndef APP-ANDROID
+ // 标准环境:直接赋值(允许稀疏数组)
+ arr[index] = value;
+ // #endif
+
+ // #ifdef APP-ANDROID
+ // Android 环境:安全赋值,避免稀疏数组导致的异常
+ if (index < arr.length) {
+ arr[index] = value;
+ } else {
+ // 手动扩展数组,中间填充 undefined
+ // 注意:使用 push(undefined) 而不是 arr[length++] = undefined,
+ // 因为某些 Android 引擎对直接 length 赋值也敏感
+ while (arr.length <= index) {
+ arr.push(null as T);
+ }
+
+ // arr.set(index, value)
+ arr[index] = value;
+ }
+ // #endif
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/base64ToArrayBuffer/index.ts b/uni_modules/lime-shared/base64ToArrayBuffer/index.ts
new file mode 100644
index 0000000..f83b640
--- /dev/null
+++ b/uni_modules/lime-shared/base64ToArrayBuffer/index.ts
@@ -0,0 +1,13 @@
+// @ts-nocheck
+// 未完成
+export function base64ToArrayBuffer(base64 : string) {
+ const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
+ if (!format) {
+ new Error('ERROR_BASE64SRC_PARSE')
+ }
+ if(uni.base64ToArrayBuffer) {
+ return uni.base64ToArrayBuffer(bodyData)
+ } else {
+
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/base64ToPath/index.ts b/uni_modules/lime-shared/base64ToPath/index.ts
new file mode 100644
index 0000000..2bd01df
--- /dev/null
+++ b/uni_modules/lime-shared/base64ToPath/index.ts
@@ -0,0 +1,11 @@
+// @ts-nocheck
+// #ifndef UNI-APP-X && APP
+// export * from './vue.ts'
+export { base64ToPath } from './vue'
+// #endif
+
+
+// #ifdef UNI-APP-X && APP
+// export * from './uvue.uts'
+export { base64ToPath } from './uvue'
+// #endif
diff --git a/uni_modules/lime-shared/base64ToPath/uvue.ts b/uni_modules/lime-shared/base64ToPath/uvue.ts
new file mode 100644
index 0000000..364b494
--- /dev/null
+++ b/uni_modules/lime-shared/base64ToPath/uvue.ts
@@ -0,0 +1,22 @@
+// @ts-nocheck
+import { processFile, type ProcessFileOptions } from '@/uni_modules/lime-file-utils'
+
+/**
+ * base64转路径
+ * @param {Object} base64
+ */
+export function base64ToPath(base64: string, filename: string | null = null):Promise {
+ return new Promise((resolve,reject) => {
+ processFile({
+ type: 'toDataURL',
+ path: base64,
+ filename,
+ success(res: string){
+ resolve(res)
+ },
+ fail(err){
+ reject(err)
+ }
+ } as ProcessFileOptions)
+ })
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/base64ToPath/uvue.uts b/uni_modules/lime-shared/base64ToPath/uvue.uts
new file mode 100644
index 0000000..364b494
--- /dev/null
+++ b/uni_modules/lime-shared/base64ToPath/uvue.uts
@@ -0,0 +1,22 @@
+// @ts-nocheck
+import { processFile, type ProcessFileOptions } from '@/uni_modules/lime-file-utils'
+
+/**
+ * base64转路径
+ * @param {Object} base64
+ */
+export function base64ToPath(base64: string, filename: string | null = null):Promise {
+ return new Promise((resolve,reject) => {
+ processFile({
+ type: 'toDataURL',
+ path: base64,
+ filename,
+ success(res: string){
+ resolve(res)
+ },
+ fail(err){
+ reject(err)
+ }
+ } as ProcessFileOptions)
+ })
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/base64ToPath/vue.ts b/uni_modules/lime-shared/base64ToPath/vue.ts
new file mode 100644
index 0000000..735000f
--- /dev/null
+++ b/uni_modules/lime-shared/base64ToPath/vue.ts
@@ -0,0 +1,75 @@
+// @ts-nocheck
+import {platform} from '../platform'
+/**
+ * base64转路径
+ * @param {Object} base64
+ */
+export function base64ToPath(base64: string, filename?: string):Promise {
+ const [, format] = /^data:image\/(\w+);base64,/.exec(base64) || [];
+ return new Promise((resolve, reject) => {
+ // #ifdef MP
+ const fs = uni.getFileSystemManager()
+ //自定义文件名
+ if (!filename && !format) {
+ reject(new Error('ERROR_BASE64SRC_PARSE'))
+ }
+ // const time = new Date().getTime();
+ const name = filename || `${new Date().getTime()}.${format}`;
+ let pre = platform()
+ const filePath = `${pre.env.USER_DATA_PATH}/${name}`
+ fs.writeFile({
+ filePath,
+ data: base64.split(',')[1],
+ encoding: 'base64',
+ success() {
+ resolve(filePath)
+ },
+ fail(err) {
+ console.error(err)
+ reject(err)
+ }
+ })
+ // #endif
+
+ // #ifdef H5
+ // mime类型
+ let mimeString = base64.split(',')[0].split(':')[1].split(';')[0];
+ //base64 解码
+ let byteString = atob(base64.split(',')[1]);
+ //创建缓冲数组
+ let arrayBuffer = new ArrayBuffer(byteString.length);
+ //创建视图
+ let intArray = new Uint8Array(arrayBuffer);
+ for (let i = 0; i < byteString.length; i++) {
+ intArray[i] = byteString.charCodeAt(i);
+ }
+ resolve(URL.createObjectURL(new Blob([intArray], {
+ type: mimeString
+ })))
+ // #endif
+
+ // #ifdef APP-PLUS
+ const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
+ bitmap.loadBase64Data(base64, () => {
+ if (!filename && !format) {
+ reject(new Error('ERROR_BASE64SRC_PARSE'))
+ }
+ // const time = new Date().getTime();
+ const name = filename || `${new Date().getTime()}.${format}`;
+ const filePath = `_doc/uniapp_temp/${name}`
+ bitmap.save(filePath, {},
+ () => {
+ bitmap.clear()
+ resolve(filePath)
+ },
+ (error) => {
+ bitmap.clear()
+ reject(error)
+ })
+ }, (error) => {
+ bitmap.clear()
+ reject(error)
+ })
+ // #endif
+ })
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/bem/index.ts b/uni_modules/lime-shared/bem/index.ts
new file mode 100644
index 0000000..f4451af
--- /dev/null
+++ b/uni_modules/lime-shared/bem/index.ts
@@ -0,0 +1,289 @@
+// @ts-nocheck
+/**
+ * BEM 配置类型定义
+ * @description 定义 BEM 命名规范中的命名空间和分隔符配置
+ */
+export type BemConfig = {
+ /** 命名空间,用于所有 BEM 类名的前缀 */
+ namespace : string
+ /** Block 和命名空间之间的分隔符 */
+ blockSeparator : string
+ /** Element 和 Block 之间的分隔符 */
+ elementSeparator : string
+ /** Modifier 和 Block/Element 之间的分隔符 */
+ modifierSeparator : string
+}
+
+/**
+ * 默认 BEM 配置
+ * @description 使用 'l' 作为命名空间,'-' 作为 block 分隔符,'__' 作为 element 分隔符,'--' 作为 modifier 分隔符
+ */
+const defaultBem : BemConfig = {
+ namespace: 'l',
+ blockSeparator: '-',
+ elementSeparator: '__',
+ modifierSeparator: '--',
+}
+
+/**
+ * 条件类型,用于判断是否需要生成类名
+ * @description 支持布尔值、数字、字符串和 null 类型
+ */
+type IsType = boolean | number | string | null// | undefined
+
+/**
+ * BEM 类
+ * @description 用于生成符合 BEM 命名规范的 CSS 类名
+ * @example
+ * const bem = createBem('button')
+ * bem.b() // 'l-button'
+ * bem.e('text') // 'l-button__text'
+ * bem.m('primary') // 'l-button--primary'
+ * bem.em('text', 'small') // 'l-button__text--small'
+ */
+export class Bem {
+ /** 命名空间 */
+ private namespace : string
+ /** Block 分隔符 */
+ private blockSeparator : string
+ /** Element 分隔符 */
+ private elementSeparator : string
+ /** Modifier 分隔符 */
+ private modifierSeparator : string
+ /** 前缀,由命名空间和 block 组成 */
+ private prefix : string
+
+ /**
+ * 构造函数
+ * @param config BEM 配置对象
+ * @param block Block 名称,可以是字符串或数字
+ */
+ constructor(
+ config : BemConfig,
+ block : string,
+ ) {
+ this.namespace = config.namespace
+ this.blockSeparator = config.blockSeparator
+ this.elementSeparator = config.elementSeparator
+ this.modifierSeparator = config.modifierSeparator
+ this.prefix = this.namespace + this.blockSeparator + block
+ }
+
+ /**
+ * 判断是否需要生成类名
+ * @description 当 is 参数为 false、0 或空字符串时,返回 false,表示不需要生成类名;否则返回 true
+ * @param is 条件参数,默认为 true
+ * @returns 是否需要生成类名
+ * @example
+ * shouldGenerate() // true
+ * shouldGenerate(true) // true
+ * shouldGenerate(false) // false
+ * shouldGenerate(0) // false
+ * shouldGenerate('') // false
+ */
+ private shouldGenerate(is : IsType = true) : boolean {
+ return !(is == false || is == 0 || is == '')
+ }
+
+ /**
+ * 生成 Block 类名
+ * @description 返回当前 block 的完整类名
+ * @returns Block 类名字符串
+ * @example
+ * const bem = createBem('button')
+ * bem.b() // 'l-button'
+ */
+ b() : string {
+ return this.prefix
+ }
+
+ /**
+ * 生成 Element 类名
+ * @description 生成 Block__Element 格式的类名,当 is 参数为 false、0 或空字符串时返回空字符串,当 element 为 null 时返回 Block 类名
+ * @param element Element 名称,可以是字符串,为 null 时返回 Block 类名
+ * @param is 条件参数,默认为 true,当为 false、0 或空字符串时不生成类名
+ * @returns Element 类名字符串或空字符串
+ * @example
+ * const bem = createBem('button')
+ * bem.e('text') // 'l-button__text'
+ * bem.e('text', true) // 'l-button__text'
+ * bem.e('text', false) // ''
+ * bem.e('text', 0) // ''
+ * bem.e(null) // 'l-button'
+ */
+ e(element : string | null, is : IsType = true) : string {
+ if (!this.shouldGenerate(is)) {
+ return ''
+ }
+ if (element == null) {
+ return this.prefix
+ }
+ return this.prefix + this.elementSeparator + element
+ }
+
+ /**
+ * 生成 Modifier 类名
+ * @description 生成 Block--modifier 格式的类名,当 is 参数为 false、0 或空字符串时返回空字符串,当 modifier 为 null 时返回 Block 类名
+ * @param modifier Modifier 名称,可以是字符串或数字,为 null 时返回 Block 类名
+ * @param is 条件参数,默认为 true,当为 false、0 或空字符串时不生成类名
+ * @returns Modifier 类名字符串或空字符串
+ * @example
+ * const bem = createBem('button')
+ * bem.m('primary') // 'l-button--primary'
+ * bem.m('primary', true) // 'l-button--primary'
+ * bem.m('primary', false) // ''
+ * bem.m('primary', 0) // ''
+ * bem.m(null) // 'l-button'
+ */
+ m(modifier : string | number | null, is : IsType = true) : string {
+ if (!this.shouldGenerate(is)) {
+ return ''
+ }
+ if (modifier == null || modifier == '') {
+ return this.prefix
+ }
+ return this.prefix + this.modifierSeparator + modifier
+ }
+
+ /**
+ * 生成 Element-Modifier 类名
+ * @description 生成 Block__Element--modifier 格式的类名,当 is 参数为 false、0 或空字符串时返回空字符串,当 modifier 为 null 时返回 Element 类名
+ * @param element Element 名称,可以是字符串
+ * @param modifier Modifier 名称,可以是字符串或数字,为 null 时返回 Element 类名
+ * @param is 条件参数,默认为 true,当为 false、0 或空字符串时不生成类名
+ * @returns Element-Modifier 类名字符串或空字符串
+ * @example
+ * const bem = createBem('button')
+ * bem.em('text', 'small') // 'l-button__text--small'
+ * bem.em('text', 'small', true) // 'l-button__text--small'
+ * bem.em('text', 'small', false) // ''
+ * bem.em('text', 'small', 0) // ''
+ * bem.em('text', null) // 'l-button__text'
+ */
+ em(
+ element : string,
+ modifier : string | number | null,
+ is : IsType = true,
+ ) : string {
+ if (!this.shouldGenerate(is)) {
+ return ''
+ }
+ if (modifier == null || modifier == '') {
+ return this.prefix + this.elementSeparator + element
+ }
+ return (
+ this.prefix + this.elementSeparator + element + this.modifierSeparator + modifier
+ )
+ }
+
+ /**
+ * 生成变体 Modifier 类名
+ * @description 生成 Block--modifier1-modifier2 格式的类名,当 is 参数为 false、0 或空字符串时返回空字符串
+ * @param modifiers Modifier 名称数组,可以是字符串或数字
+ * @param is 条件参数,默认为 true,当为 false、0 或空字符串时不生成类名
+ * @returns Modifier 类名字符串或空字符串
+ * @example
+ * const bem = createBem('button')
+ * bem.v(['active', 'primary']) // 'l-button--active-primary'
+ * bem.v(['active', 'primary'], true) // 'l-button--active-primary'
+ * bem.v(['active', 'primary'], false) // ''
+ */
+ v(modifiers : Array, is : IsType = true) : string {
+ if (!this.shouldGenerate(is) || modifiers.length == 0) {
+ return ''
+ }
+ const joinedModifier = modifiers.join('-')
+ return this.prefix + this.modifierSeparator + joinedModifier
+ }
+
+ /**
+ * 生成变体 Element-Modifier 类名
+ * @description 生成 Block__Element--modifier1-modifier2 格式的类名,当 is 参数为 false、0 或空字符串时返回空字符串
+ * @param element Element 名称,可以是字符串
+ * @param modifiers Modifier 名称数组,可以是字符串或数字
+ * @param is 条件参数,默认为 true,当为 false、0 或空字符串时不生成类名
+ * @returns Element-Modifier 类名字符串或空字符串
+ * @example
+ * const bem = createBem('button')
+ * bem.ev('text', ['active', 'small']) // 'l-button__text--active-small'
+ * bem.ev('text', ['active', 'small'], true) // 'l-button__text--active-small'
+ * bem.ev('text', ['active', 'small'], false) // ''
+ */
+ ev(
+ element : string,
+ modifiers : Array,
+ is : IsType = true,
+ ) : string {
+ if (!this.shouldGenerate(is) || modifiers.length == 0) {
+ return ''
+ }
+ const joinedModifier = modifiers.join('-')
+ return (
+ this.prefix + this.elementSeparator + element + this.modifierSeparator + joinedModifier
+ )
+ }
+
+ /**
+ * 生成完整的 BEM 类名(Block-Element-Modifier)
+ * @description 生成完整的 BEM 类名,支持 Block、Block__Element、Block--modifier、Block__Element--modifier 四种格式
+ * @param block Block 名称,可以是字符串或数字
+ * @param element Element 名称,可以是字符串,为 null 时不拼接
+ * @param modifier Modifier 名称,可以是字符串或数字,为 null 时不拼接
+ * @param is 条件参数,默认为 true,当为 false、0 或空字符串时不生成类名
+ * @returns BEM 类名字符串或空字符串
+ * @example
+ * const bem = createBem('button')
+ * bem.bem('button') // 'l-button'
+ * bem.bem('button', 'text') // 'l-button__text'
+ * bem.bem('button', null, 'primary') // 'l-button--primary'
+ * bem.bem('button', 'text', 'small') // 'l-button__text--small'
+ * bem.bem('button', 'text', 'small', false) // ''
+ */
+ bem(
+ block : string,
+ element : string | null,
+ modifier : string | number | null,
+ is : boolean | number | string | null = true,
+ ) : string {
+ if (!this.shouldGenerate(is)) {
+ return ''
+ }
+
+ let className = this.namespace + this.blockSeparator + block
+ if (element != null) {
+ className += this.elementSeparator + element
+ }
+ if (modifier != null) {
+ className += this.modifierSeparator + modifier
+ }
+ return className
+ }
+}
+
+/**
+ * 创建 BEM 结构函数
+ * @description 根据配置创建一个 BEM 类生成器函数
+ * @param config BEM 配置对象
+ * @returns 返回一个函数,该函数接收 block 名称并返回 Bem 实例
+ * @example
+ * const createBem = createBemStruct({ namespace: 'l', blockSeparator: '-', elementSeparator: '__', modifierSeparator: '--' })
+ * const bem = createBem('button')
+ * bem.b() // 'l-button'
+ */
+export function createBemStruct(config : BemConfig) : (block : string) => Bem {
+ return (block : string) => {
+ return new Bem(config, block)
+ }
+}
+
+/**
+ * 使用默认配置创建 BEM 函数
+ * @description 使用默认配置(namespace: 'l', blockSeparator: '-', elementSeparator: '__', modifierSeparator: '--')创建 BEM 类生成器
+ * @example
+ * const bem = createBem('button')
+ * bem.b() // 'l-button'
+ * bem.e('text') // 'l-button__text'
+ * bem.m('primary') // 'l-button--primary'
+ * bem.em('text', 'small') // 'l-button__text--small'
+ */
+export const createBem = createBemStruct(defaultBem)
\ No newline at end of file
diff --git a/uni_modules/lime-shared/camelCase/index.ts b/uni_modules/lime-shared/camelCase/index.ts
new file mode 100644
index 0000000..c0efd7f
--- /dev/null
+++ b/uni_modules/lime-shared/camelCase/index.ts
@@ -0,0 +1,22 @@
+// @ts-nocheck
+/**
+ * 将字符串转换为 camelCase 或 PascalCase 风格的命名约定
+ * @param str 要转换的字符串
+ * @param isPascalCase 指示是否转换为 PascalCase 的布尔值,默认为 false
+ * @returns 转换后的字符串
+ */
+export function camelCase(str: string, isPascalCase: boolean = false): string {
+ // 将字符串分割成单词数组
+ let words: string[] = str.split(/[\s_-]+/);
+
+ // 将数组中的每个单词首字母大写(除了第一个单词)
+ let camelCased: string[] = words.map((word, index):string => {
+ if (index == 0 && !isPascalCase) {
+ return word.toLowerCase(); // 第一个单词全小写
+ }
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
+ });
+
+ // 将数组中的单词拼接成一个字符串
+ return camelCased.join('');
+};
\ No newline at end of file
diff --git a/uni_modules/lime-shared/canIUseCanvas2d/index.ts b/uni_modules/lime-shared/canIUseCanvas2d/index.ts
new file mode 100644
index 0000000..be5623a
--- /dev/null
+++ b/uni_modules/lime-shared/canIUseCanvas2d/index.ts
@@ -0,0 +1,67 @@
+// @ts-nocheck
+
+// #ifndef UNI-APP-X && APP
+
+// #ifdef MP-ALIPAY
+interface My {
+ SDKVersion: string
+}
+declare var my: My
+// #endif
+
+function compareVersion(v1:string, v2:string) {
+ let a1 = v1.split('.');
+ let a2 = v2.split('.');
+ const len = Math.max(a1.length, a2.length);
+
+ while (a1.length < len) {
+ a1.push('0');
+ }
+ while (a2.length < len) {
+ a2.push('0');
+ }
+
+ for (let i = 0; i < len; i++) {
+ const num1 = parseInt(a1[i], 10);
+ const num2 = parseInt(a2[i], 10);
+
+ if (num1 > num2) {
+ return 1;
+ }
+ if (num1 < num2) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+function gte(version: string) {
+ let {SDKVersion} = uni.getSystemInfoSync();
+ // #ifdef MP-ALIPAY
+ SDKVersion = my.SDKVersion
+ // #endif
+ return compareVersion(SDKVersion, version) >= 0;
+}
+// #endif
+
+
+/** 环境是否支持canvas 2d */
+export function canIUseCanvas2d(): boolean {
+ // #ifdef MP-WEIXIN
+ return gte('2.9.0');
+ // #endif
+ // #ifdef MP-ALIPAY
+ return gte('2.7.0');
+ // #endif
+ // #ifdef MP-TOUTIAO
+ return gte('1.78.0');
+ // #endif
+ // #ifdef UNI-APP-X && WEB || UNI-APP-X && APP
+ return true;
+ // #endif
+ // #ifndef MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO
+ return false
+ // #endif
+
+}
diff --git a/uni_modules/lime-shared/capitalizedAmount/index.ts b/uni_modules/lime-shared/capitalizedAmount/index.ts
new file mode 100644
index 0000000..317e0af
--- /dev/null
+++ b/uni_modules/lime-shared/capitalizedAmount/index.ts
@@ -0,0 +1,111 @@
+// @ts-nocheck
+import { isString } from "../isString";
+import { isNumber } from "../isNumber";
+/**
+ * 将金额转换为中文大写形式
+ * @param {number | string} amount - 需要转换的金额,可以是数字或字符串
+ * @returns {string} 转换后的中文大写金额
+ */
+export function capitalizedAmount(amount : number) : string
+export function capitalizedAmount(amount : string) : string
+export function capitalizedAmount(amount : any | null) : string {
+ try {
+ let _amountStr :string;
+ let _amountNum :number = 0;
+ // 如果输入是字符串,先将其转换为数字,并去除逗号
+ if (typeof amount == 'string') {
+ _amountNum = parseFloat((amount as string).replace(/,/g, ''));
+ }
+ if(isNumber(amount)) {
+ _amountNum = amount as number
+ }
+ // 判断输入是否为有效的金额 || isNaN(amount)
+ if (amount == null) throw new Error('不是有效的金额!');
+
+ let result = '';
+
+ // 处理负数情况
+ if (_amountNum < 0) {
+ result = '欠';
+ _amountNum = Math.abs(_amountNum);
+ }
+
+ // 金额不能超过千亿以上
+ if (_amountNum >= 10e11) throw new Error('计算金额过大!');
+
+ // 保留两位小数并转换为字符串
+ _amountStr = _amountNum.toFixed(2);
+
+ // 定义数字、单位和小数单位的映射
+ const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
+ const units = ['', '拾', '佰', '仟'];
+ const bigUnits = ['', '万', '亿'];
+ const decimalUnits = ['角', '分'];
+
+ // 分离整数部分和小数部分
+ const amountArray = _amountStr.split('.');
+ let integerPart = amountArray[0]; // string| number[]
+ const decimalPart = amountArray[1];
+
+ // 处理整数部分
+ if (integerPart != '0') {
+ let _integerPart = integerPart.split('').map((item):number => parseInt(item));
+
+ // 将整数部分按四位一级进行分组
+ const levels = _integerPart.reverse().reduce((prev:string[][], item, index):string[][] => {
+ // const level = prev?.[0]?.length < 4 ? prev[0] : [];
+ const level = prev.length > 0 && prev[0].length < 4 ? prev[0]: []
+
+ const value = item == 0 ? digits[item] : digits[item] + units[index % 4];
+
+ level.unshift(value);
+
+ if (level.length == 1) {
+ prev.unshift(level);
+ } else {
+ prev[0] = level;
+ }
+
+ return prev;
+ }, [] as string[][]);
+ // 将分组后的整数部分转换为中文大写形式
+ result += levels.reduce((prev, item, index):string => {
+ let _level = bigUnits[levels.length - index - 1];
+ let _item = item.join('').replace(/(零)\1+/g, '$1');
+
+ if (_item == '零') {
+ _level = '';
+ _item = '';
+ } else if (_item.endsWith('零')) {
+ _item = _item.slice(0, _item.length - 1);
+ }
+
+ return prev + _item + _level;
+ }, '');
+ } else {
+ result += '零';
+ }
+
+ // 添加元
+ result += '元';
+
+ // 处理小数部分
+ if (decimalPart != '00') {
+ if (result == '零元') result = '';
+
+ for (let i = 0; i < decimalPart.length; i++) {
+ const digit = parseInt(decimalPart.charAt(i));
+
+ if (digit != 0) {
+ result += digits[digit] + decimalUnits[i];
+ }
+ }
+ } else {
+ result += '整';
+ }
+
+ return result;
+ } catch (error : Error) {
+ return error.message;
+ }
+};
\ No newline at end of file
diff --git a/uni_modules/lime-shared/changelog.md b/uni_modules/lime-shared/changelog.md
new file mode 100644
index 0000000..8bc96bd
--- /dev/null
+++ b/uni_modules/lime-shared/changelog.md
@@ -0,0 +1,126 @@
+## 2.0.4(2026-08-26)
+- fix: 修复非蒸版缺少getClassStr
+## 2.0.3(2026-08-03)
+- fix: 恢复obj2css
+## 2.0.2(2026-08-03)
+- fix: 恢复旧版css2obj
+## 2.0.1(2026-07-29)
+- fix: 恢复useVModel
+## 2.0.0(2026-07-21)
+- refactor: 全面兼容 uni-app x 蒸汽模式(uvue/uts)
+- feat: 新增 `useTransition`、`Timeline`、`Animation` 动画过渡能力
+- feat: 新增 `createCanvasContextAsync` 统一 Canvas 上下文创建
+- feat: 新增 `selectElement`、`findClosestElementWithStyle` DOM 查询工具
+- feat: 新增 `getDeviceInfo`、`getWindowInfo` 平台信息获取
+- feat: 新增 `obj2url`、`guid`、`uniqid`、`coalesce`、`merge` 等工具函数
+- feat: 新增 `isIP`、`isByteLength`、`isRegExp`、`isURL`、`isValidDomain` 等验证器
+- feat: 新增 `areaData` 中国省市区数据及级联选择器数据
+- feat: 新增 `Bem` 类与 `stringifyStyle`、`parseStyle`、`classNames` 样式工具
+- feat: 新增 `arrayEqual`、`findLastIndex`、`assignAtIndex`、`fillArray`、`toArray` 等数组工具
+- feat: 新增 `addSeparator`、`characterLimit`、`toBoolean` 等字符串/类型工具
+- refactor: `getRect` 重构为支持节点查询、视口滚动、滚动定位计算
+- refactor: `selectComponent`/`selectAllComponent` 支持 class/id/ref/component 多种选择器
+- docs: 文档按插件文档规范重构
+## 0.4.8(2026-03-05)
+- feat: 更新uniappx vue的实现
+## 0.4.7(2025-12-27)
+- feat: 增加`getWindowInfo`
+## 0.4.6(2025-12-26)
+- fix: 修复useVModel可选问题
+## 0.4.5(2025-12-10)
+- feat: 增加useVModel
+## 0.4.4(2025-12-07)
+- fix: 修复csstoobj uniappx安卓正则问题
+## 0.4.3(2025-12-02)
+- feat: 增加`arrayEqual`,`findLastIndex`
+## 0.4.2(2025-09-26)
+- feat: 增加`cssToObj`
+## 0.4.1(2025-06-13)
+- fix:测试hbx4.71更新
+## 0.4.0(2025-06-04)
+- fix:测试hbx4.71更新
+## 0.3.9(2025-06-04)
+ - fix: 因hbx4.71更新导致mac用户下载的文件被重命名,故退回4.66
+## 0.3.8(2025-06-03)
+- fix: 修复`while`在vue2报错的问题
+## 0.3.7(2025-05-21)
+- fix: 修复`merge`在vue2报错的问题
+## 0.3.6(2025-05-17)
+- fix: 修复`raf`导错目录的问题
+## 0.3.5(2025-05-16)
+- feat: 增加`isIP`
+- feat: 增加`merge`
+- feat: 增加`isByteLength`
+- feat: 增加`isRegExp`
+## 0.3.4(2025-04-27)
+- fix: 修复exif缺少isBase64的问题
+## 0.3.3(2025-04-14)
+- fix: 修复4.61上的类型问题
+## 0.3.2(2025-04-14)
+- fix: 修复4.61上的类型问题
+## 0.3.1(2025-03-28)
+- fix: 修复getRect在nvue上的问题
+## 0.3.0(2025-02-27)
+- fix: 修复部分函数无法在uniappx上运行的问题
+## 0.2.9(2025-02-19)
+- chore: 更新文档
+## 0.2.8(2025-02-11)
+- chore: 更新文档
+## 0.2.7(2025-01-17)
+- fix: 针对canvas 平台判断优化
+## 0.2.6(2025-01-09)
+- feat: 增加`areaData`中国省市区数据
+## 0.2.5(2025-01-07)
+- fix: animation在app上类型问题
+## 0.2.4(2025-01-04)
+- feat: getRect类型问题
+## 0.2.3(2025-01-01)
+- chore: unitConvert使用uni.rpx2px
+## 0.2.2(2024-12-11)
+- chore: 动画使用`requestAnimationFrame`
+## 0.2.1(2024-11-20)
+- feat: 增加`characterLimit`
+## 0.2.0(2024-11-14)
+- fix: vue2的类型问题
+## 0.1.9(2024-11-14)
+- feat: 增加`shuffle`
+## 0.1.8(2024-10-08)
+- fix: vue2 条件编译 // #ifdef APP-IOS || APP-ANDROID 会生效
+## 0.1.7(2024-09-23)
+- fix: raf 类型跟随版本变更
+## 0.1.6(2024-07-24)
+- fix: vue2 app ts需要明确的后缀,所有补全
+- chore: 减少依赖
+## 0.1.5(2024-07-21)
+- feat: 删除 Hooks
+- feat: 兼容uniappx
+## 0.1.4(2023-09-05)
+- feat: 增加 Hooks `useIntersectionObserver`
+- feat: 增加 `floatAdd`
+- feat: 因为本人插件兼容 vue2 需要使用 `composition-api`,故增加vue文件代码插件的条件编译
+## 0.1.3(2023-08-13)
+- feat: 增加 `camelCase`
+## 0.1.2(2023-07-17)
+- feat: 增加 `getClassStr`
+## 0.1.1(2023-07-06)
+- feat: 增加 `isNumeric`, 区别于 `isNumber`
+## 0.1.0(2023-06-30)
+- fix: `clamp`忘记导出了
+## 0.0.9(2023-06-27)
+- feat: 增加`arrayBufferToFile`
+## 0.0.8(2023-06-19)
+- feat: 增加`createAnimation`、`clamp`
+## 0.0.7(2023-06-08)
+- chore: 更新注释
+## 0.0.6(2023-06-08)
+- chore: 增加`createImage`为`lime-watermark`和`lime-qrcode`提供依赖
+## 0.0.5(2023-06-03)
+- chore: 更新注释
+## 0.0.4(2023-05-22)
+- feat: 增加`range`,`exif`,`selectComponent`
+## 0.0.3(2023-05-08)
+- feat: 增加`fillZero`,`debounce`,`throttle`,`random`
+## 0.0.2(2023-05-05)
+- chore: 更新文档
+## 0.0.1(2023-05-05)
+- 无
diff --git a/uni_modules/lime-shared/characterLimit/index.ts b/uni_modules/lime-shared/characterLimit/index.ts
new file mode 100644
index 0000000..215de23
--- /dev/null
+++ b/uni_modules/lime-shared/characterLimit/index.ts
@@ -0,0 +1,63 @@
+// @ts-nocheck
+/**
+ * 计算字符串字符的长度并可以截取字符串。
+ * @param char 传入字符串(maxcharacter条件下,一个汉字表示两个字符)
+ * @param max 规定最大字符串长度
+ * @returns 当没有传入maxCharacter/maxLength 时返回字符串字符长度,当传入maxCharacter/maxLength时返回截取之后的字符串和长度。
+ */
+export type CharacterLengthResult = {
+ length : number;
+ characters : string;
+}
+// #ifdef APP-ANDROID
+type ChartType = any
+// #endif
+// #ifndef APP-ANDROID
+type ChartType = string | number
+// #endif
+
+export function characterLimit(type : string, char : ChartType, max : number) : CharacterLengthResult {
+ const str = `${char}`;
+
+ if (str.length == 0) {
+ return {
+ length: 0,
+ characters: '',
+ } as CharacterLengthResult
+ }
+
+ if (type == 'maxcharacter') {
+ let len = 0;
+ for (let i = 0; i < str.length; i += 1) {
+ let currentStringLength : number// = 0;
+ const code = str.charCodeAt(i)!
+ if (code > 127 || code == 94) {
+ currentStringLength = 2;
+ } else {
+ currentStringLength = 1;
+ }
+ if (len + currentStringLength > max) {
+ return {
+ length: len,
+ characters: str.slice(0, i),
+ } as CharacterLengthResult
+ }
+ len += currentStringLength;
+ }
+ return {
+ length: len,
+ characters: str,
+ } as CharacterLengthResult
+ } else if (type == 'maxlength') {
+ const length = str.length > max ? max : str.length;
+ return {
+ length: length,
+ characters: str.slice(0, length),
+ } as CharacterLengthResult
+ }
+
+ return {
+ length: str.length,
+ characters: str,
+ } as CharacterLengthResult
+};
\ No newline at end of file
diff --git a/uni_modules/lime-shared/clamp/index.ts b/uni_modules/lime-shared/clamp/index.ts
new file mode 100644
index 0000000..0e16358
--- /dev/null
+++ b/uni_modules/lime-shared/clamp/index.ts
@@ -0,0 +1,16 @@
+// @ts-nocheck
+/**
+ * 将一个值限制在指定的范围内
+ * @param val 要限制的值
+ * @param min 最小值
+ * @param max 最大值
+ * @returns 限制后的值
+ */
+export function clamp(val: number, min: number, max: number): number {
+ return Math.max(min, Math.min(max, val));
+}
+
+
+// console.log(clamp(5 ,0, 10)); // 输出: 5(在范围内,不做更改)
+// console.log(clamp(-5 ,0, 10)); // 输出: 0(小于最小值,被限制为最小值)
+// console.log(clamp(15 ,0, 10)); // 输出: 10(大于最大值,被限制为最大值)
\ No newline at end of file
diff --git a/uni_modules/lime-shared/classNames/index.ts b/uni_modules/lime-shared/classNames/index.ts
new file mode 100644
index 0000000..931228d
--- /dev/null
+++ b/uni_modules/lime-shared/classNames/index.ts
@@ -0,0 +1,96 @@
+// @ts-nocheck
+/**
+ * 类名属性类型
+ * @description 支持 string、number、null、undefined、对象和数组类型
+ */
+// #ifndef UNI-APP-X
+type UTSJSONObject = Record
+// #endif
+
+export type ClassProp =
+ | string
+ | number
+ | null
+ | undefined
+ | UTSJSONObject
+ | ClassProp[]
+
+/**
+ * 把各种类型的参数拼接成字符串类名,以便解决小程序不支持 classObject 的问题
+ * @description 支持多种参数类型的类名拼接,包括字符串、数字、数组和对象
+ * @param args 类名参数,可以是字符串、数字、null、undefined、对象或数组
+ * @returns 拼接后的类名字符串
+ * @example
+ * classNames('foo', 'bar') // 'foo bar'
+ * classNames('foo', { bar: true, baz: false }) // 'foo bar'
+ * classNames('foo', ['bar', 'baz']) // 'foo bar baz'
+ * classNames('foo', null, 'bar') // 'foo bar'
+ * classNames({ foo: true, bar: false, baz: true }) // 'foo baz'
+ */
+// export function classNames(args:ClassProp[]): string
+export function classNames(...args : ClassProp[]) : string {
+ return classNamesArray(args)
+}
+
+/**
+ * 处理数组的类名
+ * @description 用于内部递归处理数组类型的类名
+ * @param args 类名数组
+ * @returns 拼接后的类名字符串
+ */
+export function classNamesArray(args : ClassProp[]) : string {
+ const result : string[] = []
+
+ for (let i = 0; i < args.length; i++) {
+ const arg = args[i]
+ if (arg == null) {
+ continue
+ }
+
+ if (typeof arg == 'string') {
+ const trimmed = (arg as string).trim()
+ if (trimmed != '') {
+ result.push(trimmed)
+ }
+ } else if (typeof arg == 'number') {
+ if (isFinite(arg as number)) {
+ result.push(`${arg}`)
+ }
+ } else if (Array.isArray(arg)) {
+ if ((arg as ClassProp[]).length > 0) {
+ const className = classNamesArray(arg)
+ if (className != '') {
+ result.push(className)
+ }
+ }
+ } else if (typeof arg == 'object') {
+ // #ifndef UNI-APP-X && APP
+ for (const key in arg) {
+ const value = arg[key]
+ if (value == true) {
+ result.push(key)
+ }
+ }
+ // #endif
+ // #ifdef UNI-APP-X && APP
+ // #ifdef VUE3-VAPOR
+ for (const key in arg) {
+ const value = arg[key]
+ if (value == true) {
+ result.push(key)
+ }
+ }
+ // #endif
+ // #ifndef VUE3-VAPOR
+ UTSJSONObject.assign({}, arg as UTSJSONObject).toMap().forEach((value, key) => {
+ if (value == true) {
+ result.push(key)
+ }
+ })
+ // #endif
+ // #endif
+ }
+ }
+
+ return result.join(' ')
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/cloneDeep/index.ts b/uni_modules/lime-shared/cloneDeep/index.ts
new file mode 100644
index 0000000..d7be272
--- /dev/null
+++ b/uni_modules/lime-shared/cloneDeep/index.ts
@@ -0,0 +1,10 @@
+// @ts-nocheck
+
+// #ifdef APP-ANDROID
+export { cloneDeep } from './uvue'
+// #endif
+
+
+// #ifndef APP-ANDROID
+export { cloneDeep } from './vue'
+// #endif
\ No newline at end of file
diff --git a/uni_modules/lime-shared/cloneDeep/uvue.ts b/uni_modules/lime-shared/cloneDeep/uvue.ts
new file mode 100644
index 0000000..49570f8
--- /dev/null
+++ b/uni_modules/lime-shared/cloneDeep/uvue.ts
@@ -0,0 +1,44 @@
+// @ts-nocheck
+/**
+ * 深度克隆一个对象或数组
+ * @param obj 要克隆的对象或数组
+ * @returns 克隆后的对象或数组
+ */
+export function cloneDeep(obj : any) : T {
+ if (obj instanceof Set) {
+ const set = new Set();
+ obj.forEach((item : any) => {
+ set.add(item)
+ })
+ return set as T;
+ }
+ if (obj instanceof Map) {
+ const map = new Map();
+ obj.forEach((value : any, key : any) => {
+ map.set(key, value)
+ })
+ return map as T;
+ }
+
+ if (obj instanceof RegExp) {
+ return new RegExp(obj) as T;
+ }
+
+ if (Array.isArray(obj)) {
+ return (obj as any[]).map((item : any):any => item) as T;
+ }
+
+ if (obj instanceof Date) {
+ return new Date(obj.getTime()) as T;
+ }
+
+ if (typeof obj == 'object') {
+ // #ifdef VUE3-VAPOR
+ return Object.assign({}, toRaw(obj))!
+ // #endif
+ // #ifndef VUE3-VAPOR
+ return UTSJSONObject.assign({}, toRaw(obj))!
+ // #endif
+ }
+ return obj as T
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/cloneDeep/uvue.uts b/uni_modules/lime-shared/cloneDeep/uvue.uts
new file mode 100644
index 0000000..9ee42d9
--- /dev/null
+++ b/uni_modules/lime-shared/cloneDeep/uvue.uts
@@ -0,0 +1,39 @@
+// @ts-nocheck
+/**
+ * 深度克隆一个对象或数组
+ * @param obj 要克隆的对象或数组
+ * @returns 克隆后的对象或数组
+ */
+export function cloneDeep(obj : any) : T {
+ if (obj instanceof Set) {
+ const set = new Set();
+ obj.forEach((item : any) => {
+ set.add(item)
+ })
+ return set as T;
+ }
+ if (obj instanceof Map) {
+ const map = new Map();
+ obj.forEach((value : any, key : any) => {
+ map.set(key, value)
+ })
+ return map as T;
+ }
+
+ if (obj instanceof RegExp) {
+ return new RegExp(obj) as T;
+ }
+
+ if (Array.isArray(obj)) {
+ return (obj as any[]).map((item : any):any => item) as T;
+ }
+
+ if (obj instanceof Date) {
+ return new Date(obj.getTime()) as T;
+ }
+
+ if (typeof obj == 'object') {
+ return UTSJSONObject.assign({}, toRaw(obj))!
+ }
+ return obj as T
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/cloneDeep/vue.ts b/uni_modules/lime-shared/cloneDeep/vue.ts
new file mode 100644
index 0000000..ded334d
--- /dev/null
+++ b/uni_modules/lime-shared/cloneDeep/vue.ts
@@ -0,0 +1,103 @@
+// @ts-nocheck
+/**
+ * 深度克隆一个对象或数组
+ * @param obj 要克隆的对象或数组
+ * @returns 克隆后的对象或数组
+ */
+export function cloneDeep(obj: any): T {
+ // 如果传入的对象为空,返回空
+ if (obj === null) {
+ return null as unknown as T;
+ }
+
+ // 如果传入的对象是 Set 类型,则将其转换为数组,并通过新的 Set 构造函数创建一个新的 Set 对象
+ if (obj instanceof Set) {
+ return new Set([...obj]) as unknown as T;
+ }
+
+ // 如果传入的对象是 Map 类型,则将其转换为数组,并通过新的 Map 构造函数创建一个新的 Map 对象
+ if (obj instanceof Map) {
+ return new Map([...obj]) as unknown as T;
+ }
+
+ // 如果传入的对象是 WeakMap 类型,则直接用传入的 WeakMap 对象进行赋值
+ if (obj instanceof WeakMap) {
+ let weakMap = new WeakMap();
+ weakMap = obj;
+ return weakMap as unknown as T;
+ }
+
+ // 如果传入的对象是 WeakSet 类型,则直接用传入的 WeakSet 对象进行赋值
+ if (obj instanceof WeakSet) {
+ let weakSet = new WeakSet();
+ weakSet = obj;
+ return weakSet as unknown as T;
+ }
+
+ // 如果传入的对象是 RegExp 类型,则通过新的 RegExp 构造函数创建一个新的 RegExp 对象
+ if (obj instanceof RegExp) {
+ return new RegExp(obj) as unknown as T;
+ }
+
+ // 如果传入的对象是 undefined 类型,则返回 undefined
+ if (typeof obj === 'undefined') {
+ return undefined as unknown as T;
+ }
+
+ // 如果传入的对象是数组,则递归调用 cloneDeep 函数对数组中的每个元素进行克隆
+ if (Array.isArray(obj)) {
+ return obj.map(cloneDeep) as unknown as T;
+ }
+
+ // 如果传入的对象是 Date 类型,则通过新的 Date 构造函数创建一个新的 Date 对象
+ if (obj instanceof Date) {
+ return new Date(obj.getTime()) as unknown as T;
+ }
+
+ // 如果传入的对象是普通对象,则使用递归调用 cloneDeep 函数对对象的每个属性进行克隆
+ if (typeof obj === 'object') {
+ const newObj: any = {};
+ for (const [key, value] of Object.entries(obj)) {
+ newObj[key] = cloneDeep(value);
+ }
+ const symbolKeys = Object.getOwnPropertySymbols(obj);
+ for (const key of symbolKeys) {
+ newObj[key] = cloneDeep(obj[key]);
+ }
+ return newObj;
+ }
+
+ // 如果传入的对象是基本数据类型(如字符串、数字等),则直接返回
+ return obj;
+}
+
+// 示例使用
+
+// // 克隆一个对象
+// const obj = { name: 'John', age: 30 };
+// const clonedObj = cloneDeep(obj);
+
+// console.log(clonedObj); // 输出: { name: 'John', age: 30 }
+// console.log(clonedObj === obj); // 输出: false (副本与原对象是独立的)
+
+// // 克隆一个数组
+// const arr = [1, 2, 3];
+// const clonedArr = cloneDeep(arr);
+
+// console.log(clonedArr); // 输出: [1, 2, 3]
+// console.log(clonedArr === arr); // 输出: false (副本与原数组是独立的)
+
+// // 克隆一个包含嵌套对象的对象
+// const person = {
+// name: 'Alice',
+// age: 25,
+// address: {
+// city: 'New York',
+// country: 'USA',
+// },
+// };
+// const clonedPerson = cloneDeep(person);
+
+// console.log(clonedPerson); // 输出: { name: 'Alice', age: 25, address: { city: 'New York', country: 'USA' } }
+// console.log(clonedPerson === person); // 输出: false (副本与原对象是独立的)
+// console.log(clonedPerson.address === person.address); // 输出: false (嵌套对象的副本也是独立的)
\ No newline at end of file
diff --git a/uni_modules/lime-shared/closest/index.ts b/uni_modules/lime-shared/closest/index.ts
new file mode 100644
index 0000000..e6e79c2
--- /dev/null
+++ b/uni_modules/lime-shared/closest/index.ts
@@ -0,0 +1,22 @@
+// @ts-nocheck
+
+/**
+ * 在给定数组中找到最接近目标数字的元素。
+ * @param arr 要搜索的数字数组。
+ * @param target 目标数字。
+ * @returns 最接近目标数字的数组元素。
+ */
+export function closest(arr: number[], target: number):number {
+ return arr.reduce((pre: number, cur: number):number =>
+ Math.abs(pre - target) < Math.abs(cur - target) ? pre : cur
+ );
+}
+
+// 示例
+// // 定义一个数字数组
+// const numbers = [1, 3, 5, 7, 9];
+
+// // 在数组中找到最接近目标数字 6 的元素
+// const closestNumber = closest(numbers, 6);
+
+// console.log(closestNumber); // 输出结果: 5
\ No newline at end of file
diff --git a/uni_modules/lime-shared/coalesce/index.ts b/uni_modules/lime-shared/coalesce/index.ts
new file mode 100644
index 0000000..bfaa1a6
--- /dev/null
+++ b/uni_modules/lime-shared/coalesce/index.ts
@@ -0,0 +1,27 @@
+// @ts-nocheck
+/**
+ * 返回第一个非空值。
+ * - 跳过 null、undefined
+ * - 对字符串类型,额外跳过空字符串 ''
+ * - 保留 0、false 等类型合法值
+ */
+// #ifdef UNI-APP-X && APP
+export function coalesce(...values : (any | null)[]) : T | null {
+ for (const value of values) {
+ if (value == null) continue;
+ if (typeof value == 'string' && value == '') continue;
+ return value as T;
+ }
+ return null;
+}
+// #endif
+// #ifndef UNI-APP-X && APP
+export function coalesce(...values : (T | null | undefined)[]) : T | null {
+ for (const value of values) {
+ if (value == null || value === undefined) continue;
+ if (typeof value === 'string' && value === '') continue;
+ return value as T;
+ }
+ return null;
+}
+// #endif
\ No newline at end of file
diff --git a/uni_modules/lime-shared/components/lime-shared/lime-shared.vue b/uni_modules/lime-shared/components/lime-shared/lime-shared.vue
new file mode 100644
index 0000000..edbf6e8
--- /dev/null
+++ b/uni_modules/lime-shared/components/lime-shared/lime-shared.vue
@@ -0,0 +1,407 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/lime-shared/computed/index.ts b/uni_modules/lime-shared/computed/index.ts
new file mode 100644
index 0000000..7b31651
--- /dev/null
+++ b/uni_modules/lime-shared/computed/index.ts
@@ -0,0 +1,16 @@
+// @ts-nocheck
+// #ifndef VUE2 && APP
+export { computed } from '../vue';
+// #endif
+
+// #ifdef VUE2 && APP
+import { watchEffect, onScopeDispose, ref } from '../vue';
+
+export function computed(getter) {
+ const result = ref();
+ watchEffect(() => {
+ result.value = getter();
+ });
+ return result;
+}
+// #endif
\ No newline at end of file
diff --git a/uni_modules/lime-shared/createAnimation/index.ts b/uni_modules/lime-shared/createAnimation/index.ts
new file mode 100644
index 0000000..db81f5c
--- /dev/null
+++ b/uni_modules/lime-shared/createAnimation/index.ts
@@ -0,0 +1,11 @@
+// @ts-nocheck
+// #ifndef UNI-APP-X
+export * from './type'
+// export * from './vue.ts'
+export { createAnimation } from './vue'
+// #endif
+
+// #ifdef UNI-APP-X
+// export * from './uvue'
+export { createAnimation } from './uvue'
+// #endif
\ No newline at end of file
diff --git a/uni_modules/lime-shared/createAnimation/type.ts b/uni_modules/lime-shared/createAnimation/type.ts
new file mode 100644
index 0000000..0f8ad34
--- /dev/null
+++ b/uni_modules/lime-shared/createAnimation/type.ts
@@ -0,0 +1,25 @@
+export type CreateAnimationOptions = {
+ /**
+ * 动画持续时间,单位ms
+ */
+ duration ?: number;
+ /**
+ * 定义动画的效果
+ * - linear: 动画从头到尾的速度是相同的
+ * - ease: 动画以低速开始,然后加快,在结束前变慢
+ * - ease-in: 动画以低速开始
+ * - ease-in-out: 动画以低速开始和结束
+ * - ease-out: 动画以低速结束
+ * - step-start: 动画第一帧就跳至结束状态直到结束
+ * - step-end: 动画一直保持开始状态,最后一帧跳到结束状态
+ */
+ timingFunction ?: string //'linear' | 'ease' | 'ease-in' | 'ease-in-out' | 'ease-out' | 'step-start' | 'step-end';
+ /**
+ * 动画延迟时间,单位 ms
+ */
+ delay ?: number;
+ /**
+ * 设置transform-origin
+ */
+ transformOrigin ?: string;
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/createAnimation/uvue.ts b/uni_modules/lime-shared/createAnimation/uvue.ts
new file mode 100644
index 0000000..96d8263
--- /dev/null
+++ b/uni_modules/lime-shared/createAnimation/uvue.ts
@@ -0,0 +1,5 @@
+// @ts-nocheck
+// export * from '@/uni_modules/lime-animateIt'
+export function createAnimation() {
+ console.error('当前环境不支持,请使用:lime-animateIt')
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/createAnimation/uvue.uts b/uni_modules/lime-shared/createAnimation/uvue.uts
new file mode 100644
index 0000000..96d8263
--- /dev/null
+++ b/uni_modules/lime-shared/createAnimation/uvue.uts
@@ -0,0 +1,5 @@
+// @ts-nocheck
+// export * from '@/uni_modules/lime-animateIt'
+export function createAnimation() {
+ console.error('当前环境不支持,请使用:lime-animateIt')
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/createAnimation/vue.ts b/uni_modules/lime-shared/createAnimation/vue.ts
new file mode 100644
index 0000000..6934f27
--- /dev/null
+++ b/uni_modules/lime-shared/createAnimation/vue.ts
@@ -0,0 +1,148 @@
+// @ts-nocheck
+// nvue 需要在节点上设置ref或在export里传入
+// const animation = createAnimation({
+// ref: this.$refs['xxx'],
+// duration: 0,
+// timingFunction: 'linear'
+// })
+// animation.opacity(1).translate(x, y).step({duration})
+// animation.export(ref)
+
+// 抹平nvue 与 uni.createAnimation的使用差距
+// 但是nvue动画太慢
+
+
+
+import { type CreateAnimationOptions } from './type'
+// #ifdef APP-NVUE
+const nvueAnimation = uni.requireNativePlugin('animation')
+
+type AnimationTypes = 'matrix' | 'matrix3d' | 'rotate' | 'rotate3d' | 'rotateX' | 'rotateY' | 'rotateZ' | 'scale' | 'scale3d' | 'scaleX' | 'scaleY' | 'scaleZ' | 'skew' | 'skewX' | 'skewY' | 'translate' | 'translate3d' | 'translateX' | 'translateY' | 'translateZ'
+ | 'opacity' | 'backgroundColor' | 'width' | 'height' | 'left' | 'right' | 'top' | 'bottom'
+
+interface Styles {
+ [key : string] : any
+}
+
+interface StepConfig {
+ duration?: number
+ timingFunction?: string
+ delay?: number
+ needLayout?: boolean
+ transformOrigin?: string
+}
+interface StepAnimate {
+ styles?: Styles
+ config?: StepConfig
+}
+interface StepAnimates {
+ [key: number]: StepAnimate
+}
+// export interface CreateAnimationOptions extends UniApp.CreateAnimationOptions {
+// ref?: string
+// }
+
+type Callback = (time: number) => void
+const animateTypes1 : AnimationTypes[] = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
+ 'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
+ 'translateZ'
+]
+const animateTypes2 : AnimationTypes[] = ['opacity', 'backgroundColor']
+const animateTypes3 : AnimationTypes[] = ['width', 'height', 'left', 'right', 'top', 'bottom']
+
+class LimeAnimation {
+ ref : any
+ context : any
+ options : UniApp.CreateAnimationOptions
+ // stack : any[] = []
+ next : number = 0
+ currentStepAnimates : StepAnimates = {}
+ duration : number = 0
+ constructor(options : CreateAnimationOptions) {
+ const {ref} = options
+ this.ref = ref
+ this.options = options
+ }
+ addAnimate(type : AnimationTypes, args: (string | number)[]) {
+ let aniObj = this.currentStepAnimates[this.next]
+ let stepAnimate:StepAnimate = {}
+ if (!aniObj) {
+ stepAnimate = {styles: {}, config: {}}
+ } else {
+ stepAnimate = aniObj
+ }
+
+ if (animateTypes1.includes(type)) {
+ if (!stepAnimate.styles.transform) {
+ stepAnimate.styles.transform = ''
+ }
+ let unit = ''
+ if (type === 'rotate') {
+ unit = 'deg'
+ }
+ stepAnimate.styles.transform += `${type}(${args.map((v: number) => v + unit).join(',')}) `
+ } else {
+ stepAnimate.styles[type] = `${args.join(',')}`
+ }
+ this.currentStepAnimates[this.next] = stepAnimate
+ }
+ animateRun(styles: Styles = {}, config:StepConfig = {}, ref: any) {
+ const el = ref || this.ref
+ if (!el) return
+ return new Promise((resolve) => {
+ const time = +new Date()
+ nvueAnimation.transition(el, {
+ styles,
+ ...config
+ }, () => {
+ resolve(+new Date() - time)
+ })
+ })
+ }
+ nextAnimate(animates: StepAnimates, step: number = 0, ref: any, cb: Callback) {
+ let obj = animates[step]
+ if (obj) {
+ let { styles, config } = obj
+ // this.duration += config.duration
+ this.animateRun(styles, config, ref).then((time: number) => {
+ step += 1
+ this.duration += time
+ this.nextAnimate(animates, step, ref, cb)
+ })
+ } else {
+ this.currentStepAnimates = {}
+ cb && cb(this.duration)
+ }
+ }
+ step(config:StepConfig = {}) {
+ this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
+ this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
+ this.next++
+ return this
+ }
+ export(ref: any, cb?: Callback) {
+ ref = ref || this.ref
+ if(!ref) return
+ this.duration = 0
+ this.next = 0
+ this.nextAnimate(this.currentStepAnimates, 0, ref, cb)
+ return null
+ }
+}
+
+
+animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
+ LimeAnimation.prototype[type] = function(...args: (string | number)[]) {
+ this.addAnimate(type, args)
+ return this
+ }
+})
+// #endif
+export function createAnimation(options : CreateAnimationOptions) {
+ // #ifndef APP-NVUE
+ return uni.createAnimation({ ...options })
+ // #endif
+ // #ifdef APP-NVUE
+ return new LimeAnimation(options)
+ // #endif
+}
diff --git a/uni_modules/lime-shared/createCanvas/index.ts b/uni_modules/lime-shared/createCanvas/index.ts
new file mode 100644
index 0000000..49c3242
--- /dev/null
+++ b/uni_modules/lime-shared/createCanvas/index.ts
@@ -0,0 +1,72 @@
+// @ts-nocheck
+// #ifndef UNI-APP-X && APP
+import type { ComponentInternalInstance } from '@/uni_modules/lime-shared/vue'
+import { getRect } from '@/uni_modules/lime-shared/getRect'
+import { canIUseCanvas2d } from '@/uni_modules/lime-shared/canIUseCanvas2d'
+export const isCanvas2d = canIUseCanvas2d()
+// #endif
+
+
+export function createCanvas(canvasId : string, component : ComponentInternalInstance) {
+ // #ifdef UNI-APP-X
+ uni.createCanvasContextAsync({
+ canvasId,
+ component,
+ success(context : CanvasContext) {
+
+ },
+ fail(error : UniError) {
+
+ }
+ })
+ // #endif
+ // #ifndef UNI-APP-X
+ const isCanvas2d = canIUseCanvas2d()
+ getRect('#' + canvasId, context, isCanvas2d).then(res => {
+ if (res.node) {
+ res.node.width = res.width
+ res.node.height = res.height
+ return res.node
+ } else {
+ const ctx = uni.createCanvasContext(canvasId, context)
+ if (!ctx._drawImage) {
+ ctx._drawImage = ctx.drawImage
+ ctx.drawImage = function (...args) {
+ const { path } = args.shift()
+ ctx._drawImage(path, ...args)
+ }
+ }
+ if (!ctx.getImageData) {
+ ctx.getImageData = function () {
+ return new Promise((resolve, reject) => {
+ uni.canvasGetImageData({
+ canvasId,
+ x: parseInt(arguments[0]),
+ y: parseInt(arguments[1]),
+ width: parseInt(arguments[2]),
+ height: parseInt(arguments[3]),
+ success(res) {
+ resolve(res)
+ },
+ fail(err) {
+ reject(err)
+ }
+ }, context)
+ })
+
+ }
+ return {
+ getContext(type: string) {
+ if(type == '2d') {
+ return ctx
+ }
+ },
+ width: res.width,
+ height: res.height,
+ createImage
+ }
+ }
+ }
+ })
+ // #endif
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/createCanvasContextAsync/index.ts b/uni_modules/lime-shared/createCanvasContextAsync/index.ts
new file mode 100644
index 0000000..b6e5cf9
--- /dev/null
+++ b/uni_modules/lime-shared/createCanvasContextAsync/index.ts
@@ -0,0 +1,329 @@
+// @ts-nocheck
+/**
+ * Canvas上下文创建结果类型
+ * 包含创建Canvas上下文时返回的各个组件
+ */
+export type LimeCanvasInstance = {
+ canvasContext : CanvasContext,
+ node : UniCanvasElement,
+ renderContext : CanvasRenderingContext2D,
+ isCanvas2d : boolean,
+ width : number,
+ height : number,
+ toDataURL : (type?: string, quality?: number) => Promise,
+ flush : (reserve?: boolean) => Promise,
+ getImageData : (x: number, y: number, width: number, height: number) => Promise
+}
+
+
+
+/**
+ * 异步创建Canvas上下文
+ * 这个函数封装了uni-app不同平台下的Canvas创建逻辑:
+ * 1. 在uni-app X中使用uni.createCanvasContextAsync API
+ * 2. 在非uni-app X中使用uni.createSelectorQuery查询Canvas元素
+ *
+ * @param canvasId - Canvas元素的id选择器
+ * @param component - Vue组件实例
+ * @returns Promise 包含Canvas上下文、元素和渲染上下文
+ *
+ */
+export function createCanvasContextAsync(
+ canvasId : string,
+ component : ComponentPublicInstance
+) : Promise {
+ return new Promise((resolve, reject) => {
+ // 条件编译:uni-app X平台
+ // uni-app X使用新的Canvas API
+ // #ifdef UNI-APP-X
+ nextTick(() => {
+ uni.createCanvasContextAsync({
+ id: canvasId,
+ component,
+ success(canvasContext : CanvasContext) {
+ // 从CanvasContext中获取2D渲染上下文
+ const ctx = canvasContext.getContext('2d')!;
+ // 获取Canvas DOM元素
+ const node = ctx.canvas;
+ // 确保获取有效的尺寸
+ const width = node.offsetWidth;
+ const height = node.offsetHeight;
+ resolve({
+ canvasContext: canvasContext,
+ node,
+ renderContext: ctx,
+ isCanvas2d: true,
+ width,
+ height,
+ toDataURL: (type?: string, quality?: number) : Promise => {
+ return new Promise((resolve) => {
+ const dataURL = canvasContext.toDataURL(type ?? 'image/png', quality ?? 1.0)
+ resolve(dataURL)
+ })
+ },
+ flush: (_reserve?: boolean):Promise => {
+ return Promise.resolve()
+ },
+ getImageData: (x: number, y: number, w: number, h: number) : Promise => {
+ return new Promise((resolve, reject) => {
+ try {
+ const imageData = ctx.getImageData(x, y, w, h)
+ resolve(imageData)
+ } catch (error) {
+ reject(error)
+ }
+ })
+ }
+ } as LimeCanvasInstance)
+ },
+ fail(error) {
+ // uni-app X中创建失败时reject错误
+ reject(error)
+ }
+ })
+ })
+
+ // #endif
+
+ // 条件编译:非uni-app X平台
+ // 包括H5、小程序等平台,使用传统的Canvas查询方式
+ // #ifndef UNI-APP-X
+ // 创建选择器查询
+ uni.createSelectorQuery()
+ // 限定在指定组件内查询
+ .in(component)
+ // 选择指定的Canvas元素
+ .select(`#${canvasId}`)
+ // 指定需要查询的字段
+ .fields({
+ node: true, // 需要获取Canvas节点
+ size: true, // 需要获取尺寸信息
+ rect: true // 需要获取位置信息
+ }, (queryResult) => {
+ // 成功查询到Canvas节点
+ if (queryResult.node && queryResult.node.getContext) {
+ // 获取Canvas的2D渲染上下文
+ const renderContext = queryResult.node.getContext('2d')!;
+
+ let isCanvas2d = true;
+
+ // #ifdef WEB
+ isCanvas2d = ![...queryResult.node.classList].includes('uni-canvas-canvas')
+ // #endif
+
+ const canvasContext = {
+ createImage: () => {
+ if(queryResult.node.createImage) {
+ return queryResult.node.createImage()
+ }
+ return new LimeCanvasImage()
+ },
+ requestAnimationFrame: (callback: (time: number) => void) : number => {
+ // #ifdef WEB
+ return requestAnimationFrame(callback)
+ // #endif
+ // #ifndef WEB
+ if (queryResult.node.requestAnimationFrame) {
+ return queryResult.node.requestAnimationFrame(callback)
+ }
+ // return setTimeout(callback, Math.floor(1000 / 60)) as unknown as number
+ return setTimeout(() => callback(Date.now()), Math.floor(1000 / 60)) as unknown as number
+ // #endif
+ },
+ cancelAnimationFrame: (id: number) : void => {
+ // #ifdef WEB
+ cancelAnimationFrame(id)
+ // #endif
+ // #ifndef WEB
+ if (queryResult.node.cancelAnimationFrame) {
+ queryResult.node.cancelAnimationFrame(id)
+ } else {
+ clearTimeout(id)
+ }
+ // #endif
+ }
+ }
+
+ resolve({
+ renderContext,
+ node: queryResult.node,
+ canvasContext,
+ isCanvas2d,
+ width: queryResult.width,
+ height: queryResult.height,
+ toDataURL: (type?: string, quality?: number) : Promise => {
+ return new Promise((resolve) => {
+ if (queryResult.node != null && typeof queryResult.node.toDataURL == 'function') {
+ const dataURL = queryResult.node.toDataURL(type ?? 'image/png', quality ?? 1.0)
+ resolve(dataURL)
+ } else {
+ uni.canvasToTempFilePath({
+ canvas: queryResult.node,
+ success: (res) => {
+ resolve(res.tempFilePath)
+ },
+ fail: () => {
+ resolve('')
+ }
+ })
+ }
+ })
+ },
+ flush: (_reserve?: boolean) => {
+ return Promise.resolve()
+ },
+ getImageData: (x: number, y: number, w: number, h: number) : Promise => {
+ return new Promise((resolve, reject) => {
+ try {
+ const result = renderContext.getImageData(x, y, w, h)
+ if (result && typeof result.then === 'function') {
+ result.then((imageData : ImageData) => {
+ resolve(imageData)
+ }).catch(reject)
+ } else {
+ resolve(result as ImageData)
+ }
+ } catch(e) {
+ reject(e)
+ }
+ })
+ }
+ } as LimeCanvasInstance)
+ } else {
+ // 未能获取到Canvas节点,回退到旧的API
+ // 使用uni.createCanvasContext创建绘图上下文
+ const renderContext = uni.createCanvasContext(canvasId, component)
+ if (!renderContext._drawImage) {
+ renderContext._drawImage = renderContext.drawImage
+ renderContext.drawImage = function (...args) {
+ const {path} = args.shift();
+ renderContext._drawImage(path, ...args)
+ }
+ }
+
+ // 创建模拟的CanvasContext对象以保持接口一致
+ const canvasContext = {
+ /**
+ * 模拟CanvasContext的getContext方法
+ * 在旧版本API中返回绘图上下文
+ */
+ getContext(type : string) {
+ return renderContext
+ },
+ createImage(){
+ return new LimeCanvasImage()
+ },
+ requestAnimationFrame: (callback: (time: number) => void) : number => {
+ return setTimeout(() => callback(Date.now()), Math.floor(1000 / 60)) as unknown as number
+ },
+ cancelAnimationFrame: (id: number) : void => {
+ clearTimeout(id)
+ }
+ }
+ const node = {
+ ...canvasContext,
+ }
+ // 在回退方案中,无法获取到Canvas DOM元素
+ resolve({
+ renderContext,
+ node,
+ canvasContext,
+ isCanvas2d: false,
+ width: queryResult.width || 300,
+ height: queryResult.height || 150,
+ toDataURL: (type?: string, quality?: number) : Promise => {
+ return new Promise((resolve) => {
+ renderContext.draw(true, () => {
+ const fileType = type == 'image/jpeg' ? 'jpg' : 'png'
+ uni.canvasToTempFilePath({
+ canvasId: canvasId,
+ fileType: fileType,
+ quality: quality ?? 1.0,
+ success: (res) => {
+ resolve(res.tempFilePath)
+ },
+ fail: (err) => {
+ console.log('[createCanvasContextAsync]:生成图片失败', err)
+ resolve('')
+ }
+ }, component)
+ })
+ })
+ },
+ flush: (reserve?: boolean) => {
+ return new Promise((resolve)=> {
+ renderContext.draw(reserve ?? true, resolve)
+ })
+ },
+ getImageData: (x: number, y: number, w: number, h: number) : Promise => {
+ return new Promise((resolve, reject) => {
+ renderContext.draw(true, () => {
+ uni.canvasGetImageData({
+ canvasId: canvasId,
+ x: Math.round(x),
+ y: Math.round(y),
+ width: Math.round(w),
+ height: Math.round(h),
+ success(res : any) {
+ resolve(res as ImageData)
+ },
+ fail(err : any) {
+ reject(err)
+ }
+ }, component)
+ })
+ })
+ }
+ } as LimeCanvasInstance)
+ }
+ }).exec() // 执行查询
+ // #endif
+ })
+}
+
+
+// #ifndef UNI-APP-X
+// #ifndef WEB
+class LimeCanvasImage {
+ currentSrc: string | null = null
+ naturalHeight: number = 0
+ naturalWidth: number = 0
+ width: number = 0
+ height: number = 0
+ tagName: string = 'IMG'
+ path: string = ''
+ crossOrigin: string = ''
+ referrerPolicy: string = ''
+ onload: () => void = () => {}
+ onerror: () => void = () => {}
+ complete: boolean = false
+ constructor() {}
+ set src(src: string) {
+ if(!src) {
+ return this.onerror()
+ }
+ src = src.replace(/^@\//,'/')
+ this.currentSrc = src
+ // this.path = src
+ // uniapp 好像不需要下载
+ // this.onload()
+ uni.getImageInfo({
+ src,
+ success: (res: any) => {
+ this.path = res.path
+ this.naturalWidth = this.width = res.width
+ this.naturalHeight = this.height = res.height
+ this.onload()
+ },
+ fail: () => { this.onerror() }
+ })
+ }
+ get src() {
+ return this.currentSrc
+ }
+}
+// #endif
+// #ifdef WEB
+const LimeCanvasImage = Image
+// #endif
+// #endif
diff --git a/uni_modules/lime-shared/createImage/index.ts b/uni_modules/lime-shared/createImage/index.ts
new file mode 100644
index 0000000..96ab11c
--- /dev/null
+++ b/uni_modules/lime-shared/createImage/index.ts
@@ -0,0 +1,70 @@
+// @ts-nocheck
+// #ifndef UNI-APP-X && APP
+import {isBrowser} from '../isBrowser'
+class Image {
+ currentSrc: string | null = null
+ naturalHeight: number = 0
+ naturalWidth: number = 0
+ width: number = 0
+ height: number = 0
+ tagName: string = 'IMG'
+ path: string = ''
+ crossOrigin: string = ''
+ referrerPolicy: string = ''
+ onload: () => void = () => {}
+ onerror: () => void = () => {}
+ complete: boolean = false
+ constructor() {}
+ set src(src: string) {
+ if(!src) {
+ return this.onerror()
+ }
+ src = src.replace(/^@\//,'/')
+ this.currentSrc = src
+ uni.getImageInfo({
+ src,
+ success: (res) => {
+ const localReg = /^\.|^\/(?=[^\/])/;
+ // #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO
+ res.path = localReg.test(src) ? `/${res.path}` : res.path;
+ // #endif
+ this.complete = true
+ this.path = res.path
+ this.naturalWidth = this.width = res.width
+ this.naturalHeight = this.height = res.height
+ this.onload()
+ },
+ fail: () => {
+ this.onerror()
+ }
+ })
+ }
+ get src() {
+ return this.currentSrc
+ }
+}
+interface UniImage extends WechatMiniprogram.Image {
+ complete?: boolean
+ naturalHeight?: number
+ naturalWidth?: number
+}
+/** 创建用于 canvas 的 img */
+export function createImage(canvas?: any): HTMLImageElement | UniImage {
+ if(canvas && canvas.createImage) {
+ return (canvas as WechatMiniprogram.Canvas).createImage()
+ } else if(this && this['tagName'] == 'canvas' && !('toBlob' in this) || canvas && !('toBlob' in canvas)){
+ return new Image()
+ } else if(isBrowser) {
+ return new window.Image()
+ }
+ return new Image()
+}
+// #endif
+
+
+// #ifdef UNI-APP-X && APP
+export function createImage():Image{
+ // console.error('当前环境不支持')
+ return new Image()
+}
+// #endif
diff --git a/uni_modules/lime-shared/cssToObj/index.ts b/uni_modules/lime-shared/cssToObj/index.ts
new file mode 100644
index 0000000..74411af
--- /dev/null
+++ b/uni_modules/lime-shared/cssToObj/index.ts
@@ -0,0 +1,50 @@
+// @ts-nocheck
+// #ifndef UNI-APP-X
+type UTSJSONObject = Record
+// #endif
+
+
+/**
+ * 将 CSS 字符串转换为样式对象
+ * @param css CSS 字符串,例如 "color: red; font-size: 16px;"
+ * @returns CSSProperties 对象
+ */
+export function cssToObj(css : string | UTSJSONObject | null) : UTSJSONObject {
+ // #ifdef APP-ANDROID
+ if(css == null) return {}
+ // #endif
+ // #ifndef APP-ANDROID
+ if(!css) return {}
+ // #endif
+ if(typeof css == 'object') return css as UTSJSONObject
+
+
+ const style : UTSJSONObject = {};
+
+ (css as string).split(';').forEach(decl => {
+ // #ifdef APP-ANDROID
+ const res = decl.split(':').map(s => s.trim());
+ if(res.length > 1) {
+ const [prop, val] = res;
+ if (prop != '' && val != '') {
+ const camelProp = prop!.replace(/-([a-z])/g, (_a: string|null, b: string|null, _c: number, _d: string):string => {
+ return b?.toUpperCase() ?? ''
+ });
+ style[camelProp] = val!;
+ }
+ }
+ // #endif
+
+ // #ifndef APP-ANDROID
+ const [prop, val] = decl.split(':').map(s => s.trim());
+ if (prop && val) {
+ const camelProp = prop.replace(/-([a-z])/g, (_, c) => {
+ return c.toUpperCase()
+ });
+ style[camelProp] = val;
+ }
+ // #endif
+ });
+
+ return style;
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/debounce/index.ts b/uni_modules/lime-shared/debounce/index.ts
new file mode 100644
index 0000000..c7aa7cb
--- /dev/null
+++ b/uni_modules/lime-shared/debounce/index.ts
@@ -0,0 +1,11 @@
+// @ts-nocheck
+
+// #ifdef UNI-APP-X && APP
+// export * from './uvue.uts'
+export { debounce } from './uvue'
+// #endif
+
+// #ifndef UNI-APP-X && APP
+// export * from './vue.ts'
+export { debounce } from './vue'
+// #endif
\ No newline at end of file
diff --git a/uni_modules/lime-shared/debounce/uvue.ts b/uni_modules/lime-shared/debounce/uvue.ts
new file mode 100644
index 0000000..f1fc29d
--- /dev/null
+++ b/uni_modules/lime-shared/debounce/uvue.ts
@@ -0,0 +1,36 @@
+// @ts-nocheck
+/**
+ * 防抖函数,通过延迟一定时间来限制函数的执行频率。
+ * @param fn 要防抖的函数。
+ * @param wait 触发防抖的等待时间,单位为毫秒。
+ * @returns 防抖函数。
+ */
+export function debounce(fn : (args: A)=> void, wait = 300): (args: A)=> void {
+ let timer = -1
+
+ return (args: A) => {
+ if (timer >-1) {clearTimeout(timer)};
+
+ timer = setTimeout(()=>{
+ fn(args)
+ }, wait)
+ }
+};
+
+
+
+// 示例
+// 定义一个函数
+// function saveData(data: string) {
+// // 模拟保存数据的操作
+// console.log(`Saving data: ${data}`);
+// }
+
+// // 创建一个防抖函数,延迟 500 毫秒后调用 saveData 函数
+// const debouncedSaveData = debounce(saveData, 500);
+
+// // 连续调用防抖函数
+// debouncedSaveData('Data 1'); // 不会立即调用 saveData 函数
+// debouncedSaveData('Data 2'); // 不会立即调用 saveData 函数
+
+// 在 500 毫秒后,只会调用一次 saveData 函数,输出结果为 "Saving data: Data 2"
\ No newline at end of file
diff --git a/uni_modules/lime-shared/debounce/uvue.uts b/uni_modules/lime-shared/debounce/uvue.uts
new file mode 100644
index 0000000..f1fc29d
--- /dev/null
+++ b/uni_modules/lime-shared/debounce/uvue.uts
@@ -0,0 +1,36 @@
+// @ts-nocheck
+/**
+ * 防抖函数,通过延迟一定时间来限制函数的执行频率。
+ * @param fn 要防抖的函数。
+ * @param wait 触发防抖的等待时间,单位为毫秒。
+ * @returns 防抖函数。
+ */
+export function debounce(fn : (args: A)=> void, wait = 300): (args: A)=> void {
+ let timer = -1
+
+ return (args: A) => {
+ if (timer >-1) {clearTimeout(timer)};
+
+ timer = setTimeout(()=>{
+ fn(args)
+ }, wait)
+ }
+};
+
+
+
+// 示例
+// 定义一个函数
+// function saveData(data: string) {
+// // 模拟保存数据的操作
+// console.log(`Saving data: ${data}`);
+// }
+
+// // 创建一个防抖函数,延迟 500 毫秒后调用 saveData 函数
+// const debouncedSaveData = debounce(saveData, 500);
+
+// // 连续调用防抖函数
+// debouncedSaveData('Data 1'); // 不会立即调用 saveData 函数
+// debouncedSaveData('Data 2'); // 不会立即调用 saveData 函数
+
+// 在 500 毫秒后,只会调用一次 saveData 函数,输出结果为 "Saving data: Data 2"
\ No newline at end of file
diff --git a/uni_modules/lime-shared/debounce/vue.ts b/uni_modules/lime-shared/debounce/vue.ts
new file mode 100644
index 0000000..694b44d
--- /dev/null
+++ b/uni_modules/lime-shared/debounce/vue.ts
@@ -0,0 +1,40 @@
+// @ts-nocheck
+type Timeout = ReturnType | null;
+/**
+ * 防抖函数,通过延迟一定时间来限制函数的执行频率。
+ * @param fn 要防抖的函数。
+ * @param wait 触发防抖的等待时间,单位为毫秒。
+ * @returns 防抖函数。
+ */
+export function debounce(
+ fn : (...args : A) => R,
+ wait : number = 300) : (...args : A) => void {
+ let timer : Timeout = null;
+
+ return function (...args : A) {
+ if (timer) clearTimeout(timer); // 如果上一个 setTimeout 存在,则清除它
+
+ // 设置一个新的 setTimeout,在指定的等待时间后调用防抖函数
+ timer = setTimeout(() => {
+ fn.apply(this, args); // 使用提供的参数调用原始函数
+ }, wait);
+ };
+};
+
+
+
+// 示例
+// 定义一个函数
+// function saveData(data: string) {
+// // 模拟保存数据的操作
+// console.log(`Saving data: ${data}`);
+// }
+
+// // 创建一个防抖函数,延迟 500 毫秒后调用 saveData 函数
+// const debouncedSaveData = debounce(saveData, 500);
+
+// // 连续调用防抖函数
+// debouncedSaveData('Data 1'); // 不会立即调用 saveData 函数
+// debouncedSaveData('Data 2'); // 不会立即调用 saveData 函数
+
+// 在 500 毫秒后,只会调用一次 saveData 函数,输出结果为 "Saving data: Data 2"
\ No newline at end of file
diff --git a/uni_modules/lime-shared/dom/index.ts b/uni_modules/lime-shared/dom/index.ts
new file mode 100644
index 0000000..9c82e09
--- /dev/null
+++ b/uni_modules/lime-shared/dom/index.ts
@@ -0,0 +1,18 @@
+// @ts-nocheck
+export function findClosestElementWithStyle(startEl: UniElement | null, styleProperty: string): UniElement | null {
+ let currentEl: UniElement | null = startEl;
+
+ while (currentEl != null) {
+ // Check if the current element has the style property with a non-empty value
+ const styleValue = currentEl?.style.getPropertyValue(styleProperty) ?? '';
+ if (styleValue.trim() != '') {
+ return currentEl;
+ }
+
+ // Move to parent element
+ currentEl = currentEl.parentElement;
+ }
+
+ // Return null if no element with the specified style was found
+ return null;
+};
\ No newline at end of file
diff --git a/uni_modules/lime-shared/exif/index.ts b/uni_modules/lime-shared/exif/index.ts
new file mode 100644
index 0000000..c9f3206
--- /dev/null
+++ b/uni_modules/lime-shared/exif/index.ts
@@ -0,0 +1,11 @@
+// @ts-nocheck
+// #ifndef UNI-APP-X && APP
+// export * from './vue'
+export { exif } from './vue'
+// #endif
+
+
+// #ifdef UNI-APP-X && APP
+// export * from './uvue.uts'
+export { exif } from './uvue'
+// #endif
diff --git a/uni_modules/lime-shared/exif/uvue.ts b/uni_modules/lime-shared/exif/uvue.ts
new file mode 100644
index 0000000..01d21a2
--- /dev/null
+++ b/uni_modules/lime-shared/exif/uvue.ts
@@ -0,0 +1,7 @@
+class EXIF {
+ constructor(){
+ console.error('当前环境不支持')
+ }
+}
+
+export const exif = new EXIF()
\ No newline at end of file
diff --git a/uni_modules/lime-shared/exif/uvue.uts b/uni_modules/lime-shared/exif/uvue.uts
new file mode 100644
index 0000000..01d21a2
--- /dev/null
+++ b/uni_modules/lime-shared/exif/uvue.uts
@@ -0,0 +1,7 @@
+class EXIF {
+ constructor(){
+ console.error('当前环境不支持')
+ }
+}
+
+export const exif = new EXIF()
\ No newline at end of file
diff --git a/uni_modules/lime-shared/exif/vue.ts b/uni_modules/lime-shared/exif/vue.ts
new file mode 100644
index 0000000..6862263
--- /dev/null
+++ b/uni_modules/lime-shared/exif/vue.ts
@@ -0,0 +1,1057 @@
+// @ts-nocheck
+import { base64ToArrayBuffer } from '../base64ToArrayBuffer';
+import { pathToBase64 } from '../pathToBase64';
+// import { isBase64 } from '../isBase64';
+import { isDataURI } from '../isBase64';
+import { isString } from '../isString';
+
+interface File {
+ exifdata : any
+ iptcdata : any
+ xmpdata : any
+ src : string
+}
+class EXIF {
+ isXmpEnabled = false
+ debug = false
+ Tags = {
+ // version tags
+ 0x9000: "ExifVersion", // EXIF version
+ 0xA000: "FlashpixVersion", // Flashpix format version
+
+ // colorspace tags
+ 0xA001: "ColorSpace", // Color space information tag
+
+ // image configuration
+ 0xA002: "PixelXDimension", // Valid width of meaningful image
+ 0xA003: "PixelYDimension", // Valid height of meaningful image
+ 0x9101: "ComponentsConfiguration", // Information about channels
+ 0x9102: "CompressedBitsPerPixel", // Compressed bits per pixel
+
+ // user information
+ 0x927C: "MakerNote", // Any desired information written by the manufacturer
+ 0x9286: "UserComment", // Comments by user
+
+ // related file
+ 0xA004: "RelatedSoundFile", // Name of related sound file
+
+ // date and time
+ 0x9003: "DateTimeOriginal", // Date and time when the original image was generated
+ 0x9004: "DateTimeDigitized", // Date and time when the image was stored digitally
+ 0x9290: "SubsecTime", // Fractions of seconds for DateTime
+ 0x9291: "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal
+ 0x9292: "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized
+
+ // picture-taking conditions
+ 0x829A: "ExposureTime", // Exposure time (in seconds)
+ 0x829D: "FNumber", // F number
+ 0x8822: "ExposureProgram", // Exposure program
+ 0x8824: "SpectralSensitivity", // Spectral sensitivity
+ 0x8827: "ISOSpeedRatings", // ISO speed rating
+ 0x8828: "OECF", // Optoelectric conversion factor
+ 0x9201: "ShutterSpeedValue", // Shutter speed
+ 0x9202: "ApertureValue", // Lens aperture
+ 0x9203: "BrightnessValue", // Value of brightness
+ 0x9204: "ExposureBias", // Exposure bias
+ 0x9205: "MaxApertureValue", // Smallest F number of lens
+ 0x9206: "SubjectDistance", // Distance to subject in meters
+ 0x9207: "MeteringMode", // Metering mode
+ 0x9208: "LightSource", // Kind of light source
+ 0x9209: "Flash", // Flash status
+ 0x9214: "SubjectArea", // Location and area of main subject
+ 0x920A: "FocalLength", // Focal length of the lens in mm
+ 0xA20B: "FlashEnergy", // Strobe energy in BCPS
+ 0xA20C: "SpatialFrequencyResponse", //
+ 0xA20E: "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit
+ 0xA20F: "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit
+ 0xA210: "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution
+ 0xA214: "SubjectLocation", // Location of subject in image
+ 0xA215: "ExposureIndex", // Exposure index selected on camera
+ 0xA217: "SensingMethod", // Image sensor type
+ 0xA300: "FileSource", // Image source (3 == DSC)
+ 0xA301: "SceneType", // Scene type (1 == directly photographed)
+ 0xA302: "CFAPattern", // Color filter array geometric pattern
+ 0xA401: "CustomRendered", // Special processing
+ 0xA402: "ExposureMode", // Exposure mode
+ 0xA403: "WhiteBalance", // 1 = auto white balance, 2 = manual
+ 0xA404: "DigitalZoomRation", // Digital zoom ratio
+ 0xA405: "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm)
+ 0xA406: "SceneCaptureType", // Type of scene
+ 0xA407: "GainControl", // Degree of overall image gain adjustment
+ 0xA408: "Contrast", // Direction of contrast processing applied by camera
+ 0xA409: "Saturation", // Direction of saturation processing applied by camera
+ 0xA40A: "Sharpness", // Direction of sharpness processing applied by camera
+ 0xA40B: "DeviceSettingDescription", //
+ 0xA40C: "SubjectDistanceRange", // Distance to subject
+
+ // other tags
+ 0xA005: "InteroperabilityIFDPointer",
+ 0xA420: "ImageUniqueID" // Identifier assigned uniquely to each image
+ }
+ TiffTags = {
+ 0x0100: "ImageWidth",
+ 0x0101: "ImageHeight",
+ 0x8769: "ExifIFDPointer",
+ 0x8825: "GPSInfoIFDPointer",
+ 0xA005: "InteroperabilityIFDPointer",
+ 0x0102: "BitsPerSample",
+ 0x0103: "Compression",
+ 0x0106: "PhotometricInterpretation",
+ 0x0112: "Orientation",
+ 0x0115: "SamplesPerPixel",
+ 0x011C: "PlanarConfiguration",
+ 0x0212: "YCbCrSubSampling",
+ 0x0213: "YCbCrPositioning",
+ 0x011A: "XResolution",
+ 0x011B: "YResolution",
+ 0x0128: "ResolutionUnit",
+ 0x0111: "StripOffsets",
+ 0x0116: "RowsPerStrip",
+ 0x0117: "StripByteCounts",
+ 0x0201: "JPEGInterchangeFormat",
+ 0x0202: "JPEGInterchangeFormatLength",
+ 0x012D: "TransferFunction",
+ 0x013E: "WhitePoint",
+ 0x013F: "PrimaryChromaticities",
+ 0x0211: "YCbCrCoefficients",
+ 0x0214: "ReferenceBlackWhite",
+ 0x0132: "DateTime",
+ 0x010E: "ImageDescription",
+ 0x010F: "Make",
+ 0x0110: "Model",
+ 0x0131: "Software",
+ 0x013B: "Artist",
+ 0x8298: "Copyright"
+ }
+ GPSTags = {
+ 0x0000: "GPSVersionID",
+ 0x0001: "GPSLatitudeRef",
+ 0x0002: "GPSLatitude",
+ 0x0003: "GPSLongitudeRef",
+ 0x0004: "GPSLongitude",
+ 0x0005: "GPSAltitudeRef",
+ 0x0006: "GPSAltitude",
+ 0x0007: "GPSTimeStamp",
+ 0x0008: "GPSSatellites",
+ 0x0009: "GPSStatus",
+ 0x000A: "GPSMeasureMode",
+ 0x000B: "GPSDOP",
+ 0x000C: "GPSSpeedRef",
+ 0x000D: "GPSSpeed",
+ 0x000E: "GPSTrackRef",
+ 0x000F: "GPSTrack",
+ 0x0010: "GPSImgDirectionRef",
+ 0x0011: "GPSImgDirection",
+ 0x0012: "GPSMapDatum",
+ 0x0013: "GPSDestLatitudeRef",
+ 0x0014: "GPSDestLatitude",
+ 0x0015: "GPSDestLongitudeRef",
+ 0x0016: "GPSDestLongitude",
+ 0x0017: "GPSDestBearingRef",
+ 0x0018: "GPSDestBearing",
+ 0x0019: "GPSDestDistanceRef",
+ 0x001A: "GPSDestDistance",
+ 0x001B: "GPSProcessingMethod",
+ 0x001C: "GPSAreaInformation",
+ 0x001D: "GPSDateStamp",
+ 0x001E: "GPSDifferential"
+ }
+ // EXIF 2.3 Spec
+ IFD1Tags = {
+ 0x0100: "ImageWidth",
+ 0x0101: "ImageHeight",
+ 0x0102: "BitsPerSample",
+ 0x0103: "Compression",
+ 0x0106: "PhotometricInterpretation",
+ 0x0111: "StripOffsets",
+ 0x0112: "Orientation",
+ 0x0115: "SamplesPerPixel",
+ 0x0116: "RowsPerStrip",
+ 0x0117: "StripByteCounts",
+ 0x011A: "XResolution",
+ 0x011B: "YResolution",
+ 0x011C: "PlanarConfiguration",
+ 0x0128: "ResolutionUnit",
+ 0x0201: "JpegIFOffset", // When image format is JPEG, this value show offset to JPEG data stored.(aka "ThumbnailOffset" or "JPEGInterchangeFormat")
+ 0x0202: "JpegIFByteCount", // When image format is JPEG, this value shows data size of JPEG image (aka "ThumbnailLength" or "JPEGInterchangeFormatLength")
+ 0x0211: "YCbCrCoefficients",
+ 0x0212: "YCbCrSubSampling",
+ 0x0213: "YCbCrPositioning",
+ 0x0214: "ReferenceBlackWhite"
+ }
+ StringValues = {
+ ExposureProgram: {
+ 0: "Not defined",
+ 1: "Manual",
+ 2: "Normal program",
+ 3: "Aperture priority",
+ 4: "Shutter priority",
+ 5: "Creative program",
+ 6: "Action program",
+ 7: "Portrait mode",
+ 8: "Landscape mode"
+ },
+ MeteringMode: {
+ 0: "Unknown",
+ 1: "Average",
+ 2: "CenterWeightedAverage",
+ 3: "Spot",
+ 4: "MultiSpot",
+ 5: "Pattern",
+ 6: "Partial",
+ 255: "Other"
+ },
+ LightSource: {
+ 0: "Unknown",
+ 1: "Daylight",
+ 2: "Fluorescent",
+ 3: "Tungsten (incandescent light)",
+ 4: "Flash",
+ 9: "Fine weather",
+ 10: "Cloudy weather",
+ 11: "Shade",
+ 12: "Daylight fluorescent (D 5700 - 7100K)",
+ 13: "Day white fluorescent (N 4600 - 5400K)",
+ 14: "Cool white fluorescent (W 3900 - 4500K)",
+ 15: "White fluorescent (WW 3200 - 3700K)",
+ 17: "Standard light A",
+ 18: "Standard light B",
+ 19: "Standard light C",
+ 20: "D55",
+ 21: "D65",
+ 22: "D75",
+ 23: "D50",
+ 24: "ISO studio tungsten",
+ 255: "Other"
+ },
+ Flash: {
+ 0x0000: "Flash did not fire",
+ 0x0001: "Flash fired",
+ 0x0005: "Strobe return light not detected",
+ 0x0007: "Strobe return light detected",
+ 0x0009: "Flash fired, compulsory flash mode",
+ 0x000D: "Flash fired, compulsory flash mode, return light not detected",
+ 0x000F: "Flash fired, compulsory flash mode, return light detected",
+ 0x0010: "Flash did not fire, compulsory flash mode",
+ 0x0018: "Flash did not fire, auto mode",
+ 0x0019: "Flash fired, auto mode",
+ 0x001D: "Flash fired, auto mode, return light not detected",
+ 0x001F: "Flash fired, auto mode, return light detected",
+ 0x0020: "No flash function",
+ 0x0041: "Flash fired, red-eye reduction mode",
+ 0x0045: "Flash fired, red-eye reduction mode, return light not detected",
+ 0x0047: "Flash fired, red-eye reduction mode, return light detected",
+ 0x0049: "Flash fired, compulsory flash mode, red-eye reduction mode",
+ 0x004D: "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
+ 0x004F: "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
+ 0x0059: "Flash fired, auto mode, red-eye reduction mode",
+ 0x005D: "Flash fired, auto mode, return light not detected, red-eye reduction mode",
+ 0x005F: "Flash fired, auto mode, return light detected, red-eye reduction mode"
+ },
+ SensingMethod: {
+ 1: "Not defined",
+ 2: "One-chip color area sensor",
+ 3: "Two-chip color area sensor",
+ 4: "Three-chip color area sensor",
+ 5: "Color sequential area sensor",
+ 7: "Trilinear sensor",
+ 8: "Color sequential linear sensor"
+ },
+ SceneCaptureType: {
+ 0: "Standard",
+ 1: "Landscape",
+ 2: "Portrait",
+ 3: "Night scene"
+ },
+ SceneType: {
+ 1: "Directly photographed"
+ },
+ CustomRendered: {
+ 0: "Normal process",
+ 1: "Custom process"
+ },
+ WhiteBalance: {
+ 0: "Auto white balance",
+ 1: "Manual white balance"
+ },
+ GainControl: {
+ 0: "None",
+ 1: "Low gain up",
+ 2: "High gain up",
+ 3: "Low gain down",
+ 4: "High gain down"
+ },
+ Contrast: {
+ 0: "Normal",
+ 1: "Soft",
+ 2: "Hard"
+ },
+ Saturation: {
+ 0: "Normal",
+ 1: "Low saturation",
+ 2: "High saturation"
+ },
+ Sharpness: {
+ 0: "Normal",
+ 1: "Soft",
+ 2: "Hard"
+ },
+ SubjectDistanceRange: {
+ 0: "Unknown",
+ 1: "Macro",
+ 2: "Close view",
+ 3: "Distant view"
+ },
+ FileSource: {
+ 3: "DSC"
+ },
+
+ Components: {
+ 0: "",
+ 1: "Y",
+ 2: "Cb",
+ 3: "Cr",
+ 4: "R",
+ 5: "G",
+ 6: "B"
+ }
+ }
+ enableXmp() {
+ this.isXmpEnabled = true
+ }
+ disableXmp() {
+ this.isXmpEnabled = false;
+ }
+ /**
+ * 获取图片数据
+ * @param img 图片地址
+ * @param callback 回调 返回图片数据
+ * */
+ getData(img : any, callback : Function) {
+ // if (((self.Image && img instanceof self.Image) || (self.HTMLImageElement && img instanceof self.HTMLImageElement)) && !img.complete)
+ // return false;
+ let file : File = {
+ src: '',
+ exifdata: null,
+ iptcdata: null,
+ xmpdata: null,
+ }
+ if (isDataURI(img)) {
+ file.src = img
+ } else if (img.path) {
+ file.src = img.path
+ } else if (isString(img)) {
+ file.src = img
+ } else {
+ return false;
+ }
+
+
+ if (!imageHasData(file)) {
+ getImageData(file, callback);
+ } else {
+ if (callback) {
+ callback.call(file);
+ }
+ }
+ return true;
+ }
+ /**
+ * 获取图片tag
+ * @param img 图片数据
+ * @param tag tag 类型
+ * */
+ getTag(img : File, tag : string) {
+ if (!imageHasData(img)) return;
+ return img.exifdata[tag];
+ }
+ getIptcTag(img : File, tag : string) {
+ if (!imageHasData(img)) return;
+ return img.iptcdata[tag];
+ }
+ getAllTags(img : File) {
+ if (!imageHasData(img)) return {};
+ let a,
+ data = img.exifdata,
+ tags = {};
+ for (a in data) {
+ if (data.hasOwnProperty(a)) {
+ tags[a] = data[a];
+ }
+ }
+ return tags;
+ }
+ getAllIptcTags(img : File) {
+ if (!imageHasData(img)) return {};
+ let a,
+ data = img.iptcdata,
+ tags = {};
+ for (a in data) {
+ if (data.hasOwnProperty(a)) {
+ tags[a] = data[a];
+ }
+ }
+ return tags;
+ }
+ pretty(img : File) {
+ if (!imageHasData(img)) return "";
+ let a,
+ data = img.exifdata,
+ strPretty = "";
+ for (a in data) {
+ if (data.hasOwnProperty(a)) {
+ if (typeof data[a] == "object") {
+ if (data[a] instanceof Number) {
+ strPretty += a + " : " + data[a] + " [" + data[a].numerator + "/" + data[a]
+ .denominator + "]\r\n";
+ } else {
+ strPretty += a + " : [" + data[a].length + " values]\r\n";
+ }
+ } else {
+ strPretty += a + " : " + data[a] + "\r\n";
+ }
+ }
+ }
+ return strPretty;
+ }
+ readFromBinaryFile(file: ArrayBuffer) {
+ return findEXIFinJPEG(file);
+ }
+}
+
+export const exif = new EXIF()
+// export function getData(img, callback) {
+// const exif = new EXIF()
+// exif.getData(img, callback)
+// }
+
+// export default {getData}
+const ExifTags = exif.Tags
+const TiffTags = exif.TiffTags
+const IFD1Tags = exif.IFD1Tags
+const GPSTags = exif.GPSTags
+const StringValues = exif.StringValues
+
+
+function imageHasData(img : File) : boolean {
+ return !!(img.exifdata);
+}
+
+function objectURLToBlob(url : string, callback : Function) {
+ try {
+ const http = new XMLHttpRequest();
+ http.open("GET", url, true);
+ http.responseType = "blob";
+ http.onload = function (e) {
+ if (this.status == 200 || this.status === 0) {
+ callback(this.response);
+ }
+ };
+ http.send();
+ } catch (e) {
+ console.warn(e)
+ }
+}
+
+
+function getImageData(img : File, callback : Function) {
+ function handleBinaryFile(binFile: ArrayBuffer) {
+ const data = findEXIFinJPEG(binFile);
+ img.exifdata = data ?? {};
+ const iptcdata = findIPTCinJPEG(binFile);
+ img.iptcdata = iptcdata ?? {};
+ if (exif.isXmpEnabled) {
+ const xmpdata = findXMPinJPEG(binFile);
+ img.xmpdata = xmpdata ?? {};
+ }
+ if (callback) {
+ callback.call(img);
+ }
+ }
+
+ if (img.src) {
+ if (/^data\:/i.test(img.src)) { // Data URI
+ // var arrayBuffer = base64ToArrayBuffer(img.src);
+ handleBinaryFile(base64ToArrayBuffer(img.src));
+
+ } else if (/^blob\:/i.test(img.src) && typeof FileReader !== 'undefined') { // Object URL
+ var fileReader = new FileReader();
+ fileReader.onload = function (e) {
+ handleBinaryFile(e.target.result);
+ };
+ objectURLToBlob(img.src, function (blob : Blob) {
+ fileReader.readAsArrayBuffer(blob);
+ });
+ } else if (typeof XMLHttpRequest !== 'undefined') {
+ var http = new XMLHttpRequest();
+ http.onload = function () {
+ if (this.status == 200 || this.status === 0) {
+ handleBinaryFile(http.response);
+ } else {
+ throw "Could not load image";
+ }
+ http = null;
+ };
+ http.open("GET", img.src, true);
+ http.responseType = "arraybuffer";
+ http.send(null);
+ } else {
+ pathToBase64(img.src).then(res => {
+ handleBinaryFile(base64ToArrayBuffer(res));
+ })
+ }
+ } else if (typeof FileReader !== 'undefined' && self.FileReader && (img instanceof self.Blob || img instanceof self.File)) {
+ var fileReader = new FileReader();
+ fileReader.onload = function (e : any) {
+ if (exif.debug) console.log("Got file of length " + e.target.result.byteLength);
+ handleBinaryFile(e.target.result);
+ };
+
+ fileReader.readAsArrayBuffer(img);
+ }
+}
+
+function findEXIFinJPEG(file: ArrayBuffer) {
+ const dataView = new DataView(file);
+
+ if (exif.debug) console.log("Got file of length " + file.byteLength);
+ if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
+ if (exif.debug) console.log("Not a valid JPEG");
+ return false; // not a valid jpeg
+ }
+
+ let offset = 2,
+ length = file.byteLength,
+ marker;
+
+ while (offset < length) {
+ if (dataView.getUint8(offset) != 0xFF) {
+ if (exif.debug) console.log("Not a valid marker at offset " + offset + ", found: " + dataView.getUint8(
+ offset));
+ return false; // not a valid marker, something is wrong
+ }
+
+ marker = dataView.getUint8(offset + 1);
+ if (exif.debug) console.log(marker);
+
+ // we could implement handling for other markers here,
+ // but we're only looking for 0xFFE1 for EXIF data
+
+ if (marker == 225) {
+ if (exif.debug) console.log("Found 0xFFE1 marker");
+
+ return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2);
+
+ // offset += 2 + file.getShortAt(offset+2, true);
+
+ } else {
+ offset += 2 + dataView.getUint16(offset + 2);
+ }
+
+ }
+
+}
+
+function findIPTCinJPEG(file: ArrayBuffer) {
+ const dataView = new DataView(file);
+
+ if (exif.debug) console.log("Got file of length " + file.byteLength);
+ if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
+ if (exif.debug) console.log("Not a valid JPEG");
+ return false; // not a valid jpeg
+ }
+
+ let offset = 2,
+ length = file.byteLength;
+
+
+ const isFieldSegmentStart = function (dataView, offset: number) {
+ return (
+ dataView.getUint8(offset) === 0x38 &&
+ dataView.getUint8(offset + 1) === 0x42 &&
+ dataView.getUint8(offset + 2) === 0x49 &&
+ dataView.getUint8(offset + 3) === 0x4D &&
+ dataView.getUint8(offset + 4) === 0x04 &&
+ dataView.getUint8(offset + 5) === 0x04
+ );
+ };
+
+ while (offset < length) {
+
+ if (isFieldSegmentStart(dataView, offset)) {
+
+ // Get the length of the name header (which is padded to an even number of bytes)
+ var nameHeaderLength = dataView.getUint8(offset + 7);
+ if (nameHeaderLength % 2 !== 0) nameHeaderLength += 1;
+ // Check for pre photoshop 6 format
+ if (nameHeaderLength === 0) {
+ // Always 4
+ nameHeaderLength = 4;
+ }
+
+ var startOffset = offset + 8 + nameHeaderLength;
+ var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength);
+
+ return readIPTCData(file, startOffset, sectionLength);
+
+ break;
+
+ }
+
+
+ // Not the marker, continue searching
+ offset++;
+
+ }
+
+}
+
+const IptcFieldMap = {
+ 0x78: 'caption',
+ 0x6E: 'credit',
+ 0x19: 'keywords',
+ 0x37: 'dateCreated',
+ 0x50: 'byline',
+ 0x55: 'bylineTitle',
+ 0x7A: 'captionWriter',
+ 0x69: 'headline',
+ 0x74: 'copyright',
+ 0x0F: 'category'
+};
+
+function readIPTCData(file: ArrayBuffer, startOffset: number, sectionLength: number) {
+ const dataView = new DataView(file);
+ let data = {};
+ let fieldValue, fieldName, dataSize, segmentType, segmentSize;
+ let segmentStartPos = startOffset;
+ while (segmentStartPos < startOffset + sectionLength) {
+ if (dataView.getUint8(segmentStartPos) === 0x1C && dataView.getUint8(segmentStartPos + 1) === 0x02) {
+ segmentType = dataView.getUint8(segmentStartPos + 2);
+ if (segmentType in IptcFieldMap) {
+ dataSize = dataView.getInt16(segmentStartPos + 3);
+ segmentSize = dataSize + 5;
+ fieldName = IptcFieldMap[segmentType];
+ fieldValue = getStringFromDB(dataView, segmentStartPos + 5, dataSize);
+ // Check if we already stored a value with this name
+ if (data.hasOwnProperty(fieldName)) {
+ // Value already stored with this name, create multivalue field
+ if (data[fieldName] instanceof Array) {
+ data[fieldName].push(fieldValue);
+ } else {
+ data[fieldName] = [data[fieldName], fieldValue];
+ }
+ } else {
+ data[fieldName] = fieldValue;
+ }
+ }
+
+ }
+ segmentStartPos++;
+ }
+ return data;
+}
+
+function readTags(file: DataView, tiffStart: number, dirStart: number, strings: any[], bigEnd: number) {
+ let entries = file.getUint16(dirStart, !bigEnd),
+ tags = {},
+ entryOffset, tag;
+
+ for (let i = 0; i < entries; i++) {
+ entryOffset = dirStart + i * 12 + 2;
+ tag = strings[file.getUint16(entryOffset, !bigEnd)];
+ if (!tag && exif.debug) console.log("Unknown tag: " + file.getUint16(entryOffset, !bigEnd));
+ tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd);
+ }
+ return tags;
+}
+
+function readTagValue(file: DataView, entryOffset: number, tiffStart: number, dirStart: number, bigEnd: number) {
+ let type = file.getUint16(entryOffset + 2, !bigEnd),
+ numValues = file.getUint32(entryOffset + 4, !bigEnd),
+ valueOffset = file.getUint32(entryOffset + 8, !bigEnd) + tiffStart,
+ offset,
+ vals, val, n,
+ numerator, denominator;
+
+ switch (type) {
+ case 1: // byte, 8-bit unsigned int
+ case 7: // undefined, 8-bit byte, value depending on field
+ if (numValues == 1) {
+ return file.getUint8(entryOffset + 8, !bigEnd);
+ } else {
+ offset = numValues > 4 ? valueOffset : (entryOffset + 8);
+ vals = [];
+ for (n = 0; n < numValues; n++) {
+ vals[n] = file.getUint8(offset + n);
+ }
+ return vals;
+ }
+
+ case 2: // ascii, 8-bit byte
+ offset = numValues > 4 ? valueOffset : (entryOffset + 8);
+ return getStringFromDB(file, offset, numValues - 1);
+
+ case 3: // short, 16 bit int
+ if (numValues == 1) {
+ return file.getUint16(entryOffset + 8, !bigEnd);
+ } else {
+ offset = numValues > 2 ? valueOffset : (entryOffset + 8);
+ vals = [];
+ for (n = 0; n < numValues; n++) {
+ vals[n] = file.getUint16(offset + 2 * n, !bigEnd);
+ }
+ return vals;
+ }
+
+ case 4: // long, 32 bit int
+ if (numValues == 1) {
+ return file.getUint32(entryOffset + 8, !bigEnd);
+ } else {
+ vals = [];
+ for (n = 0; n < numValues; n++) {
+ vals[n] = file.getUint32(valueOffset + 4 * n, !bigEnd);
+ }
+ return vals;
+ }
+
+ case 5: // rational = two long values, first is numerator, second is denominator
+ if (numValues == 1) {
+ numerator = file.getUint32(valueOffset, !bigEnd);
+ denominator = file.getUint32(valueOffset + 4, !bigEnd);
+ val = new Number(numerator / denominator);
+ val.numerator = numerator;
+ val.denominator = denominator;
+ return val;
+ } else {
+ vals = [];
+ for (n = 0; n < numValues; n++) {
+ numerator = file.getUint32(valueOffset + 8 * n, !bigEnd);
+ denominator = file.getUint32(valueOffset + 4 + 8 * n, !bigEnd);
+ vals[n] = new Number(numerator / denominator);
+ vals[n].numerator = numerator;
+ vals[n].denominator = denominator;
+ }
+ return vals;
+ }
+
+ case 9: // slong, 32 bit signed int
+ if (numValues == 1) {
+ return file.getInt32(entryOffset + 8, !bigEnd);
+ } else {
+ vals = [];
+ for (n = 0; n < numValues; n++) {
+ vals[n] = file.getInt32(valueOffset + 4 * n, !bigEnd);
+ }
+ return vals;
+ }
+
+ case 10: // signed rational, two slongs, first is numerator, second is denominator
+ if (numValues == 1) {
+ return file.getInt32(valueOffset, !bigEnd) / file.getInt32(valueOffset + 4, !bigEnd);
+ } else {
+ vals = [];
+ for (n = 0; n < numValues; n++) {
+ vals[n] = file.getInt32(valueOffset + 8 * n, !bigEnd) / file.getInt32(valueOffset + 4 + 8 *
+ n, !bigEnd);
+ }
+ return vals;
+ }
+ }
+}
+/**
+ * Given an IFD (Image File Directory) start offset
+ * returns an offset to next IFD or 0 if it's the last IFD.
+ */
+function getNextIFDOffset(dataView: DataView, dirStart: number, bigEnd: number) {
+ //the first 2bytes means the number of directory entries contains in this IFD
+ var entries = dataView.getUint16(dirStart, !bigEnd);
+
+ // After last directory entry, there is a 4bytes of data,
+ // it means an offset to next IFD.
+ // If its value is '0x00000000', it means this is the last IFD and there is no linked IFD.
+
+ return dataView.getUint32(dirStart + 2 + entries * 12, !bigEnd); // each entry is 12 bytes long
+}
+
+function readThumbnailImage(dataView: DataView, tiffStart: number, firstIFDOffset: number, bigEnd: number) {
+ // get the IFD1 offset
+ const IFD1OffsetPointer = getNextIFDOffset(dataView, tiffStart + firstIFDOffset, bigEnd);
+
+ if (!IFD1OffsetPointer) {
+ // console.log('******** IFD1Offset is empty, image thumb not found ********');
+ return {};
+ } else if (IFD1OffsetPointer > dataView.byteLength) { // this should not happen
+ // console.log('******** IFD1Offset is outside the bounds of the DataView ********');
+ return {};
+ }
+ // console.log('******* thumbnail IFD offset (IFD1) is: %s', IFD1OffsetPointer);
+
+ let thumbTags : any = readTags(dataView, tiffStart, tiffStart + IFD1OffsetPointer, IFD1Tags, bigEnd)
+
+ // EXIF 2.3 specification for JPEG format thumbnail
+
+ // If the value of Compression(0x0103) Tag in IFD1 is '6', thumbnail image format is JPEG.
+ // Most of Exif image uses JPEG format for thumbnail. In that case, you can get offset of thumbnail
+ // by JpegIFOffset(0x0201) Tag in IFD1, size of thumbnail by JpegIFByteCount(0x0202) Tag.
+ // Data format is ordinary JPEG format, starts from 0xFFD8 and ends by 0xFFD9. It seems that
+ // JPEG format and 160x120pixels of size are recommended thumbnail format for Exif2.1 or later.
+
+ if (thumbTags['Compression'] && typeof Blob !== 'undefined') {
+ // console.log('Thumbnail image found!');
+
+ switch (thumbTags['Compression']) {
+ case 6:
+ // console.log('Thumbnail image format is JPEG');
+ if (thumbTags.JpegIFOffset && thumbTags.JpegIFByteCount) {
+ // extract the thumbnail
+ var tOffset = tiffStart + thumbTags.JpegIFOffset;
+ var tLength = thumbTags.JpegIFByteCount;
+ thumbTags['blob'] = new Blob([new Uint8Array(dataView.buffer, tOffset, tLength)], {
+ type: 'image/jpeg'
+ });
+ }
+ break;
+
+ case 1:
+ console.log("Thumbnail image format is TIFF, which is not implemented.");
+ break;
+ default:
+ console.log("Unknown thumbnail image format '%s'", thumbTags['Compression']);
+ }
+ } else if (thumbTags['PhotometricInterpretation'] == 2) {
+ console.log("Thumbnail image format is RGB, which is not implemented.");
+ }
+ return thumbTags;
+}
+
+function getStringFromDB(buffer: DataView, start: number, length: number) {
+ let outstr = "";
+ for (let n = start; n < start + length; n++) {
+ outstr += String.fromCharCode(buffer.getUint8(n));
+ }
+ return outstr;
+}
+
+function readEXIFData(file: DataView, start: number) {
+ if (getStringFromDB(file, start, 4) != "Exif") {
+ if (exif.debug) console.log("Not valid EXIF data! " + getStringFromDB(file, start, 4));
+ return false;
+ }
+
+ let bigEnd,
+ tags, tag,
+ exifData, gpsData,
+ tiffOffset = start + 6;
+
+ // test for TIFF validity and endianness
+ if (file.getUint16(tiffOffset) == 0x4949) {
+ bigEnd = false;
+ } else if (file.getUint16(tiffOffset) == 0x4D4D) {
+ bigEnd = true;
+ } else {
+ if (exif.debug) console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)");
+ return false;
+ }
+
+ if (file.getUint16(tiffOffset + 2, !bigEnd) != 0x002A) {
+ if (exif.debug) console.log("Not valid TIFF data! (no 0x002A)");
+ return false;
+ }
+
+ const firstIFDOffset = file.getUint32(tiffOffset + 4, !bigEnd);
+
+ if (firstIFDOffset < 0x00000008) {
+ if (exif.debug) console.log("Not valid TIFF data! (First offset less than 8)", file.getUint32(tiffOffset + 4,
+ !bigEnd));
+ return false;
+ }
+
+ tags = readTags(file, tiffOffset, tiffOffset + firstIFDOffset, TiffTags, bigEnd);
+
+ if (tags.ExifIFDPointer) {
+ exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd);
+ for (tag in exifData) {
+ switch (tag) {
+ case "LightSource":
+ case "Flash":
+ case "MeteringMode":
+ case "ExposureProgram":
+ case "SensingMethod":
+ case "SceneCaptureType":
+ case "SceneType":
+ case "CustomRendered":
+ case "WhiteBalance":
+ case "GainControl":
+ case "Contrast":
+ case "Saturation":
+ case "Sharpness":
+ case "SubjectDistanceRange":
+ case "FileSource":
+ exifData[tag] = StringValues[tag][exifData[tag]];
+ break;
+
+ case "ExifVersion":
+ case "FlashpixVersion":
+ exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2],
+ exifData[tag][3]);
+ break;
+
+ case "ComponentsConfiguration":
+ exifData[tag] =
+ StringValues.Components[exifData[tag][0]] +
+ StringValues.Components[exifData[tag][1]] +
+ StringValues.Components[exifData[tag][2]] +
+ StringValues.Components[exifData[tag][3]];
+ break;
+ }
+ tags[tag] = exifData[tag];
+ }
+ }
+
+ if (tags.GPSInfoIFDPointer) {
+ gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd);
+ for (tag in gpsData) {
+ switch (tag) {
+ case "GPSVersionID":
+ gpsData[tag] = gpsData[tag][0] +
+ "." + gpsData[tag][1] +
+ "." + gpsData[tag][2] +
+ "." + gpsData[tag][3];
+ break;
+ }
+ tags[tag] = gpsData[tag];
+ }
+ }
+
+ // extract thumbnail
+ tags['thumbnail'] = readThumbnailImage(file, tiffOffset, firstIFDOffset, bigEnd);
+
+ return tags;
+}
+
+function findXMPinJPEG(file: ArrayBuffer) {
+
+ if (!('DOMParser' in self)) {
+ // console.warn('XML parsing not supported without DOMParser');
+ return;
+ }
+ const dataView = new DataView(file);
+
+ if (exif.debug) console.log("Got file of length " + file.byteLength);
+ if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
+ if (exif.debug) console.log("Not a valid JPEG");
+ return false; // not a valid jpeg
+ }
+
+ let offset = 2,
+ length = file.byteLength,
+ dom = new DOMParser();
+
+ while (offset < (length - 4)) {
+ if (getStringFromDB(dataView, offset, 4) == "http") {
+ const startOffset = offset - 1;
+ const sectionLength = dataView.getUint16(offset - 2) - 1;
+ let xmpString = getStringFromDB(dataView, startOffset, sectionLength)
+ const xmpEndIndex = xmpString.indexOf('xmpmeta>') + 8;
+ xmpString = xmpString.substring(xmpString.indexOf(' 0) {
+ json['@attributes'] = {};
+ for (var j = 0; j < xml.attributes.length; j++) {
+ var attribute = xml.attributes.item(j);
+ json['@attributes'][attribute.nodeName] = attribute.nodeValue;
+ }
+ }
+ } else if (xml.nodeType == 3) { // text node
+ return xml.nodeValue;
+ }
+
+ // deal with children
+ if (xml.hasChildNodes()) {
+ for (var i = 0; i < xml.childNodes.length; i++) {
+ var child = xml.childNodes.item(i);
+ var nodeName = child.nodeName;
+ if (json[nodeName] == null) {
+ json[nodeName] = xml2json(child);
+ } else {
+ if (json[nodeName].push == null) {
+ var old = json[nodeName];
+ json[nodeName] = [];
+ json[nodeName].push(old);
+ }
+ json[nodeName].push(xml2json(child));
+ }
+ }
+ }
+
+ return json;
+}
+
+function xml2Object(xml: any) {
+ try {
+ var obj = {};
+ if (xml.children.length > 0) {
+ for (var i = 0; i < xml.children.length; i++) {
+ var item = xml.children.item(i);
+ var attributes = item.attributes;
+ for (var idx in attributes) {
+ var itemAtt = attributes[idx];
+ var dataKey = itemAtt.nodeName;
+ var dataValue = itemAtt.nodeValue;
+
+ if (dataKey !== undefined) {
+ obj[dataKey] = dataValue;
+ }
+ }
+ var nodeName = item.nodeName;
+
+ if (typeof (obj[nodeName]) == "undefined") {
+ obj[nodeName] = xml2json(item);
+ } else {
+ if (typeof (obj[nodeName].push) == "undefined") {
+ var old = obj[nodeName];
+
+ obj[nodeName] = [];
+ obj[nodeName].push(old);
+ }
+ obj[nodeName].push(xml2json(item));
+ }
+ }
+ } else {
+ obj = xml.textContent;
+ }
+ return obj;
+ } catch (e) {
+ console.log(e.message);
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/fillArray/index.ts b/uni_modules/lime-shared/fillArray/index.ts
new file mode 100644
index 0000000..9f40008
--- /dev/null
+++ b/uni_modules/lime-shared/fillArray/index.ts
@@ -0,0 +1,18 @@
+// @ts-nocheck
+export function fillArray(length: number, value: T): T[] {
+
+ // #ifdef APP-ANDROID
+ const result:T[] = []
+ for (let i = 0; i < length; i++) {
+ result.push(value)
+ }
+ // #endif
+
+ // #ifndef APP-ANDROID
+ const result = new Array(length).fill(value)
+ // #endif
+
+
+
+ return result
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/fillZero/index.ts b/uni_modules/lime-shared/fillZero/index.ts
new file mode 100644
index 0000000..9952c45
--- /dev/null
+++ b/uni_modules/lime-shared/fillZero/index.ts
@@ -0,0 +1,11 @@
+// @ts-nocheck
+/**
+ * 在数字前填充零,返回字符串形式的结果
+ * @param number 要填充零的数字
+ * @param length 填充零后的字符串长度,默认为2
+ * @returns 填充零后的字符串
+ */
+export function fillZero(number: number, length: number = 2): string {
+ // 将数字转换为字符串,然后使用 padStart 方法填充零到指定长度
+ return `${number}`.padStart(length, '0');
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/findLastIndex/index.ts b/uni_modules/lime-shared/findLastIndex/index.ts
new file mode 100644
index 0000000..620750f
--- /dev/null
+++ b/uni_modules/lime-shared/findLastIndex/index.ts
@@ -0,0 +1,23 @@
+// @/uni_modules/lime-shared/arrayUtils.ts
+
+/**
+ * 兼容版 findLastIndex(ES2023 polyfill)
+ */
+export function findLastIndex(
+ array : T[],
+ predicate : (value : T, index : number, obj : T[]) => boolean) : number {
+
+ // #ifndef UNI-APP-X && APP-ANDROID
+ if (typeof array.findLastIndex == 'function') {
+ // 如果原生支持,直接用(未来兼容)
+ return array.findLastIndex(predicate);
+ }
+ // #endif
+ // 否则降级到 for 循环
+ for (let i = array.length - 1; i >= 0; i--) {
+ if (predicate(array[i], i, array)) {
+ return i;
+ }
+ }
+ return -1;
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/floatAdd/index.ts b/uni_modules/lime-shared/floatAdd/index.ts
new file mode 100644
index 0000000..13557f8
--- /dev/null
+++ b/uni_modules/lime-shared/floatAdd/index.ts
@@ -0,0 +1,27 @@
+import { isNumber } from '../isNumber'
+/**
+ * 返回两个浮点数相加的结果
+ * @param num1 第一个浮点数
+ * @param num2 第二个浮点数
+ * @returns 两个浮点数的相加结果
+ */
+export function floatAdd(num1 : number, num2 : number) : number {
+ // 检查 num1 和 num2 是否为数字类型
+ if (!isNumber(num1) || !isNumber(num2)) {
+ console.warn('Please pass in the number type');
+ return NaN;
+ }
+
+ const getDecimalLen = (n: number):number => {
+ const arr = n.toString().split('.')
+ if(arr.length > 0) return arr[1].length
+ return 0
+ };
+ const r1 = getDecimalLen(num1);
+ const r2 = getDecimalLen(num2);
+
+ // 计算需要扩大的倍数
+ const m = Math.pow(10, Math.max(r1, r2));
+ // 返回相加结果
+ return Math.round((num1 * m + num2 * m)) / m;
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/floatDiv/index.ts b/uni_modules/lime-shared/floatDiv/index.ts
new file mode 100644
index 0000000..195d4ab
--- /dev/null
+++ b/uni_modules/lime-shared/floatDiv/index.ts
@@ -0,0 +1,45 @@
+import { floatMul } from '../floatMul';
+import { isNumber } from '../isNumber';
+
+/**
+ * 除法函数,用于处理浮点数除法并保持精度。
+ * @param {number} num1 - 被除数。
+ * @param {number} num2 - 除数。
+ * @returns {number} 除法运算的结果,保留正确的精度。
+ */
+export function floatDiv(num1:number, num2:number):number {
+ // 如果传入的不是数字类型,则打印警告并返回NaN
+ if (!isNumber(num1) || !isNumber(num2)) {
+ console.warn('请传入数字类型');
+ return NaN;
+ }
+
+ let m1 = 0, // 被除数小数点后的位数
+ m2 = 0, // 除数小数点后的位数
+ s1 = num1.toString(), // 将被除数转换为字符串
+ s2 = num2.toString(); // 将除数转换为字符串
+
+ // 计算被除数小数点后的位数
+ try {
+ m1 += s1.split('.')[1].length;
+ } catch (error) {}
+
+ // 计算除数小数点后的位数
+ try {
+ m2 += s2.split('.')[1].length;
+ } catch (error) {}
+
+ // 进行除法运算并处理小数点后的位数,使用之前定义的乘法函数保持精度
+ // #ifdef APP-ANDROID
+ return floatMul(
+ parseFloat(s1.replace('.', '')) / parseFloat(s2.replace('.', '')),
+ Math.pow(10, m2 - m1),
+ );
+ // #endif
+ // #ifndef APP-ANDROID
+ return floatMul(
+ Number(s1.replace('.', '')) / Number(s2.replace('.', '')),
+ Math.pow(10, m2 - m1),
+ );
+ // #endif
+}
diff --git a/uni_modules/lime-shared/floatMul/index.ts b/uni_modules/lime-shared/floatMul/index.ts
new file mode 100644
index 0000000..51a867e
--- /dev/null
+++ b/uni_modules/lime-shared/floatMul/index.ts
@@ -0,0 +1,44 @@
+// @ts-nocheck
+import {isNumber} from '../isNumber';
+// #ifdef APP-ANDROID
+import BigDecimal from 'java.math.BigDecimal'
+// import BigDecimal from 'java.math.BigDecimal'
+// import StringBuilder from 'java.lang.StringBuilder'
+// import java.math.BigDecimal;
+// #endif
+
+/**
+ * 乘法函数,用于处理浮点数乘法并保持精度。
+ * @param {number} num1 - 第一个乘数。
+ * @param {number} num2 - 第二个乘数。
+ * @returns {number} 乘法运算的结果,保留正确的精度。
+ */
+export function floatMul(num1 : number, num2 : number) : number {
+ if (!(isNumber(num1) || isNumber(num2))) {
+ console.warn('Please pass in the number type');
+ return NaN;
+ }
+ let m = 0;
+ // #ifdef APP-ANDROID
+ let s1 = BigDecimal.valueOf(num1.toDouble()).toPlainString(); //new UTSNumber(num1).toString() // //`${num1.toFloat()}`// num1.toString(),
+ let s2 = BigDecimal.valueOf(num2.toDouble()).toPlainString(); //new UTSNumber(num2).toString() //`${num2.toFloat()}`//.toString();
+ // #endif
+ // #ifndef APP-ANDROID
+ let s1:string = `${num1}`// num1.toString(),
+ let s2:string = `${num2}`//.toString();
+ // #endif
+
+ try {
+ m += s1.split('.')[1].length;
+ } catch (error) { }
+ try {
+ m += s2.split('.')[1].length;
+ } catch (error) { }
+
+ // #ifdef APP-ANDROID
+ return parseFloat(s1.replace('.', '')) * parseFloat(s2.replace('.', '')) / Math.pow(10, m);
+ // #endif
+ // #ifndef APP-ANDROID
+ return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m);
+ // #endif
+}
diff --git a/uni_modules/lime-shared/floatSub/index.ts b/uni_modules/lime-shared/floatSub/index.ts
new file mode 100644
index 0000000..9bc25cb
--- /dev/null
+++ b/uni_modules/lime-shared/floatSub/index.ts
@@ -0,0 +1,32 @@
+import { isNumber } from '../isNumber';
+/**
+ * 减法函数,用于处理浮点数减法并保持精度。
+ * @param {number} num1 - 被减数。
+ * @param {number} num2 - 减数。
+ * @returns {number} 减法运算的结果,保留正确的精度。
+ */
+export function floatSub(num1 : number, num2 : number) : number {
+ if (!(isNumber(num1) || isNumber(num2))) {
+ console.warn('Please pass in the number type');
+ return NaN;
+ }
+ let r1:number, r2:number, m:number, n:number;
+ try {
+ r1 = num1.toString().split('.')[1].length;
+ } catch (error) {
+ r1 = 0;
+ }
+ try {
+ r2 = num2.toString().split('.')[1].length;
+ } catch (error) {
+ r2 = 0;
+ }
+ m = Math.pow(10, Math.max(r1, r2));
+ n = r1 >= r2 ? r1 : r2;
+ // #ifndef APP-ANDROID
+ return Number(((num1 * m - num2 * m) / m).toFixed(n));
+ // #endif
+ // #ifdef APP-ANDROID
+ return parseFloat(((num1 * m - num2 * m) / m).toFixed(n));
+ // #endif
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/getClassStr/index.ts b/uni_modules/lime-shared/getClassStr/index.ts
new file mode 100644
index 0000000..34a26d2
--- /dev/null
+++ b/uni_modules/lime-shared/getClassStr/index.ts
@@ -0,0 +1,53 @@
+// @ts-nocheck
+
+// #ifdef UNI-APP-X && APP
+import { isNumber } from '../isNumber'
+import { isString } from '../isString'
+import { isDef } from '../isDef'
+// #endif
+
+/**
+ * 获取对象的类名字符串
+ * @param obj - 需要处理的对象
+ * @returns 由对象属性作为类名组成的字符串
+ */
+export function getClassStr(obj : T) : string {
+ let classNames : string[] = [];
+ // #ifdef APP-ANDROID
+ if (obj instanceof UTSJSONObject) {
+ (obj as UTSJSONObject).toMap().forEach((value, key) => {
+ if (isDef(value)) {
+ if (isNumber(value)) {
+ classNames.push(key);
+ }
+ if (isString(value) && value !== '') {
+ classNames.push(key);
+ }
+ if (typeof value == 'boolean' && (value as boolean)) {
+ classNames.push(key);
+ }
+ }
+ })
+ }
+ // #endif
+ // #ifndef APP-ANDROID
+ // 遍历对象的属性
+ for (let key in obj) {
+ // 检查属性确实属于对象自身且其值为true
+ if ((obj as any).hasOwnProperty(key) && obj[key]) {
+ // 将属性名添加到类名数组中
+ classNames.push(key);
+ }
+ }
+ // #endif
+
+
+ // 将类名数组用空格连接成字符串并返回
+ return classNames.join(' ');
+}
+
+
+// 示例
+// const obj = { foo: true, bar: false, baz: true };
+// const classNameStr = getClassStr(obj);
+// console.log(classNameStr); // 输出: "foo baz"
\ No newline at end of file
diff --git a/uni_modules/lime-shared/getCurrentPage/index.ts b/uni_modules/lime-shared/getCurrentPage/index.ts
new file mode 100644
index 0000000..dd8dcb5
--- /dev/null
+++ b/uni_modules/lime-shared/getCurrentPage/index.ts
@@ -0,0 +1,9 @@
+// @ts-nocheck
+// #ifndef UNI-APP-X && APP
+export { getCurrentPage } from './vue'
+// #endif
+
+
+// #ifdef UNI-APP-X && APP
+export { getCurrentPage } from './uvue'
+// #endif
diff --git a/uni_modules/lime-shared/getCurrentPage/uvue.ts b/uni_modules/lime-shared/getCurrentPage/uvue.ts
new file mode 100644
index 0000000..9e96d2b
--- /dev/null
+++ b/uni_modules/lime-shared/getCurrentPage/uvue.ts
@@ -0,0 +1,5 @@
+// @ts-nocheck
+export const getCurrentPage = ():Page => {
+ const pages = getCurrentPages();
+ return pages[pages.length - 1]
+};
\ No newline at end of file
diff --git a/uni_modules/lime-shared/getCurrentPage/uvue.uts b/uni_modules/lime-shared/getCurrentPage/uvue.uts
new file mode 100644
index 0000000..9e96d2b
--- /dev/null
+++ b/uni_modules/lime-shared/getCurrentPage/uvue.uts
@@ -0,0 +1,5 @@
+// @ts-nocheck
+export const getCurrentPage = ():Page => {
+ const pages = getCurrentPages();
+ return pages[pages.length - 1]
+};
\ No newline at end of file
diff --git a/uni_modules/lime-shared/getCurrentPage/vue.ts b/uni_modules/lime-shared/getCurrentPage/vue.ts
new file mode 100644
index 0000000..79ecac8
--- /dev/null
+++ b/uni_modules/lime-shared/getCurrentPage/vue.ts
@@ -0,0 +1,6 @@
+// @ts-nocheck
+/** 获取当前页 */
+export const getCurrentPage = () => {
+ const pages = getCurrentPages();
+ return pages[pages.length - 1] //as T & WechatMiniprogram.Page.TrivialInstance;
+};
\ No newline at end of file
diff --git a/uni_modules/lime-shared/getDeviceInfo/index.ts b/uni_modules/lime-shared/getDeviceInfo/index.ts
new file mode 100644
index 0000000..cdeee18
--- /dev/null
+++ b/uni_modules/lime-shared/getDeviceInfo/index.ts
@@ -0,0 +1,13 @@
+// @ts-nocheck
+export function getDeviceInfo():GetDeviceInfoResult {
+ // #ifndef UNI-APP-X && APP
+ if (uni.getDeviceInfo || uni.canIUse('getDeviceInfo')) {
+ return uni.getDeviceInfo();
+ } else {
+ return uni.getSystemInfoSync();
+ }
+ // #endif
+ // #ifdef UNI-APP-X && APP
+ return uni.getDeviceInfo()
+ // #endif
+}
\ No newline at end of file
diff --git a/uni_modules/lime-shared/getLocalFilePath/index.ts b/uni_modules/lime-shared/getLocalFilePath/index.ts
new file mode 100644
index 0000000..373ece2
--- /dev/null
+++ b/uni_modules/lime-shared/getLocalFilePath/index.ts
@@ -0,0 +1,64 @@
+// @ts-nocheck
+// #ifdef APP-NVUE || APP-VUE
+export const getLocalFilePath = (path : string) => {
+ if (typeof plus == 'undefined') return path
+ if (/^(_www|_doc|_documents|_downloads|file:\/\/|\/storage\/emulated\/0\/)/.test(path)) return path
+ console.log(/^\//.test(path))
+ if (/^\//.test(path)) {
+ const localFilePath = plus.io.convertAbsoluteFileSystem(path)
+ console.log('localFilePath', localFilePath)
+ if (localFilePath !== path) {
+ return localFilePath
+ } else {
+ path = path.slice(1)
+ }
+ }
+ return '_www/' + path
+}
+// #endif
+
+
+// #ifdef UNI-APP-X && APP
+export { getResourcePath as getLocalFilePath } from '@/uni_modules/lime-file-utils'
+// export const getLocalFilePath = (path : string) : string => {
+// let uri = path
+// if (uri.startsWith("http") || uri.startsWith("