| | |
| | | } |
| | | } |
| | | |
| | | 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) |
| | |
| | | } |
| | | |
| | | // ==================== 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; |
| | |
| | | // -------- 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; |
| | | |
| | |
| | | CString headers[] = { |
| | | _T(""), |
| | | _T("id"), |
| | | _T("Cassette Sequence No"), |
| | | _T("Job Sequence No"), |
| | | _T("Cassette SN"), |
| | | _T("Job SN"), |
| | | _T("Class ID"), |
| | | _T("物料类型"), |
| | | _T("状态"), |