分类 未分类 下的文章

核心名 带外围名称 CPU类型 无线类型
ESP8285 ESP8266 Xtensa 2.4G WIFI
ESP8684 ESP32-C2 RISC-V 2.4G WIFI+BLE5
ESP8686/8685 ESP32-C3 RISC-V 2.4G WIFI+BLE5
-- ESP32-C5 RISC-V 2.4G/5G WIFI6+BLE5
-- ESP32-C6 RISC-V 2.4G WIFI6+BLE5+IEEE 802.15.4
-- ESP32-C61 RISC-V 2.4G WIFI6+BLE5
-- ESP32-P4 RISC-V
-- ESP32-H2 RISC-V BLE5+IEEE 802.15.4
-- ESP32-S2 Xtensa 2.4G WIFI
-- ESP32-S3 Xtensa 2.4G WIFI+BLE5
-- ESP32 Xtensa 2.4G WIFI+BLE
系列名称 功能
P系列 片上温感,jpeg编解码,isp(图像信号控制器如摄像头),camera,
H系列 片上温感
C系列 片上温感
S系列 触摸,片上温感,SDIO, DAC
32系列 SPI,SDIO, DAC

通用功能(包括P/H/C/S/32系列):

  • Sigma Delta DAC
  • SPI
  • RMT红外遥控(C2不支持)
  • PCNT计数器
  • mcpwm电机PWM(C2不支持)
  • LEDC(即LED PWM控制)
  • LCD屏幕,支持I2C/i80/mipi_dsi/rgb_panel/spi.
  • I2S(C2不支持)
  • I2C
  • GPIO
  • 软I2C/软SPI/软Uart
  • ADC

似乎比较靠谱的是mongoDB atlas和google的firebase. mongoDB atlas也是基于azure aws google的云服务的,估计需要选择其一。google的firebase在国内官网居然是可以访问的,但就是无法登录购买。这意思是google部分服务国内可用?
如果在腾讯云阿里云上正儿八经的买个数据库服务,起步价一年最少都是好几千的。
我的需求是本地使用indexedDB存储,并且与云端同步。似乎pouchDB https://pouchdb.com/ 很好满足这个任务,因为他号称可以与couchDB同步,并且本地是使用indexedDB存储的。
其他简化indexedDB的js库包括

下载: https://github.com/filebrowser/filebrowser
文档: https://filebrowser.org/
以前一直用npm库http-server做简单的文件浏览, 以及远程下载, 也http-server也支持https, 对于页面测试是足够了. 不过最近有远程查看图片的需求, 想先看是哪些再下载. 本来用cursor写了一个python版本的, 无奈需求略显复杂以后就有一些bug和效率问题. 想看网上有没有现成的, 觉得这应该是一个广泛需求, 就找到了filebrowser.
文档里的安装其实不如直接下载更方便, 但文档里的配置可以看下.
下载后, 在当前目录运行时, 会自动新建.db文件, 直接访问http://127.0.0.1:8080就可以 ,用户名密码默认都是admin
但此时不允许外部地址访问, 运行filebrowser config set -a 0.0.0.0就可以了. config存在db文件里.
注意, filebrowser 会在运行的每一个目录都生成db文件, 所以直接运行在根目录就好了.
db文件似乎不会存储文件索引之类的信息, 更不会存储缩略图, 所以比文件比较多,图片比较多的文件夹访问比较慢. 我的手机相机文件夹打开大概就需要20秒

优点不说了, 需要说说这个AI助手的主要问题. 大模型使用迄今编程最强的claude 3.5 sonnet

  1. 中文有时候会转成乱码, 尤其在一个对话回合比较多了以后.
  2. cursor默认使用英文作为界面语言, 在一个回合中翻译成中文后,后面做界面修改时,又会写成英文. 并且会呈现不理解中文含义的状态, 已经有的但是中文标识的按键,它会再重复添加一个英文的.
  3. 已经弃用的代码并不一定会删除, 有时候需要明确的指令告诉它删除无用的代码.
  4. 虽然简单的应用中cursor可以一路无脑点确定, 但是如果让cursor代码进行优化重构的时候, 留下的垃圾代码容易在下一个回合中干扰cursor,所以严谨的项目还是应该阅读其代码后再确定.
  5. cursor虽然具有多文件同步编辑的能力, 但是似乎不足以解决复杂的架构问题, 所以似乎还是应该提前进行模块化和模块解耦,让cursor工作在一个模块中, 准确度会更高.

参考https://www.bilibili.com/video/BV1p7H1eyEuY/?spm_id_from=333.788&vd_source=a24e9520e198932372f0c014624cafa4
传统编程,要分工前端后端, 做需求做系统架构, 编码测试.
而Cursor编程, 是做项目提示词.cursorrules, 通过composerCtrl+I创建项目结构, 从基础需求从简单到复杂一步步用自然语言表述, 并转换为代码. 对不满意的部分逐步添加细节.
参考上面教程里的 Cursor 五步

  1. 项目提示词.cursorrules, 建立项目的编程规范, 项目架构, 参考文档等, 自然语言描述
  2. 创建项目和多文件编写 composerCtrl+ICtrl+Shift+I
  3. 单文件修改 Chat
  4. 部分文件内容修改 Ctrl+K
  5. 编写中提示Tab
    另外提到一个前端AI编写工具 https://v0.dev

Cursor试用非常震撼。我第一次用就想尝试一下一直以来想做的一个东西: 就是通过Chrome浏览器来用蓝牙BLE连接设备。之前在b站上看过Chrome浏览器支持蓝牙的视频, 这对我来说是一个完全不熟悉的新的API。然后就尝试用cursor, 看他的能力如何。竟然从无到有。一两个小时的时间完全编写了一个蓝牙BLE的调试器。从HTML到css到JavaScript。从书写逻辑到页面布局。
看看Cursor的成果吧! BLE Scanner
浏览器去调试BLE设备有一个缺陷, 就是要搜寻的服务必须预先定义。

官方文档
首先需要用openssl创建证书. windows环境下如果找不到openssl, 可以进入bash创建后再exit

openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem

创建中问好几个name,其中问到common name的时候,要填写127.0.0.1, 其他时候用默认值就可以. 完成后在本目录下会生成证书文件
启动https服务:

http-server -S -C cert.pem

鼠标手一般被叫做腕管综合症。这主要是因为鼠标用的是食指和中指,这两个手指的神经是由腕管中的神经来控制。而我的症状则不一样,是小拇指和无名指。我感觉是因为小拇指的负担太重,打键盘太多导致的。小拇指要负责非常多的按键,再加上enter键这个用很多的按键。
因此我想未来鼠标和键盘可能会被淘汰。即使是在生产力工具中,例如笔记本电脑和台式机。因为鼠标和键盘会带来的身体的健康问题,对手指神经的危害,未来势必会被其他的输入方式所取代。一种方式就是语音输入。但是语音输入的准确度不高,并且无法进行中英文混合的准确输入。这对日常办公打中文倒是没有太大问题,然而对于编码的话就问题比较大,还有就是干扰周围的人,并且对噪音非常敏感。还有一种方式就是通过思维。马斯克正在做的脑机接口如果能应用于普通人,则是一个伟大的进步。现在的脑机接口必须嵌入式植入一个芯片在大脑皮层上面。嗯未来如果要普通人使用,则不能用这种侵入式的方式。
尺神经受压通常指的是尺神经综合征(Ulnar Nerve Compression Syndrome),又称肘管综合征(Cubital Tunnel Syndrome)。它是由尺神经在肘部或前臂受压迫造成的,常见症状和相关治疗方法如下:

症状

  1. 麻木与刺痛

    • 小拇指及无名指的麻木或刺痛感,尤其是在手肘屈曲时更为明显。
  2. 疼痛

    • 肘部内侧或前臂内侧可能感到疼痛,通常伴随刺痛感。
  3. 握力减弱

    • 在抓握物体时,可能会发现握力减弱,影响抓握能力。
  4. 运动障碍

    • 有时可能出现手指协调能力下降,例如难以完成细致的动作(如扣扣子)。
  5. 肌肉萎缩

    • 长期受压可导致小拇指根部或无名指根部肌肉萎缩,使得该区域看上去凹陷。

治疗方法

  1. 非手术治疗

    • 休息:避免重复性的手部和肘部活动。
    • 物理治疗:使用拉伸和加强练习,以恢复正常功能。物理治疗师可以帮助设计个性化的锻炼方案。
    • 护具:佩戴护具(如肘托)以保持肘部在良好的位置,特别是在夜间。
    • 药物治疗:使用非处方的消炎药,如布洛芬等,可以缓解疼痛和炎症。
  2. 手术治疗

    • 如果非手术治疗效果不佳且症状持续加重,医生可能建议进行手术。常见的手术方式包括:
      • 尺神经减压术:通过释放对尺神经的压迫。
      • 尺神经转位术:将尺神经移动到一个更少受压迫的位置。

注意事项

如果您有上述症状,请尽快就医,以便进行专业评估并制定相应的治疗计划。尽早干预有助于改善预后。

虽然尺神经受压和腱鞘炎(Tenosynovitis)都是与手部和腕部相关的疾病,但它们是不同的状况。

尺神经受压

  • 定义:尺神经受压通常指的是尺神经在肘部或前臂位置受到压迫,导致其通行受阻,并引发一系列症状,如麻木、刺痛和疼痛。
  • 症状主要包括:小拇指和无名指的麻木感、掌握力减弱以及肘部内侧疼痛等。

腱鞘炎

  • 定义:腱鞘炎是指肌腱周围的腱鞘发生炎症,导致滑动不畅和疼痛。常见于重复性运动或过度使用导致的肌腱疲劳。
  • 症状主要包括:局部疼痛(通常是在手腕或手指部位)、肿胀、触摸时有压痛感,以及活动受限。

主要区别

  • 病因:尺神经受压通常是由于解剖结构问题(如肘部位的压迫),而腱鞘炎通常与过度使用或外伤有关。
  • 出现位置:尺神经受压症状集中在小拇指和无名指,而腱鞘炎则多集中在关节处,如手腕、拇指根部等。

总结

总之,这两种病症虽有相似之处,但应根据具体的症状及体检结果进行区分。如有疑问或出现相关症状,请咨询医生以获得准确诊断和治疗建议。

一种是页面json不变, 使用css属性position: fixed;,同时规定css属性 top bottom left right.
第二种是可以在页面的json文件中写: "disableScroll": true, 然后再元素上使用css属性position: absolute;,再规定css属性top bottom left right. 这种方式是结合recycle-view来用的, 原因太久不太记得了, 似乎因为Recycle-view的一些特性导致position:fixed不能正常固定或页面不能正常滚动.

进入一个页面有几种方式: navigateTo,navigateBack, 熄屏再显示(从其他应用切换回来也是类似).
GPT给的建议是通过app.globalData存储全局变量来判断, 或者用storage存储判断, 感觉都不优雅, 把单个页面问题的解决扩展到了全局去了. 但目前还没有其他好办法, 因为navigateBack和熄屏再显示都是触发onHide,所以无法区分.
GPT还给了一个幻觉建议, 说onShow会带形参进来,判断形参就可以, 但事实上onShow不会带任何形参
通过navigateTo,navigateBack从其他页面导航到这个页面, 通常都需要更新页面数据. 因为上一页导航过来, 这一页的数据还没有生成过; 从下一页返回, 可能在下一页对数据做了更新.

因为CSS的flex每次用的时候总是忘记, 这儿总结一下:
将flex理解为一个画框(父元素)里面的元素(子元素)的排列.
父元素设置整体遵从的规则, 子元素设置个别规则.
总体规格包括:

  • flex-direction: 确定主轴方向. 元素是沿着主轴方向依次放置的. 交叉轴(副轴)即主轴的垂直轴, 确定一行主轴放不下的元素,下一行放的方向.
  • flex-wrap: 元素在主轴放不下, 要不要回车放在下一行.
  • justify-content: 确定元素主轴排列, 以及元素间距
  • align-items: 确定副轴的元素排列
  • align-content: 确定wrap后多行的元素怎么放置,以及行间距等.

银行送了10元京东E卡,进了京东APP完全找不到绑定的地方, 搜索兰搜索京东E卡也只是给了一堆的卖E卡的连接. 说是付款的时候京东自营商品可用, 然后下了好几个单,专门找京东自营,充话费,京东超市和京喜自营买纸巾,付款的时候都没有选择E卡的界面. 找了一圈只好问机器客服, 这才找到绑卡的地方: 我的钱包-点击查看全部-礼品卡,此时注意不要点那个大大的京东E卡图标, 而要点击绑定新卡! 然后输入卡的密码, 只用输入密码, 而不用输入卡号, 虽然给了卡号但并没有什么用.
然后, 我去找下单但没有付款的链接, 还是不能选择! 接着下新单, 也没有选E卡的地方! 我勒个去, 赶快百度找下经验, 发现百度经验这个下单支付界面怎么和我不一样? 终于发现了, 原来从购物车购买,点击去结算才有这个选礼品卡(京东卡/E卡)订单界面, 这个订单界面选择地址, 查看订单详情,选择开票等等.
从商品链接直接购买真的不能选择吗? 我又研究了下,发现不是的!商品链接里面直接点击下单购买(这个名字在不同的商品链接还不一样, 有的叫到手价购买, 有的叫立即购买预估), 这时候不会转到新页面,而是从下方弹出来一个半屏, 上面显示收货地址,选择商品数量和规格, 平常这个时候就选"支付"了, 但注意, 这个半屏是可以上滑的! 你如果上滑一点点, 发现滑出来的只是其他商品的广告, 但如果继续上滑, 才出现商品价格详情,支付方式,发票,留言...等等这些订单信息, 全部堆在这个半屏的最下面, 跟从购物车结算的订单界面是类似的. 但是等等, 为什么礼品卡的选择消失了? 哈哈,这时候还要去选择商品金额栏目里的四个灰色小字:展开更多,才看得到优惠券礼品卡两个选项,不得不说比购物车结算订单界面要隐蔽得多.
京东这个界面设计真的很滑头, 其逻辑是想方设法让你别用券直接支付, 让你确认地址/品类/数量这些最重要信息后就直接付款, 这样 一方面京东拿到最多的钱, 另一方面你买了以后即使想到有优惠券, 也只能下次用了, 又引诱你下次继续在京东购物. 之前几次买东西找不到开票的地方也是这个原因.

引用自定义组件

组件名 组件json-> 页面json-> 页面wxml
mycomp.js/.json/.wxml/.css {"component": true,usingComponents": {}} {"usingComponents": {"mycomp": "../../components/mycomp/mycomp"}} <mycomp></mycomp>

组件展现页面的数据和自身的数据

没有区分. 如

`Component({
  properties: {propFromPage: 1},
  data: {propFromData: 2}
})

在组件wxml中都是同样方式引用{{propFromPage}} {{propFromData}}. 但在组件js中引用方式有区分:this.properties.propFromPage, this.data.propFromData

调用函数

组件中的函数引用方式在js中不需要加methods, 如

`Component({
  properties:{prop:{type: Number, value:1, observer(newValue, oldValue){this.f1();}}},
  lifetimes:{attached(){this.f1();}},
  methods: {
    f1(){},
    f2(){this.f1()},
  }
})

页面向组件传数据

页面js-> 页面wxml-> 页面wxml 组件js
Page({data:{pageprop: 1}}) <mycomp prop="{{pageprop}}"></mycomp> {{prop}} Component({ properties: {prop: 默认值}, methods:{f(){this.properties.prop },}})

组件向页面传数据(通过事件)

组件js-> 页面wxml-> 页面js
Component({method:{f(){this.triggerEvent('someevent', somevalue) },}}) <mycomp bind:someevent="processIt"></mycomp> processIt(e){let value = e.detail}

GPT4o给了三种方法, 在后面给出. 第三种方法是改赋值=为调用函数, 意味着所有的赋值都要改为函数调用,不实用. 分析下方法一Proxy和方法二getter/setter.
Proxy的好处是不会改变原对象,而是建立了一个新的proxy对象, 对新对象的赋值会触发set函数. 问题有三个: 一是任何键值变化都会触发set,需要自己判断键值; 二是需要将原对象的名称指向新proxy对象, 才能实现无感. 三是无法通过instanceof判断pr一个实例是不是Proxy的实例, 只能自己在创建实例时添加一个自定义key如_isProxy来判断.(后面段落说明)

getter/setter是最早一代vue.js用的方法, 所有在app.data中的键都会用Object.defineProperty()的get/set重定义一遍, 做到自动更新UI. 好处是可以逐个key定义,不影响其他key, 问题是原key值就会消失,除非定义前先保存一下. 并且需要保存到另一个键下面(一般前面加个下划线_)而不能保存在本key下面, 否则会导致反复触发直到调用栈溢出Maximum call stack size exceeded.
这个话题在vue的官方文档中也有讨论: https://cn.vuejs.org/guide/extras/reactivity-in-depth, 里面也提到在vue3中,从getter/setter已经转向了proxy机制.
另外一个问题, 是如果键值是一个对象(数组也是),也就是下面有子键, 对子键键值的修改, 只会调用get而不是set!并且对Proxy和getter/setter机制都一样. 如:

proxyObject.k = {k1:1} // 调用set
proxyObject.k.k1 = 2 //调用get

怎么解决这个问题,GPT4o的回答是递归代理. 见后面一节.

方法1: 使用Proxy

在 JavaScript 中,如果希望在对象的某个键变化时自动调用一个函数,可以使用 Proxy 对象来实现。Proxy 允许你创建一个代理对象,以便对其属性的访问和修改进行拦截。

下面是一个使用 Proxy 的示例代码,当对象的 someKey 属性变化时,会自动调用函数 a

// 定义要监控的对象
const targetObject = {
    someKey: '初始值',
};

// 定义要自动调用的函数
function a(newValue) {
    console.log(`someKey 的新值是: ${newValue}`);
}

// 创建 Proxy 代理对象
const proxyObject = new Proxy(targetObject, {
    set(target, property, value) {
        // 当设置 someKey 时,调用函数 a
        if (property === 'someKey') {
            a(value);
        }
        // 修改属性的值
        target[property] = value;
        // 返回 true 表示设置成功
        return true;
    }
});

// 测试:修改 someKey
proxyObject.someKey = '新的值'; // 这将输出: someKey 的新值是: 新的值

// 可以继续修改其他属性也不会触发 a()
proxyObject.anotherKey = '另一个值';
console.log(proxyObject.anotherKey); // 输出: 另一个值

说明:

  • 在上面的示例中,创建了一个对象 targetObject 和一个函数 a
  • 使用 Proxy 创建了代理对象 proxyObject,并在其 set 拦截器中定义了逻辑。当 someKey 被修改时,会自动调用函数 a(newValue)
  • 修改其他属性不会触发这个函数,只会在指定的键发生变化时执行。

通过这种方式,你可以灵活地监控对象属性的变化,并根据需要触发相应的逻辑。

方法2 使用getter/setter

你可以使用 JavaScript 的 Object.defineProperty() 方法来定义 getter 和 setter,使得每次修改对象属性时都能执行相应的函数。

const targetObject = {
    _someKey: '初始值',  // 私有变量
};

// 定义要自动调用的函数
function a(newValue) {
    console.log(`someKey 的新值是: ${newValue}`);
}

// 使用 Object.defineProperty 定义 getter 和 setter
Object.defineProperty(targetObject, 'someKey', {
    get() {
        return this._someKey;
    },
    set(value) {
        this._someKey = value;
        a(value);  // 调用函数 a
    },
});

// 测试:修改 someKey
targetObject.someKey = '新的值'; // 这将输出: someKey 的新值是: 新的值

// 读取 someKey
console.log(targetObject.someKey); // 输出: 新的值

// 可以继续修改其他属性也不会触发 a()
targetObject.anotherKey = '另一个值';
console.log(targetObject.anotherKey); // 输出: undefined (因为没有定义)

方法3:使用自定义方法

另一种简单的方法是创建一个用于更新对象属性的方法,以便所有更改都通过此方法发生,这样就可以在里面调用相关逻辑。

const targetObject = {
    someKey: '初始值',
};

// 定义要自动调用的函数
function a(newValue) {
    console.log(`someKey 的新值是: ${newValue}`);
}

// 自定义方法用于更新键并执行相应逻辑
function setSomeKey(value) {
    targetObject.someKey = value;   // 更新属性
    a(value);                       // 调用函数 a
}

// 测试:修改 someKey
setSomeKey('新的值'); // 这将输出: someKey 的新值是: 新的值

// 读取 someKey
console.log(targetObject.someKey); // 输出: 新的值

// 修改其他属性的方法不变,不会触发函数 a()
targetObject.anotherKey = '另一个值';
console.log(targetObject.anotherKey); // 输出: 另一个值

如何知道实例是Proxy的实例

在 JavaScript 中,可以使用 Proxy 对象来创建代理,从而控制对某个对象的基本操作,如属性查找、赋值、枚举、函数调用等。如果想要判断一个对象是否是一个 Proxy 实例,可以通过以下方法:

  1. 利用 WeakMap: 在将对象包装为 Proxy 时,可以使用 WeakMap 来存储原始对象和其代理之间的关系。通过这种方式,你可以检查某个代理是否存在于这个 WeakMap 中。

     const originalObject = {};
     const proxyMap = new WeakMap();
    
     const proxy = new Proxy(originalObject, {
         set(target, prop, value) {
             return Reflect.set(target, prop, value);
         }
     });
    
     // 存入 WeakMap
     proxyMap.set(proxy, originalObject);
    
     function isProxy(obj) {
         return proxyMap.has(obj);
     }
    
     console.log(isProxy(proxy)); // true
     console.log(isProxy(originalObject)); // false
  2. 标记法: 你可以在创建 Proxy 时在其内部设置一个标记属性,以便后续判断。

     const originalObject = {};
    
     const proxy = new Proxy(originalObject, {
         get(target, prop) {
             if (prop === '_isProxy') return true;
             return Reflect.get(target, prop);
         }
     });
    
     function isProxy(obj) {
         return obj && obj._isProxy === true;
     }
    
     console.log(isProxy(proxy)); // true
     console.log(isProxy(originalObject)); // false
  3. 使用 instanceof 关键字: 不幸的是,直接通过 instanceof 来判断是否为 Proxy 是不可能的,因为我们无法直接访问 Proxy 的构造函数。

总的来说,上述方法是较常见的判断一个对象是否为 Proxy 的方案。选择适合自己需求的方法来实现即可。

递归代理

要解决object下的key是对象或者数组, 其子键或者子元素被修改时调用的是get而不是set的问题, GPTo答复是递归代理. 下面给出的是对象的递归代理, 实际还需要考虑数组.
递归代理是对了,但是GPT4o在第一次给出的代码中并没有实现递归代理,指出后,在第二次实现了递归代理, 但设置深层子元素的时候调用了2次set(重复代理了),指出问题后, GPT4o再修改了6次, 也没能回答正确. 感觉是黔驴技穷啦. 这儿贴出其中一次的代码.

function createReactiveObject(obj) {
    return new Proxy(obj, {
        set(target, property, value) {
            // 检查设置的值是否为对象
            if (typeof value === 'object' && value !== null) {
                // 用 Proxy 处理该对象
                value = createReactiveObject(value);
            }
            // 设置实际值
            target[property] = value;
            console.log(`属性 ${property} 被设置为: ${value}`); // 输出变更信息
            return true; // 返回成功标识
        },
        get(target, property) {
            const value = target[property];
            // 仅当获取的是对象时才返回新的代理,避免递归代理产生重复
            if (typeof value === 'object' && value !== null) {
                return createReactiveObject(value); // 利用递归返回新的代理而不是多次创建同一对象的代理
            }
            return value;
        }
    });
}

// 创建动态对象并使用代理函数包裹它
const dynamicObject = createReactiveObject({
    k1: { k2: { k3: {} } } // 初始化嵌套结构 
});

// 测试:设置嵌套属性
dynamicObject.k1.k2.k3.k4 = 20; // 应只输出一次: 属性 k4 被设置为: 20

// 打印查看当前状态
console.log(dynamicObject);