diff --git a/SmartAssiter/.gitignore b/SmartAssiter/.gitignore new file mode 100644 index 0000000..165c177 --- /dev/null +++ b/SmartAssiter/.gitignore @@ -0,0 +1 @@ +/unpackage \ No newline at end of file diff --git a/SmartAssiter/.hbuilderx/launch.json b/SmartAssiter/.hbuilderx/launch.json new file mode 100644 index 0000000..559d13f --- /dev/null +++ b/SmartAssiter/.hbuilderx/launch.json @@ -0,0 +1,15 @@ +{ + "version" : "1.0", + "configurations" : [ + { + "customPlaygroundType" : "local", + "playground" : "standard", + "type" : "uni-app:app-android" + }, + { + "customPlaygroundType" : "local", + "playground" : "custom", + "type" : "uni-app:app-ios" + } + ] +} diff --git a/SmartAssiter/App.vue b/SmartAssiter/App.vue new file mode 100644 index 0000000..03e555a --- /dev/null +++ b/SmartAssiter/App.vue @@ -0,0 +1,93 @@ + + + diff --git a/SmartAssiter/api/httpInstance.ts b/SmartAssiter/api/httpInstance.ts new file mode 100644 index 0000000..8b5b209 --- /dev/null +++ b/SmartAssiter/api/httpInstance.ts @@ -0,0 +1,89 @@ +import { getToken } from "../common/auth" +import { clearAllData } from "../common/auth" + +// 通用 JSON 对象类型(替代 uts 的 UTSJSONObject) +export type JSONObject = Record + +// 可选后台服务器列表 +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.length > 0) { + return recordBaseUrl + } + const t = uni.getStorageSync('serverBaseUrl') as string + 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?: JSONObject +} + +// 封装请求 +export function $http(params: HttpRequest): Promise { + return new Promise((resolve, reject) => { + + // 1. 准备 header + let baseHeader: JSONObject = params.header ?? {} as JSONObject + + baseHeader["Content-Type"] = "application/json" + + // 2. 读 token,有就加 Authorization + const token = getToken() + if (token != null && token.length > 0) { + baseHeader["Authorization"] = token + } + + // 3. 发请求 + uni.request({ + url: getBaseUrl() + params.url, + method: params.method ?? "GET", + data: params.data ?? {} as JSONObject, + header: baseHeader, + success: (res) => { + if(res.statusCode == 401) { + clearAllData() + uni.reLaunch({ + url: '/pages/login/login' + }) + } + const result: JSONObject = { + statusCode: res.statusCode, + data: res.data + } as JSONObject + resolve(result) + }, + fail: (err) => { + reject(err) + } + }) + }) +} diff --git a/SmartAssiter/api/requestApi.ts b/SmartAssiter/api/requestApi.ts new file mode 100644 index 0000000..2940d37 --- /dev/null +++ b/SmartAssiter/api/requestApi.ts @@ -0,0 +1,80 @@ +import { $http, JSONObject } from "@/api/httpInstance" + +export type RegisteredUserReq = { + userName: string, + userPassword: string, + rndcode: string +} +// 获取pinCode +export async function GetPindCodeReqApi(params: RegisteredUserReq): Promise{ + const body: JSONObject = { + userName: params.userName, + userPassword: params.userPassword, + rndcode: params.rndcode + } as JSONObject + + 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: JSONObject = { + userName: params.userName, + userPassword: params.userPassword, + rndcode: params.rndcode + } as JSONObject + + 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: JSONObject = { + username: params.username, + password: params.password, + expirationTime: 0 + } as JSONObject + + 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: JSONObject = { + name: params.name, + phone: params.phone, + password: params.password + } as JSONObject + + const res = await $http({ + url: `/ocpp-mtapp-api/api/v1/users/${params.name}`, + method: "GET", + data: body + }) + return res +} diff --git a/SmartAssiter/api/responseData.ts b/SmartAssiter/api/responseData.ts new file mode 100644 index 0000000..14bce7c --- /dev/null +++ b/SmartAssiter/api/responseData.ts @@ -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/SmartAssiter/common/auth.ts b/SmartAssiter/common/auth.ts new file mode 100644 index 0000000..9b7a3b1 --- /dev/null +++ b/SmartAssiter/common/auth.ts @@ -0,0 +1,164 @@ +let recordToken: string | null = null +let recordUserName: string | null = null +let recordCharging: string | null = null + +// 读取本地存储字符串;vue3 的 getStorageSync 无 key 时返回 "",统一转成 null +function getStorageStr(key: string): string | null { + const t = uni.getStorageSync(key) as string + return (t != null && t.length > 0) ? t : null +} + +// 保存 token(内存 + 本地) +export function setToken(token: string) { + recordToken = token + uni.setStorageSync('loginToken', token) +} + +// 读取 token +export function getToken(): string | null { + if (recordToken != null) { + return recordToken + } + recordToken = getStorageStr('loginToken') + 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 + } + recordUserName = getStorageStr('loginUserName') + return recordUserName +} + + +// 保存充电标记 +export function setCharging(flag: string) { + recordCharging = flag + uni.setStorageSync('ChargingFlag', flag) +} + +// 读取充电标记 +export function getCharging(): string | null { + if (recordCharging != null) { + return recordCharging + } + recordCharging = getStorageStr('ChargingFlag') + 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 { + return getStorageStr('savedAccount') ?? "" +} + +// 读取保存的密码 +export function getSavedPassword(): string { + return getStorageStr('savedPassword') ?? "" +} + +// 清除保存的账号密码 +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 = getStorageStr('accountHistory') + if (raw == null) { + return result + } + try { + const arr = JSON.parse(raw) as Record[] | 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) { + 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/SmartAssiter/common/bleInfo.ts b/SmartAssiter/common/bleInfo.ts new file mode 100644 index 0000000..3870ce1 --- /dev/null +++ b/SmartAssiter/common/bleInfo.ts @@ -0,0 +1,45 @@ +let bleName: string | null = null +let bleMac: string | null = null + +// 读取本地存储字符串;vue3 的 getStorageSync 无 key 时返回 "",统一转成 null +function getStorageStr(key: string): string | null { + const t = uni.getStorageSync(key) as string + return (t != null && t.length > 0) ? t : null +} + +// 保存蓝牙名字 +export function setName(name: string) { + bleName = name + uni.setStorageSync('bleName', name) +} + +// 读取蓝牙名字 +export function getName(): string | null { + if (bleName != null) { + return bleName + } + bleName = getStorageStr('bleName') + 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 + } + bleMac = getStorageStr('bleMac') + return bleMac +} + +export function clearAllData() { + bleName = null + bleMac = null + uni.removeStorageSync('bleName') + uni.removeStorageSync('bleMac') +} diff --git a/SmartAssiter/docs/修改记录_2026-07-17_服务器切换与记住密码.md b/SmartAssiter/docs/修改记录_2026-07-17_服务器切换与记住密码.md new file mode 100644 index 0000000..97f76b4 --- /dev/null +++ b/SmartAssiter/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/SmartAssiter/docs/修改记录_2026-07-17_登录页历史账号选择.md b/SmartAssiter/docs/修改记录_2026-07-17_登录页历史账号选择.md new file mode 100644 index 0000000..9d2f319 --- /dev/null +++ b/SmartAssiter/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/SmartAssiter/docs/修改记录_2026-09-02_母乳杯蓝牙改造.md b/SmartAssiter/docs/修改记录_2026-09-02_母乳杯蓝牙改造.md new file mode 100644 index 0000000..e663e2a --- /dev/null +++ b/SmartAssiter/docs/修改记录_2026-09-02_母乳杯蓝牙改造.md @@ -0,0 +1,92 @@ +# 修改记录 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 扇区内加小窗口节流。 + +## 后续调整(同日) + +- **uts 编译修复**:`match()` 结果元素可空需先判空;uts 函数不能直接当 lambda 传递 + (改为箭头函数包装);uvue 页面内函数需"定义在前、使用在后";`setTimeout` 不接受 + async 回调(改 then 链);`java.io` 交互处需显式 `Int` 类型。 +- **UI 全面英文化**:pages.json 导航/ tab(Device/Status/OTA/my/CLI Terminal)、 + home/sysStatus/ota/my/CLI 终端全部文案与 toast、OtaManager 升级日志消息、 + manifest 应用名 `smartAssister`、iOS 蓝牙用途英文描述。 +- **扫描不过滤**:设备页去掉 `CAIIC-MCM` 名称前缀过滤,扫到的设备全部列出; + `BluetoothManager.startScan` 的 `showEmptyName` 改为 1(无广播名设备也返回, + 列表中匿名设备显示 N/A)。 +- **APP 图标**:新增 `static/appIcon.png`(1024×1024,蓝色渐变底 + 白色母乳杯 + + 蓝牙徽标,脚本生成),manifest 的 Android 四档与 iOS appstore 图标均指向它; + 图标与应用名需重打自定义基座并重装后生效。 diff --git a/SmartAssiter/docs/修改记录_2026-09-03_BLE协议V1.1适配.md b/SmartAssiter/docs/修改记录_2026-09-03_BLE协议V1.1适配.md new file mode 100644 index 0000000..586454f --- /dev/null +++ b/SmartAssiter/docs/修改记录_2026-09-03_BLE协议V1.1适配.md @@ -0,0 +1,72 @@ +# 修改记录 2026-09-03:BLE 协议 V1.1 适配 + +设备协议升级到 `D:\caic-workspace\mothercup\docs\ble_protocol.md`(CAIIC BLE 通信协议 V1.1), +APP 侧同步适配。差异与改动如下。 + +## 协议差异(V1.0 → V1.1) + +- GATT 更换为 SDK rdtss 自定义 128-bit UUID:Service `00002760-08C2-11E1-9073-0E8AC72E1001`, + 下行写特征(Write Without Response),上行 notify;广播名 `CAIIC-MCM-20260902`。 + MTU=247 由设备连接后主动发起,手机端也可自行 requestMtu。 + **注意**:协议文档表格中下行/上行 UUID 写作 `...C72EE001` / `...C72EE002`, + 与固件 `app_rdtss.h` 实际值 `...C72E0001` / `...C72E0002` 不符,APP 以固件为准 + (协议文档此处需修正)。 +- 新增设备信息查询帧:`INFO_QUERY(0x20)` / `INFO_RSP(0x21)`,payload 为 TLV 序列 + `{id u8, len u8, value…}×n`(数值小端)。信息项:0x01 FW_VERSION(u32)、 + 0x02 CHIP_TEMP(i16, 0.1℃, 0x7FFF=读取失败)、0x03 FAN_RPM(u16, 0xFFFF=无硬件)、 + 0x04 VDD_MV(u16, 0=失败)、0x05 UPTIME_S(u32)、0x06 FREE_HEAP(u32)。 + 空 payload 或单个 0xFF = 查询全部;设备不认识的 id 不出现在应答中。 +- 帧格式(SOF 0xCA / CRC16-CCITT)、CLI 透传(0x01/0x02/0x03)、OTA(0x10/0x11/0x12/0x1F) + 与 V1.0 一致,既有 `FrameCodec`/`McmChannel` CLI 通道/`OtaManager` 无需改动。 + +## 修改文件 + +- `src/mcm/FrameCodec.uts` — 新增 `TYPE_INFO_QUERY=0x20` / `TYPE_INFO_RSP=0x21` 常量, + 新增 `readU16LE` / `readI16LE`(i16 处理符号位)解包工具。 +- `src/mcm/McmChannel.uts` — 新增 INFO_QUERY 请求-响应通道:`queryInfo(items, timeoutMs)` + 串行执行(infoChain),单 pending infoJob,超时/发送失败/断连 resolve null; + `dispatchFrame` 分发 INFO_RSP;`resetChannel` 清理挂起的信息查询。 + **修复 notify 数据解析**:插件 `onNotityReadBleData` 数据回调(type=0)的 `res.data` + 是 `NotityData` 结构(`{data: number[], mac, serviceId, characteristicsId}`), + 此前按裸 `number[]` 解析导致设备应答字节永远进不了帧重组器,表现为 + "写入成功但 help 无应答(status=-1)"。 + CLI 命令**不追加** `\r\n`:固件 `app_ble_proto.c` 把 CLI_REQ payload 直接交给 + `cli_exec_line`(只按空格/Tab 分词),尾随 `\r\n` 会被当作命令名一部分导致 + unknown command(曾按"行编辑器需要结束符"的假设加过,查固件源码后撤销)。 +- `src/BluetoothManager.uts` — 新增 `MCM_SERVICE_UUID` / `MCM_WRITE_UUID` / + `MCM_NOTIFY_UUID` 常量(取固件 `app_rdtss.h` 实际值,小写); + `sendDataBytes` 与 `onNotityReadBleData` 改用显式 UUID,不再依赖插件的 + service/notify/write 自动识别(下行特征只有 Write Without Response 属性, + 自动识别的写特征可能为空,表现为"发送 help 无应答")。 + notify 订阅从旧接口 `onNotityReadBleData` 换成插件推荐的 `onNotityBleData` + (旧接口已标记弃用,数据统一走 `NotityData` 结构)。 +- `App.uvue` — `setBleNotity()` 改用 `Ble.MCM_SERVICE_UUID` / `Ble.MCM_NOTIFY_UUID` + 显式订阅 notify;`ConnectBle()` 移除 `selectService(...)` 调用(已不再需要)。 +- 调试日志:BLE 收发全链路加了 `[MCM]` 前缀的 console 打印——`McmChannel` 的 + TX 帧(完整 hex)、写入失败、RX notify 原始字节、RX 重组帧; + `App.uvue` 的 connect/scanServices/notify 订阅结果。真机调试时在 HBuilderX + 控制台可直接观察报文流。 +- `src/mcm/McmInfo.uts`(新增)— 信息项 id 常量、`parseTlv`(未知 id 按 len 跳过、 + 长度不符项忽略、0x7FFF/0xFFFF/0 等无效值归一为 null)、`DeviceInfo` 类型、 + `queryDeviceInfo(items)`(空数组=查全部)、`formatVersion`(u32 → "V1.00.01")。 +- `src/mcm/McmCommands.uts` — `getTemperature`/`getFanSpeed`/`getVersion` 改走 + INFO_QUERY(函数签名不变);废弃未实现的 CLI `temp`/`fan`/`version` 命令常量; + `getSysInfo`/`getBankInfo`/`execCli` 保持 CLI 透传不变。 +- `pages/tabbar/home.uvue` — 扫描过滤前缀更新为 `CAIIC-MCM-20260902`; + 参数轮询合并为一次 `queryDeviceInfo([CHIP_TEMP, FAN_RPM])`(温度显示 1 位小数, + 风扇无硬件显示 `N/A`)。 + +## 未改动 + +- `src/mcm/OtaManager.uts`:OTA 流程与 V1.1 第 6 节一致(BEGIN→DATA 扇区级 ack 流控→END + 整镜像 CRC32 校验,status=2 按应答 offset 重同步)。 +- `pages/tabbar/sysStatus.uvue` / `pages/tabbar/ota.uvue` / CLI 调试终端: + 调用的 `McmCommands` API 签名不变,无需修改。 + +## 验证 + +- 用 Node 脚本按协议 8.2 节参考实现复算 8.3 节全部字节级示例(CLI_REQ "help"、 + INFO_QUERY 查全部/温度+风扇、OTA_BEGIN/OTA_DATA/OTA_END),与 `encodeFrame`/CRC16 + 输出逐字节一致;INFO_RSP 样例(26.6℃ + 无风扇)经 `parseTlv` 解析结果正确。 +- 真机回归(HBuilderX 编译):扫描连接 `CAIIC-MCM-20260902`、主页温度/风扇显示、 + 系统状态页、CLI 终端、本地 bin OTA 升级。 diff --git a/SmartAssiter/docs/修改记录_2026-09-03_迁移uni-app-vue3与蓝牙去插件.md b/SmartAssiter/docs/修改记录_2026-09-03_迁移uni-app-vue3与蓝牙去插件.md new file mode 100644 index 0000000..78446c9 --- /dev/null +++ b/SmartAssiter/docs/修改记录_2026-09-03_迁移uni-app-vue3与蓝牙去插件.md @@ -0,0 +1,62 @@ +# 修改记录 2026-09-03:迁移 uni-app vue3,蓝牙去第三方插件改 uni 内置 API + +## 背景 + +- 原工程为 uni-app x(uvue/uts),蓝牙依赖第三方插件 `uni_modules/android-ble`。 +- 先尝试过"uni-app x 下去插件改 uni 内置 BLE API":不可行——uni-app x 官方未内置 + 蓝牙 API(官方文档明确"蓝牙:见插件市场"),全量编译确认 + `createBLEConnection` 等接口在 app-android 平台不存在。 +- 因对渲染性能要求不高,改为**整体迁移到 uni-app vue3(vue/ts)**: + vue3 App 端内置完整 BLE API,蓝牙彻底去插件化;且标准基座即可运行,无需自定义基座。 + +## 工程骨架 + +- `main.uts` → `main.js`;`App.uvue` → `App.vue`(`onLastPageBackPress` 中 + `uni.exit()` 改 `plus.runtime.quit()`;globalData 保留 `setBleNotity`/`ConnectBle`, + 改走新 `src/BluetoothManager.ts`,流程不变:connect → scanServices → 订阅 notify)。 +- `manifest.json`:删除 `"uni-app-x": {}`;`app-android`/`app-ios` 合并为 vue3 的 + `app-plus` 结构;Android permissions 补 `BLUETOOTH`/`BLUETOOTH_ADMIN`/ + `BLUETOOTH_SCAN`(neverForLocation)/`BLUETOOTH_CONNECT`;iOS 蓝牙隐私描述并入 + `app-plus.distribute.ios.privacyDescription`。 +- `pages.json` 内容不变(globalStyle 标题改为 smartAssister)。 +- 删除 `platformConfig.json`(uni-app x 专用)。 +- uni_modules:删除 `android-ble`、`lime-*`(8 个,页面未使用且为 uts 组件)、 + `uni-wifi`(未使用);保留 `uni-icons`(自带 vue 版本组件)、`uni-scss`。 + +## 蓝牙:`src/BluetoothManager.ts`(替代插件) + +- 基于 uni 内置 BLE API:`openBluetoothAdapter` / `startBluetoothDevicesDiscovery` / + `createBLEConnection` / `getBLEDeviceServices` / `getBLEDeviceCharacteristics` / + `notifyBLECharacteristicValueChange` / `writeBLECharacteristicValue` / + `setBLEMTU` / `getBLEDeviceRSSI` / `closeBLEConnection`。 +- 保持原插件的对外约定:`MyApiResult.type==0` 成功;1000=订阅成功 1001=订阅失败; + 10000=连接失败 10001=已断开 10002=蓝牙未开 10003=无权限; + notify 数据包装为 `{type:0, data:{data:number[], mac}}` 经 `uni.$emit("bleData")` 分发; + 意外断开经 `uni.$emit("bleState", 10001)` 广播(主动断开不广播)。 +- `sendDataBytes` 用 `writeType:"writeNoResponse"`(下行特征仅支持无响应写)。 +- 自动重连未实现(业务恒传 false),`setBtAutoConnectTime`/`cancelAutoConnectBt` 为空实现。 + +## uts → ts 移植 + +- `src/commonFunctions`、`src/mcm/*`(Crc/FrameCodec/McmChannel/McmCommands/McmInfo/ + OtaManager)、`api/*`、`common/*`:机械移植,去 `.uts` import 后缀; + `UTSJSONObject` → `Record`(httpInstance 统一定义 `JSONObject`)。 +- **vue3 的 `uni.getStorageSync` 无 key 返回 `""` 而非 null**:`common/auth.ts`、 + `common/bleInfo.ts` 统一经 `getStorageStr()` 读取(空串转 null)。 + +## 页面 .uvue → .vue(7 个) + +- template 不动;`script setup lang="ts"`;显式 `import { ref } from 'vue'`、 + 生命周期钩子 `import { onLoad, onShow, ... } from '@dcloudio/uni-app'`。 +- `pages/tabbar/ota.vue` 固件读取由 java.io 改为 `plus.io`(FileReader.readAsDataURL → + base64 → `uni.base64ToArrayBuffer` → number[]),`#ifdef APP-PLUS` 包裹。 +- `getApp().globalData.ConnectBle` 调用处加 `as any` 断言。 + +## 验证与注意事项 + +- HBuilderX CLI `launch app-android --compile true` 编译通过(编译器识别为 vue3 工程)。 +- 运行用**标准基座**即可(vue3 基座内置 BLE);原自定义基座配置不再适用。 +- 真机回归重点:扫描/连接/notify/MTU 247、home 轮询与设备信息、CLI 收发与 rssi、 + **OTA 全流程**(高频无响应写,丢包风险最高)。 +- OTA 固件路径 `/sdcard/Download/embeddedSrc.bin` 在 Android 11+ 分区存储下可能读不到 + (与原 java.io 行为一致,未劣化);如不可用,改用应用私有目录或加文件选择器。 diff --git a/SmartAssiter/docs/修改记录_2026-09-03_页面结构调整.md b/SmartAssiter/docs/修改记录_2026-09-03_页面结构调整.md new file mode 100644 index 0000000..59274ab --- /dev/null +++ b/SmartAssiter/docs/修改记录_2026-09-03_页面结构调整.md @@ -0,0 +1,34 @@ +# 修改记录 2026-09-03:页面结构调整(Status 合并进 Device,CLI 终端改为底部 Tab) + +## 改动 + +- **Status 页合并进 Device 页**:`pages/tabbar/home.uvue` 新增 sysinfo 控制台卡片 + (固件版本/运行 Bank 卡片下方),`loadDeviceInfo()` 顺带拉取 `sysinfo`; + 已连接时下拉刷新改为重新拉取设备信息(版本/bank/sysinfo);断开时清空 sysinfo 显示。 + 新增 `consoleTitle`/`consoleText` 样式(沿用原 sysStatus 页样式)。 +- **CLI 终端改为底部 Tab**:`pages/bleTest/bluetoothOperation/bluetoothOperation.uvue` + 进入 tabbar(text "CLI",图标复用原 Status 的 `mapDefault/mapActive.png`)。 + - 连接地址不再有页面参数来源:`onConnect()` 在未连接时取 `bleInfo` 中最近连接过的 + MAC,没有记录则提示先去 Device 页扫描连接; + - 生命周期钩子由 `onPageShow/onPageHide` 改为 tabbar 适用的 `onShow/onHide`; + - `onShow` 时若应用内已连接(如在 Device 页连上后切过来)自动进入可用状态。 +- **my 页移除 "CLI Terminal" 入口**(已成为 tab),删除不再使用的 `onBluetooth`、 + Ble 相关 import 与 `cardBle/cardBox/textType` 样式。 + +## pages.json + +- tabbar 四 tab:Device / OTA / CLI / my(原 Status 位替换为 CLI)。 +- 移除 `pages/tabbar/sysStatus` 与 `pages/bleTest/bluetoothList/bluetoothList` 的页面注册; + `bluetoothOperation` 保留注册(现为 tab 页),导航栏背景色与整体风格统一为 `#3A6FA1`。 + +## 删除 + +- `pages/tabbar/sysStatus.uvue`(内容已并入 home.uvue)。 +- `pages/bleTest/bluetoothList/`(扫描选设备页:唯一入口是 my 页的 CLI Terminal, + 设备选择统一由 Device 主页承担后该页成为死代码)。 + +## 验证 + +- 无 CLI 构建手段,需 HBuilderX 编译真机验证:四个 tab 切换正常;Device 页连接后 + sysinfo 控制台有内容;切到 CLI tab 自动可用、发 `help` 有应答;未连接时 CLI tab + 点 connect 给出提示。 diff --git a/SmartAssiter/docs/修改记录_2026-09-04_BLE连接ASI分号修复.md b/SmartAssiter/docs/修改记录_2026-09-04_BLE连接ASI分号修复.md new file mode 100644 index 0000000..b8cb663 --- /dev/null +++ b/SmartAssiter/docs/修改记录_2026-09-04_BLE连接ASI分号修复.md @@ -0,0 +1,18 @@ +# 修改记录 2026-09-04:修复 ASI 导致 BLE 连接调用失效 + +## 问题现象 + +点击设备列表连接、CLI 页点 connect 均无反应,控制台报 +`TypeError: setMac(...) is not a function` / `pushMsg(...) is not a function`。 + +## 根因 + +`pages/tabbar/home.vue:230` 与 `pages/bleTest/bluetoothOperation/bluetoothOperation.vue:147` +两处,前一行语句末尾没有分号,下一行又以 `(` 开头(`(getApp().globalData as any).ConnectBle(...)`), +JavaScript 自动分号插入(ASI)对此不生效,编译产物把两行合并成 +`setMac(...)(getApp()...)` —— 拿上一行的返回值(undefined)当函数调用。 + +## 修改 + +两处行末补分号。排查技巧:源码函数明明存在却报 "is not a function" 时, +到 `unpackage/dist/dev/app-plus/app-service.js` 搜编译产物即可定位。 diff --git a/SmartAssiter/docs/修改记录_2026-09-04_设备参数只读特征.md b/SmartAssiter/docs/修改记录_2026-09-04_设备参数只读特征.md new file mode 100644 index 0000000..91feebf --- /dev/null +++ b/SmartAssiter/docs/修改记录_2026-09-04_设备参数只读特征.md @@ -0,0 +1,39 @@ +# 修改记录 2026-09-04:设备参数改为只读特征 + App 定时读 + +设备参数查看(温度/风扇/电压/版本/uptime/heap)从「INFO_QUERY 帧 + notify 应答」 +改为「只读特征 + App 定时 read」,不再依赖 notify 上行通道。 + +## GATT 变更(固件 mcm-ddc-ble V1.00.04) + +- 新增只读特征 `00002760-08c2-11e1-9073-0e8ac72e0004`(`MCM_INFO_UUID`): + 读出内容为全部信息项的 TLV 序列 `{id u8, len u8, value…}×n`(数值小端), + 与 INFO_RSP(all) 帧 payload 完全一致。信息项 id 不变: + 0x01 FW_VERSION(u32)、0x02 CHIP_TEMP(i16, 0.1℃, 0x7FFF=失败)、 + 0x03 FAN_RPM(u16, 0xFFFF=无硬件)、0x04 VDD_MV(u16, 0=失败)、 + 0x05 UPTIME_S(u32)、0x06 FREE_HEAP(u32)。 +- 既有特征不变:`...e0001`(帧协议下行,无响应写)、`...e0002`(notify 上行)、 + `...e0003`(CLI 读写特征:写原始命令行、读输出文本,V1.00.02 新增)。 +- 固件侧实现:`app_info.c` 抽出 `app_info_tlv_snapshot()`(INFO_QUERY 帧应答与其 + 共用同一组包逻辑);`app_rdtss.c` 读请求按 `att_idx` 分发(CLI_VAL → 命令输出, + INFO_VAL → TLV 快照,其余保持原 "NSING" 行为)。 +- 附带修复(V1.00.03):芯片温度采样前先给 TS 5ms 稳定时间并丢弃第一次转换, + 修复 `devinfo`/INFO_QUERY 温度显示 287.6°C 的问题(传感器未稳定即采样)。 + +## App 侧修改 + +- `src/BluetoothManager.ts`:新增 `MCM_INFO_UUID` 常量与 `readInfoChar(cb)`; + `onBLECharacteristicValueChange` 监听重构为 `ensureValueChangeListener()`, + notify 数据与 read 结果按特征 UUID 分发(uni 的读结果经此回调返回); + 读操作带 3s 超时。 +- `src/mcm/McmInfo.ts`:新增 `readDeviceInfo()`,读参数特征并复用 `parseTlv` 解析; + 原 `queryDeviceInfo()`(帧协议 INFO_QUERY)保留。 +- `pages/tabbar/home.vue`:参数轮询 `pollParams()` 从 `queryDeviceInfo` 切到 + `readDeviceInfo`,一次读出全部项取温度+风扇。 + +## 验证 + +- 固件 `UV4 -r` 全量重建 0 Error / 0 Warning;`tools/out/caiic_ble_full.hex` 出包, + OTA 清单 version=0x00010004。 +- PC 侧验证(烧录后):`tools\ble_cli.bat param` 读参数特征并解析 TLV; + `tools\ble_cli.bat -rw help` 验证 CLI 读写特征。 +- 真机回归:主页温度/风扇显示(轮询走 read),CLI 终端、OTA 流程不受影响。 diff --git a/SmartAssiter/index.html b/SmartAssiter/index.html new file mode 100644 index 0000000..8793fef --- /dev/null +++ b/SmartAssiter/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/SmartAssiter/main.js b/SmartAssiter/main.js new file mode 100644 index 0000000..c32858f --- /dev/null +++ b/SmartAssiter/main.js @@ -0,0 +1,9 @@ +import App from './App.vue' + +import { createSSRApp } from 'vue' +export function createApp() { + const app = createSSRApp(App) + return { + app + } +} diff --git a/SmartAssiter/manifest.json b/SmartAssiter/manifest.json new file mode 100644 index 0000000..e4a6d4a --- /dev/null +++ b/SmartAssiter/manifest.json @@ -0,0 +1,81 @@ +{ + "name": "smartAssister", + "appid": "__UNI__884AC41", + "description": "", + "versionName": "1.0.1", + "versionCode": "110", + /* 快应用特有相关 */ + "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-plus": { + "usingComponents": true, + "modules": {}, + "distribute": { + "android": { + "permissions": [ + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "ios": { + "privacyDescription": { + "NSBluetoothPeripheralUsageDescription": "Please allow Bluetooth access. It is used to connect to the mother cup device: displaying runtime parameters such as temperature and fan speed, querying device system status, and upgrading the device firmware over Bluetooth.", + "NSBluetoothAlwaysUsageDescription": "Please allow Bluetooth access. It is used to connect to the mother cup device: displaying runtime parameters such as temperature and fan speed, querying device system status, and upgrading the device firmware over Bluetooth.", + "NSLocationWhenInUseUsageDescription": "Please allow the use of location services to identify the position of charging stations. This app promises that this function will not be used for any other purposes.", + "NSLocalNetworkUsageDescription": "Please allow the use of network connectivity for communication between the app and the cloud service backend to enable charging-related features. This app promises that it will only be used to complete charging-related services with the cloud backend and will not be utilized for any other purposes.", + "NSCameraUsageDescription": "Please allow the use of the camera function. This function is solely used for scanning the QR codes on charging piles to obtain information corresponding to the QR codes. This app promises that the function will not be used for any other purposes." + } + }, + "icons": { + "android": { + "hdpi": "/static/appIcon.png", + "xhdpi": "/static/appIcon.png", + "xxhdpi": "/static/appIcon.png", + "xxxhdpi": "/static/appIcon.png" + }, + "ios": { + "appstore": "/static/appIcon.png" + } + }, + "splashscreen": { + "alwaysShowBeforeRender": true, + "waiting": true, + "autoclose": true, + "delay": 0 + } + } + }, + "web": { + "router": { + "mode": "" + } + } +} diff --git a/SmartAssiter/pages.json b/SmartAssiter/pages.json new file mode 100644 index 0000000..5ef832b --- /dev/null +++ b/SmartAssiter/pages.json @@ -0,0 +1,97 @@ +{ + "pages": [ //pages数组中第一项表示应用启动页,参考:https://doc.dcloud.net.cn/uni-app-x/collocation/pagesjson.html + { + "path": "pages/tabbar/home", + "style": { + "navigationBarTitleText": "Device", + "navigationBarBackgroundColor": "#3A6FA1", + "enablePullDownRefresh": true + } + }, + { + "path": "pages/tabbar/ota", + "style": { + "navigationBarTitleText": "Firmware Upgrade", + "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/bluetoothOperation/bluetoothOperation", + "style": { + "navigationBarTitleText": "CLI Terminal", + "navigationBarBackgroundColor": "#3A6FA1" + } + } + ], + "tabBar": { + "color": "#999999", + "selectedColor": "#3A6FA1", + "backgroundColor": "#ffffff", + "borderStyle": "black", + "list": [ + { + "pagePath": "pages/tabbar/home", + "text": "Device", + "iconPath": "/static/tabbar/homeDefault.png", + "selectedIconPath": "/static/tabbar/homeActive.png" + }, + { + "pagePath": "pages/tabbar/ota", + "text": "OTA", + "iconPath": "/static/tabbar/chargeDefault.png", + "selectedIconPath": "/static/tabbar/chargeActive.png" + }, + { + "pagePath": "pages/bleTest/bluetoothOperation/bluetoothOperation", + "text": "CLI", + "iconPath": "/static/tabbar/mapDefault.png", + "selectedIconPath": "/static/tabbar/mapActive.png" + }, + { + "pagePath": "pages/tabbar/my", + "text": "my", + "iconPath": "/static/tabbar/myDefault.png", + "selectedIconPath": "/static/tabbar/myActive.png" + } + ] + } + }, + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "smartAssister", + "navigationBarBackgroundColor": "#F8F8F8", + "backgroundColor": "#F8F8F8", + "navigationStyle": "default" + }, + "uniIdRouter": {} +} diff --git a/SmartAssiter/pages/bleTest/bluetoothOperation/bluetoothOperation.vue b/SmartAssiter/pages/bleTest/bluetoothOperation/bluetoothOperation.vue new file mode 100644 index 0000000..8557a04 --- /dev/null +++ b/SmartAssiter/pages/bleTest/bluetoothOperation/bluetoothOperation.vue @@ -0,0 +1,339 @@ + + + + + diff --git a/SmartAssiter/pages/displayUserInfo/displayUserInfo.vue b/SmartAssiter/pages/displayUserInfo/displayUserInfo.vue new file mode 100644 index 0000000..fb55a40 --- /dev/null +++ b/SmartAssiter/pages/displayUserInfo/displayUserInfo.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/SmartAssiter/pages/login/login.vue b/SmartAssiter/pages/login/login.vue new file mode 100644 index 0000000..62e07e3 --- /dev/null +++ b/SmartAssiter/pages/login/login.vue @@ -0,0 +1,410 @@ + + + + + diff --git a/SmartAssiter/pages/register/register.vue b/SmartAssiter/pages/register/register.vue new file mode 100644 index 0000000..5e105f2 --- /dev/null +++ b/SmartAssiter/pages/register/register.vue @@ -0,0 +1,345 @@ + + + + + diff --git a/SmartAssiter/pages/tabbar/home.vue b/SmartAssiter/pages/tabbar/home.vue new file mode 100644 index 0000000..eb770aa --- /dev/null +++ b/SmartAssiter/pages/tabbar/home.vue @@ -0,0 +1,459 @@ + + + + + diff --git a/SmartAssiter/pages/tabbar/my.vue b/SmartAssiter/pages/tabbar/my.vue new file mode 100644 index 0000000..bd4075a --- /dev/null +++ b/SmartAssiter/pages/tabbar/my.vue @@ -0,0 +1,200 @@ + + + + + diff --git a/SmartAssiter/pages/tabbar/ota.vue b/SmartAssiter/pages/tabbar/ota.vue new file mode 100644 index 0000000..529d925 --- /dev/null +++ b/SmartAssiter/pages/tabbar/ota.vue @@ -0,0 +1,388 @@ + + + + + diff --git a/SmartAssiter/src/BluetoothManager.ts b/SmartAssiter/src/BluetoothManager.ts new file mode 100644 index 0000000..b7d29a8 --- /dev/null +++ b/SmartAssiter/src/BluetoothManager.ts @@ -0,0 +1,493 @@ +/** + * 蓝牙管理模块(基于 uni 内置 BLE API,替代原 android-ble 第三方插件) + * + * 对外约定与原插件保持一致,业务页面无需改动逻辑: + * - MyApiResult.type == 0 表示成功 + * - 1000=notify 订阅成功 1001=订阅失败 + * - 10000=连接失败 10001=已断开 10002=蓝牙未开/不可用 10003=无蓝牙权限 + * - notify 数据通过回调返回 {type:0, data: NotityData},由 App.vue 转发 uni.$emit("bleData") + * - 连接状态变化通过 uni.$emit("bleState", state) 通知(0=已连接 10000=连接失败 10001=已断开) + */ + +// CAIIC-MCM 设备 GATT(固件 mcm-ddc-ble app_rdtss.h 实际值,小写与 Android UUID.toString 格式一致) +// 注意:ble_protocol.md V1.1 表格写作 ...C72EE001/...C72EE002,与固件实际 ...C72E0001/...C72E0002 不符,以固件为准 +export const MCM_SERVICE_UUID = "00002760-08c2-11e1-9073-0e8ac72e1001" +export const MCM_WRITE_UUID = "00002760-08c2-11e1-9073-0e8ac72e0001" // Write Without Response +export const MCM_NOTIFY_UUID = "00002760-08c2-11e1-9073-0e8ac72e0002" // Notify +export const MCM_INFO_UUID = "00002760-08c2-11e1-9073-0e8ac72e0004" // 只读:设备参数 TLV(同 INFO_RSP 全部项) + +export type MyApiResult = { + type: number, + message: string, + data: any +} + +export type BleScanDevice = { + name: string | null, + address: string +} + +export type BleScanResult = { + device: BleScanDevice, + rssi: number +} + +// notify 回调数据结构(对齐原插件 NotityData 的 {data, mac} 字段) +export type NotityData = { + data: number[], + mac: string +} + +// ================= 模块状态 ================= + +let connectedDeviceId: string | null = null +let manualDisconnect = false +let connTimeoutMs = 0 // createBLEConnection 超时,0 表示不设置 +let btEnabled = false // 缓存的蓝牙开关状态(由 adapter 状态监听/操作结果维护) +let scanning = false +let scanTimeoutTimer = 0 + +let stateListenerRegistered = false +let connListenerRegistered = false +let scanListenerRegistered = false +let notifyListenerRegistered = false +let notifyDataCb: ((res: MyApiResult) => void) | null = null +let readPendingChar: string | null = null // 等待读结果的特征 UUID +let readCb: ((res: MyApiResult) => void) | null = null +let readTimer = 0 + +// 注册特征值变化监听(notify 数据与 read 结果都经此回调分发,幂等) +function ensureValueChangeListener() { + if (notifyListenerRegistered) return + notifyListenerRegistered = true + uni.onBLECharacteristicValueChange((res: any) => { + const charId = res.characteristicId as string + const bytes = new Uint8Array(res.value as ArrayBuffer) + const arr = [] as number[] + for (let i = 0; i < bytes.length; i++) { + arr.push(bytes[i]) + } + if (uuidEq(charId, MCM_NOTIFY_UUID)) { + const nd: NotityData = { data: arr, mac: res.deviceId } as NotityData + const c = notifyDataCb + if (c != null) { + c({ type: 0, message: "notify", data: nd } as MyApiResult) + } + return + } + if (readPendingChar != null && uuidEq(charId, readPendingChar)) { + const c = readCb + readCb = null + readPendingChar = null + clearTimeout(readTimer) + if (c != null) { + c({ type: 0, message: "read ok", data: arr } as MyApiResult) + } + } + }) +} + +function ok(message: string): MyApiResult { + return { type: 0, message: message, data: null } as MyApiResult +} + +function failRes(type: number, err: any): MyApiResult { + return { type: type, message: (err && err.errMsg) ? err.errMsg : "fail", data: null } as MyApiResult +} + +// uni BLE 错误 → 本模块 type 约定:蓝牙未开/不可用 10002,权限类 10003 +function mapBleError(err: any): number { + const msg: string = (err && err.errMsg) ? err.errMsg : "" + if (msg.indexOf("ermission") >= 0) return 10003 + return 10002 +} + +function uuidEq(a: string, b: string): boolean { + return a.toUpperCase() == b.toUpperCase() +} + +// 注册系统蓝牙开关状态监听并同步一次当前状态(幂等) +function ensureStateListener() { + if (stateListenerRegistered) return + stateListenerRegistered = true + uni.onBluetoothAdapterStateChange((res: any) => { + btEnabled = res.available + }) + uni.getBluetoothAdapterState({ + success: (res: any) => { + btEnabled = res.available + }, + fail: () => {} + }) +} + +// 注册连接状态监听(幂等):意外断开时广播 bleState 10001 +function ensureConnListener() { + if (connListenerRegistered) return + connListenerRegistered = true + uni.onBLEConnectionStateChange((res: any) => { + if (res.connected) { + connectedDeviceId = res.deviceId + } else { + const wasConnected = connectedDeviceId != null && connectedDeviceId == res.deviceId + connectedDeviceId = null + if (wasConnected && !manualDisconnect) { + uni.$emit("bleState", 10001) + } + manualDisconnect = false + } + }) +} + +// ================= 适配器 ================= + +// 手机蓝牙是否打开 true-打开 false-未打开 +export function isEnabled(): boolean { + ensureStateListener() + return btEnabled +} + +// 监听系统蓝牙开关状态 res.data 的值为 true-打开 false-关闭 +export function OpenStateListener(cb: (res: MyApiResult) => void) { + ensureStateListener() + uni.onBluetoothAdapterStateChange((res: any) => { + cb({ type: 0, message: "adapter state changed", data: res.available } as MyApiResult) + }) +} + +// 打开蓝牙(初始化蓝牙适配器;蓝牙关闭/无权限时走 fail) +export function openBluetooth(cb: (res: MyApiResult) => void) { + ensureStateListener() + uni.openBluetoothAdapter({ + success: () => { + btEnabled = true + cb(ok("bluetooth adapter opened")) + }, + fail: (err: any) => { + cb(failRes(mapBleError(err), err)) + } + }) +} + +// ================= 扫描 ================= + +// 开始蓝牙扫描:每个发现的设备回调一次 {type:0, data: BleScanResult};超时自动停止 +export function startScan(timeoutMs: number, cb: (res: MyApiResult) => void) { + ensureStateListener() + uni.openBluetoothAdapter({ + success: () => { + btEnabled = true + if (scanning) return + if (!scanListenerRegistered) { + scanListenerRegistered = true + uni.onBluetoothDeviceFound((res: any) => { + const devices = res.devices as any[] + for (let i = 0; i < devices.length; i++) { + const d = devices[i] + let name: string | null = d.name as string + if (name == null || name.length == 0) { + const ln = (d.localName ?? "") as string + name = ln.length > 0 ? ln : null + } + const sr: BleScanResult = { + device: { name: name, address: d.deviceId } as BleScanDevice, + rssi: d.RSSI as number + } as BleScanResult + cb({ type: 0, message: "device found", data: sr } as MyApiResult) + } + }) + } + scanning = true + uni.startBluetoothDevicesDiscovery({ + allowDuplicatesKey: false, + success: () => {}, + fail: (err: any) => { + scanning = false + cb(failRes(mapBleError(err), err)) + } + }) + if (scanTimeoutTimer != 0) { + clearTimeout(scanTimeoutTimer) + } + scanTimeoutTimer = setTimeout(() => { + stopScan() + }, timeoutMs) + }, + fail: (err: any) => { + cb(failRes(mapBleError(err), err)) + } + }) +} + +// 停止蓝牙扫描 +export function stopScan() { + if (scanTimeoutTimer != 0) { + clearTimeout(scanTimeoutTimer) + scanTimeoutTimer = 0 + } + if (!scanning) return + scanning = false + uni.stopBluetoothDevicesDiscovery({ + success: () => {}, + fail: () => {} + }) +} + +// ================= 连接 ================= + +// 蓝牙是否已连接 +export function isConnected(): boolean { + return connectedDeviceId != null +} + +// 获取已连接设备id(未连接返回空串) +export function getConnectMac(): string { + return connectedDeviceId ?? "" +} + +// 连接蓝牙(autoReconnect 保留兼容签名,当前业务恒传 false,未实现自动重连) +export function connect(mac: string, autoReconnect: boolean, cb: (res: MyApiResult) => void) { + ensureStateListener() + ensureConnListener() + manualDisconnect = false + if (connTimeoutMs > 0) { + uni.createBLEConnection({ + deviceId: mac, + timeout: connTimeoutMs, + success: () => { + connectedDeviceId = mac + cb(ok("connected")) + }, + fail: (err: any) => { + cb(failRes(10000, err)) + } + }) + } else { + uni.createBLEConnection({ + deviceId: mac, + success: () => { + connectedDeviceId = mac + cb(ok("connected")) + }, + fail: (err: any) => { + cb(failRes(10000, err)) + } + }) + } +} + +// 设置蓝牙连接超时时间(ms) +export function connectTimeout(timeout: number): void { + connTimeoutMs = timeout +} + +// 设置自动连接间隔(保留兼容,未实现) +export function setBtAutoConnectTime(timeout: number): void { +} + +// 取消自动连接蓝牙(保留兼容,未实现) +export function cancelAutoConnectBt(): void { +} + +// 断开蓝牙 +export function disConnect() { + const id = connectedDeviceId + manualDisconnect = true + connectedDeviceId = null + if (id != null) { + uni.closeBLEConnection({ + deviceId: id, + success: () => {}, + fail: () => {} + }) + } +} + +// ================= 服务与订阅 ================= + +// 连接后扫描服务:确认 MCM 服务及写/notify 特征存在(替代插件的自动识别) +export function scanServices(cb: (res: MyApiResult) => void) { + const id = connectedDeviceId + if (id == null) { + cb({ type: 10001, message: "not connected", data: null } as MyApiResult) + return + } + uni.getBLEDeviceServices({ + deviceId: id, + success: (res: any) => { + const services = res.services as any[] + let found = false + for (let i = 0; i < services.length; i++) { + if (uuidEq(services[i].uuid as string, MCM_SERVICE_UUID)) { + found = true + break + } + } + if (!found) { + cb({ type: 10004, message: "MCM service not found", data: null } as MyApiResult) + return + } + uni.getBLEDeviceCharacteristics({ + deviceId: id as string, + serviceId: MCM_SERVICE_UUID, + success: () => { + cb(ok("services ready")) + }, + fail: (err: any) => { + cb(failRes(10005, err)) + } + }) + }, + fail: (err: any) => { + cb(failRes(10004, err)) + } + }) +} + +// 开启服务消息通知(显式使用 CAIIC-MCM 的服务/notify UUID) +// 订阅成功回调 type=1000,失败 type=1001;之后 notify 数据以 type=0 持续回调 +export function onNotityReadBleData(readNotification: boolean, cb: (res: MyApiResult) => void) { + const id = connectedDeviceId + if (id == null) { + cb({ type: 10001, message: "not connected", data: null } as MyApiResult) + return + } + notifyDataCb = cb + ensureValueChangeListener() + uni.notifyBLECharacteristicValueChange({ + deviceId: id, + serviceId: MCM_SERVICE_UUID, + characteristicId: MCM_NOTIFY_UUID, + state: readNotification, + success: () => { + cb({ type: 1000, message: "notify subscribed", data: null } as MyApiResult) + }, + fail: (err: any) => { + cb(failRes(1001, err)) + } + }) +} + +// ================= 写入 ================= + +// 发送数据(显式写类型):writeType 0=WRITE(有响应) 1=WRITE_NO_RESPONSE(无响应) +// CAIIC-MCM 设备的下行特征仅支持 Write Without Response,必须用 writeType=1。 +// 服务/特征 UUID 用固件实际值显式指定(下行特征只有 WRITE_NO_RESPONSE 属性) +export function sendDataBytes(data: number[], writeType: number, cb: (res: MyApiResult) => void) { + const id = connectedDeviceId + if (id == null) { + cb({ type: 10001, message: "not connected", data: null } as MyApiResult) + return + } + const buf = new Uint8Array(data.length) + for (let i = 0; i < data.length; i++) { + buf[i] = data[i] & 0xFF + } + if (writeType == 1) { + uni.writeBLECharacteristicValue({ + deviceId: id, + serviceId: MCM_SERVICE_UUID, + characteristicId: MCM_WRITE_UUID, + value: buf.buffer, + writeType: "writeNoResponse", + success: () => { + cb(ok("write ok")) + }, + fail: (err: any) => { + cb(failRes(10008, err)) + } + }) + } else { + uni.writeBLECharacteristicValue({ + deviceId: id, + serviceId: MCM_SERVICE_UUID, + characteristicId: MCM_WRITE_UUID, + value: buf.buffer, + writeType: "write", + success: () => { + cb(ok("write ok")) + }, + fail: (err: any) => { + cb(failRes(10008, err)) + } + }) + } +} + +// ================= 读取 ================= + +// 读取设备参数特征(只读 MCM_INFO_UUID,内容为全部信息项 TLV,同 INFO_RSP(all))。 +// uni 的读结果经 onBLECharacteristicValueChange 回传;type==0 时 data 为 number[]。 +export function readInfoChar(cb: (res: MyApiResult) => void) { + const id = connectedDeviceId + if (id == null) { + cb({ type: 10001, message: "not connected", data: null } as MyApiResult) + return + } + ensureValueChangeListener() + readPendingChar = MCM_INFO_UUID + readCb = cb + readTimer = setTimeout(() => { + if (readCb != null) { + readCb = null + readPendingChar = null + cb({ type: 10008, message: "read timeout", data: null } as MyApiResult) + } + }, 3000) + uni.readBLECharacteristicValue({ + deviceId: id, + serviceId: MCM_SERVICE_UUID, + characteristicId: MCM_INFO_UUID, + success: () => {}, + fail: (err: any) => { + readPendingChar = null + readCb = null + clearTimeout(readTimer) + cb(failRes(10008, err)) + } + }) +} + +// ================= 其他 ================= + +// 设置 mtu(仅 App 端有效;其他平台回调失败,调用方保持默认 20 字节切片) +export function setMtu(len: number, cb: (res: MyApiResult) => void) { + const id = connectedDeviceId + if (id == null) { + cb({ type: 10001, message: "not connected", data: null } as MyApiResult) + return + } + // #ifdef APP-PLUS + uni.setBLEMTU({ + deviceId: id, + mtu: len, + success: (res: any) => { + cb({ type: 0, message: "mtu " + res.mtu, data: null } as MyApiResult) + }, + fail: (err: any) => { + cb(failRes(10008, err)) + } + }) + // #endif + // #ifndef APP-PLUS + cb({ type: 10008, message: "set mtu not supported on this platform", data: null } as MyApiResult) + // #endif +} + +// 读取 rssi 信号 需要蓝牙已经连接 +export function readRssi(cb: (res: MyApiResult) => void) { + const id = connectedDeviceId + if (id == null) { + cb({ type: 10001, message: "not connected", data: null } as MyApiResult) + return + } + uni.getBLEDeviceRSSI({ + deviceId: id, + success: (res: any) => { + cb({ type: 0, message: "rssi", data: res.RSSI } as MyApiResult) + }, + fail: (err: any) => { + cb(failRes(10008, err)) + } + }) +} diff --git a/SmartAssiter/src/commonFunctions.ts b/SmartAssiter/src/commonFunctions.ts new file mode 100644 index 0000000..954ce65 --- /dev/null +++ b/SmartAssiter/src/commonFunctions.ts @@ -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/SmartAssiter/src/mcm/Crc.ts b/SmartAssiter/src/mcm/Crc.ts new file mode 100644 index 0000000..6795e13 --- /dev/null +++ b/SmartAssiter/src/mcm/Crc.ts @@ -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/SmartAssiter/src/mcm/FrameCodec.ts b/SmartAssiter/src/mcm/FrameCodec.ts new file mode 100644 index 0000000..271fe80 --- /dev/null +++ b/SmartAssiter/src/mcm/FrameCodec.ts @@ -0,0 +1,126 @@ +/** + * CAIIC BLE 协议 V1.1 帧编解码 + * 帧格式(小端):[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" + +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 const TYPE_INFO_QUERY = 0x20 // {item_id u8}×n;空 payload 或单个 0xFF = 查询全部 +export const TYPE_INFO_RSP = 0x21 // TLV 序列 {id u8, len u8, value…}×n + +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 +} + +/** 小端 u16 解包 */ +export function readU16LE(data: number[], offset: number): number { + return (data[offset] & 0xFF) + (data[offset + 1] & 0xFF) * 256 +} + +/** 小端 i16 解包(处理符号位) */ +export function readI16LE(data: number[], offset: number): number { + const v = readU16LE(data, offset) + return v >= 0x8000 ? v - 0x10000 : v +} diff --git a/SmartAssiter/src/mcm/McmChannel.ts b/SmartAssiter/src/mcm/McmChannel.ts new file mode 100644 index 0000000..5097bee --- /dev/null +++ b/SmartAssiter/src/mcm/McmChannel.ts @@ -0,0 +1,274 @@ +/** + * 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" +import { MyApiResult, NotityData } from "../BluetoothManager" +import { + FrameDecoder, BleFrame, encodeFrame, + TYPE_CLI_REQ, TYPE_CLI_RSP, TYPE_CLI_RSP_END, TYPE_OTA_RSP, TYPE_INFO_QUERY, TYPE_INFO_RSP +} from "./FrameCodec" +import { asciiToBytes, bytesToAscii, bytesToHex } from "../commonFunctions" + +export type CliResult = { + text: string, // 聚合的应答文本 + status: number // 0=ok 1=未知命令/行超长 -1=超时 -2=命令超长 -3=发送失败 -4=连接断开 +} + +type CliJob = { + textChunks: string[], + resolve: (r: CliResult) => void, + timer: number +} + +type InfoJob = { + resolve: (payload: number[] | null) => 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 infoJob: InfoJob | null = null +let infoChain: Promise = Promise.resolve(true) + +let otaRspHandler: ((payload: number[]) => void) | null = null +let disconnectHandler: (() => void) | null = null + +// ================= 接收 ================= + +function dispatchFrame(frame: BleFrame) { + console.log("[MCM] RX frame type=0x" + frame.type.toString(16) + " seq=" + frame.seq + " payload=" + bytesToHex(frame.payload)) + 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) + } + } else if (frame.type == TYPE_INFO_RSP) { + const job = infoJob + if (job != null) { + infoJob = null + clearTimeout(job.timer) + job.resolve(frame.payload) + } + } +} + +function onBleData(res: MyApiResult) { + if (res.type != 0) return + // 插件 notify 回调的 data 是 NotityData 结构({data: number[], mac, serviceId, ...}), + // 不是裸字节数组,需取 .data 字段 + const nd = res.data as NotityData + console.log("[MCM] RX notify " + nd.data.length + "B: " + bytesToHex(nd.data)) + const frames = decoder.feed(nd.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 结束,挂起的信息查询以 null 结束 */ +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) + } + const iJob = infoJob + if (iJob != null) { + infoJob = null + clearTimeout(iJob.timer) + iJob.resolve(null) + } +} + +// ================= 生命周期 ================= + +/** 注册全局蓝牙事件监听(幂等,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 + console.log("[MCM] TX frame type=0x" + type.toString(16) + " len=" + frame.length + ": " + bytesToHex(frame)) + 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) { + console.log("[MCM] TX write failed: type=" + res.type + " msg=" + res.message) + 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) => { + // 固件 CLI_REQ 为整行 payload 直接执行(协议 V1.1:无结束符) + 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 +} + +// ================= 信息查询(INFO_QUERY / INFO_RSP) ================= + +function queryInfoImpl(items: number[], timeoutMs: number): Promise { + return new Promise((resolve) => { + const job: InfoJob = { + resolve: resolve, + timer: 0 + } as InfoJob + job.timer = setTimeout(() => { + if (infoJob == job) { + infoJob = null + resolve(null) + } + }, timeoutMs) + infoJob = job + // payload = {item_id u8}×n;空数组 = 查询全部 + sendFrame(TYPE_INFO_QUERY, items).then((ok) => { + if (!ok && infoJob == job) { + infoJob = null + clearTimeout(job.timer) + resolve(null) + } + }) + }) +} + +/** + * 设备信息查询:串行执行,返回 INFO_RSP 的 TLV payload; + * 超时/发送失败/断连返回 null;只 resolve 不 reject + */ +export function queryInfo(items: number[], timeoutMs: number = 2000): Promise { + attach() + const p = infoChain.then((): Promise => queryInfoImpl(items, timeoutMs)) + infoChain = p.then((): boolean => true) + return p +} diff --git a/SmartAssiter/src/mcm/McmCommands.ts b/SmartAssiter/src/mcm/McmCommands.ts new file mode 100644 index 0000000..24d84a8 --- /dev/null +++ b/SmartAssiter/src/mcm/McmCommands.ts @@ -0,0 +1,51 @@ +/** + * CAIIC-MCM 业务命令封装 + * 温度/风扇/固件版本走 INFO_QUERY(协议 V1.1 第 5 节); + * sysinfo/bankinfo 等系统状态仍走 CLI 透传,命令名集中在顶部常量。 + */ +import { sendCli, CliResult } from "./McmChannel" +import { + queryDeviceInfo, formatVersion, + INFO_FW_VERSION, INFO_CHIP_TEMP, INFO_FAN_RPM +} from "./McmInfo" + +const CMD_SYSINFO = "sysinfo" +const CMD_BANKINFO = "bankinfo" + +/** 固件版本号,如 "V1.00.01"(来自 INFO_QUERY FW_VERSION);失败返回 null */ +export async function getVersion(): Promise { + const info = await queryDeviceInfo([INFO_FW_VERSION]) + const v = info.fwVersion + return v != null ? formatVersion(v) : 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 info = await queryDeviceInfo([INFO_CHIP_TEMP]) + return info.chipTemp +} + +/** 风扇转速(RPM),无测速硬件/读取失败返回 null */ +export async function getFanSpeed(): Promise { + const info = await queryDeviceInfo([INFO_FAN_RPM]) + return info.fanRpm +} + +/** 通用 CLI 命令(调试终端用) */ +export async function execCli(cmd: string, timeoutMs: number = 5000): Promise { + return await sendCli(cmd, timeoutMs) +} diff --git a/SmartAssiter/src/mcm/McmInfo.ts b/SmartAssiter/src/mcm/McmInfo.ts new file mode 100644 index 0000000..352f9d4 --- /dev/null +++ b/SmartAssiter/src/mcm/McmInfo.ts @@ -0,0 +1,101 @@ +/** + * CAIIC-MCM 设备信息查询(协议 V1.1 第 5 节:INFO_QUERY / INFO_RSP) + * INFO_RSP payload 为 TLV 序列 {id u8, len u8, value…}×n,数值均小端; + * 设备不认识的 id 不出现在应答中;解析时未知 id 按 len 跳过。 + */ +import { queryInfo } from "./McmChannel" +import { readU16LE, readI16LE, readU32LE } from "./FrameCodec" +import * as Ble from "../BluetoothManager" +import { MyApiResult } from "../BluetoothManager" + +// 信息项 id +export const INFO_FW_VERSION = 0x01 // u32 固件版本号,如 0x00010001 = V1.00.01 +export const INFO_CHIP_TEMP = 0x02 // i16 芯片温度,单位 0.1℃(0x7FFF = 读取失败) +export const INFO_FAN_RPM = 0x03 // u16 风扇转速 rpm(0xFFFF = 无此硬件/未接入) +export const INFO_VDD_MV = 0x04 // u16 电源电压 mV(0 = 读取失败) +export const INFO_UPTIME_S = 0x05 // u32 系统运行时间 s +export const INFO_FREE_HEAP = 0x06 // u32 FreeRTOS 堆剩余 B + +export type DeviceInfo = { + fwVersion: number | null, // 原始 u32 版本号 + chipTemp: number | null, // 温度,已换算为 ℃ + fanRpm: number | null, // 风扇转速 rpm;null = 无硬件/未应答 + vddMv: number | null, // 电源电压 mV + uptimeS: number | null, // 运行时间 s + freeHeap: number | null // 堆剩余 B +} + +function emptyInfo(): DeviceInfo { + return { + fwVersion: null, + chipTemp: null, + fanRpm: null, + vddMv: null, + uptimeS: null, + freeHeap: null + } as DeviceInfo +} + +/** 解析 INFO_RSP 的 TLV payload;未知 id 按 len 跳过,数值长度不符的项忽略 */ +export function parseTlv(payload: number[]): DeviceInfo { + const info = emptyInfo() + let i = 0 + while (i + 2 <= payload.length) { + const id = payload[i] + const len = payload[i + 1] + if (i + 2 + len > payload.length) break // 长度越界,丢弃剩余 + const off = i + 2 + if (id == INFO_FW_VERSION && len == 4) { + info.fwVersion = readU32LE(payload, off) + } else if (id == INFO_CHIP_TEMP && len == 2) { + const raw = readI16LE(payload, off) + info.chipTemp = raw == 0x7FFF ? null : raw / 10.0 + } else if (id == INFO_FAN_RPM && len == 2) { + const raw = readU16LE(payload, off) + info.fanRpm = raw == 0xFFFF ? null : raw + } else if (id == INFO_VDD_MV && len == 2) { + const raw = readU16LE(payload, off) + info.vddMv = raw == 0 ? null : raw + } else if (id == INFO_UPTIME_S && len == 4) { + info.uptimeS = readU32LE(payload, off) + } else if (id == INFO_FREE_HEAP && len == 4) { + info.freeHeap = readU32LE(payload, off) + } + i += 2 + len + } + return info +} + +/** + * 查询设备信息;items 为空数组 = 查询全部。 + * 超时/断连/发送失败返回各字段全 null 的 DeviceInfo。 + */ +export async function queryDeviceInfo(items: number[]): Promise { + const payload = await queryInfo(items, 2000) + if (payload == null) return emptyInfo() + return parseTlv(payload) +} + +/** + * 读只读参数特征(MCM_INFO_UUID,内容为全部信息项 TLV,同 INFO_RSP(all))。 + * 不依赖 notify 通道;失败/超时返回各字段全 null 的 DeviceInfo。 + */ +export async function readDeviceInfo(): Promise { + const payload = await new Promise((resolve) => { + Ble.readInfoChar((res: MyApiResult) => { + resolve(res.type == 0 ? res.data as number[] : null) + }) + }) + if (payload == null) return emptyInfo() + return parseTlv(payload) +} + +/** 固件版本号 u32 格式化,如 0x00010001 → "V1.00.01" */ +export function formatVersion(v: number): string { + const major = Math.floor(v / 65536) % 256 + const minor = Math.floor(v / 256) % 256 + const patch = v % 256 + const ms = minor < 10 ? "0" + minor : "" + minor + const ps = patch < 10 ? "0" + patch : "" + patch + return "V" + major + "." + ms + "." + ps +} diff --git a/SmartAssiter/src/mcm/OtaManager.ts b/SmartAssiter/src/mcm/OtaManager.ts new file mode 100644 index 0000000..1450104 --- /dev/null +++ b/SmartAssiter/src/mcm/OtaManager.ts @@ -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" +import { + TYPE_OTA_BEGIN, TYPE_OTA_DATA, TYPE_OTA_END, + MAX_PAYLOAD, pushU32LE, readU32LE +} from "./FrameCodec" +import { crc32Ieee } from "./Crc" + +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("Sending OTA_BEGIN (" + total + " bytes)...") + if (!await sendFrame(TYPE_OTA_BEGIN, pb)) { + cb.onState("Failed to send OTA_BEGIN") + return false + } + let ack = await waitRsp(TYPE_OTA_BEGIN, 5000) + if (!ack.received) { + cb.onState(ack.status == -2 ? "Upgrade aborted" : "Timeout waiting for OTA_BEGIN ack") + return false + } + if (ack.status != 0) { + cb.onState("Device rejected upgrade: " + statusText(ack.status)) + return false + } + + // 2. OTA_DATA:按 4KB 扇区发送,满扇区等 ack(流控) + let offset = 0 + while (offset < total) { + if (aborted) { + cb.onState("Upgrade aborted") + 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("Failed to write 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 ? "Upgrade aborted" : "Timeout waiting for sector ack, offset=" + offset) + return false + } + if (ack.status == 2) { + // offset 乱序:从设备期望的 offset 重发重同步 + offset = ack.offset + cb.onState("Resync to offset=" + offset) + continue + } + if (ack.status != 0) { + cb.onState("Device write error: " + statusText(ack.status)) + return false + } + } + offset = sectorEnd + } + + // 3. OTA_END:设备 flush 尾部并整镜像回读校验 CRC32 + const pe = [] as number[] + pushU32LE(pe, crc) + cb.onState("Sending OTA_END, verifying image on device...") + if (!await sendFrame(TYPE_OTA_END, pe)) { + cb.onState("Failed to send OTA_END") + return false + } + ack = await waitRsp(TYPE_OTA_END, 15000) + if (!ack.received) { + cb.onState(ack.status == -2 ? "Upgrade aborted" : "Timeout waiting for OTA_END ack") + return false + } + if (ack.status != 0) { + cb.onState("Image verification failed: " + statusText(ack.status)) + return false + } + cb.onProgress(100) + cb.onState("Upgrade succeeded. Device will reboot into the new firmware in ~200ms, please reconnect.") + 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("Upgrade already in progress") + return false + } + if (image.length < 4 || image.length > BANK_MAX) { + cb.onState("Invalid image size (4 ~ " + BANK_MAX + " bytes)") + 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/SmartAssiter/static/androidApp.png b/SmartAssiter/static/androidApp.png new file mode 100644 index 0000000..8737512 Binary files /dev/null and b/SmartAssiter/static/androidApp.png differ diff --git a/SmartAssiter/static/androidApp22.png b/SmartAssiter/static/androidApp22.png new file mode 100644 index 0000000..baceb27 Binary files /dev/null and b/SmartAssiter/static/androidApp22.png differ diff --git a/SmartAssiter/static/appIcon.png b/SmartAssiter/static/appIcon.png new file mode 100644 index 0000000..7f4a13a Binary files /dev/null and b/SmartAssiter/static/appIcon.png differ diff --git a/SmartAssiter/static/charge/UnAvailable.png b/SmartAssiter/static/charge/UnAvailable.png new file mode 100644 index 0000000..55442c6 Binary files /dev/null and b/SmartAssiter/static/charge/UnAvailable.png differ diff --git a/SmartAssiter/static/charge/Unkonw.png b/SmartAssiter/static/charge/Unkonw.png new file mode 100644 index 0000000..987b654 Binary files /dev/null and b/SmartAssiter/static/charge/Unkonw.png differ diff --git a/SmartAssiter/static/charge/addDevice.png b/SmartAssiter/static/charge/addDevice.png new file mode 100644 index 0000000..0a5f92c Binary files /dev/null and b/SmartAssiter/static/charge/addDevice.png differ diff --git a/SmartAssiter/static/charge/charging.png b/SmartAssiter/static/charge/charging.png new file mode 100644 index 0000000..b0701e5 Binary files /dev/null and b/SmartAssiter/static/charge/charging.png differ diff --git a/SmartAssiter/static/charge/connectGun.png b/SmartAssiter/static/charge/connectGun.png new file mode 100644 index 0000000..fd5e9d1 Binary files /dev/null and b/SmartAssiter/static/charge/connectGun.png differ diff --git a/SmartAssiter/static/charge/error.png b/SmartAssiter/static/charge/error.png new file mode 100644 index 0000000..7ad8276 Binary files /dev/null and b/SmartAssiter/static/charge/error.png differ diff --git a/SmartAssiter/static/charge/finishing.png b/SmartAssiter/static/charge/finishing.png new file mode 100644 index 0000000..a4e321a Binary files /dev/null and b/SmartAssiter/static/charge/finishing.png differ diff --git a/SmartAssiter/static/charge/insertGun.png b/SmartAssiter/static/charge/insertGun.png new file mode 100644 index 0000000..7ac80ba Binary files /dev/null and b/SmartAssiter/static/charge/insertGun.png differ diff --git a/SmartAssiter/static/charge/noDevice.png b/SmartAssiter/static/charge/noDevice.png new file mode 100644 index 0000000..2fa1a1f Binary files /dev/null and b/SmartAssiter/static/charge/noDevice.png differ diff --git a/SmartAssiter/static/charge/reserve.png b/SmartAssiter/static/charge/reserve.png new file mode 100644 index 0000000..3f11c73 Binary files /dev/null and b/SmartAssiter/static/charge/reserve.png differ diff --git a/SmartAssiter/static/charge/scanCode.png b/SmartAssiter/static/charge/scanCode.png new file mode 100644 index 0000000..430a6bd Binary files /dev/null and b/SmartAssiter/static/charge/scanCode.png differ diff --git a/SmartAssiter/static/logo.png b/SmartAssiter/static/logo.png new file mode 100644 index 0000000..b5771e2 Binary files /dev/null and b/SmartAssiter/static/logo.png differ diff --git a/SmartAssiter/static/miniApp.png b/SmartAssiter/static/miniApp.png new file mode 100644 index 0000000..8737512 Binary files /dev/null and b/SmartAssiter/static/miniApp.png differ diff --git a/SmartAssiter/static/miniApp22.png b/SmartAssiter/static/miniApp22.png new file mode 100644 index 0000000..79bd8fe Binary files /dev/null and b/SmartAssiter/static/miniApp22.png differ diff --git a/SmartAssiter/static/tabbar/chargeActive.png b/SmartAssiter/static/tabbar/chargeActive.png new file mode 100644 index 0000000..1be9f60 Binary files /dev/null and b/SmartAssiter/static/tabbar/chargeActive.png differ diff --git a/SmartAssiter/static/tabbar/chargeDefault.png b/SmartAssiter/static/tabbar/chargeDefault.png new file mode 100644 index 0000000..2f2df6b Binary files /dev/null and b/SmartAssiter/static/tabbar/chargeDefault.png differ diff --git a/SmartAssiter/static/tabbar/homeActive.png b/SmartAssiter/static/tabbar/homeActive.png new file mode 100644 index 0000000..254070a Binary files /dev/null and b/SmartAssiter/static/tabbar/homeActive.png differ diff --git a/SmartAssiter/static/tabbar/homeDefault.png b/SmartAssiter/static/tabbar/homeDefault.png new file mode 100644 index 0000000..4b25dcd Binary files /dev/null and b/SmartAssiter/static/tabbar/homeDefault.png differ diff --git a/SmartAssiter/static/tabbar/mapActive.png b/SmartAssiter/static/tabbar/mapActive.png new file mode 100644 index 0000000..4a687b8 Binary files /dev/null and b/SmartAssiter/static/tabbar/mapActive.png differ diff --git a/SmartAssiter/static/tabbar/mapDefault.png b/SmartAssiter/static/tabbar/mapDefault.png new file mode 100644 index 0000000..45263f9 Binary files /dev/null and b/SmartAssiter/static/tabbar/mapDefault.png differ diff --git a/SmartAssiter/static/tabbar/myActive.png b/SmartAssiter/static/tabbar/myActive.png new file mode 100644 index 0000000..5fdf9d2 Binary files /dev/null and b/SmartAssiter/static/tabbar/myActive.png differ diff --git a/SmartAssiter/static/tabbar/myDefault.png b/SmartAssiter/static/tabbar/myDefault.png new file mode 100644 index 0000000..6e82cff Binary files /dev/null and b/SmartAssiter/static/tabbar/myDefault.png differ diff --git a/SmartAssiter/static/tabbar/orderActive.png b/SmartAssiter/static/tabbar/orderActive.png new file mode 100644 index 0000000..9021628 Binary files /dev/null and b/SmartAssiter/static/tabbar/orderActive.png differ diff --git a/SmartAssiter/static/tabbar/orderDefault.png b/SmartAssiter/static/tabbar/orderDefault.png new file mode 100644 index 0000000..62fa210 Binary files /dev/null and b/SmartAssiter/static/tabbar/orderDefault.png differ diff --git a/SmartAssiter/static/tabbar/scanCode.png b/SmartAssiter/static/tabbar/scanCode.png new file mode 100644 index 0000000..750f029 Binary files /dev/null and b/SmartAssiter/static/tabbar/scanCode.png differ diff --git a/SmartAssiter/static/user.png b/SmartAssiter/static/user.png new file mode 100644 index 0000000..1e07409 Binary files /dev/null and b/SmartAssiter/static/user.png differ diff --git a/SmartAssiter/static/userDefault.png b/SmartAssiter/static/userDefault.png new file mode 100644 index 0000000..a141aa3 Binary files /dev/null and b/SmartAssiter/static/userDefault.png differ diff --git a/SmartAssiter/uni.scss b/SmartAssiter/uni.scss new file mode 100644 index 0000000..b9249e9 --- /dev/null +++ b/SmartAssiter/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/SmartAssiter/uni_modules/uni-icons/changelog.md b/SmartAssiter/uni_modules/uni-icons/changelog.md new file mode 100644 index 0000000..a287acc --- /dev/null +++ b/SmartAssiter/uni_modules/uni-icons/changelog.md @@ -0,0 +1,46 @@ +## 2.0.13(2026-07-22) +- 优化 选项式改成组合式写法 +## 2.0.12(2025-08-26) +- 优化 uni-app x 下 size 类型问题 +## 2.0.11(2025-08-18) +- 修复 图标点击事件返回 +## 2.0.9(2024-01-12) +fix: 修复图标大小默认值错误的问题 +## 2.0.8(2023-12-14) +- 修复 项目未使用 ts 情况下,打包报错的bug +## 2.0.7(2023-12-14) +- 修复 size 属性为 string 时,不加单位导致尺寸异常的bug +## 2.0.6(2023-12-11) +- 优化 兼容老版本icon类型,如 top ,bottom 等 +## 2.0.5(2023-12-11) +- 优化 兼容老版本icon类型,如 top ,bottom 等 +## 2.0.4(2023-12-06) +- 优化 uni-app x 下示例项目图标排序 +## 2.0.3(2023-12-06) +- 修复 nvue下引入组件报错的bug +## 2.0.2(2023-12-05) +-优化 size 属性支持单位 +## 2.0.1(2023-12-05) +- 新增 uni-app x 支持定义图标 +## 1.3.5(2022-01-24) +- 优化 size 属性可以传入不带单位的字符串数值 +## 1.3.4(2022-01-24) +- 优化 size 支持其他单位 +## 1.3.3(2022-01-17) +- 修复 nvue 有些图标不显示的bug,兼容老版本图标 +## 1.3.2(2021-12-01) +- 优化 示例可复制图标名称 +## 1.3.1(2021-11-23) +- 优化 兼容旧组件 type 值 +## 1.3.0(2021-11-19) +- 新增 更多图标 +- 优化 自定义图标使用方式 +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-icons](https://uniapp.dcloud.io/component/uniui/uni-icons) +## 1.1.7(2021-11-08) +## 1.2.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.5(2021-05-12) +- 新增 组件示例地址 +## 1.1.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uni-icons.uvue b/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uni-icons.uvue new file mode 100644 index 0000000..de6d019 --- /dev/null +++ b/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uni-icons.uvue @@ -0,0 +1,88 @@ + + + + + diff --git a/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uni-icons.vue b/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uni-icons.vue new file mode 100644 index 0000000..2bd7cd3 --- /dev/null +++ b/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uni-icons.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uniicons.css b/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uniicons.css new file mode 100644 index 0000000..0a6b6fe --- /dev/null +++ b/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uniicons.css @@ -0,0 +1,664 @@ + +.uniui-cart-filled:before { + content: "\e6d0"; +} + +.uniui-gift-filled:before { + content: "\e6c4"; +} + +.uniui-color:before { + content: "\e6cf"; +} + +.uniui-wallet:before { + content: "\e6b1"; +} + +.uniui-settings-filled:before { + content: "\e6ce"; +} + +.uniui-auth-filled:before { + content: "\e6cc"; +} + +.uniui-shop-filled:before { + content: "\e6cd"; +} + +.uniui-staff-filled:before { + content: "\e6cb"; +} + +.uniui-vip-filled:before { + content: "\e6c6"; +} + +.uniui-plus-filled:before { + content: "\e6c7"; +} + +.uniui-folder-add-filled:before { + content: "\e6c8"; +} + +.uniui-color-filled:before { + content: "\e6c9"; +} + +.uniui-tune-filled:before { + content: "\e6ca"; +} + +.uniui-calendar-filled:before { + content: "\e6c0"; +} + +.uniui-notification-filled:before { + content: "\e6c1"; +} + +.uniui-wallet-filled:before { + content: "\e6c2"; +} + +.uniui-medal-filled:before { + content: "\e6c3"; +} + +.uniui-fire-filled:before { + content: "\e6c5"; +} + +.uniui-refreshempty:before { + content: "\e6bf"; +} + +.uniui-location-filled:before { + content: "\e6af"; +} + +.uniui-person-filled:before { + content: "\e69d"; +} + +.uniui-personadd-filled:before { + content: "\e698"; +} + +.uniui-arrowthinleft:before { + content: "\e6d2"; +} + +.uniui-arrowthinup:before { + content: "\e6d3"; +} + +.uniui-arrowthindown:before { + content: "\e6d4"; +} + +.uniui-back:before { + content: "\e6b9"; +} + +.uniui-forward:before { + content: "\e6ba"; +} + +.uniui-arrow-right:before { + content: "\e6bb"; +} + +.uniui-arrow-left:before { + content: "\e6bc"; +} + +.uniui-arrow-up:before { + content: "\e6bd"; +} + +.uniui-arrow-down:before { + content: "\e6be"; +} + +.uniui-arrowthinright:before { + content: "\e6d1"; +} + +.uniui-down:before { + content: "\e6b8"; +} + +.uniui-bottom:before { + content: "\e6b8"; +} + +.uniui-arrowright:before { + content: "\e6d5"; +} + +.uniui-right:before { + content: "\e6b5"; +} + +.uniui-up:before { + content: "\e6b6"; +} + +.uniui-top:before { + content: "\e6b6"; +} + +.uniui-left:before { + content: "\e6b7"; +} + +.uniui-arrowup:before { + content: "\e6d6"; +} + +.uniui-eye:before { + content: "\e651"; +} + +.uniui-eye-filled:before { + content: "\e66a"; +} + +.uniui-eye-slash:before { + content: "\e6b3"; +} + +.uniui-eye-slash-filled:before { + content: "\e6b4"; +} + +.uniui-info-filled:before { + content: "\e649"; +} + +.uniui-reload:before { + content: "\e6b2"; +} + +.uniui-micoff-filled:before { + content: "\e6b0"; +} + +.uniui-map-pin-ellipse:before { + content: "\e6ac"; +} + +.uniui-map-pin:before { + content: "\e6ad"; +} + +.uniui-location:before { + content: "\e6ae"; +} + +.uniui-starhalf:before { + content: "\e683"; +} + +.uniui-star:before { + content: "\e688"; +} + +.uniui-star-filled:before { + content: "\e68f"; +} + +.uniui-calendar:before { + content: "\e6a0"; +} + +.uniui-fire:before { + content: "\e6a1"; +} + +.uniui-medal:before { + content: "\e6a2"; +} + +.uniui-font:before { + content: "\e6a3"; +} + +.uniui-gift:before { + content: "\e6a4"; +} + +.uniui-link:before { + content: "\e6a5"; +} + +.uniui-notification:before { + content: "\e6a6"; +} + +.uniui-staff:before { + content: "\e6a7"; +} + +.uniui-vip:before { + content: "\e6a8"; +} + +.uniui-folder-add:before { + content: "\e6a9"; +} + +.uniui-tune:before { + content: "\e6aa"; +} + +.uniui-auth:before { + content: "\e6ab"; +} + +.uniui-person:before { + content: "\e699"; +} + +.uniui-email-filled:before { + content: "\e69a"; +} + +.uniui-phone-filled:before { + content: "\e69b"; +} + +.uniui-phone:before { + content: "\e69c"; +} + +.uniui-email:before { + content: "\e69e"; +} + +.uniui-personadd:before { + content: "\e69f"; +} + +.uniui-chatboxes-filled:before { + content: "\e692"; +} + +.uniui-contact:before { + content: "\e693"; +} + +.uniui-chatbubble-filled:before { + content: "\e694"; +} + +.uniui-contact-filled:before { + content: "\e695"; +} + +.uniui-chatboxes:before { + content: "\e696"; +} + +.uniui-chatbubble:before { + content: "\e697"; +} + +.uniui-upload-filled:before { + content: "\e68e"; +} + +.uniui-upload:before { + content: "\e690"; +} + +.uniui-weixin:before { + content: "\e691"; +} + +.uniui-compose:before { + content: "\e67f"; +} + +.uniui-qq:before { + content: "\e680"; +} + +.uniui-download-filled:before { + content: "\e681"; +} + +.uniui-pyq:before { + content: "\e682"; +} + +.uniui-sound:before { + content: "\e684"; +} + +.uniui-trash-filled:before { + content: "\e685"; +} + +.uniui-sound-filled:before { + content: "\e686"; +} + +.uniui-trash:before { + content: "\e687"; +} + +.uniui-videocam-filled:before { + content: "\e689"; +} + +.uniui-spinner-cycle:before { + content: "\e68a"; +} + +.uniui-weibo:before { + content: "\e68b"; +} + +.uniui-videocam:before { + content: "\e68c"; +} + +.uniui-download:before { + content: "\e68d"; +} + +.uniui-help:before { + content: "\e679"; +} + +.uniui-navigate-filled:before { + content: "\e67a"; +} + +.uniui-plusempty:before { + content: "\e67b"; +} + +.uniui-smallcircle:before { + content: "\e67c"; +} + +.uniui-minus-filled:before { + content: "\e67d"; +} + +.uniui-micoff:before { + content: "\e67e"; +} + +.uniui-closeempty:before { + content: "\e66c"; +} + +.uniui-clear:before { + content: "\e66d"; +} + +.uniui-navigate:before { + content: "\e66e"; +} + +.uniui-minus:before { + content: "\e66f"; +} + +.uniui-image:before { + content: "\e670"; +} + +.uniui-mic:before { + content: "\e671"; +} + +.uniui-paperplane:before { + content: "\e672"; +} + +.uniui-close:before { + content: "\e673"; +} + +.uniui-help-filled:before { + content: "\e674"; +} + +.uniui-paperplane-filled:before { + content: "\e675"; +} + +.uniui-plus:before { + content: "\e676"; +} + +.uniui-mic-filled:before { + content: "\e677"; +} + +.uniui-image-filled:before { + content: "\e678"; +} + +.uniui-locked-filled:before { + content: "\e668"; +} + +.uniui-info:before { + content: "\e669"; +} + +.uniui-locked:before { + content: "\e66b"; +} + +.uniui-camera-filled:before { + content: "\e658"; +} + +.uniui-chat-filled:before { + content: "\e659"; +} + +.uniui-camera:before { + content: "\e65a"; +} + +.uniui-circle:before { + content: "\e65b"; +} + +.uniui-checkmarkempty:before { + content: "\e65c"; +} + +.uniui-chat:before { + content: "\e65d"; +} + +.uniui-circle-filled:before { + content: "\e65e"; +} + +.uniui-flag:before { + content: "\e65f"; +} + +.uniui-flag-filled:before { + content: "\e660"; +} + +.uniui-gear-filled:before { + content: "\e661"; +} + +.uniui-home:before { + content: "\e662"; +} + +.uniui-home-filled:before { + content: "\e663"; +} + +.uniui-gear:before { + content: "\e664"; +} + +.uniui-smallcircle-filled:before { + content: "\e665"; +} + +.uniui-map-filled:before { + content: "\e666"; +} + +.uniui-map:before { + content: "\e667"; +} + +.uniui-refresh-filled:before { + content: "\e656"; +} + +.uniui-refresh:before { + content: "\e657"; +} + +.uniui-cloud-upload:before { + content: "\e645"; +} + +.uniui-cloud-download-filled:before { + content: "\e646"; +} + +.uniui-cloud-download:before { + content: "\e647"; +} + +.uniui-cloud-upload-filled:before { + content: "\e648"; +} + +.uniui-redo:before { + content: "\e64a"; +} + +.uniui-images-filled:before { + content: "\e64b"; +} + +.uniui-undo-filled:before { + content: "\e64c"; +} + +.uniui-more:before { + content: "\e64d"; +} + +.uniui-more-filled:before { + content: "\e64e"; +} + +.uniui-undo:before { + content: "\e64f"; +} + +.uniui-images:before { + content: "\e650"; +} + +.uniui-paperclip:before { + content: "\e652"; +} + +.uniui-settings:before { + content: "\e653"; +} + +.uniui-search:before { + content: "\e654"; +} + +.uniui-redo-filled:before { + content: "\e655"; +} + +.uniui-list:before { + content: "\e644"; +} + +.uniui-mail-open-filled:before { + content: "\e63a"; +} + +.uniui-hand-down-filled:before { + content: "\e63c"; +} + +.uniui-hand-down:before { + content: "\e63d"; +} + +.uniui-hand-up-filled:before { + content: "\e63e"; +} + +.uniui-hand-up:before { + content: "\e63f"; +} + +.uniui-heart-filled:before { + content: "\e641"; +} + +.uniui-mail-open:before { + content: "\e643"; +} + +.uniui-heart:before { + content: "\e639"; +} + +.uniui-loop:before { + content: "\e633"; +} + +.uniui-pulldown:before { + content: "\e632"; +} + +.uniui-scan:before { + content: "\e62a"; +} + +.uniui-bars:before { + content: "\e627"; +} + +.uniui-checkbox:before { + content: "\e62b"; +} + +.uniui-checkbox-filled:before { + content: "\e62c"; +} + +.uniui-shop:before { + content: "\e62f"; +} + +.uniui-headphones:before { + content: "\e630"; +} + +.uniui-cart:before { + content: "\e631"; +} diff --git a/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uniicons.ttf b/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uniicons.ttf new file mode 100644 index 0000000..14696d0 Binary files /dev/null and b/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uniicons.ttf differ diff --git a/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uniicons_file.ts b/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uniicons_file.ts new file mode 100644 index 0000000..98e93aa --- /dev/null +++ b/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uniicons_file.ts @@ -0,0 +1,664 @@ + +export type IconsData = { + id : string + name : string + font_family : string + css_prefix_text : string + description : string + glyphs : Array +} + +export type IconsDataItem = { + font_class : string + unicode : string +} + + +export const fontData = [ + { + "font_class": "arrow-down", + "unicode": "\ue6be" + }, + { + "font_class": "arrow-left", + "unicode": "\ue6bc" + }, + { + "font_class": "arrow-right", + "unicode": "\ue6bb" + }, + { + "font_class": "arrow-up", + "unicode": "\ue6bd" + }, + { + "font_class": "auth", + "unicode": "\ue6ab" + }, + { + "font_class": "auth-filled", + "unicode": "\ue6cc" + }, + { + "font_class": "back", + "unicode": "\ue6b9" + }, + { + "font_class": "bars", + "unicode": "\ue627" + }, + { + "font_class": "calendar", + "unicode": "\ue6a0" + }, + { + "font_class": "calendar-filled", + "unicode": "\ue6c0" + }, + { + "font_class": "camera", + "unicode": "\ue65a" + }, + { + "font_class": "camera-filled", + "unicode": "\ue658" + }, + { + "font_class": "cart", + "unicode": "\ue631" + }, + { + "font_class": "cart-filled", + "unicode": "\ue6d0" + }, + { + "font_class": "chat", + "unicode": "\ue65d" + }, + { + "font_class": "chat-filled", + "unicode": "\ue659" + }, + { + "font_class": "chatboxes", + "unicode": "\ue696" + }, + { + "font_class": "chatboxes-filled", + "unicode": "\ue692" + }, + { + "font_class": "chatbubble", + "unicode": "\ue697" + }, + { + "font_class": "chatbubble-filled", + "unicode": "\ue694" + }, + { + "font_class": "checkbox", + "unicode": "\ue62b" + }, + { + "font_class": "checkbox-filled", + "unicode": "\ue62c" + }, + { + "font_class": "checkmarkempty", + "unicode": "\ue65c" + }, + { + "font_class": "circle", + "unicode": "\ue65b" + }, + { + "font_class": "circle-filled", + "unicode": "\ue65e" + }, + { + "font_class": "clear", + "unicode": "\ue66d" + }, + { + "font_class": "close", + "unicode": "\ue673" + }, + { + "font_class": "closeempty", + "unicode": "\ue66c" + }, + { + "font_class": "cloud-download", + "unicode": "\ue647" + }, + { + "font_class": "cloud-download-filled", + "unicode": "\ue646" + }, + { + "font_class": "cloud-upload", + "unicode": "\ue645" + }, + { + "font_class": "cloud-upload-filled", + "unicode": "\ue648" + }, + { + "font_class": "color", + "unicode": "\ue6cf" + }, + { + "font_class": "color-filled", + "unicode": "\ue6c9" + }, + { + "font_class": "compose", + "unicode": "\ue67f" + }, + { + "font_class": "contact", + "unicode": "\ue693" + }, + { + "font_class": "contact-filled", + "unicode": "\ue695" + }, + { + "font_class": "down", + "unicode": "\ue6b8" + }, + { + "font_class": "bottom", + "unicode": "\ue6b8" + }, + { + "font_class": "download", + "unicode": "\ue68d" + }, + { + "font_class": "download-filled", + "unicode": "\ue681" + }, + { + "font_class": "email", + "unicode": "\ue69e" + }, + { + "font_class": "email-filled", + "unicode": "\ue69a" + }, + { + "font_class": "eye", + "unicode": "\ue651" + }, + { + "font_class": "eye-filled", + "unicode": "\ue66a" + }, + { + "font_class": "eye-slash", + "unicode": "\ue6b3" + }, + { + "font_class": "eye-slash-filled", + "unicode": "\ue6b4" + }, + { + "font_class": "fire", + "unicode": "\ue6a1" + }, + { + "font_class": "fire-filled", + "unicode": "\ue6c5" + }, + { + "font_class": "flag", + "unicode": "\ue65f" + }, + { + "font_class": "flag-filled", + "unicode": "\ue660" + }, + { + "font_class": "folder-add", + "unicode": "\ue6a9" + }, + { + "font_class": "folder-add-filled", + "unicode": "\ue6c8" + }, + { + "font_class": "font", + "unicode": "\ue6a3" + }, + { + "font_class": "forward", + "unicode": "\ue6ba" + }, + { + "font_class": "gear", + "unicode": "\ue664" + }, + { + "font_class": "gear-filled", + "unicode": "\ue661" + }, + { + "font_class": "gift", + "unicode": "\ue6a4" + }, + { + "font_class": "gift-filled", + "unicode": "\ue6c4" + }, + { + "font_class": "hand-down", + "unicode": "\ue63d" + }, + { + "font_class": "hand-down-filled", + "unicode": "\ue63c" + }, + { + "font_class": "hand-up", + "unicode": "\ue63f" + }, + { + "font_class": "hand-up-filled", + "unicode": "\ue63e" + }, + { + "font_class": "headphones", + "unicode": "\ue630" + }, + { + "font_class": "heart", + "unicode": "\ue639" + }, + { + "font_class": "heart-filled", + "unicode": "\ue641" + }, + { + "font_class": "help", + "unicode": "\ue679" + }, + { + "font_class": "help-filled", + "unicode": "\ue674" + }, + { + "font_class": "home", + "unicode": "\ue662" + }, + { + "font_class": "home-filled", + "unicode": "\ue663" + }, + { + "font_class": "image", + "unicode": "\ue670" + }, + { + "font_class": "image-filled", + "unicode": "\ue678" + }, + { + "font_class": "images", + "unicode": "\ue650" + }, + { + "font_class": "images-filled", + "unicode": "\ue64b" + }, + { + "font_class": "info", + "unicode": "\ue669" + }, + { + "font_class": "info-filled", + "unicode": "\ue649" + }, + { + "font_class": "left", + "unicode": "\ue6b7" + }, + { + "font_class": "link", + "unicode": "\ue6a5" + }, + { + "font_class": "list", + "unicode": "\ue644" + }, + { + "font_class": "location", + "unicode": "\ue6ae" + }, + { + "font_class": "location-filled", + "unicode": "\ue6af" + }, + { + "font_class": "locked", + "unicode": "\ue66b" + }, + { + "font_class": "locked-filled", + "unicode": "\ue668" + }, + { + "font_class": "loop", + "unicode": "\ue633" + }, + { + "font_class": "mail-open", + "unicode": "\ue643" + }, + { + "font_class": "mail-open-filled", + "unicode": "\ue63a" + }, + { + "font_class": "map", + "unicode": "\ue667" + }, + { + "font_class": "map-filled", + "unicode": "\ue666" + }, + { + "font_class": "map-pin", + "unicode": "\ue6ad" + }, + { + "font_class": "map-pin-ellipse", + "unicode": "\ue6ac" + }, + { + "font_class": "medal", + "unicode": "\ue6a2" + }, + { + "font_class": "medal-filled", + "unicode": "\ue6c3" + }, + { + "font_class": "mic", + "unicode": "\ue671" + }, + { + "font_class": "mic-filled", + "unicode": "\ue677" + }, + { + "font_class": "micoff", + "unicode": "\ue67e" + }, + { + "font_class": "micoff-filled", + "unicode": "\ue6b0" + }, + { + "font_class": "minus", + "unicode": "\ue66f" + }, + { + "font_class": "minus-filled", + "unicode": "\ue67d" + }, + { + "font_class": "more", + "unicode": "\ue64d" + }, + { + "font_class": "more-filled", + "unicode": "\ue64e" + }, + { + "font_class": "navigate", + "unicode": "\ue66e" + }, + { + "font_class": "navigate-filled", + "unicode": "\ue67a" + }, + { + "font_class": "notification", + "unicode": "\ue6a6" + }, + { + "font_class": "notification-filled", + "unicode": "\ue6c1" + }, + { + "font_class": "paperclip", + "unicode": "\ue652" + }, + { + "font_class": "paperplane", + "unicode": "\ue672" + }, + { + "font_class": "paperplane-filled", + "unicode": "\ue675" + }, + { + "font_class": "person", + "unicode": "\ue699" + }, + { + "font_class": "person-filled", + "unicode": "\ue69d" + }, + { + "font_class": "personadd", + "unicode": "\ue69f" + }, + { + "font_class": "personadd-filled", + "unicode": "\ue698" + }, + { + "font_class": "personadd-filled-copy", + "unicode": "\ue6d1" + }, + { + "font_class": "phone", + "unicode": "\ue69c" + }, + { + "font_class": "phone-filled", + "unicode": "\ue69b" + }, + { + "font_class": "plus", + "unicode": "\ue676" + }, + { + "font_class": "plus-filled", + "unicode": "\ue6c7" + }, + { + "font_class": "plusempty", + "unicode": "\ue67b" + }, + { + "font_class": "pulldown", + "unicode": "\ue632" + }, + { + "font_class": "pyq", + "unicode": "\ue682" + }, + { + "font_class": "qq", + "unicode": "\ue680" + }, + { + "font_class": "redo", + "unicode": "\ue64a" + }, + { + "font_class": "redo-filled", + "unicode": "\ue655" + }, + { + "font_class": "refresh", + "unicode": "\ue657" + }, + { + "font_class": "refresh-filled", + "unicode": "\ue656" + }, + { + "font_class": "refreshempty", + "unicode": "\ue6bf" + }, + { + "font_class": "reload", + "unicode": "\ue6b2" + }, + { + "font_class": "right", + "unicode": "\ue6b5" + }, + { + "font_class": "scan", + "unicode": "\ue62a" + }, + { + "font_class": "search", + "unicode": "\ue654" + }, + { + "font_class": "settings", + "unicode": "\ue653" + }, + { + "font_class": "settings-filled", + "unicode": "\ue6ce" + }, + { + "font_class": "shop", + "unicode": "\ue62f" + }, + { + "font_class": "shop-filled", + "unicode": "\ue6cd" + }, + { + "font_class": "smallcircle", + "unicode": "\ue67c" + }, + { + "font_class": "smallcircle-filled", + "unicode": "\ue665" + }, + { + "font_class": "sound", + "unicode": "\ue684" + }, + { + "font_class": "sound-filled", + "unicode": "\ue686" + }, + { + "font_class": "spinner-cycle", + "unicode": "\ue68a" + }, + { + "font_class": "staff", + "unicode": "\ue6a7" + }, + { + "font_class": "staff-filled", + "unicode": "\ue6cb" + }, + { + "font_class": "star", + "unicode": "\ue688" + }, + { + "font_class": "star-filled", + "unicode": "\ue68f" + }, + { + "font_class": "starhalf", + "unicode": "\ue683" + }, + { + "font_class": "trash", + "unicode": "\ue687" + }, + { + "font_class": "trash-filled", + "unicode": "\ue685" + }, + { + "font_class": "tune", + "unicode": "\ue6aa" + }, + { + "font_class": "tune-filled", + "unicode": "\ue6ca" + }, + { + "font_class": "undo", + "unicode": "\ue64f" + }, + { + "font_class": "undo-filled", + "unicode": "\ue64c" + }, + { + "font_class": "up", + "unicode": "\ue6b6" + }, + { + "font_class": "top", + "unicode": "\ue6b6" + }, + { + "font_class": "upload", + "unicode": "\ue690" + }, + { + "font_class": "upload-filled", + "unicode": "\ue68e" + }, + { + "font_class": "videocam", + "unicode": "\ue68c" + }, + { + "font_class": "videocam-filled", + "unicode": "\ue689" + }, + { + "font_class": "vip", + "unicode": "\ue6a8" + }, + { + "font_class": "vip-filled", + "unicode": "\ue6c6" + }, + { + "font_class": "wallet", + "unicode": "\ue6b1" + }, + { + "font_class": "wallet-filled", + "unicode": "\ue6c2" + }, + { + "font_class": "weibo", + "unicode": "\ue68b" + }, + { + "font_class": "weixin", + "unicode": "\ue691" + } +] as IconsDataItem[] + +// export const fontData = JSON.parse(fontDataJson) diff --git a/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js b/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js new file mode 100644 index 0000000..1cd11e1 --- /dev/null +++ b/SmartAssiter/uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js @@ -0,0 +1,649 @@ + +export const fontData = [ + { + "font_class": "arrow-down", + "unicode": "\ue6be" + }, + { + "font_class": "arrow-left", + "unicode": "\ue6bc" + }, + { + "font_class": "arrow-right", + "unicode": "\ue6bb" + }, + { + "font_class": "arrow-up", + "unicode": "\ue6bd" + }, + { + "font_class": "auth", + "unicode": "\ue6ab" + }, + { + "font_class": "auth-filled", + "unicode": "\ue6cc" + }, + { + "font_class": "back", + "unicode": "\ue6b9" + }, + { + "font_class": "bars", + "unicode": "\ue627" + }, + { + "font_class": "calendar", + "unicode": "\ue6a0" + }, + { + "font_class": "calendar-filled", + "unicode": "\ue6c0" + }, + { + "font_class": "camera", + "unicode": "\ue65a" + }, + { + "font_class": "camera-filled", + "unicode": "\ue658" + }, + { + "font_class": "cart", + "unicode": "\ue631" + }, + { + "font_class": "cart-filled", + "unicode": "\ue6d0" + }, + { + "font_class": "chat", + "unicode": "\ue65d" + }, + { + "font_class": "chat-filled", + "unicode": "\ue659" + }, + { + "font_class": "chatboxes", + "unicode": "\ue696" + }, + { + "font_class": "chatboxes-filled", + "unicode": "\ue692" + }, + { + "font_class": "chatbubble", + "unicode": "\ue697" + }, + { + "font_class": "chatbubble-filled", + "unicode": "\ue694" + }, + { + "font_class": "checkbox", + "unicode": "\ue62b" + }, + { + "font_class": "checkbox-filled", + "unicode": "\ue62c" + }, + { + "font_class": "checkmarkempty", + "unicode": "\ue65c" + }, + { + "font_class": "circle", + "unicode": "\ue65b" + }, + { + "font_class": "circle-filled", + "unicode": "\ue65e" + }, + { + "font_class": "clear", + "unicode": "\ue66d" + }, + { + "font_class": "close", + "unicode": "\ue673" + }, + { + "font_class": "closeempty", + "unicode": "\ue66c" + }, + { + "font_class": "cloud-download", + "unicode": "\ue647" + }, + { + "font_class": "cloud-download-filled", + "unicode": "\ue646" + }, + { + "font_class": "cloud-upload", + "unicode": "\ue645" + }, + { + "font_class": "cloud-upload-filled", + "unicode": "\ue648" + }, + { + "font_class": "color", + "unicode": "\ue6cf" + }, + { + "font_class": "color-filled", + "unicode": "\ue6c9" + }, + { + "font_class": "compose", + "unicode": "\ue67f" + }, + { + "font_class": "contact", + "unicode": "\ue693" + }, + { + "font_class": "contact-filled", + "unicode": "\ue695" + }, + { + "font_class": "down", + "unicode": "\ue6b8" + }, + { + "font_class": "bottom", + "unicode": "\ue6b8" + }, + { + "font_class": "download", + "unicode": "\ue68d" + }, + { + "font_class": "download-filled", + "unicode": "\ue681" + }, + { + "font_class": "email", + "unicode": "\ue69e" + }, + { + "font_class": "email-filled", + "unicode": "\ue69a" + }, + { + "font_class": "eye", + "unicode": "\ue651" + }, + { + "font_class": "eye-filled", + "unicode": "\ue66a" + }, + { + "font_class": "eye-slash", + "unicode": "\ue6b3" + }, + { + "font_class": "eye-slash-filled", + "unicode": "\ue6b4" + }, + { + "font_class": "fire", + "unicode": "\ue6a1" + }, + { + "font_class": "fire-filled", + "unicode": "\ue6c5" + }, + { + "font_class": "flag", + "unicode": "\ue65f" + }, + { + "font_class": "flag-filled", + "unicode": "\ue660" + }, + { + "font_class": "folder-add", + "unicode": "\ue6a9" + }, + { + "font_class": "folder-add-filled", + "unicode": "\ue6c8" + }, + { + "font_class": "font", + "unicode": "\ue6a3" + }, + { + "font_class": "forward", + "unicode": "\ue6ba" + }, + { + "font_class": "gear", + "unicode": "\ue664" + }, + { + "font_class": "gear-filled", + "unicode": "\ue661" + }, + { + "font_class": "gift", + "unicode": "\ue6a4" + }, + { + "font_class": "gift-filled", + "unicode": "\ue6c4" + }, + { + "font_class": "hand-down", + "unicode": "\ue63d" + }, + { + "font_class": "hand-down-filled", + "unicode": "\ue63c" + }, + { + "font_class": "hand-up", + "unicode": "\ue63f" + }, + { + "font_class": "hand-up-filled", + "unicode": "\ue63e" + }, + { + "font_class": "headphones", + "unicode": "\ue630" + }, + { + "font_class": "heart", + "unicode": "\ue639" + }, + { + "font_class": "heart-filled", + "unicode": "\ue641" + }, + { + "font_class": "help", + "unicode": "\ue679" + }, + { + "font_class": "help-filled", + "unicode": "\ue674" + }, + { + "font_class": "home", + "unicode": "\ue662" + }, + { + "font_class": "home-filled", + "unicode": "\ue663" + }, + { + "font_class": "image", + "unicode": "\ue670" + }, + { + "font_class": "image-filled", + "unicode": "\ue678" + }, + { + "font_class": "images", + "unicode": "\ue650" + }, + { + "font_class": "images-filled", + "unicode": "\ue64b" + }, + { + "font_class": "info", + "unicode": "\ue669" + }, + { + "font_class": "info-filled", + "unicode": "\ue649" + }, + { + "font_class": "left", + "unicode": "\ue6b7" + }, + { + "font_class": "link", + "unicode": "\ue6a5" + }, + { + "font_class": "list", + "unicode": "\ue644" + }, + { + "font_class": "location", + "unicode": "\ue6ae" + }, + { + "font_class": "location-filled", + "unicode": "\ue6af" + }, + { + "font_class": "locked", + "unicode": "\ue66b" + }, + { + "font_class": "locked-filled", + "unicode": "\ue668" + }, + { + "font_class": "loop", + "unicode": "\ue633" + }, + { + "font_class": "mail-open", + "unicode": "\ue643" + }, + { + "font_class": "mail-open-filled", + "unicode": "\ue63a" + }, + { + "font_class": "map", + "unicode": "\ue667" + }, + { + "font_class": "map-filled", + "unicode": "\ue666" + }, + { + "font_class": "map-pin", + "unicode": "\ue6ad" + }, + { + "font_class": "map-pin-ellipse", + "unicode": "\ue6ac" + }, + { + "font_class": "medal", + "unicode": "\ue6a2" + }, + { + "font_class": "medal-filled", + "unicode": "\ue6c3" + }, + { + "font_class": "mic", + "unicode": "\ue671" + }, + { + "font_class": "mic-filled", + "unicode": "\ue677" + }, + { + "font_class": "micoff", + "unicode": "\ue67e" + }, + { + "font_class": "micoff-filled", + "unicode": "\ue6b0" + }, + { + "font_class": "minus", + "unicode": "\ue66f" + }, + { + "font_class": "minus-filled", + "unicode": "\ue67d" + }, + { + "font_class": "more", + "unicode": "\ue64d" + }, + { + "font_class": "more-filled", + "unicode": "\ue64e" + }, + { + "font_class": "navigate", + "unicode": "\ue66e" + }, + { + "font_class": "navigate-filled", + "unicode": "\ue67a" + }, + { + "font_class": "notification", + "unicode": "\ue6a6" + }, + { + "font_class": "notification-filled", + "unicode": "\ue6c1" + }, + { + "font_class": "paperclip", + "unicode": "\ue652" + }, + { + "font_class": "paperplane", + "unicode": "\ue672" + }, + { + "font_class": "paperplane-filled", + "unicode": "\ue675" + }, + { + "font_class": "person", + "unicode": "\ue699" + }, + { + "font_class": "person-filled", + "unicode": "\ue69d" + }, + { + "font_class": "personadd", + "unicode": "\ue69f" + }, + { + "font_class": "personadd-filled", + "unicode": "\ue698" + }, + { + "font_class": "personadd-filled-copy", + "unicode": "\ue6d1" + }, + { + "font_class": "phone", + "unicode": "\ue69c" + }, + { + "font_class": "phone-filled", + "unicode": "\ue69b" + }, + { + "font_class": "plus", + "unicode": "\ue676" + }, + { + "font_class": "plus-filled", + "unicode": "\ue6c7" + }, + { + "font_class": "plusempty", + "unicode": "\ue67b" + }, + { + "font_class": "pulldown", + "unicode": "\ue632" + }, + { + "font_class": "pyq", + "unicode": "\ue682" + }, + { + "font_class": "qq", + "unicode": "\ue680" + }, + { + "font_class": "redo", + "unicode": "\ue64a" + }, + { + "font_class": "redo-filled", + "unicode": "\ue655" + }, + { + "font_class": "refresh", + "unicode": "\ue657" + }, + { + "font_class": "refresh-filled", + "unicode": "\ue656" + }, + { + "font_class": "refreshempty", + "unicode": "\ue6bf" + }, + { + "font_class": "reload", + "unicode": "\ue6b2" + }, + { + "font_class": "right", + "unicode": "\ue6b5" + }, + { + "font_class": "scan", + "unicode": "\ue62a" + }, + { + "font_class": "search", + "unicode": "\ue654" + }, + { + "font_class": "settings", + "unicode": "\ue653" + }, + { + "font_class": "settings-filled", + "unicode": "\ue6ce" + }, + { + "font_class": "shop", + "unicode": "\ue62f" + }, + { + "font_class": "shop-filled", + "unicode": "\ue6cd" + }, + { + "font_class": "smallcircle", + "unicode": "\ue67c" + }, + { + "font_class": "smallcircle-filled", + "unicode": "\ue665" + }, + { + "font_class": "sound", + "unicode": "\ue684" + }, + { + "font_class": "sound-filled", + "unicode": "\ue686" + }, + { + "font_class": "spinner-cycle", + "unicode": "\ue68a" + }, + { + "font_class": "staff", + "unicode": "\ue6a7" + }, + { + "font_class": "staff-filled", + "unicode": "\ue6cb" + }, + { + "font_class": "star", + "unicode": "\ue688" + }, + { + "font_class": "star-filled", + "unicode": "\ue68f" + }, + { + "font_class": "starhalf", + "unicode": "\ue683" + }, + { + "font_class": "trash", + "unicode": "\ue687" + }, + { + "font_class": "trash-filled", + "unicode": "\ue685" + }, + { + "font_class": "tune", + "unicode": "\ue6aa" + }, + { + "font_class": "tune-filled", + "unicode": "\ue6ca" + }, + { + "font_class": "undo", + "unicode": "\ue64f" + }, + { + "font_class": "undo-filled", + "unicode": "\ue64c" + }, + { + "font_class": "up", + "unicode": "\ue6b6" + }, + { + "font_class": "top", + "unicode": "\ue6b6" + }, + { + "font_class": "upload", + "unicode": "\ue690" + }, + { + "font_class": "upload-filled", + "unicode": "\ue68e" + }, + { + "font_class": "videocam", + "unicode": "\ue68c" + }, + { + "font_class": "videocam-filled", + "unicode": "\ue689" + }, + { + "font_class": "vip", + "unicode": "\ue6a8" + }, + { + "font_class": "vip-filled", + "unicode": "\ue6c6" + }, + { + "font_class": "wallet", + "unicode": "\ue6b1" + }, + { + "font_class": "wallet-filled", + "unicode": "\ue6c2" + }, + { + "font_class": "weibo", + "unicode": "\ue68b" + }, + { + "font_class": "weixin", + "unicode": "\ue691" + } +] + +// export const fontData = JSON.parse(fontDataJson) diff --git a/SmartAssiter/uni_modules/uni-icons/package.json b/SmartAssiter/uni_modules/uni-icons/package.json new file mode 100644 index 0000000..a59acb4 --- /dev/null +++ b/SmartAssiter/uni_modules/uni-icons/package.json @@ -0,0 +1,112 @@ +{ + "id": "uni-icons", + "displayName": "uni-icons 图标", + "version": "2.0.13", + "description": "图标组件,用于展示移动端常见的图标,可自定义颜色、大小。", + "keywords": [ + "uni-ui", + "uniui", + "icon", + "图标" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.2.14", + "uni-app": "^4.08", + "uni-app-x": "^4.61" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue", + "darkmode": "x", + "i18n": "x", + "widescreen": "x" + }, + "uni_modules": { + "dependencies": [ + "uni-scss" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app": { + "vue": { + "vue2": "√", + "vue3": "√" + }, + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "vue": "√", + "nvue": "-", + "android": { + "extVersion": "", + "minVersion": "29" + }, + "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": "29" + }, + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√" + } + } + } + } + } +} \ No newline at end of file diff --git a/SmartAssiter/uni_modules/uni-icons/readme.md b/SmartAssiter/uni_modules/uni-icons/readme.md new file mode 100644 index 0000000..86234ba --- /dev/null +++ b/SmartAssiter/uni_modules/uni-icons/readme.md @@ -0,0 +1,8 @@ +## Icons 图标 +> **组件名:uni-icons** +> 代码块: `uIcons` + +用于展示 icons 图标 。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-icons) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 diff --git a/SmartAssiter/uni_modules/uni-scss/changelog.md b/SmartAssiter/uni_modules/uni-scss/changelog.md new file mode 100644 index 0000000..b863bb0 --- /dev/null +++ b/SmartAssiter/uni_modules/uni-scss/changelog.md @@ -0,0 +1,8 @@ +## 1.0.3(2022-01-21) +- 优化 组件示例 +## 1.0.2(2021-11-22) +- 修复 / 符号在 vue 不同版本兼容问题引起的报错问题 +## 1.0.1(2021-11-22) +- 修复 vue3中scss语法兼容问题 +## 1.0.0(2021-11-18) +- init diff --git a/SmartAssiter/uni_modules/uni-scss/index.scss b/SmartAssiter/uni_modules/uni-scss/index.scss new file mode 100644 index 0000000..1744a5f --- /dev/null +++ b/SmartAssiter/uni_modules/uni-scss/index.scss @@ -0,0 +1 @@ +@import './styles/index.scss'; diff --git a/SmartAssiter/uni_modules/uni-scss/package.json b/SmartAssiter/uni_modules/uni-scss/package.json new file mode 100644 index 0000000..7cc0ccb --- /dev/null +++ b/SmartAssiter/uni_modules/uni-scss/package.json @@ -0,0 +1,82 @@ +{ + "id": "uni-scss", + "displayName": "uni-scss 辅助样式", + "version": "1.0.3", + "description": "uni-sass是uni-ui提供的一套全局样式 ,通过一些简单的类名和sass变量,实现简单的页面布局操作,比如颜色、边距、圆角等。", + "keywords": [ + "uni-scss", + "uni-ui", + "辅助样式" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "JS SDK", + "通用 SDK" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "n", + "联盟": "n" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/SmartAssiter/uni_modules/uni-scss/readme.md b/SmartAssiter/uni_modules/uni-scss/readme.md new file mode 100644 index 0000000..b7d1c25 --- /dev/null +++ b/SmartAssiter/uni_modules/uni-scss/readme.md @@ -0,0 +1,4 @@ +`uni-sass` 是 `uni-ui`提供的一套全局样式 ,通过一些简单的类名和`sass`变量,实现简单的页面布局操作,比如颜色、边距、圆角等。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-sass) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/SmartAssiter/uni_modules/uni-scss/styles/index.scss b/SmartAssiter/uni_modules/uni-scss/styles/index.scss new file mode 100644 index 0000000..ffac4fe --- /dev/null +++ b/SmartAssiter/uni_modules/uni-scss/styles/index.scss @@ -0,0 +1,7 @@ +@import './setting/_variables.scss'; +@import './setting/_border.scss'; +@import './setting/_color.scss'; +@import './setting/_space.scss'; +@import './setting/_radius.scss'; +@import './setting/_text.scss'; +@import './setting/_styles.scss'; diff --git a/SmartAssiter/uni_modules/uni-scss/styles/setting/_border.scss b/SmartAssiter/uni_modules/uni-scss/styles/setting/_border.scss new file mode 100644 index 0000000..12a11c3 --- /dev/null +++ b/SmartAssiter/uni_modules/uni-scss/styles/setting/_border.scss @@ -0,0 +1,3 @@ +.uni-border { + border: 1px $uni-border-1 solid; +} \ No newline at end of file diff --git a/SmartAssiter/uni_modules/uni-scss/styles/setting/_color.scss b/SmartAssiter/uni_modules/uni-scss/styles/setting/_color.scss new file mode 100644 index 0000000..1ededd9 --- /dev/null +++ b/SmartAssiter/uni_modules/uni-scss/styles/setting/_color.scss @@ -0,0 +1,66 @@ + +// TODO 暂时不需要 class ,需要用户使用变量实现 ,如果使用类名其实并不推荐 +// @mixin get-styles($k,$c) { +// @if $k == size or $k == weight{ +// font-#{$k}:#{$c} +// }@else{ +// #{$k}:#{$c} +// } +// } +$uni-ui-color:( + // 主色 + primary: $uni-primary, + primary-disable: $uni-primary-disable, + primary-light: $uni-primary-light, + // 辅助色 + success: $uni-success, + success-disable: $uni-success-disable, + success-light: $uni-success-light, + warning: $uni-warning, + warning-disable: $uni-warning-disable, + warning-light: $uni-warning-light, + error: $uni-error, + error-disable: $uni-error-disable, + error-light: $uni-error-light, + info: $uni-info, + info-disable: $uni-info-disable, + info-light: $uni-info-light, + // 中性色 + main-color: $uni-main-color, + base-color: $uni-base-color, + secondary-color: $uni-secondary-color, + extra-color: $uni-extra-color, + // 背景色 + bg-color: $uni-bg-color, + // 边框颜色 + border-1: $uni-border-1, + border-2: $uni-border-2, + border-3: $uni-border-3, + border-4: $uni-border-4, + // 黑色 + black:$uni-black, + // 白色 + white:$uni-white, + // 透明 + transparent:$uni-transparent +) !default; +@each $key, $child in $uni-ui-color { + .uni-#{"" + $key} { + color: $child; + } + .uni-#{"" + $key}-bg { + background-color: $child; + } +} +.uni-shadow-sm { + box-shadow: $uni-shadow-sm; +} +.uni-shadow-base { + box-shadow: $uni-shadow-base; +} +.uni-shadow-lg { + box-shadow: $uni-shadow-lg; +} +.uni-mask { + background-color:$uni-mask; +} diff --git a/SmartAssiter/uni_modules/uni-scss/styles/setting/_radius.scss b/SmartAssiter/uni_modules/uni-scss/styles/setting/_radius.scss new file mode 100644 index 0000000..9a0428b --- /dev/null +++ b/SmartAssiter/uni_modules/uni-scss/styles/setting/_radius.scss @@ -0,0 +1,55 @@ +@mixin radius($r,$d:null ,$important: false){ + $radius-value:map-get($uni-radius, $r) if($important, !important, null); + // Key exists within the $uni-radius variable + @if (map-has-key($uni-radius, $r) and $d){ + @if $d == t { + border-top-left-radius:$radius-value; + border-top-right-radius:$radius-value; + }@else if $d == r { + border-top-right-radius:$radius-value; + border-bottom-right-radius:$radius-value; + }@else if $d == b { + border-bottom-left-radius:$radius-value; + border-bottom-right-radius:$radius-value; + }@else if $d == l { + border-top-left-radius:$radius-value; + border-bottom-left-radius:$radius-value; + }@else if $d == tl { + border-top-left-radius:$radius-value; + }@else if $d == tr { + border-top-right-radius:$radius-value; + }@else if $d == br { + border-bottom-right-radius:$radius-value; + }@else if $d == bl { + border-bottom-left-radius:$radius-value; + } + }@else{ + border-radius:$radius-value; + } +} + +@each $key, $child in $uni-radius { + @if($key){ + .uni-radius-#{"" + $key} { + @include radius($key) + } + }@else{ + .uni-radius { + @include radius($key) + } + } +} + +@each $direction in t, r, b, l,tl, tr, br, bl { + @each $key, $child in $uni-radius { + @if($key){ + .uni-radius-#{"" + $direction}-#{"" + $key} { + @include radius($key,$direction,false) + } + }@else{ + .uni-radius-#{$direction} { + @include radius($key,$direction,false) + } + } + } +} diff --git a/SmartAssiter/uni_modules/uni-scss/styles/setting/_space.scss b/SmartAssiter/uni_modules/uni-scss/styles/setting/_space.scss new file mode 100644 index 0000000..3c89528 --- /dev/null +++ b/SmartAssiter/uni_modules/uni-scss/styles/setting/_space.scss @@ -0,0 +1,56 @@ + +@mixin fn($space,$direction,$size,$n) { + @if $n { + #{$space}-#{$direction}: #{$size*$uni-space-root}px + } @else { + #{$space}-#{$direction}: #{-$size*$uni-space-root}px + } +} +@mixin get-styles($direction,$i,$space,$n){ + @if $direction == t { + @include fn($space, top,$i,$n); + } + @if $direction == r { + @include fn($space, right,$i,$n); + } + @if $direction == b { + @include fn($space, bottom,$i,$n); + } + @if $direction == l { + @include fn($space, left,$i,$n); + } + @if $direction == x { + @include fn($space, left,$i,$n); + @include fn($space, right,$i,$n); + } + @if $direction == y { + @include fn($space, top,$i,$n); + @include fn($space, bottom,$i,$n); + } + @if $direction == a { + @if $n { + #{$space}:#{$i*$uni-space-root}px; + } @else { + #{$space}:#{-$i*$uni-space-root}px; + } + } +} + +@each $orientation in m,p { + $space: margin; + @if $orientation == m { + $space: margin; + } @else { + $space: padding; + } + @for $i from 0 through 16 { + @each $direction in t, r, b, l, x, y, a { + .uni-#{$orientation}#{$direction}-#{$i} { + @include get-styles($direction,$i,$space,true); + } + .uni-#{$orientation}#{$direction}-n#{$i} { + @include get-styles($direction,$i,$space,false); + } + } + } +} \ No newline at end of file diff --git a/SmartAssiter/uni_modules/uni-scss/styles/setting/_styles.scss b/SmartAssiter/uni_modules/uni-scss/styles/setting/_styles.scss new file mode 100644 index 0000000..689afec --- /dev/null +++ b/SmartAssiter/uni_modules/uni-scss/styles/setting/_styles.scss @@ -0,0 +1,167 @@ +/* #ifndef APP-NVUE */ + +$-color-white:#fff; +$-color-black:#000; +@mixin base-style($color) { + color: #fff; + background-color: $color; + border-color: mix($-color-black, $color, 8%); + &:not([hover-class]):active { + background: mix($-color-black, $color, 10%); + border-color: mix($-color-black, $color, 20%); + color: $-color-white; + outline: none; + } +} +@mixin is-color($color) { + @include base-style($color); + &[loading] { + @include base-style($color); + &::before { + margin-right:5px; + } + } + &[disabled] { + &, + &[loading], + &:not([hover-class]):active { + color: $-color-white; + border-color: mix(darken($color,10%), $-color-white); + background-color: mix($color, $-color-white); + } + } + +} +@mixin base-plain-style($color) { + color:$color; + background-color: mix($-color-white, $color, 90%); + border-color: mix($-color-white, $color, 70%); + &:not([hover-class]):active { + background: mix($-color-white, $color, 80%); + color: $color; + outline: none; + border-color: mix($-color-white, $color, 50%); + } +} +@mixin is-plain($color){ + &[plain] { + @include base-plain-style($color); + &[loading] { + @include base-plain-style($color); + &::before { + margin-right:5px; + } + } + &[disabled] { + &, + &:active { + color: mix($-color-white, $color, 40%); + background-color: mix($-color-white, $color, 90%); + border-color: mix($-color-white, $color, 80%); + } + } + } +} + + +.uni-btn { + margin: 5px; + color: #393939; + border:1px solid #ccc; + font-size: 16px; + font-weight: 200; + background-color: #F9F9F9; + // TODO 暂时处理边框隐藏一边的问题 + overflow: visible; + &::after{ + border: none; + } + + &:not([type]),&[type=default] { + color: #999; + &[loading] { + background: none; + &::before { + margin-right:5px; + } + } + + + + &[disabled]{ + color: mix($-color-white, #999, 60%); + &, + &[loading], + &:active { + color: mix($-color-white, #999, 60%); + background-color: mix($-color-white,$-color-black , 98%); + border-color: mix($-color-white, #999, 85%); + } + } + + &[plain] { + color: #999; + background: none; + border-color: $uni-border-1; + &:not([hover-class]):active { + background: none; + color: mix($-color-white, $-color-black, 80%); + border-color: mix($-color-white, $-color-black, 90%); + outline: none; + } + &[disabled]{ + &, + &[loading], + &:active { + background: none; + color: mix($-color-white, #999, 60%); + border-color: mix($-color-white, #999, 85%); + } + } + } + } + + &:not([hover-class]):active { + color: mix($-color-white, $-color-black, 50%); + } + + &[size=mini] { + font-size: 16px; + font-weight: 200; + border-radius: 8px; + } + + + + &.uni-btn-small { + font-size: 14px; + } + &.uni-btn-mini { + font-size: 12px; + } + + &.uni-btn-radius { + border-radius: 999px; + } + &[type=primary] { + @include is-color($uni-primary); + @include is-plain($uni-primary) + } + &[type=success] { + @include is-color($uni-success); + @include is-plain($uni-success) + } + &[type=error] { + @include is-color($uni-error); + @include is-plain($uni-error) + } + &[type=warning] { + @include is-color($uni-warning); + @include is-plain($uni-warning) + } + &[type=info] { + @include is-color($uni-info); + @include is-plain($uni-info) + } +} +/* #endif */ diff --git a/SmartAssiter/uni_modules/uni-scss/styles/setting/_text.scss b/SmartAssiter/uni_modules/uni-scss/styles/setting/_text.scss new file mode 100644 index 0000000..a34d08f --- /dev/null +++ b/SmartAssiter/uni_modules/uni-scss/styles/setting/_text.scss @@ -0,0 +1,24 @@ +@mixin get-styles($k,$c) { + @if $k == size or $k == weight{ + font-#{$k}:#{$c} + }@else{ + #{$k}:#{$c} + } +} + +@each $key, $child in $uni-headings { + /* #ifndef APP-NVUE */ + .uni-#{$key} { + @each $k, $c in $child { + @include get-styles($k,$c) + } + } + /* #endif */ + /* #ifdef APP-NVUE */ + .container .uni-#{$key} { + @each $k, $c in $child { + @include get-styles($k,$c) + } + } + /* #endif */ +} diff --git a/SmartAssiter/uni_modules/uni-scss/styles/setting/_variables.scss b/SmartAssiter/uni_modules/uni-scss/styles/setting/_variables.scss new file mode 100644 index 0000000..557d3d7 --- /dev/null +++ b/SmartAssiter/uni_modules/uni-scss/styles/setting/_variables.scss @@ -0,0 +1,146 @@ +// @use "sass:math"; +@import '../tools/functions.scss'; +// 间距基础倍数 +$uni-space-root: 2 !default; +// 边框半径默认值 +$uni-radius-root:5px !default; +$uni-radius: () !default; +// 边框半径断点 +$uni-radius: map-deep-merge( + ( + 0: 0, + // TODO 当前版本暂时不支持 sm 属性 + // 'sm': math.div($uni-radius-root, 2), + null: $uni-radius-root, + 'lg': $uni-radius-root * 2, + 'xl': $uni-radius-root * 6, + 'pill': 9999px, + 'circle': 50% + ), + $uni-radius +); +// 字体家族 +$body-font-family: 'Roboto', sans-serif !default; +// 文本 +$heading-font-family: $body-font-family !default; +$uni-headings: () !default; +$letterSpacing: -0.01562em; +$uni-headings: map-deep-merge( + ( + 'h1': ( + size: 32px, + weight: 300, + line-height: 50px, + // letter-spacing:-0.01562em + ), + 'h2': ( + size: 28px, + weight: 300, + line-height: 40px, + // letter-spacing: -0.00833em + ), + 'h3': ( + size: 24px, + weight: 400, + line-height: 32px, + // letter-spacing: normal + ), + 'h4': ( + size: 20px, + weight: 400, + line-height: 30px, + // letter-spacing: 0.00735em + ), + 'h5': ( + size: 16px, + weight: 400, + line-height: 24px, + // letter-spacing: normal + ), + 'h6': ( + size: 14px, + weight: 500, + line-height: 18px, + // letter-spacing: 0.0125em + ), + 'subtitle': ( + size: 12px, + weight: 400, + line-height: 20px, + // letter-spacing: 0.00937em + ), + 'body': ( + font-size: 14px, + font-weight: 400, + line-height: 22px, + // letter-spacing: 0.03125em + ), + 'caption': ( + 'size': 12px, + 'weight': 400, + 'line-height': 20px, + // 'letter-spacing': 0.03333em, + // 'text-transform': false + ) + ), + $uni-headings +); + + + +// 主色 +$uni-primary: #2979ff !default; +$uni-primary-disable:lighten($uni-primary,20%) !default; +$uni-primary-light: lighten($uni-primary,25%) !default; + +// 辅助色 +// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。 +$uni-success: #18bc37 !default; +$uni-success-disable:lighten($uni-success,20%) !default; +$uni-success-light: lighten($uni-success,25%) !default; + +$uni-warning: #f3a73f !default; +$uni-warning-disable:lighten($uni-warning,20%) !default; +$uni-warning-light: lighten($uni-warning,25%) !default; + +$uni-error: #e43d33 !default; +$uni-error-disable:lighten($uni-error,20%) !default; +$uni-error-light: lighten($uni-error,25%) !default; + +$uni-info: #8f939c !default; +$uni-info-disable:lighten($uni-info,20%) !default; +$uni-info-light: lighten($uni-info,25%) !default; + +// 中性色 +// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。 +$uni-main-color: #3a3a3a !default; // 主要文字 +$uni-base-color: #6a6a6a !default; // 常规文字 +$uni-secondary-color: #909399 !default; // 次要文字 +$uni-extra-color: #c7c7c7 !default; // 辅助说明 + +// 边框颜色 +$uni-border-1: #F0F0F0 !default; +$uni-border-2: #EDEDED !default; +$uni-border-3: #DCDCDC !default; +$uni-border-4: #B9B9B9 !default; + +// 常规色 +$uni-black: #000000 !default; +$uni-white: #ffffff !default; +$uni-transparent: rgba($color: #000000, $alpha: 0) !default; + +// 背景色 +$uni-bg-color: #f7f7f7 !default; + +/* 水平间距 */ +$uni-spacing-sm: 8px !default; +$uni-spacing-base: 15px !default; +$uni-spacing-lg: 30px !default; + +// 阴影 +$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5) !default; +$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default; +$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5) !default; + +// 蒙版 +$uni-mask: rgba($color: #000000, $alpha: 0.4) !default; diff --git a/SmartAssiter/uni_modules/uni-scss/styles/tools/functions.scss b/SmartAssiter/uni_modules/uni-scss/styles/tools/functions.scss new file mode 100644 index 0000000..ac6f63e --- /dev/null +++ b/SmartAssiter/uni_modules/uni-scss/styles/tools/functions.scss @@ -0,0 +1,19 @@ +// 合并 map +@function map-deep-merge($parent-map, $child-map){ + $result: $parent-map; + @each $key, $child in $child-map { + $parent-has-key: map-has-key($result, $key); + $parent-value: map-get($result, $key); + $parent-type: type-of($parent-value); + $child-type: type-of($child); + $parent-is-map: $parent-type == map; + $child-is-map: $child-type == map; + + @if (not $parent-has-key) or ($parent-type != $child-type) or (not ($parent-is-map and $child-is-map)){ + $result: map-merge($result, ( $key: $child )); + }@else { + $result: map-merge($result, ( $key: map-deep-merge($parent-value, $child) )); + } + } + @return $result; +}; diff --git a/SmartAssiter/uni_modules/uni-scss/theme.scss b/SmartAssiter/uni_modules/uni-scss/theme.scss new file mode 100644 index 0000000..80ee62f --- /dev/null +++ b/SmartAssiter/uni_modules/uni-scss/theme.scss @@ -0,0 +1,31 @@ +// 间距基础倍数 +$uni-space-root: 2; +// 边框半径默认值 +$uni-radius-root:5px; +// 主色 +$uni-primary: #2979ff; +// 辅助色 +$uni-success: #4cd964; +// 警告色 +$uni-warning: #f0ad4e; +// 错误色 +$uni-error: #dd524d; +// 描述色 +$uni-info: #909399; +// 中性色 +$uni-main-color: #303133; +$uni-base-color: #606266; +$uni-secondary-color: #909399; +$uni-extra-color: #C0C4CC; +// 背景色 +$uni-bg-color: #f5f5f5; +// 边框颜色 +$uni-border-1: #DCDFE6; +$uni-border-2: #E4E7ED; +$uni-border-3: #EBEEF5; +$uni-border-4: #F2F6FC; + +// 常规色 +$uni-black: #000000; +$uni-white: #ffffff; +$uni-transparent: rgba($color: #000000, $alpha: 0); diff --git a/SmartAssiter/uni_modules/uni-scss/variables.scss b/SmartAssiter/uni_modules/uni-scss/variables.scss new file mode 100644 index 0000000..1c062d4 --- /dev/null +++ b/SmartAssiter/uni_modules/uni-scss/variables.scss @@ -0,0 +1,62 @@ +@import './styles/setting/_variables.scss'; +// 间距基础倍数 +$uni-space-root: 2; +// 边框半径默认值 +$uni-radius-root:5px; + +// 主色 +$uni-primary: #2979ff; +$uni-primary-disable:mix(#fff,$uni-primary,50%); +$uni-primary-light: mix(#fff,$uni-primary,80%); + +// 辅助色 +// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。 +$uni-success: #18bc37; +$uni-success-disable:mix(#fff,$uni-success,50%); +$uni-success-light: mix(#fff,$uni-success,80%); + +$uni-warning: #f3a73f; +$uni-warning-disable:mix(#fff,$uni-warning,50%); +$uni-warning-light: mix(#fff,$uni-warning,80%); + +$uni-error: #e43d33; +$uni-error-disable:mix(#fff,$uni-error,50%); +$uni-error-light: mix(#fff,$uni-error,80%); + +$uni-info: #8f939c; +$uni-info-disable:mix(#fff,$uni-info,50%); +$uni-info-light: mix(#fff,$uni-info,80%); + +// 中性色 +// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。 +$uni-main-color: #3a3a3a; // 主要文字 +$uni-base-color: #6a6a6a; // 常规文字 +$uni-secondary-color: #909399; // 次要文字 +$uni-extra-color: #c7c7c7; // 辅助说明 + +// 边框颜色 +$uni-border-1: #F0F0F0; +$uni-border-2: #EDEDED; +$uni-border-3: #DCDCDC; +$uni-border-4: #B9B9B9; + +// 常规色 +$uni-black: #000000; +$uni-white: #ffffff; +$uni-transparent: rgba($color: #000000, $alpha: 0); + +// 背景色 +$uni-bg-color: #f7f7f7; + +/* 水平间距 */ +$uni-spacing-sm: 8px; +$uni-spacing-base: 15px; +$uni-spacing-lg: 30px; + +// 阴影 +$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5); +$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2); +$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5); + +// 蒙版 +$uni-mask: rgba($color: #000000, $alpha: 0.4); diff --git a/mcm-ddc-ble/MDK-ARM/Objects/ExtDll.iex b/mcm-ddc-ble/MDK-ARM/Objects/ExtDll.iex new file mode 100644 index 0000000..6c0896e --- /dev/null +++ b/mcm-ddc-ble/MDK-ARM/Objects/ExtDll.iex @@ -0,0 +1,2 @@ +[EXTDLL] +Count=0