SourceCode/Bond/Servo/CPageGlassList.cpp
@@ -8,12 +8,12 @@
#include "GlassJson.h"
#include "CServoUtilsTool.h"
#include "ToolUnits.h"
#include <optional>
#include <unordered_set>
#include <unordered_map>
#include <vector>
#include <string>
#include "CProcessDataListDlg.h"
#define PAGE_SIZE                       50
#define PAGE_BACKGROUND_COLOR           RGB(252, 252, 255)
@@ -340,6 +340,39 @@
    }
}
bool CopyUtf8ToClipboard(const std::string& utf8)
{
    // 1) UTF-8 -> UTF-16 长度(含结尾 '\0')
    int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, nullptr, 0);
    if (wlen <= 0) return false;
    // 2) 为剪贴板分配全局可移动内存(必须 GMEM_MOVEABLE)
    SIZE_T bytes = static_cast<SIZE_T>(wlen) * sizeof(wchar_t);
    HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, bytes);
    if (!hMem) return false;
    // 3) 填充 UTF-16 文本
    wchar_t* wbuf = static_cast<wchar_t*>(GlobalLock(hMem));
    if (!wbuf) { GlobalFree(hMem); return false; }
    MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, wbuf, wlen);
    GlobalUnlock(hMem);
    // 4) 打开剪贴板并设置数据(CF_UNICODETEXT)
    if (!OpenClipboard(nullptr)) { GlobalFree(hMem); return false; }
    if (!EmptyClipboard()) { CloseClipboard(); GlobalFree(hMem); return false; }
    // 成功后,内存所有权交给剪贴板,不能再 GlobalFree
    if (!SetClipboardData(CF_UNICODETEXT, hMem)) {
        CloseClipboard();
        GlobalFree(hMem);
        return false;
    }
    CloseClipboard();
    return true;
}
// CPageGlassList 对话框
IMPLEMENT_DYNAMIC(CPageGlassList, CDialogEx)
@@ -403,7 +436,7 @@
}
// ===== CPageGlassList 消息处理程序 =====
void CPageGlassList::InitRxWindow()
void CPageGlassList::InitRxWindows()
{
    // 订阅数据
    IRxWindows* pRxWindows = RX_GetRxWindows();
@@ -619,12 +652,42 @@
    }
    // ==================== 2) DB 当前页(两阶段构建,处理单向 buddy) ====================
    const int rawLimit = PAGE_SIZE + 1;
    const int rawOffset = PAGE_SIZE * (m_nCurPage - 1);
#if USE_FAKE_DB_DEMO
    auto page = _make_page_fake(m_filters, PAGE_SIZE, PAGE_SIZE * (m_nCurPage - 1));
    auto page = _make_page_fake(m_filters, rawLimit, rawOffset);
#else
    auto& db = GlassLogDb::Instance();
    auto page = db.queryPaged(m_filters, PAGE_SIZE, PAGE_SIZE * (m_nCurPage - 1));
    auto pageFull = db.queryPaged(m_filters, rawLimit, rawOffset);
#endif
    // 如果多出一条,看看它是否是“本页最后一条”的 buddy
    std::optional<decltype(pageFull.items)::value_type> lookahead; // 预读记录(若与最后一条配对)
    auto iEquals = [](const std::string& a, const std::string& b) {
#ifdef _WIN32
        return _stricmp(a.c_str(), b.c_str()) == 0;
#else
        return strcasecmp(a.c_str(), b.c_str()) == 0;
#endif
    };
    if (pageFull.items.size() == rawLimit) {
        const auto& last = pageFull.items[PAGE_SIZE - 1];
        const auto& extra = pageFull.items[PAGE_SIZE];
        bool pair =
            (!last.buddyId.empty() && iEquals(last.buddyId, extra.classId)) ||
            (!extra.buddyId.empty() && iEquals(extra.buddyId, last.classId));
        if (pair) {
            lookahead = extra;           // 把预读保存下来,稍后补成子行
        }
        // 无论是否配对,列表都缩回 PAGE_SIZE 条(预读不算入本页数据集)
        pageFull.items.pop_back();
    }
    // 之后正常按 page 构建
    auto& page = pageFull; // 为了复用你原有变量名
    // 建索引:classId -> index
    std::unordered_map<std::string, size_t> idxById;
@@ -643,6 +706,8 @@
    // -------- Phase 1: 先处理“有 buddyId 的记录”(能配就配;单向也配) ----------
    for (size_t i = 0; i < page.items.size(); ++i) {
        const auto& r = page.items[i];
        // CopyUtf8ToClipboard(r.pretty);
        if (consumed.count(r.classId)) continue;
        if (r.buddyId.empty()) continue;
@@ -801,8 +866,8 @@
    CString headers[] = {
        _T(""),
        _T("id"),
        _T("Cassette Sequence No"),
        _T("Job Sequence No"),
        _T("Cassette SN"),
        _T("Job SN"),
        _T("Class ID"),
        _T("物料类型"),
        _T("状态"),
@@ -881,7 +946,7 @@
{
    if (nIDEvent == 1) {
        KillTimer(1);
        InitRxWindow();
        InitRxWindows();
    }
    else if (nIDEvent == 2) {
        UpdateWipData();  // 只做增量,不重建
@@ -1000,10 +1065,11 @@
{
    auto* p = reinterpret_cast<NMC_ELC_SHOWFULLTEXT*>(pNMHDR);
    // 这里暂时用消息框显示;后续可换成你的详情页
    CString strNewMsg = p->text;
    strNewMsg.Replace(_T(","), _T("\n"));
    MessageBox(strNewMsg, _T("详细信息"), MB_OK | MB_ICONINFORMATION);
    // 对话框显示工艺参数
    CProcessDataListDlg dlg;
    dlg.setRawText(p->text);
    dlg.DoModal();
    *pResult = 0;
}
@@ -1330,3 +1396,12 @@
    return true;
}
BOOL CPageGlassList::PreTranslateMessage(MSG* pMsg)
{
    if (pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE) {
        return TRUE;
    }
    return CDialogEx::PreTranslateMessage(pMsg);
}