xy 和 pdd 上好像有很多美版的,要便宜好几千,这种机子有问题吗?是怎么回事?老哥们有靠谱店铺推荐一下更好

最近想买一个笔记本做开发机,刷到闲鱼的 es 工程机好便宜,大概知道可能有 bug ,看网上评论踩雷的不多。

联想小新 pro 14 imh9 2024 款的,处理器是 u7 155h ,R7 8845h ,32+512 配置的价格 2900

我现在每天回家就是打游戏 以前还打打联盟 现在沉迷手游和平暖暖 越打越上头
以前还偶尔学习一下 但现在每天八点半到家 打到十点半十一点 然后直接睡觉

某考公做题软件是收藏的历年真题、考点解答之类的
他们应该是收集后人工清洗了一遍。
本来是付费的,结果他们的软件贼卡贼难用,反馈客服只会让我清理电脑。我就想着看能不能把我付费的科目保存下来,
结果发现他们其余课程接口只做了前端鉴权。。。就顺手都扒了下来

这玩意有版权吗?我直接搞个 web 不收费共享人类没毛病吧?还是说我使用最好再自己混编一遍?

上个月底写的,类似扫雷的小游戏:
https://blog.mybatis.io/post/hormuz-game

🚢 游戏背景
霍尔木兹海峡是波斯湾通往阿拉伯海的咽喉要道,全球约 20%的石油经此运输。如今,海峡水域布满水雷,你的任务是驾驶货轮找到一条安全航道,成功穿越封锁。

🎮 操作方法
左键点击水域格子:探测该区域,如果安全则显示周围水雷数量,货轮将自动沿新开辟的安全路径前进。
右键点击水域格子:标记/取消标记疑似水雷位置(旗帜标记)。
数字提示周围 8 格中的水雷数量,利用这些信息判断安全路径。
货轮将自动沿从左到右的最短安全路径行进,到达右侧出口即为胜利。
⚙️ 难度说明
低( 35 枚水雷):水雷稀疏,安全区域宽阔,适合熟悉游戏机制。偶尔有导弹威胁。
中( 55 枚水雷):标准难度,需要谨慎探测才能找到安全航道。导弹频率提高。
高( 80 枚水雷):水雷密布,右侧出口受限,导弹追踪更密集,考验快速判断能力。



地址: https://blog.mybatis.io/post/hormuz-game

这是 github👈
chrome 应用商店上找要么是要注册和 AI 结合的过于复杂的,要么是只能记录单页的。
都不太符合我的要求,所以花了几分钟 vibe 了一个,使用几天感觉还不错,对于轻量化记录来说还挺方便的doge

该说不说 4.7 这审美真不错,我就丢了个 PRD 给他,生成的 UI 看着真不赖

前情回顾:

一年多没联系的大学女同学突然找我帮忙办事,重新联系了起来。随后我们连续约了三次见面:从吃饭散步,到小酌聊天,再到去她家放包、一起唱 K 。相处过程轻松自然,彼此不排斥,甚至有一些靠近的信号,但我始终犹豫,没有迈出更进一步的肢体接触。
具体可以看:https://www.v2ex.com/t/1201054


后续:

这个后续,我想我拖了很久。其实不太愿意交代后面的事情,但想到上一次有很多兄弟在等更新,还是写一下。

在写这个后续之前,我也陷入了大思考。现在的相处方式,真的已经变得这么浮躁了吗?

从上次唱 K 结束之后,我们中间一直都有联系。但期间有三个小插曲,让我感到不太舒服。


第一个:

月初某个工作日,我约她吃饭。因为我在越秀,她在番禺,我就说让她来越秀。我的考虑是,如果她没上班,过来可以早点吃饭;而我下班再去番禺,可能要一个多小时。

我说:那你来越秀,请你吃饭。
她回:会有惊喜吗?最近喜欢阿玛尼的兰岩草香水。

我的大小姐,这也太直接了吧。

这让我“肥肠虾头”。我不太清楚别的男生怎么想,但在我看来,如果关系自然推进,我是知道该怎么做的;
但这种直接表达,会让我不太舒服。

最后的结果是,我还是去了番禺和她吃饭。


第二个:

有一次我在上班。可能是我平时比较常找她聊天,她就觉得我比较闲,于是让我帮她做 PPT 。她最近在面试,可能需要试讲。

她问我:闲着吗?闲着用 AI 帮我做个 PPT 。
我还没答应,她就已经把资料发过来了。

我的大小姐,我在上班诶,给我的感觉就是我有点被当成工具人。

后来我想想也没什么,就用豆包帮她做了一个 PPT 。
做完之后我打趣说:帮你这么大忙,不得请我吃顿饭?


第三个:

因为我帮她做了 PPT ,所以我提出让她请我吃饭。于是我们又去了番禺。

但这顿“她请的饭”,过程让我非常难受。

我们到了一个商场,她问我吃什么。我说可以先逛一逛。
到了负一楼,她带我逛了很久。我说楼上好像有家烤鱼,可以吃烤鱼。她看了美团,说有优惠券,于是我们上楼。

上去之后她说,这个优惠券中午才能用,晚上不能用。
我内心 OS:那我大概明白了。

我说那就换一家。于是又走了很久。
最后我提议吃粥底火锅,这个我之前一直很想吃,还没吃过。

她说这个贵,而且没什么东西吃的。
来回拉扯了很久,整个过程都很纠结。

最后我直接说:就吃这个吧,我想吃这个,我请你。

结果,这顿本来说好她请的饭,最后还是变成我请她。

在这之前,我们一共吃了六次饭、两次唱 K ,都是我买单。我觉得她请我吃一顿,并不过分。

我也在想,可能是她现在待业,所以比较节俭。但整个过程带给我的感受确实不太好。我认为,情绪价值也是一种价值,不应该因为一些优惠的问题反复拉扯,这样其实挺不体面的。


基于以上三个插曲,其实让我很不舒服,我逐渐意识到,我们大概率走不到一起。
可能是消费观不太合得来?但我再往下想,可能不完全是这个原因。

更核心的是,在一些具体互动里,我感受到自己被放在一个相对次的位置
与其说是消费观不一致,我更看重人本身和相处体验,而她在一些场景下表现得相对功利,这让我很不舒服。


来吧,我想评论区应该会出现很多乌龟表情包,会有很多评论“龟男”。
其实我确实应该在兰岩草的那个点,就不再和她继续了,但我不确定兰岩草这个点她是不是开玩笑,所以才有了后面的相处,后面发生的事更加确定了她不是我需要找的人。

唉,就这样吧。

现在的相处方式真的很浮躁。

但我应该不会因为这个女生,就对下一段关系提前带上偏见。
每一个出现在我人生里的女孩,我依然会认真对待。

老妈已经退休 4 年了,单身,退休之前是护士,退休工资对于生活来说完全足够。
但老妈常常想找点挣钱的事情做,尝试过卖保健品,体制内上班惯了,不擅长销售,效果不好,反而产生挫败感。
前段时间还去美容院帮人按摩,体力劳动消耗太大,会引起劳损,累死累活一天也就几十块,商量下来还是不做了。

其实老妈退休算是有钱有闲,一年可能会和姐妹出去 3 ~ 4 次的自驾旅行,每周 2 节老年大学声乐课程,但还是一直想赚点钱。
在我打工人的角度,这是我向往的生活,而老妈退休了整天还想着打工。
分析下来,老妈没有伴侣,也没有想找伴侣,我也没有孙子给她带,缺乏一起消磨时间的伴侣、消磨时间的兴趣爱好,总之可能是缺乏一种正反馈或者说是价值感吧。

不知道各位 V 友有没有适合退休人士做的事情或者是兴趣爱好,最好满足:
1.时间自由,因为经常也要出去旅行
2.能挣点小钱最好,不能挣的话,能够有益身心健康也可以
3.不是重体力劳动

我想了下,炒股竟然能满足以上条件,但是有门槛,不放心老妈去尝试。

想做大型协作性项目,不想让 AI 偏离设计文档,所以要上一个 spec 系统做唯一的真相来源,网上不少推荐 openspec+superpowers ,都是说 openspec 做 proposal 跟 archive ,让 superpowers 做实现,但是没一个说到底怎么提示的。 我的疑问是,openspec 的 proposal 会生成 proposal.mddesing.md 以及 task.md ,但是 superpower 的 brainstorm 以哪个为来源,他本身也会生成 spec.md ,plan.md ,这两个是不是需要合并到 openspec 生成的一些列文档中去。

网上不少的说明都每说这个事情,有人尝试两个一起用的么?

一、系统概述

本程序使用VC++ MFC对话框实现PC与单片机的串口通信,支持数据发送、接收、显示和存储功能,适用于工业控制、数据采集等场景。

二、实现原理

2.1 通信架构

graph LR
    A[PC端VC++程序] --串口--> B[51单片机]
    B --串口--> A
    A --> C[用户界面]
    C --> A

2.2 数据流

  1. PC发送控制命令到单片机
  2. 单片机执行命令并返回数据
  3. PC接收数据并显示在界面
  4. 用户可保存数据或发送新命令

三、完整实现代码

3.1 资源文件定义 (Resource.h)

#define IDD_SERIAL_COMM_DIALOG       102
#define IDC_MSCOMM1                  1001
#define IDC_COMBO_COM_PORT           1002
#define IDC_COMBO_BAUD_RATE          1003
#define IDC_BUTTON_OPEN              1004
#define IDC_BUTTON_CLOSE             1005
#define IDC_EDIT_SEND                1006
#define IDC_BUTTON_SEND              1007
#define IDC_EDIT_RECEIVE             1008
#define IDC_CHECK_HEX_SEND           1009
#define IDC_CHECK_HEX_RECEIVE         1010
#define IDC_BUTTON_CLEAR             1011
#define IDC_BUTTON_SAVE              1012
#define IDC_STATIC_STATUS            1013
#define IDC_COMBO_DATA_BITS          1014
#define IDC_COMBO_PARITY             1015
#define IDC_COMBO_STOP_BITS          1016

3.2 对话框类头文件 (SerialCommDlg.h)

#if !defined(AFX_SERIALCOMMDLG_H__)
#define AFX_SERIALCOMMDLG_H__

#include <afxwin.h>
#include <afxdisp.h>
#include <afxcmn.h>
#include <mscomm.h>

class CSerialCommDlg : public CDialog {
public:
    CSerialCommDlg(CWnd* pParent = NULL);
    enum { IDD = IDD_SERIAL_COMM_DIALOG };
    
protected:
    virtual void DoDataExchange(CDataExchange* pDX);
    virtual BOOL OnInitDialog();
    
    // 控件变量
    CMSComm m_mscom;
    CComboBox m_comboComPort;
    CComboBox m_comboBaudRate;
    CComboBox m_comboDataBits;
    CComboBox m_comboParity;
    CComboBox m_comboStopBits;
    CEdit m_editSend;
    CEdit m_editReceive;
    CButton m_checkHexSend;
    CButton m_checkHexReceive;
    CStatic m_staticStatus;
    
    // 消息处理
    afx_msg void OnButtonOpen();
    afx_msg void OnButtonClose();
    afx_msg void OnButtonSend();
    afx_msg void OnButtonClear();
    afx_msg void OnButtonSave();
    afx_msg void OnCommMscomm();
    afx_msg void OnClose();
    
    // 辅助函数
    void UpdateStatus(LPCTSTR msg);
    void ProcessReceivedData();
    CString FormatHexData(const BYTE* data, int len);
    void AddToReceiveBox(LPCTSTR str);
    
    DECLARE_MESSAGE_MAP()
    DECLARE_EVENTSINK_MAP()
};

#endif

3.3 对话框实现文件 (SerialCommDlg.cpp)

#include "stdafx.h"
#include "SerialComm.h"
#include "SerialCommDlg.h"
#include <afxmt.h>
#include <fstream>

// 事件映射
BEGIN_EVENTSINK_MAP(CSerialCommDlg, CDialog)
    ON_EVENT(CSerialCommDlg, IDC_MSCOMM1, 1, OnCommMscomm, VTS_NONE)
END_EVENTSINK_MAP()

// 消息映射
BEGIN_MESSAGE_MAP(CSerialCommDlg, CDialog)
    ON_BN_CLICKED(IDC_BUTTON_OPEN, OnButtonOpen)
    ON_BN_CLICKED(IDC_BUTTON_CLOSE, OnButtonClose)
    ON_BN_CLICKED(IDC_BUTTON_SEND, OnButtonSend)
    ON_BN_CLICKED(IDC_BUTTON_CLEAR, OnButtonClear)
    ON_BN_CLICKED(IDC_BUTTON_SAVE, OnButtonSave)
    ON_WM_CLOSE()
END_MESSAGE_MAP()

// 构造函数
CSerialCommDlg::CSerialCommDlg(CWnd* pParent) : CDialog(CSerialCommDlg::IDD, pParent) {
    m_mscom.m_bAutoSize = TRUE;
}

// 数据交换
void CSerialCommDlg::DoDataExchange(CDataExchange* pDX) {
    CDialog::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_MSCOMM1, m_mscom);
    DDX_Control(pDX, IDC_COMBO_COM_PORT, m_comboComPort);
    DDX_Control(pDX, IDC_COMBO_BAUD_RATE, m_comboBaudRate);
    DDX_Control(pDX, IDC_COMBO_DATA_BITS, m_comboDataBits);
    DDX_Control(pDX, IDC_COMBO_PARITY, m_comboParity);
    DDX_Control(pDX, IDC_COMBO_STOP_BITS, m_comboStopBits);
    DDX_Control(pDX, IDC_EDIT_SEND, m_editSend);
    DDX_Control(pDX, IDC_EDIT_RECEIVE, m_editReceive);
    DDX_Control(pDX, IDC_CHECK_HEX_SEND, m_checkHexSend);
    DDX_Control(pDX, IDC_CHECK_HEX_RECEIVE, m_checkHexReceive);
    DDX_Control(pDX, IDC_STATIC_STATUS, m_staticStatus);
}

// 初始化对话框
BOOL CSerialCommDlg::OnInitDialog() {
    CDialog::OnInitDialog();
    
    // 初始化串口列表
    for (int i = 1; i <= 16; i++) {
        CString str;
        str.Format(_T("COM%d"), i);
        m_comboComPort.AddString(str);
    }
    m_comboComPort.SetCurSel(0);
    
    // 初始化波特率列表
    CString baudRates[] = {_T("1200"), _T("2400"), _T("4800"), 
                          _T("9600"), _T("19200"), _T("38400"),
                          _T("57600"), _T("115200")};
    for (int i = 0; i < 8; i++) {
        m_comboBaudRate.AddString(baudRates[i]);
    }
    m_comboBaudRate.SetCurSel(3); // 默认9600
    
    // 初始化数据位列表
    CString dataBits[] = {_T("5"), _T("6"), _T("7"), _T("8")};
    for (int i = 0; i < 4; i++) {
        m_comboDataBits.AddString(dataBits[i]);
    }
    m_comboDataBits.SetCurSel(3); // 默认8位
    
    // 初始化校验位列表
    CString parity[] = {_T("无"), _T("奇校验"), _T("偶校验")};
    for (int i = 0; i < 3; i++) {
        m_comboParity.AddString(parity[i]);
    }
    m_comboParity.SetCurSel(0); // 默认无校验
    
    // 初始化停止位列表
    CString stopBits[] = {_T("1"), _T("1.5"), _T("2")};
    for (int i = 0; i < 3; i++) {
        m_comboStopBits.AddString(stopBits[i]);
    }
    m_comboStopBits.SetCurSel(0); // 默认1位
    
    // 初始化MSComm控件
    if (!m_mscom.Create(NULL, WS_VISIBLE | WS_CHILD, CRect(0,0,0,0), this, IDC_MSCOMM1)) {
        AfxMessageBox(_T("无法创建MSComm控件!"));
        return FALSE;
    }
    
    // 设置默认参数
    m_mscom.SetCommPort(1);       // 默认COM1
    m_mscom.SetSettings(_T("9600,n,8,1")); // 默认参数
    m_mscom.SetInputMode(1);      // 二进制模式
    m_mscom.SetRThreshold(1);     // 每接收1个字符触发事件
    m_mscom.SetSThreshold(0);     // 不触发发送事件
    m_mscom.SetPortOpen(FALSE);   // 初始关闭
    
    UpdateStatus(_T("就绪"));
    
    return TRUE;
}

// 打开串口
void CSerialCommDlg::OnButtonOpen() {
    CString strCom, strBaud, strDataBits, strParity, strStopBits;
    m_comboComPort.GetWindowText(strCom);
    m_comboBaudRate.GetWindowText(strBaud);
    m_comboDataBits.GetWindowText(strDataBits);
    m_comboParity.GetWindowText(strParity);
    m_comboStopBits.GetWindowText(strStopBits);
    
    int nCom = _ttoi(strCom.Mid(3));
    
    // 转换校验位
    CString parityCode;
    if (strParity == _T("无")) parityCode = _T("n");
    else if (strParity == _T("奇校验")) parityCode = _T("o");
    else if (strParity == _T("偶校验")) parityCode = _T("e");
    else parityCode = _T("n");
    
    CString strSettings;
    strSettings.Format(_T("%s,%s,%s,%s"), strBaud, parityCode, strDataBits, strStopBits);
    
    // 设置串口参数
    if (m_mscom.GetPortOpen()) {
        m_mscom.SetPortOpen(FALSE);
    }
    
    m_mscom.SetCommPort(nCom);
    m_mscom.SetSettings(strSettings);
    
    // 打开串口
    if (!m_mscom.GetPortOpen()) {
        if (m_mscom.SetPortOpen(TRUE)) {
            UpdateStatus(_T("串口已打开: ") + strCom + _T(" ") + strSettings);
            GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(FALSE);
            GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(TRUE);
        } else {
            AfxMessageBox(_T("无法打开串口!"));
        }
    }
}

// 关闭串口
void CSerialCommDlg::OnButtonClose() {
    if (m_mscom.GetPortOpen()) {
        m_mscom.SetPortOpen(FALSE);
        UpdateStatus(_T("串口已关闭"));
        GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(TRUE);
        GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(FALSE);
    }
}

// 发送数据
void CSerialCommDlg::OnButtonSend() {
    if (!m_mscom.GetPortOpen()) {
        AfxMessageBox(_T("串口未打开!"));
        return;
    }
    
    CString strSend;
    m_editSend.GetWindowText(strSend);
    if (strSend.IsEmpty()) {
        return;
    }
    
    // 十六进制发送处理
    if (m_checkHexSend.GetCheck()) {
        CString strHex = strSend;
        strHex.Remove(' ');
        strHex.Remove('-');
        
        if (strHex.GetLength() % 2 != 0) {
            AfxMessageBox(_T("HEX数据长度必须为偶数!"));
            return;
        }
        
        int len = strHex.GetLength() / 2;
        BYTE* data = new BYTE[len];
        ZeroMemory(data, len);
        
        for (int i = 0; i < len; i++) {
            CString byteStr = strHex.Mid(i*2, 2);
            data[i] = (BYTE)strtol(byteStr, NULL, 16);
        }
        
        COleVariant var((BYTE*)data, len);
        m_mscom.SetOutput(var);
        delete[] data;
        
        AddToReceiveBox(_T("[发送] ") + FormatHexData(data, len));
    } 
    // 文本发送
    else {
        COleVariant var(strSend);
        m_mscom.SetOutput(var);
        AddToReceiveBox(_T("[发送] ") + strSend);
    }
    
    UpdateStatus(_T("数据已发送"));
}

// 清空接收区
void CSerialCommDlg::OnButtonClear() {
    m_editReceive.SetWindowText(_T(""));
}

// 保存数据
void CSerialCommDlg::OnButtonSave() {
    CString strData;
    m_editReceive.GetWindowText(strData);
    if (strData.IsEmpty()) {
        AfxMessageBox(_T("接收区为空!"));
        return;
    }
    
    CFileDialog dlg(FALSE, _T("txt"), _T("serial_data.txt"), 
                   OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
                   _T("文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*||"));
    
    if (dlg.DoModal() == IDOK) {
        CStdioFile file;
        if (file.Open(dlg.GetPathName(), CFile::modeCreate | CFile::modeWrite)) {
            file.WriteString(strData);
            file.Close();
            UpdateStatus(_T("数据已保存: ") + dlg.GetFileName());
        } else {
            AfxMessageBox(_T("保存文件失败!"));
        }
    }
}

// 关闭窗口
void CSerialCommDlg::OnClose() {
    if (m_mscom.GetPortOpen()) {
        m_mscom.SetPortOpen(FALSE);
    }
    CDialog::OnClose();
}

// 串口事件处理
void CSerialCommDlg::OnCommMscomm() {
    VARIANT variant_inp;
    COleSafeArray safearray_inp;
    LONG len, k;
    BYTE rxdata[2048]; // 接收缓冲区
    
    switch (m_mscom.GetCommEvent()) {
    case 2: // 接收事件
        variant_inp = m_mscom.GetInput();
        safearray_inp = variant_inp;
        len = safearray_inp.GetOneDimSize();
        
        // 数据复制到缓冲区
        for (k = 0; k < len; k++) {
            safearray_inp.GetElement(&k, rxdata + k);
        }
        
        // 处理接收到的数据
        ProcessReceivedData(rxdata, len);
        break;
        
    case 4: // 发送事件
        UpdateStatus(_T("数据发送完成"));
        break;
        
    case 5: // 错误事件
        UpdateStatus(_T("通信错误!"));
        break;
        
    default:
        break;
    }
}

// 处理接收数据
void CSerialCommDlg::ProcessReceivedData(BYTE* data, int len) {
    CString strDisplay;
    
    // 十六进制显示
    if (m_checkHexReceive.GetCheck()) {
        strDisplay = FormatHexData(data, len);
    } 
    // 文本显示
    else {
        // 转换为CString
        data[len] = '\0';
        strDisplay = CString((char*)data);
    }
    
    AddToReceiveBox(_T("[接收] ") + strDisplay);
    UpdateStatus(_T("收到数据"));
}

// 格式化十六进制数据
CString CSerialCommDlg::FormatHexData(const BYTE* data, int len) {
    CString strHex;
    for (int i = 0; i < len; i++) {
        CString byteStr;
        byteStr.Format(_T("%02X "), data[i]);
        strHex += byteStr;
        
        if ((i+1) % 16 == 0) {
            strHex += _T("\r\n");
        }
    }
    return strHex;
}

// 添加到接收框
void CSerialCommDlg::AddToReceiveBox(LPCTSTR str) {
    CString strOld;
    m_editReceive.GetWindowText(strOld);
    if (!strOld.IsEmpty()) {
        strOld += _T("\r\n");
    }
    m_editReceive.SetWindowText(strOld + str);
    
    // 滚动到最后一行
    int nLength = m_editReceive.GetWindowTextLength();
    m_editReceive.SetSel(nLength, nLength);
    m_editReceive.ReplaceSel(_T(""));
}

// 更新状态
void CSerialCommDlg::UpdateStatus(LPCTSTR msg) {
    m_staticStatus.SetWindowText(msg);
}

3.4 应用程序类 (SerialCommApp.cpp)

#include "stdafx.h"
#include "SerialComm.h"
#include "SerialCommDlg.h"

BEGIN_MESSAGE_MAP(CSerialCommApp, CWinApp)
    ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()

CSerialCommApp::CSerialCommApp() {
    m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;
}

CSerialCommApp theApp;

BOOL CSerialCommApp::InitInstance() {
    INITCOMMONCONTROLSEX InitCtrls;
    InitCtrls.dwSize = sizeof(InitCtrls);
    InitCtrls.dwICC = ICC_WIN95_CLASSES;
    InitCommonControlsEx(&InitCtrls);
    
    CWinApp::InitInstance();
    
    CSerialCommDlg dlg;
    m_pMainWnd = &dlg;
    dlg.DoModal();
    
    return FALSE;
}

四、单片机端示例代码 (C51)

4.1 串口初始化

#include <reg52.h>

#define FOSC 11059200L      // 晶振频率
#define BAUD 9600           // 波特率

void UART_Init() {
    SCON = 0x50;            // 8位数据,可变波特率
    TMOD |= 0x20;           // 定时器1工作方式2
    TH1 = TL1 = -(FOSC/12/32/BAUD); // 设置波特率
    TR1 = 1;                // 启动定时器1
    EA = 1;                 // 开总中断
    ES = 1;                 // 开串口中断
}

void UART_SendByte(unsigned char dat) {
    SBUF = dat;
    while(!TI);
    TI = 0;
}

void UART_SendString(char *s) {
    while(*s) {
        UART_SendByte(*s++);
    }
}

4.2 主程序逻辑

unsigned char receivedData[32];
unsigned char receiveCount = 0;

void main() {
    UART_Init();
    while(1) {
        // 主循环可以添加其他任务
    }
}

// 串口中断服务函数
void UART_ISR() interrupt 4 {
    if(RI) {
        RI = 0; // 清除接收中断标志
        
        unsigned char dat = SBUF;
        
        // 简单协议:收到0x01返回温度数据
        if(dat == 0x01) {
            // 模拟温度数据:25.5℃
            UART_SendByte(0x25); // 整数部分
            UART_SendByte(0x50); // 小数部分
        }
        // 收到0x02返回状态信息
        else if(dat == 0x02) {
            UART_SendString("System OK\r\n");
        }
        // 其他数据回显
        else {
            UART_SendByte(dat);
        }
    }
}

参考代码 利用串口和单片机进行通讯,VC用对话框实现 www.youwenfan.com/contentsfa123975.html

五、使用说明

5.1 系统要求

  • Windows XP/7/10/11
  • Visual C++ 6.0 或更高版本
  • MSComm控件 (mscomm32.ocx)

5.2 安装步骤

  1. 注册MSComm控件:

    regsvr32 mscomm32.ocx
  2. 在VC++项目中:

    • 添加mscomm.h和mscomm.cpp到项目
    • 在stdafx.h中添加:

      #include <mscomm.h>
      #pragma comment(lib, "mscomm.lib")

5.3 操作流程

  1. 选择串口号和通信参数(波特率、数据位、校验位、停止位)
  2. 点击"打开串口"按钮
  3. 在发送区输入数据(文本或HEX格式)
  4. 点击"发送"按钮
  5. 接收区显示返回数据
  6. 使用"清空"、"保存"按钮管理数据

5.4 通信协议示例

方向数据说明
PC→MCU0x01请求温度数据
MCU→PC0x25 0x50温度数据(25.5℃)
PC→MCU0x02请求状态信息
MCU→PC"System OK"状态信息
PC→MCU其他回显数据

六、常见问题解决

6.1 无法打开串口

  • 检查串口号是否正确
  • 确认没有其他程序占用串口
  • 检查串口线连接是否正常
  • 尝试降低波特率

6.2 数据乱码

  • 检查波特率是否匹配
  • 确认数据位、校验位、停止位设置一致
  • 检查是否有干扰源
  • 尝试使用较低的波特率

6.3 接收数据不完整

  • 增加接收缓冲区大小
  • 检查RThreshold设置(建议1-10)
  • 使用轮询方式补充接收
  • 检查硬件流控制设置

七、扩展功能

7.1 添加数据校验

// 在发送数据前添加校验和
BYTE checksum = 0;
for (int i = 0; i < len; i++) {
    checksum ^= data[i];
}
// 将校验和附加到数据末尾

7.2 添加自动发送功能

// 在OnInitDialog中添加定时器
SetTimer(1, 1000, NULL); // 1秒定时

// 添加定时器处理
void CSerialCommDlg::OnTimer(UINT_PTR nIDEvent) {
    if (nIDEvent == 1 && m_mscom.GetPortOpen()) {
        // 自动发送请求
        BYTE cmd = 0x01;
        COleVariant var(cmd);
        m_mscom.SetOutput(var);
    }
    CDialog::OnTimer(nIDEvent);
}

7.3 添加数据绘图功能

// 添加绘图控件
#include <ChartCtrl.h> // 使用第三方图表控件

// 在接收数据中更新图表
CChartCtrl m_chart;
m_chart.AddPoint(x, y); // 添加新数据点
m_chart.Invalidate();

八、总结

本程序使用VC++ MFC对话框实现了PC与单片机的串口通信,具有以下特点:

  1. 完整的通信功能

    • 支持多种串口参数配置
    • 支持文本和十六进制双模式
    • 支持数据发送、接收、显示和存储
  2. 用户友好界面

    • 直观的参数配置
    • 清晰的接收显示区
    • 实时的状态反馈
  3. 稳定的通信机制

    • 基于MSComm控件的中断接收
    • 完善的错误处理
    • 数据校验机制
  4. 良好的扩展性

    • 可添加自定义通信协议
    • 可集成数据分析和可视化
    • 可扩展为多设备通信平台

Lab4AI大模型实验室是面向AI开发者、科研党与学习者打造的一站式AI实践平台,深度绑定高性能弹性算力,支持模型复现、训练、推理全流程,以按需计费、低价高效破解高端算力紧缺与成本高昂难题;同步Arxiv前沿论文并提供翻译、导读、分析服务,支持各类大模型一键复现与数据集微调,对接孵化资源助力科研成果转化;同时搭载多样化AI在线课程,实现理论学习与代码实操同步推进,全方位覆盖AI研发、科研创新与技能学习全场景需求。

大模型实验室官网链接: https://www.lab4ai.cn/arxiv?utm_source=sf_daily_paper

作者单位:北京大学计算机学院

研究背景

  1. 场景图是计算机视觉中建模物体及其语义、空间关系的结构化表示,广泛应用于机器人、自主导航、具身智能等3D环境理解任务。
  2. 现有多视图场景图方法(如MSG)在欧氏空间中通过对比学习与注意力关联学习嵌入,虽能实现较好的位置识别精度,但欧氏几何无法显式建模场景中场所-物体的层级蕴含关系,导致学习到的表示结构一致性不足。
  3. 真实场景具备天然层级结构:场所语义蕴含物体,物体间也存在层级语义关系,而欧氏嵌入难以高效表示这类层级与蕴含关系,常需更高维度或导致结构组织劣化。
  4. 双曲空间具备指数级容量增长特性,天然适配层级与蕴含关系建模,已在视觉表征、视觉-语言对齐等任务中验证有效性,但尚未被用于多视图场景图的层级结构学习。

研究目的

  1. 解决欧氏空间场景图嵌入无法有效捕获场所-物体层级蕴含关系的核心问题,提升场景图的结构一致性与质量。
  2. 提出适配场景图建模的双曲表示学习框架,在保持位置检索性能的同时,显著优化场景图层级结构指标。
  3. 设计显式的层级约束损失,强化场所与物体间的蕴含关系建模,兼容现有MSG构建流程。

本文核心贡献

image

  1. 提出双曲场景图(HSG)框架,首次将双曲几何引入多视图场景图学习,利用双曲空间天然特性编码场景层级关系。
  2. 设计双曲空间下的蕴含损失(Entailment Loss),显式约束场所-物体的层级蕴含结构,提升表示的结构一致性。
  3. 通过大量定量与定性实验验证,HSG在保持高位置检索精度的同时,大幅超越欧氏基线方法,在图级指标上实现显著提升。
  4. 提供兼容现有MSG pipeline的双曲嵌入映射方案,可无缝集成到多视图场景图构建流程中,具备良好扩展性。

研究方法

image

  1. 双曲空间基础:采用洛伦兹双曲面模型(Lorentz hyperboloid model),将欧氏嵌入通过指数映射映射到双曲空间,原点表示最抽象概念,越具体实体距离原点越远。
  2. 模型架构:沿用MSG整体架构,将L2归一化超球嵌入与余弦相似度替换为洛伦兹双曲面嵌入与负洛伦兹距离,新增蕴含损失。
  3. 核心技术

    • 双曲对比学习:基于负洛伦兹距离重构InfoNCE损失,分别优化场所级与物体级对比目标。
    • 蕴含损失:采用双曲蕴含锥,定义场所嵌入为锥中心,约束物体嵌入位于锥内,控制层级约束强度。
    • 总损失:总损失=场所对比损失+物体对比损失+λ×蕴含损失。
  4. 实验设置

    • 骨干网络:DINOv2-Base(最优),辅以ConvNeXt、ViT、ResNet等对比。
    • 数据集:ARKitScenes,4492个训练场景、200个测试场景。
    • 训练配置:AdamW优化器,学习率2e-6,曲率初始值80(可学习),损失权重比1:1:20。
    • 评价指标:Recall@1、PP IoU(场所-场所交并比)、PO IoU(场所-物体交并比)、Graph IoU(图交并比)。

研究结果

  1. 核心性能:HSG的Recall@1达98.39%,与最优欧氏基线相当;PP IoU为33.17%,Graph IoU为33.51%,超越最优AoMSG变体25.37%,提升8.14%。
  2. 维度与骨干影响:投影头维度1024时性能最优;DINOv2-Base作为骨干效果最佳,自监督预训练对双曲场景图学习至关重要。
  3. 消融实验

    • 移除蕴含损失:图级指标小幅下降,验证蕴含损失对层级结构的优化作用。
    • 固定曲率c=1:PP IoU骤降,层级结构失效,说明可学习曲率的必要性。
    • 替换为欧氏损失:图级指标大幅退化,证明双曲表示的核心价值。
  4. 定性结果:HSG的场所嵌入更靠近双曲原点(更抽象),物体嵌入远离原点,呈现清晰层级分布;欧氏基线无明显层级结构。
  5. 超参数鲁棒性:对蕴含锥孔径阈值、InfoNCE温度参数(最优0.1)具备良好鲁棒性。

总结与展望

  1. 研究总结:HSG通过双曲几何学习场景图嵌入,有效捕获场所-物体层级蕴含关系,在保持检索性能的同时大幅提升场景图结构质量,验证了双曲表示在结构化视觉推理中的有效性。
  2. 局限性

    • 双曲投影对投影器维度敏感,过大或过小均影响性能。
    • 性能高度依赖底层编码器质量。
    • 曲率优化方式较为简单,极端曲率易引发数值不稳定。
  3. 未来展望

    • 采用自适应或多阶段曲率优化,提升几何表达能力与训练稳定性。
    • 集成更强基础模型(如DINOv3)与开放词汇检测器(如GroundingDINO),提升泛化性。
    • 结合多模态线索与下游任务联合优化,拓展双曲场景图的应用场景。
    • 探索时序场景图框架,适配长期、大规模环境理解。

laminin521(LN521)在hPSC人多能干细胞培养成本、扩增效率与操作简化对比研究

本文摘要:人多能干细胞培养长期面临成本高、操作繁琐、稳定性差三大痛点。本文基于实验数据,系统解析laminin521(LN521)重组层粘连蛋白作为包被基质的核心优势,从单位面积成本、细胞扩增效率、单位细胞成本、培养便利性四个维度,与截短层粘连蛋白、EHS提取物、玻连蛋白进行对比,证明LN521可显著提升扩增效率、降低整体培养成本、简化操作流程,是hPSC基础研究与临床前培养的高性价比优选方案。


一、hPSC 培养的典型痛点与基质选择重要性

在人多能干细胞(hPSC)日常培养中,科研人员普遍面临三大核心问题:

  1. 成本高:基质、培养基消耗大,长期实验开销显著
  2. 周期长:细胞增殖慢,传代频繁,实验周期不可控
  3. 操作繁琐:需每日换液、手动剔除分化细胞,重复性差

包被基质直接决定细胞黏附、增殖、稳定性与整体成本,选择适配的基质是提升hPSC培养效率的关键(点击查看细胞外基质技术详情)。


二、Biolaminin 521(LN521)核心特点

laminin521是全长重组人源层粘连蛋白,为hPSC生理环境天然存在的细胞外基质,具备多重实验与产业化优势:

2.1 临床级合规稳定

无动物源、无异种成分,批次一致性高,满足标准化与可重复性要求。

2.2 高质量干细胞培养

支持均一、遗传稳定的 hPSC 生长,无需手动去除分化细胞。

2.3 高生理相关性

激活细胞天然信号通路,促进基因均匀表达,提升分化效率与成熟度。

2.4 易用灵活体系

兼容多种培养基;周末无需换液;支持单细胞传代,无需ROCKi;接种密度范围宽,易上手。


三、实验数据对比:LN521vs其他包被基质

本文数据基于 HS181(人胚胎干细胞)、iPSC3(诱导多能干细胞)两株细胞系,对比底物包括:

  • LN521(全长层粘连蛋白)
  • 截短型 LN 片段
  • EHS 肿瘤提取物
  • 玻连蛋白(Vitronectin)

3.1 单位面积培养成本更低

LN521使用浓度仅0.5 μg/cm²,基质消耗量少,单次传代单位面积总成本(基质+培养基)显著低于同类产品。

3.2 细胞扩增效率更高

  • 单次传代增殖倍数更高
  • 生长速率更快,在hESC与iPSC均表现一致优势
  • 相同时间内可获得更高总细胞产量

3.3 单位细胞成本显著更低

因增殖快、产量高,LN521的单次传代单位细胞成本远低于截短LN、EHS、玻连蛋白,整体性价比突出。

3.4 实验更省心:简化操作流程

  • 无需每日换液,**周末可停换液**
  • 单细胞传代,**不用 ROCKi**
  • 无需频繁挑取分化克隆,降低人为误差
  • 接种密度宽松,工艺容错率高

四、laminin521成本及hPSC培养效率数据展示

4.1 单位面积成本分析

hPSC培养的经济可行性首先体现在单位面积的培养成本上。实验数据显示(数据图1),在单次传代过程中,LN521 的单位面积成本(基质与培养基成本合计)低于同类但为截短/片段型层粘连蛋白。较低的基质用量使得单次细胞培养的耗材支出更加可控,为长期培养项目提供了经济基础。

不同包被基质下综合培养基传代的单位面积hPSC培养成本汇算
数据图1. LN521和其他包被基质结合培养基在单位面积下平均的单次传代hPSC培养成本汇算(包被基质(深色)和培养基(浅色斜线)的配制成本均根据市售产品定价计算。实验采用LN521涂层浓度为0.5微克/平方厘米,其他基质材料(截短型片段层粘连蛋白、EHS肿瘤提取物及玻连蛋白Vitronectin)均按制造商说明书使用。培养基用量为每平方厘米0.2毫升,并每日更换。传代次数取两个细胞系(HS181和iPSC3)的平均5-6次传代数据作为基准。)

4.2 细胞扩增效率

细胞产量与生长速度是评估培养体系的核心指标。在 HS181(人胚胎干细胞系)与 iPSC3(诱导多能干细胞系)的对比实验中(数据图2-4),Biolaminin全长LN521 相比于其他类型包被基质,如截短/片段型层粘连蛋白、EHS肿瘤提取物以及玻连蛋白Vitronectin,均获得了更高的细胞产量与更快的生长速度。

相较于其他包被基质BioLamina品牌LN521的每代次hPSC具更高增殖次数
数据图2. LN521和其他包被基质培养HS181和IPSC3单次传代hPSC扩增倍数统计(LN521相较于其他包被基质在不同细胞系中均表现较高的扩增倍数。HS181(人胚胎干细胞)和iPSC3(诱导多能干细胞)细胞系的单次传代中增殖次数基于5-6次传代的平均值计算,接种密度为50,000个细胞/平方厘米。)

相较于其他包被基质BioLamina品牌LN521对HS181细胞系培养具较高扩增速率
数据图3. LN521和其他包被基质培养HS181时细胞的生长速率统计(LN521相较于其他包被基质在HS181细胞系显示出更高的增殖速率。随时间的倍增累积计算为每次传代(5-6次传代)的HS181倍增(log2)的总和。)

相较于其他包被基质BioLamina品牌LN521对生长较慢的IPSC3培养也支持较高扩增速率
数据图4. LN521和其他包被基质培养HS181时细胞的生长速率统计(LN521相较于其他包被基质在生长较慢的IPS C3细胞系显示出更高的增殖速率。)

这种稳定的扩增表现意味着:

  • 相同时间内可获得更多细胞
  • 对生长特性不同的细胞系具有较好的适应性

4.3 单位细胞成本

将成本与产量结合分析,单位细胞成本更能反映培养体系的经济性。由于 LN521 培养体系下 hPSC 生长更快、周期内产量更高,其单次传代的单位细胞成本远低于其他基质(数据图5)。

相较于其他包被基质BioLamina品牌LN521对hPSC培养具每单位细胞省时与省钱成本优势
数据图5. 不同包被基质结合培养基在单次传代的单位细胞hPSC的培养成本汇算(LN521相较于其他包被基质的单次传代周期中基质(深色)和培养基(浅色斜线)单位细胞的成本较低。)

值得注意的是:

  • 结合细胞产量速率分析,基质与培养基的成本分配更均衡
  • 使用全长LN521无需通过过度增加培养基用量来增加产量
  • 接种密度为50,000细胞/cm²,无需特殊调整即可达到良好效果

以上hPSC培养良好表现,主要得益于全长的Biolaminin521(LN521)为hPSC天然环境存在的细胞外基质,在生物相关性较高的环境中,有助于细胞适应体外培养体系,获得较高细胞扩增倍数,获得较高的总细胞产量,进而分摊培养过程中基质和培养基的总体支出。


五、使用laminin521进行hPSC培养的常见问题FAQ

Q1:LN521 包被的使用浓度是多少?

A:标准使用浓度为0.5 μg/cm²,按厂家推荐即可,用量低、性价比高。

Q2:用LN521培养hPSC真的可以周末不换液吗?

A:可以。该体系支持周末无需换液,大幅降低操作负担,且不影响细胞状态。

Q3:使用LN521需要添加ROCKi吗?

A:不需要。支持单细胞直接传代,可省去 ROCKi,降低成本与操作步骤。

Q4:LN521 相比截短型层粘连蛋白优势在哪里?

A:LN521是全长层粘连蛋白,生理信号更完整,细胞黏附/增殖/稳定性更优,成本更低。

Q5:适用于哪些干细胞培养?

A:适用于hESC、iPSC等人多能干细胞的扩增、维持、分化及临床前研究。


六、总结

Biolaminin521(LN521)作为全长重组人源层粘连蛋白包被基质,在hPSC培养中实现了低成本、高扩增、易操作、高稳定的均衡优势,您可点击此处了解曼博生物提供的laminin521产品详情

它不仅能明显降低单位细胞培养成本,还能简化实验流程、提升结果可靠性,是人多能干细胞基础研究、药物筛选、临床前开发的理想选择,真正做到省时、省力、省钱。

在设备登录活动中,计算机账户与用户账户同样重要。如果用户的 Active Directory(AD)计算机账户被删除,该用户将无法登录其设备继续工作。这不仅会增加 IT 团队恢复账户的时间成本,还会导致员工无法正常工作的生产力损失。因此,找出是谁删除了计算机账户,对于管理员分析删除原因、防止类似问题再次发生具有重要意义。下面将介绍具体方法。

使用 PowerShell 查找删除计算机账户的用户步骤

在域控制器(Domain Controller,DC)上执行以下操作:

点击“开始”,搜索 Windows PowerShell,右键选择“以管理员身份运行”。

在控制台中输入以下脚本:

Get-EventLog -LogName Security | Where-Object {$_.EventID -eq 4743} | Select-Object -Property *

按下 Enter 键执行命令。

该脚本会显示已删除的计算机账户相关事件。在输出结果中,定位到 Message > Subject > Account Name,即可查看执行删除操作的用户名称及其安全标识符(SID)。

注意: 如果你是在工作站上执行查询,则需要使用以下脚本:

Get-EventLog -LogName Security -ComputerName <DC name> | Where-Object {$_.EventID -eq 4743} | Select-Object -Property *

image.png

其中 <DC name> 表示你要查询的域控制器名称。

image.png
通过这种方式,可以基于安全日志筛选出计算机账户被删除的事件,从而定位具体执行操作的用户。

需要注意的是,虽然通过原生审计功能可以查询对象删除事件,但当环境中存在大量计算机账户时,需要持续跟踪和分析每一次事件,这种方式会变得非常低效且难以维护。

使用 ManageEngine ADAudit Plus 查找删除计算机账户的用户

相比手动查询日志,使用专业审计工具可以大幅提升效率。通过 ManageEngine ADAudit Plus,管理员可以更快速、直观地获取删除操作的详细信息。

操作步骤如下:

打开 ADAudit Plus 控制台,并使用管理员账号登录。

导航至: Active Directory >计算机管理>最近删除的计算机,导航到报表。

管理员还可以根据不同条件对报告进行筛选,例如:

计算机名称

操作用户创建、删除或修改时间

目标计算机名称
image.png

通过这些筛选条件,可以有针对性地监控关键计算机账户,快速定位删除行为。

使用 ADAudit Plus 相比原生审计的优势

与 Active Directory 原生审计功能相比,ADAudit Plus 提供了更强大、更高效的审计能力。

首先,ADAudit Plus 能够对所有 Active Directory 变更进行持续审计和报告,确保形成完整、可靠的审计轨迹。管理员无需记住每种操作对应的事件 ID,也不需要手动筛选日志,大大简化了运维工作。

其次,平台支持基于机器学习的异常行为检测,可以针对异常操作设置实时告警,并自动触发响应机制,从而帮助企业及时发现和应对内部威胁。

此外,ADAudit Plus 还内置多种合规性报告模板,可帮助组织满足包括 SOX、HIPAA、GLBA、PCI-DSS、FISMA 和 GDPR 在内的多项法规要求,减少审计压力,提高合规效率。

感觉以后的软件发展方向可能不像现在这样了,人们要查询数据的时候,通过网页上的筛选框去搜,我觉得通过 AI 来实现问数才是更方便的一种形式,比如直接和 AI 说,这会儿系统在线人数是多少,然后他就去查询数据库,然后在对话框里面把人数返回给我。再比如我要问我今天的待办事项有多少,他就把事件列表返回给我。和阿里的千问那样很像,直接说我要点外卖,然后就把外卖订单发给我。

我想讨论的是,这个技术路线应该怎么样才比较合理?我现在想到的两种方案:

1 、让大模型理解我的业务系统以及 sql 数据库结构,然后让模型根据问题自己生成 sql 语句,再去执行的话。这中间有个问题,就是它生成的 sql 并不正确,如果是语法错误还好解决,让他再修复一下,但是如果它都理解错了,这个问题就大了。

2 、提供常问的一些固定问题,通过 function call 的方式来实现,这种方式肯定出错的概率就很低,但是吧,用户的问题又受限制,最终模型能够回答多少问题,完全取决于我们给他提供多少接口函数。

还请大家解答一下,关于 AI 问数,有啥很好的参考吗

早上想找个文件,才发现文件打不开了,其中有几个文件夹的文件图标都多了个黄色盒子,点击没有任何反应。可以排除电脑中毒(电脑有些文件夹的文件是正常的),在网上搜了好久,貌似是 Onedrive ,但是不确定。请大佬帮助指点一下怎么处理。

感谢感谢感谢鞠躬

【前言】

在生成式 AI 技术的推动下,企业级办公应用正经历着深刻的范式重构。作为业务数据处理与分析的基石,传统的电子表格如何跨越复杂的交互壁垒,真正迈入“自然语言驱动”的智能时代?面对高达数千个接口的表格 API 与大语言模型固有的“幻觉”挑战,开发者又该如何构建安全、稳定的 AI Agent 工程落地架构?

在近日落幕的“2026赋能开发者大会”产品技术分论坛上,葡萄城 SpreadJS / GcExcel 产品经理张明以《智慧表格:拥抱Web端对话式办公》为主题,全方位揭秘了 SpreadJS 结合 AI 的底层工程化实践,并深度剖析了行业内各大主流智能表格的技术演进路线。

一、 破局与重构:AI 赋予电子表格的新生命

传统电子表格在企业级业务应用中,长期面临着来自终端用户和前端开发者的双重痛点:

  • 终端用户困境:陡峭的学习曲线。传统的 Excel 交互繁杂且不够直观。对于非专业数据人员而言,诸如 VLOOKUP、INDEX、MATCH 等复杂函数和公式的学习成本极高,高级数据分析与报表美化更是存在难以跨越的技术门槛。
    在这里插入图片描述
  • 前端开发者困境:工程化落地的鸿沟。面对 SpreadJS 庞大的 API 库(包含数千个接口),直接结合大语言模型极易触发“模型幻觉”。此外,如何维持表格实例与 AI 之间的状态同步,也是工程落地中的核心难题。
    在这里插入图片描述

为了打破“人找工具”的壁垒,实现从传统操作向“自然语言驱动”的转变,葡萄城 2026 客户峰会-表格分论坛展示了 SpreadJS 结合 AI 后的三大核心业务场景(Demo):

1.具备业务 Know-how 的数据理解:AI 能够智能分析导入的报表结构。例如在处理资产负债表时,AI 不仅能自动生成汇总计算公式,还能执行如 Validations = 资产总计 - (负债合计 + 所有者权益合计) 的专业财务校验逻辑,确保底层计算逻辑的一致性与准确性。
在这里插入图片描述

2.自然语言驱动的高级美化:用户仅需输入简单的格式指令,AI 即可自动应用复杂的商务风格设计规范。系统能够自动执行包括设置主标题为海军蓝粗体、年份表头中蓝底色、明细数据白浅蓝斑马纹、层级缩进以及千位分隔符等一系列繁琐操作,瞬间完成报表的专业化重构。
在这里插入图片描述

3.一键可视化及数据洞察:根据当前选区的数据,AI 可快速提炼出资产规模、流动性、资本结构等关键业务洞察,并自动生成结构清晰的图表(如“2025 年财务健康程度分析”视图),辅助管理层进行快速决策。
在这里插入图片描述

这些能力的落地,使得智能表格能够广泛应用于智能财务风控、自动化审计以及管理层数据驾驶舱等垂直场景。

二、 稳若磐石:工程化架构深度揭秘

要将 AI 可靠地接入极其复杂的表格系统中,底层的架构设计至关重要。本次分论坛公开了 SpreadJS AI Agent 的六层核心架构,并详细拆解了其中的三大技术创新点:

1.全局架构总览

系统自上而下划分为六层:表现层 (Presentation) 负责 UI 与用户交互;状态层 (State) 解决多端同步;业务逻辑层 (Business Logic) 处理对话与分发;服务与 AI 层 (Service / AI) 进行路由与幻觉治理;工具层 (Tool) 定义具体操作;数据与底层操作层 (Data) 负责 API 执行与外部通信。
在这里插入图片描述

2.核心技术点解析

  • 打造专属的工具库 (Tool Registry)
  • 鉴于 SpreadJS 功能跨度极大,且大模型存在上下文限制(一次性暴露过多工具极易导致“认知过载”),系统放弃了单一通用的工具设计。取而代之的是原子化执行策略:将复杂操作拆解为 read_rangeswrite_dataset_cell 等基础原子工具,再结合网关工具和特定行业工具,以此保障极高的执行成功率和精细化的安全合规管控。
    在这里插入图片描述

在这里插入图片描述

  • 基于 SpreadContext 的全局状态同步

为了解决 AI 对话框、Spread 设计器界面、后台工具执行流这三方访问同一工作簿时的冲突问题,引入了 SpreadContext 作为全局状态中心。它安全、高效地管理着 Spread 工作簿实例,确保数据读写的绝对一致性。
在这里插入图片描述

  • 渐进式 API 披露 (ModuleTracker)
  • 这是治理大模型“工具选择幻觉”的核心机制。ModuleTracker 依托于基于有限状态机 (FSM) 的动态路由系统:

    • 默认模式:仅向 LLM 暴露约 30 个最常用的基础数据读写工具和网关工具。
    • 网关触发:当用户下达特定指令(如“创建图表”),LLM 调用 manage_chart 网关工具,触发状态机切换。
    • 进入模块:系统进入专属的 Chart 模块,此时才向 LLM 暴露 add_chart 等深度操作 API。
    • 任务闭环:操作完成后,自动调用 exit_module 退出并重置回默认状态,彻底隔绝无关上下文的干扰。

在这里插入图片描述

在这里插入图片描述

三、 行业视野:对话式智能表格的三大主流实现路线

立足于当前的行业生态,实现对话式智能表格主要有三条主流技术路线,各自具备不同的技术特征与适用场景:

路线模式代表案例原理简述核心优势局限性
Tool-based (后端解析模式)RampLLM 仅输出结构化 JSON,后端进行参数验证 (Schema Validation) 后执行具体操作。具备极高的系统安全性,执行结果高度可预测,且审计链路清晰。灵活性较低,受限于预设的工具集合,难以组合出不可预知的复杂操作。
Code Gen (前端代码生成模式)Shortcut将全量 API 文档作为上下文输入,动态生成 JS 源码块,并通过前端浏览器原生引擎执行。极致灵活,无预设代码限速,可原生支持复杂图表与深度数据验证。安全管控挑战极大,极度依赖顶级大模型的海量 Token 消耗与自我纠错能力。
Python Sandbox (后端沙箱模式)Sourcetable利用云端隔离沙箱运行 Python/Pandas 代码,随后通过 WebSockets 将 DataFrames 渲染结果传输至前端。完美契合数据科学工作流,能够突破 Token 限制处理 PB 级海量数据。系统架构沉重复杂,AI 对前端表格 API 的控制较弱,难以实现单元格级别的精细化格式还原。

(注:业内如扣子(Coze)、Genspark、Skywork 等平台也在积极探索 AI 与办公场景的结合,但底层核心逻辑多跳脱不出上述三种范式。)

在这里插入图片描述

四、 殊途同归:为什么 SpreadJS 是无法替代的底座?

无论企业选择上述哪种工程化架构来构建智能表格系统,最终都需要一个极其强大的前端电子表格组件作为承载底座。SpreadJS 之所以成为通向成功的最优解,原因在于以下三项核心能力:

  1. 高度兼容任意 AI 架构:无论是基于后端的 Tool Calling 验证执行流、前端动态生成的代码执行沙箱,还是前文提到的渐进式状态机,SpreadJS 开放且灵活的 API 体系均能提供完美支撑。
  2. 全面覆盖复杂业务场景的 API 深度:企业级需求远不止于简单的“数据填充”。面对 AI 生成的复杂逻辑(如透视表 PivotTable、条件格式 ConditionFormat、高级图表 Charts 等),SpreadJS 具备无损的、企业级的渲染与还原能力。
  3. 卓越的运行态控制与快照提取:SpreadJS 提供的原生 Headless 能力以及结构化数据导出功能,是支撑 AI 获取精准上下文 (Context) 并进行多模态理解的底层基石。
    在这里插入图片描述
    为了进一步降低企业接入门槛,赋能开发者群体,SpreadJS AI Agent Framework 现已正式开源。开发者可通过访问 Gitee 官方仓库 (gitee.com/grapecity/spreadjs-ai-agent) 获取完整源码,深入体验这套融合了结构化 Tool Calling、模块化状态机与受保护沙箱的先进框架体系,共同迈向对话式办公的新纪元。
    在这里插入图片描述