| | |
| | | #include "GlassJson.h" |
| | | #include "CServoUtilsTool.h" |
| | | #include "ToolUnits.h" |
| | | |
| | | #include <optional> |
| | | #include <unordered_set> |
| | | #include <unordered_map> |
| | | #include <vector> |
| | | #include <string> |
| | | #include <algorithm> |
| | | #include "CProcessDataListDlg.h" |
| | | |
| | | #define PAGE_SIZE 50 |
| | | #define PAGE_BACKGROUND_COLOR RGB(252, 252, 255) |
| | |
| | | |
| | | // ====== 开关:1=启用假数据(只替换 DB 查询);0=用真实 DB ====== |
| | | #define USE_FAKE_DB_DEMO 0 |
| | | |
| | | // ====== 开关:1=启用模拟传感器数据生成;0=使用真实数据 ====== |
| | | #define USE_MOCK_SENSOR_DATA 0 |
| | | |
| | | #if USE_FAKE_DB_DEMO |
| | | #include <ctime> |
| | |
| | | } |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | |
| | | // 辅助函数:将 ANSI CString 写入文件为 UTF-8 编码 |
| | | bool CPageGlassList::WriteAnsiStringAsUtf8ToFile(const CString& ansiContent, const CString& filePath) |
| | | { |
| | | CFile file; |
| | | if (!file.Open(filePath, CFile::modeCreate | CFile::modeWrite)) { |
| | | return false; |
| | | } |
| | | |
| | | // 写入 UTF-8 BOM |
| | | const unsigned char bom[] = { 0xEF, 0xBB, 0xBF }; |
| | | file.Write(bom, 3); |
| | | |
| | | // 将 ANSI 转换为 Unicode |
| | | int unicodeLength = MultiByteToWideChar(CP_ACP, 0, |
| | | ansiContent, ansiContent.GetLength(), |
| | | NULL, 0); |
| | | |
| | | if (unicodeLength <= 0) { |
| | | file.Close(); |
| | | return false; |
| | | } |
| | | |
| | | wchar_t* unicodeBuffer = new wchar_t[unicodeLength + 1]; |
| | | MultiByteToWideChar(CP_ACP, 0, |
| | | ansiContent, ansiContent.GetLength(), |
| | | unicodeBuffer, unicodeLength); |
| | | unicodeBuffer[unicodeLength] = 0; |
| | | |
| | | // 将 Unicode 转换为 UTF-8 |
| | | int utf8Length = WideCharToMultiByte(CP_UTF8, 0, |
| | | unicodeBuffer, unicodeLength, |
| | | NULL, 0, NULL, NULL); |
| | | |
| | | bool success = false; |
| | | if (utf8Length > 0) { |
| | | char* utf8Buffer = new char[utf8Length]; |
| | | WideCharToMultiByte(CP_UTF8, 0, |
| | | unicodeBuffer, unicodeLength, |
| | | utf8Buffer, utf8Length, NULL, NULL); |
| | | |
| | | file.Write(utf8Buffer, utf8Length); |
| | | delete[] utf8Buffer; |
| | | success = true; |
| | | } |
| | | |
| | | delete[] unicodeBuffer; |
| | | file.Close(); |
| | | return success; |
| | | } |
| | | |
| | | // CPageGlassList 对话框 |
| | | |
| | | IMPLEMENT_DYNAMIC(CPageGlassList, CDialogEx) |
| | |
| | | ON_BN_CLICKED(IDC_BUTTON_PREV_PAGE, &CPageGlassList::OnBnClickedButtonPrevPage) |
| | | ON_BN_CLICKED(IDC_BUTTON_NEXT_PAGE, &CPageGlassList::OnBnClickedButtonNextPage) |
| | | ON_NOTIFY(ELCN_SHOWFULLTEXT, IDC_LIST_ALARM, &CPageGlassList::OnShowFullText) |
| | | ON_BN_CLICKED(IDC_BUTTON_EXPORT_ROW, &CPageGlassList::OnBnClickedButtonExportRow) |
| | | END_MESSAGE_MAP() |
| | | |
| | | // ===== 私有小工具 ===== |
| | |
| | | } |
| | | |
| | | // ===== CPageGlassList 消息处理程序 ===== |
| | | void CPageGlassList::InitRxWindow() |
| | | void CPageGlassList::InitRxWindows() |
| | | { |
| | | // 订阅数据 |
| | | IRxWindows* pRxWindows = RX_GetRxWindows(); |
| | |
| | | } |
| | | |
| | | // ==================== 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 |
| | | |
| | | // 建索引:classId -> index |
| | | std::unordered_map<std::string, size_t> idxById; |
| | | idxById.reserve(page.items.size()); |
| | | for (size_t i = 0; i < page.items.size(); ++i) { |
| | | idxById[page.items[i].classId] = i; |
| | | #if !USE_FAKE_DB_DEMO |
| | | // —— 三元键工具:<classId>|C<cassette>|J<job> —— // |
| | | // —— 三元键工具:<classId>|C<cassette>|J<job> —— // |
| | | auto makeKey = [](const std::string& cls, int csn, int jsn) -> std::string { |
| | | std::string k; |
| | | k.reserve(cls.size() + 32); |
| | | k.append(cls); |
| | | k.push_back('|'); k.push_back('C'); |
| | | k.append(std::to_string(csn)); |
| | | k.push_back('|'); k.push_back('J'); |
| | | k.append(std::to_string(jsn)); |
| | | return k; |
| | | }; |
| | | |
| | | // ★★★ 这里是关键修复:接收“const Row&”,不要非 const 引用 |
| | | using RowT = std::decay<decltype(pageFull.items.front())>::type; |
| | | auto makeKeyR = [&](const RowT& r) -> std::string { |
| | | return makeKey(r.classId, r.cassetteSeqNo, r.jobSeqNo); |
| | | }; |
| | | |
| | | // 不区分大小写 classId 相等 |
| | | 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 |
| | | }; |
| | | |
| | | |
| | | // —— lookahead 预读:若超出 1 条,尝试把“最后一条”与“预读”判为一对(严格优先)—— |
| | | std::optional<decltype(pageFull.items)::value_type> lookahead; |
| | | if (pageFull.items.size() == rawLimit) { |
| | | const auto& last = pageFull.items[PAGE_SIZE - 1]; |
| | | const auto& extra = pageFull.items[PAGE_SIZE]; |
| | | |
| | | bool strictPair = |
| | | (!last.buddyId.empty() && iEquals(last.buddyId, extra.classId) |
| | | && last.cassetteSeqNo == extra.cassetteSeqNo |
| | | && last.jobSeqNo == extra.jobSeqNo) |
| | | || (!extra.buddyId.empty() && iEquals(extra.buddyId, last.classId) |
| | | && extra.cassetteSeqNo == last.cassetteSeqNo |
| | | && extra.jobSeqNo == last.jobSeqNo); |
| | | |
| | | bool loosePair = |
| | | (!last.buddyId.empty() && iEquals(last.buddyId, extra.classId)) || |
| | | (!extra.buddyId.empty() && iEquals(extra.buddyId, last.classId)); |
| | | |
| | | if (strictPair || loosePair) { |
| | | lookahead = extra; |
| | | } |
| | | // 预读不算入本页 |
| | | pageFull.items.pop_back(); |
| | | } |
| | | |
| | | // 已消费(已插入为父或子) |
| | | // 之后正常按 page 构建 |
| | | auto& pageRef = pageFull; |
| | | |
| | | // —— 建两个索引 —— // |
| | | // A) byTriple: 三元键 -> index(唯一/已消费依据) |
| | | // B) byClass : classId -> indices(buddy 候选池,允许多个) |
| | | std::unordered_map<std::string, size_t> byTriple; |
| | | std::unordered_map<std::string, std::vector<size_t>> byClass; |
| | | byTriple.reserve(pageRef.items.size()); |
| | | byClass.reserve(pageRef.items.size()); |
| | | |
| | | for (size_t i = 0; i < pageRef.items.size(); ++i) { |
| | | const auto& r = pageRef.items[i]; |
| | | byTriple[makeKeyR(r)] = i; |
| | | byClass[r.classId].push_back(i); |
| | | } |
| | | |
| | | // —— 已消费集合(用三元键)—— |
| | | std::unordered_set<std::string> consumed; |
| | | consumed.reserve(pageRef.items.size()); |
| | | |
| | | int zebra = 0; |
| | | auto zebraBk = [&](int z) -> COLORREF { |
| | | return (z % 2 == 0) ? RGB(255, 255, 255) : RGB(235, 235, 235); |
| | | }; |
| | | |
| | | // -------- Phase 1: 先处理“有 buddyId 的记录”(能配就配;单向也配) ---------- |
| | | for (size_t i = 0; i < page.items.size(); ++i) { |
| | | const auto& r = page.items[i]; |
| | | if (consumed.count(r.classId)) continue; |
| | | // -------- Phase 1: 先处理“有 buddyId 的记录” ---------- |
| | | for (size_t i = 0; i < pageRef.items.size(); ++i) { |
| | | const auto& r = pageRef.items[i]; |
| | | if (consumed.count(makeKeyR(r))) continue; |
| | | if (r.buddyId.empty()) continue; |
| | | |
| | | COLORREF bk = zebraBk(zebra); |
| | | // 在同页里为 r 找 buddy 候选 |
| | | size_t buddyIdx = (size_t)-1; |
| | | auto itVec = byClass.find(r.buddyId); |
| | | if (itVec != byClass.end()) { |
| | | const auto& vec = itVec->second; |
| | | |
| | | auto it = idxById.find(r.buddyId); |
| | | if (it != idxById.end()) { |
| | | const auto& br = page.items[it->second]; |
| | | if (!consumed.count(br.classId)) { |
| | | // —— 以“有 buddyId 的这条 r”为父,buddy 作为子(单向也能配)—— |
| | | std::vector<CString> pcols(colCount); |
| | | pcols[1] = std::to_string(r.id).c_str(); |
| | | pcols[2] = std::to_string(r.cassetteSeqNo).c_str(); |
| | | pcols[3] = std::to_string(r.jobSeqNo).c_str(); |
| | | pcols[4] = r.classId.c_str(); |
| | | pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str(); |
| | | pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str(); |
| | | pcols[7] = r.tStart.c_str(); |
| | | pcols[8] = r.tEnd.c_str(); |
| | | pcols[9] = r.buddyId.c_str(); |
| | | pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str(); |
| | | pcols[11] = r.path.c_str(); |
| | | pcols[12] = r.params.c_str(); |
| | | |
| | | auto* nParent = m_listCtrl.InsertRoot(pcols); |
| | | MaybeRestoreExpandByKey(nParent, expandedKeys); |
| | | m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk); |
| | | |
| | | std::vector<CString> ccols(colCount); |
| | | ccols[1] = std::to_string(br.id).c_str(); |
| | | ccols[2] = std::to_string(br.cassetteSeqNo).c_str(); |
| | | ccols[3] = std::to_string(br.jobSeqNo).c_str(); |
| | | ccols[4] = br.classId.c_str(); |
| | | ccols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)br.materialType).c_str(); |
| | | ccols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)br.state).c_str(); |
| | | ccols[7] = br.tStart.c_str(); |
| | | ccols[8] = br.tEnd.c_str(); |
| | | ccols[9] = br.buddyId.c_str(); |
| | | ccols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)br.aoiResult).c_str(); |
| | | ccols[11] = br.path.c_str(); |
| | | ccols[12] = br.params.c_str(); |
| | | |
| | | auto* nChild = m_listCtrl.InsertChild(nParent, ccols); |
| | | m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk); |
| | | |
| | | consumed.insert(r.classId); |
| | | consumed.insert(br.classId); |
| | | ++zebra; |
| | | continue; |
| | | // 1) 严格匹配:Cassette/Job 一致 |
| | | for (size_t j : vec) { |
| | | const auto& br = pageRef.items[j]; |
| | | if (br.cassetteSeqNo == r.cassetteSeqNo && br.jobSeqNo == r.jobSeqNo) { |
| | | if (!consumed.count(makeKeyR(br))) { buddyIdx = j; break; } |
| | | } |
| | | } |
| | | // 2) 宽松匹配:同 classId 未消费的任意一条 |
| | | if (buddyIdx == (size_t)-1) { |
| | | for (size_t j : vec) { |
| | | const auto& br = pageRef.items[j]; |
| | | if (!consumed.count(makeKeyR(br))) { buddyIdx = j; break; } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 同页没找到 buddy(或已被消费)→ 插占位子行 |
| | | COLORREF bk = zebraBk(zebra); |
| | | |
| | | if (buddyIdx != (size_t)-1) { |
| | | const auto& br = pageRef.items[buddyIdx]; |
| | | |
| | | // 父:r(有 buddyId),子:br |
| | | std::vector<CString> pcols(colCount); |
| | | pcols[1] = std::to_string(r.id).c_str(); |
| | | pcols[2] = std::to_string(r.cassetteSeqNo).c_str(); |
| | | pcols[3] = std::to_string(r.jobSeqNo).c_str(); |
| | | pcols[4] = r.classId.c_str(); |
| | | pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str(); |
| | | pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str(); |
| | | pcols[7] = r.tStart.c_str(); |
| | | pcols[8] = r.tEnd.c_str(); |
| | | pcols[9] = r.buddyId.c_str(); |
| | | pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str(); |
| | | pcols[11] = r.path.c_str(); |
| | | pcols[12] = r.params.c_str(); |
| | | |
| | | auto* nParent = m_listCtrl.InsertRoot(pcols); |
| | | MaybeRestoreExpandByKey(nParent, expandedKeys); |
| | | m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk); |
| | | |
| | | std::vector<CString> ccols(colCount); |
| | | ccols[1] = std::to_string(br.id).c_str(); |
| | | ccols[2] = std::to_string(br.cassetteSeqNo).c_str(); |
| | | ccols[3] = std::to_string(br.jobSeqNo).c_str(); |
| | | ccols[4] = br.classId.c_str(); |
| | | ccols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)br.materialType).c_str(); |
| | | ccols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)br.state).c_str(); |
| | | ccols[7] = br.tStart.c_str(); |
| | | ccols[8] = br.tEnd.c_str(); |
| | | ccols[9] = br.buddyId.c_str(); |
| | | ccols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)br.aoiResult).c_str(); |
| | | ccols[11] = br.path.c_str(); |
| | | ccols[12] = br.params.c_str(); |
| | | |
| | | auto* nChild = m_listCtrl.InsertChild(nParent, ccols); |
| | | m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk); |
| | | |
| | | consumed.insert(makeKeyR(r)); |
| | | consumed.insert(makeKeyR(br)); |
| | | ++zebra; |
| | | continue; |
| | | } |
| | | |
| | | // 没找到 buddy → 插占位子行(只写 ClassID) |
| | | std::vector<CString> pcols(colCount); |
| | | pcols[1] = std::to_string(r.id).c_str(); |
| | | pcols[2] = std::to_string(r.cassetteSeqNo).c_str(); |
| | |
| | | |
| | | auto* nParent = m_listCtrl.InsertRoot(pcols); |
| | | MaybeRestoreExpandByKey(nParent, expandedKeys); |
| | | m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk); |
| | | m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), zebraBk(zebra)); |
| | | |
| | | std::vector<CString> ccols(colCount); // 占位只写 ClassID |
| | | ccols[4] = r.buddyId.c_str(); |
| | | std::vector<CString> ccols(colCount); |
| | | ccols[4] = r.buddyId.c_str(); // 占位 |
| | | auto* nChild = m_listCtrl.InsertChild(nParent, ccols); |
| | | m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk); |
| | | m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), zebraBk(zebra)); |
| | | |
| | | consumed.insert(r.classId); |
| | | consumed.insert(makeKeyR(r)); |
| | | ++zebra; |
| | | } |
| | | |
| | | // -------- Phase 2: 剩余未消费的,作为“单条根行” ---------- |
| | | for (size_t i = 0; i < page.items.size(); ++i) { |
| | | const auto& r = page.items[i]; |
| | | if (consumed.count(r.classId)) continue; |
| | | for (size_t i = 0; i < pageRef.items.size(); ++i) { |
| | | const auto& r = pageRef.items[i]; |
| | | if (consumed.count(makeKeyR(r))) continue; |
| | | |
| | | COLORREF bk = zebraBk(zebra); |
| | | |
| | | std::vector<CString> cols(colCount); |
| | | cols[1] = std::to_string(r.id).c_str(); |
| | | cols[2] = std::to_string(r.cassetteSeqNo).c_str(); |
| | | cols[3] = std::to_string(r.jobSeqNo).c_str(); |
| | | cols[4] = r.classId.c_str(); |
| | | cols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str(); |
| | | cols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str(); |
| | | cols[7] = r.tStart.c_str(); |
| | | cols[8] = r.tEnd.c_str(); |
| | | cols[9] = r.buddyId.c_str(); |
| | | cols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str(); |
| | | cols[11] = r.path.c_str(); |
| | | cols[12] = r.params.c_str(); |
| | | |
| | | auto* n = m_listCtrl.InsertRoot(cols); |
| | | m_listCtrl.SetNodeColor(n, RGB(0, 0, 0), bk); |
| | | |
| | | consumed.insert(makeKeyR(r)); |
| | | ++zebra; |
| | | } |
| | | |
| | | // 一次性重绘 |
| | | m_listCtrl.RebuildVisible(); |
| | | |
| | | #else |
| | | // ===== DEMO 分支(保持原样;若要演示同样逻辑,可仿照上面改造)===== |
| | | // 如果多出一条,看看它是否是“本页最后一条”的 buddy |
| | | std::optional<decltype(page.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 (page.items.size() == rawLimit) { |
| | | const auto& last = page.items[PAGE_SIZE - 1]; |
| | | const auto& extra = page.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.items.pop_back(); |
| | | } |
| | | |
| | | // 你可以把 DEMO 分支也切到三元键逻辑;这里从略 |
| | | auto& pageRef = page; |
| | | std::unordered_map<std::string, size_t> idxById; |
| | | idxById.reserve(pageRef.items.size()); |
| | | for (size_t i = 0; i < pageRef.items.size(); ++i) idxById[pageRef.items[i].classId] = i; |
| | | |
| | | std::unordered_set<std::string> consumed; |
| | | int zebra = 0; |
| | | auto zebraBk = [&](int z) -> COLORREF { |
| | | return (z % 2 == 0) ? RGB(255, 255, 255) : RGB(235, 235, 235); |
| | | }; |
| | | |
| | | for (size_t i = 0; i < pageRef.items.size(); ++i) { |
| | | const auto& r = pageRef.items[i]; |
| | | if (consumed.count(r.classId)) continue; |
| | | if (!r.buddyId.empty()) { |
| | | auto it = idxById.find(r.buddyId); |
| | | if (it != idxById.end()) { |
| | | const auto& br = pageRef.items[it->second]; |
| | | if (!consumed.count(br.classId)) { |
| | | COLORREF bk = zebraBk(zebra); |
| | | std::vector<CString> pcols(colCount), ccols(colCount); |
| | | pcols[1] = std::to_string(r.id).c_str(); |
| | | pcols[2] = std::to_string(r.cassetteSeqNo).c_str(); |
| | | pcols[3] = std::to_string(r.jobSeqNo).c_str(); |
| | | pcols[4] = r.classId.c_str(); |
| | | pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str(); |
| | | pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str(); |
| | | pcols[7] = r.tStart.c_str(); |
| | | pcols[8] = r.tEnd.c_str(); |
| | | pcols[9] = r.buddyId.c_str(); |
| | | pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str(); |
| | | pcols[11] = r.path.c_str(); |
| | | pcols[12] = r.params.c_str(); |
| | | auto* nParent = m_listCtrl.InsertRoot(pcols); |
| | | MaybeRestoreExpandByKey(nParent, expandedKeys); |
| | | m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk); |
| | | |
| | | ccols[1] = std::to_string(br.id).c_str(); |
| | | ccols[2] = std::to_string(br.cassetteSeqNo).c_str(); |
| | | ccols[3] = std::to_string(br.jobSeqNo).c_str(); |
| | | ccols[4] = br.classId.c_str(); |
| | | ccols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)br.materialType).c_str(); |
| | | ccols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)br.state).c_str(); |
| | | ccols[7] = br.tStart.c_str(); |
| | | ccols[8] = br.tEnd.c_str(); |
| | | ccols[9] = br.buddyId.c_str(); |
| | | ccols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)br.aoiResult).c_str(); |
| | | ccols[11] = br.path.c_str(); |
| | | ccols[12] = br.params.c_str(); |
| | | auto* nChild = m_listCtrl.InsertChild(nParent, ccols); |
| | | m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk); |
| | | |
| | | consumed.insert(r.classId); |
| | | consumed.insert(br.classId); |
| | | ++zebra; |
| | | continue; |
| | | } |
| | | } |
| | | |
| | | // 插占位子 |
| | | COLORREF bk = zebraBk(zebra); |
| | | std::vector<CString> pcols(colCount), ccols(colCount); |
| | | pcols[1] = std::to_string(r.id).c_str(); |
| | | pcols[2] = std::to_string(r.cassetteSeqNo).c_str(); |
| | | pcols[3] = std::to_string(r.jobSeqNo).c_str(); |
| | | pcols[4] = r.classId.c_str(); |
| | | pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str(); |
| | | pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str(); |
| | | pcols[7] = r.tStart.c_str(); |
| | | pcols[8] = r.tEnd.c_str(); |
| | | pcols[9] = r.buddyId.c_str(); |
| | | pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str(); |
| | | pcols[11] = r.path.c_str(); |
| | | pcols[12] = r.params.c_str(); |
| | | auto* nParent = m_listCtrl.InsertRoot(pcols); |
| | | MaybeRestoreExpandByKey(nParent, expandedKeys); |
| | | m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk); |
| | | |
| | | ccols[4] = r.buddyId.c_str(); |
| | | auto* nChild = m_listCtrl.InsertChild(nParent, ccols); |
| | | m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk); |
| | | |
| | | consumed.insert(r.classId); |
| | | ++zebra; |
| | | } |
| | | } |
| | | for (size_t i = 0; i < pageRef.items.size(); ++i) { |
| | | const auto& r = pageRef.items[i]; |
| | | if (consumed.count(r.classId)) continue; |
| | | |
| | | COLORREF bk = zebraBk(zebra); |
| | | std::vector<CString> cols(colCount); |
| | | cols[1] = std::to_string(r.id).c_str(); |
| | | cols[2] = std::to_string(r.cassetteSeqNo).c_str(); |
| | |
| | | ++zebra; |
| | | } |
| | | |
| | | // 一次性重绘 |
| | | m_listCtrl.RebuildVisible(); |
| | | #endif |
| | | |
| | | // 上一页 / 下一页 |
| | | UpdatePageControls(); |
| | |
| | | { |
| | | CDialogEx::OnInitDialog(); |
| | | |
| | | // 定时器:1=初始化订阅,2=周期刷新(只增量) |
| | | // 定时器:1=初始化订阅,2=周期刷新(只增量),3=延迟加载首屏数据 |
| | | SetTimer(1, 3000, nullptr); |
| | | SetTimer(2, 2000, nullptr); |
| | | SetTimer(3, 10, nullptr); |
| | | |
| | | // 下拉框控件 |
| | | InitStatusCombo(); |
| | |
| | | CString headers[] = { |
| | | _T(""), |
| | | _T("id"), |
| | | _T("Cassette Sequence No"), |
| | | _T("Job Sequence No"), |
| | | _T("Cassette SN"), |
| | | _T("Job SN"), |
| | | _T("Class ID"), |
| | | _T("物料类型"), |
| | | _T("状态"), |
| | |
| | | m_listCtrl.SetPopupFullTextColumns({ 11, 12 }); |
| | | |
| | | Resize(); |
| | | OnBnClickedButtonSearch(); // 触发一次查询与首屏填充 |
| | | |
| | | return TRUE; // return TRUE unless you set the focus to a control |
| | | } |
| | |
| | | { |
| | | if (nIDEvent == 1) { |
| | | KillTimer(1); |
| | | InitRxWindow(); |
| | | InitRxWindows(); |
| | | } |
| | | else if (nIDEvent == 2) { |
| | | UpdateWipData(); // 只做增量,不重建 |
| | | } |
| | | else if (nIDEvent == 3) { |
| | | KillTimer(3); |
| | | OnBnClickedButtonSearch(); // 延迟首屏查询,避免卡住 OnInitDialog |
| | | } |
| | | |
| | | CDialogEx::OnTimer(nIDEvent); |
| | |
| | | |
| | | void CPageGlassList::OnBnClickedButtonSearch() |
| | | { |
| | | CWaitCursor wait; // 显示等待光标,提示正在加载 |
| | | |
| | | // 获取关键字输入框内容 |
| | | CString strKeyword; |
| | | GetDlgItemText(IDC_EDIT_KEYWORD, strKeyword); |
| | |
| | | } |
| | | } |
| | | |
| | | void CPageGlassList::OnBnClickedButtonExportRow() |
| | | { |
| | | int nSelected = m_listCtrl.GetSelectionMark(); |
| | | if (nSelected == -1) { |
| | | AfxMessageBox(_T("请先选择一行记录!")); |
| | | return; |
| | | } |
| | | |
| | | // 直接从第一列获取 ID |
| | | CString strId = m_listCtrl.GetItemText(nSelected, 1); |
| | | |
| | | if (strId.IsEmpty()) { |
| | | AfxMessageBox(_T("WIP记录暂不支持保存")); |
| | | return; |
| | | } |
| | | |
| | | // 数据库记录 |
| | | long long recordId = _ttoi64(strId); |
| | | |
| | | // 从数据库查询完整记录 |
| | | auto& db = GlassLogDb::Instance(); |
| | | auto row = db.queryById(recordId); |
| | | |
| | | if (!row) { |
| | | AfxMessageBox(_T("查询记录失败")); |
| | | return; |
| | | } |
| | | |
| | | // 使用 Glass ID 构建默认文件名 |
| | | CString strDefaultFileName; |
| | | CString strGlassId = row->classId.c_str(); |
| | | |
| | | // 移除文件名中的非法字符 |
| | | CString strSanitizedGlassId = strGlassId; |
| | | strSanitizedGlassId.Remove('\\'); |
| | | strSanitizedGlassId.Remove('/'); |
| | | strSanitizedGlassId.Remove(':'); |
| | | strSanitizedGlassId.Remove('*'); |
| | | strSanitizedGlassId.Remove('?'); |
| | | strSanitizedGlassId.Remove('"'); |
| | | strSanitizedGlassId.Remove('<'); |
| | | strSanitizedGlassId.Remove('>'); |
| | | strSanitizedGlassId.Remove('|'); |
| | | |
| | | strDefaultFileName.Format(_T("Glass_%s.csv"), strSanitizedGlassId); |
| | | |
| | | // 文件保存对话框,设置默认文件名 |
| | | CFileDialog fileDialog(FALSE, _T("csv"), strDefaultFileName, |
| | | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, |
| | | _T("CSV Files (*.csv)|*.csv|JSON Files (*.json)|*.json||")); |
| | | |
| | | if (fileDialog.DoModal() != IDOK) return; |
| | | |
| | | CString filePath = fileDialog.GetPathName(); |
| | | CString fileExt = fileDialog.GetFileExt(); |
| | | |
| | | if (fileExt.CompareNoCase(_T("json")) == 0) { |
| | | ExportToJson(*row, filePath); |
| | | } |
| | | else { |
| | | ExportToCsv(*row, filePath); |
| | | } |
| | | } |
| | | |
| | | void CPageGlassList::ExportToJson(const GlassLogDb::Row& row, const CString& filePath) |
| | | { |
| | | // 保存为 JSON |
| | | if (!row.pretty.empty()) { |
| | | CFile file; |
| | | if (file.Open(filePath, CFile::modeCreate | CFile::modeWrite)) { |
| | | file.Write(row.pretty.c_str(), (UINT)row.pretty.length()); |
| | | file.Close(); |
| | | |
| | | CString strSuccess; |
| | | strSuccess.Format(_T("记录已保存为JSON文件:\n%s"), filePath); |
| | | AfxMessageBox(strSuccess); |
| | | } |
| | | else { |
| | | AfxMessageBox(_T("保存文件失败")); |
| | | } |
| | | } |
| | | else { |
| | | AfxMessageBox(_T("该记录没有JSON数据")); |
| | | } |
| | | } |
| | | |
| | | void CPageGlassList::ExportToCsv(const GlassLogDb::Row& row, const CString& filePath) |
| | | { |
| | | CString csvContent; |
| | | |
| | | // === 第一部分:基础信息 === |
| | | ExportBasicInfo(csvContent, row); |
| | | |
| | | // === 第二部分:工艺参数 === |
| | | ExportProcessParams(csvContent, row); |
| | | |
| | | // === 第三部分:传感器数据详情 === |
| | | ExportSensorData(csvContent, row); |
| | | |
| | | // 使用辅助函数保存为 UTF-8 编码 |
| | | if (WriteAnsiStringAsUtf8ToFile(csvContent, filePath)) { |
| | | CString strSuccess; |
| | | strSuccess.Format(_T("记录已保存为CSV文件:\n%s"), filePath); |
| | | AfxMessageBox(strSuccess); |
| | | } |
| | | else { |
| | | AfxMessageBox(_T("保存文件失败")); |
| | | } |
| | | } |
| | | |
| | | void CPageGlassList::ExportBasicInfo(CString& csvContent, const GlassLogDb::Row& row) |
| | | { |
| | | csvContent += _T("=== 基础信息 ===\n"); |
| | | csvContent += _T("ID,Cassette序列号,Job序列号,Glass ID,物料类型,状态,开始时间,结束时间,绑定Glass ID,AOI结果,路径\n"); |
| | | |
| | | CString baseInfoRow; |
| | | baseInfoRow.Format(_T("%lld,%d,%d,%s,%d,%d,%s,%s,%s,%d,%s\n"), |
| | | row.id, row.cassetteSeqNo, row.jobSeqNo, |
| | | CString(row.classId.c_str()), row.materialType, row.state, |
| | | CString(row.tStart.c_str()), CString(row.tEnd.c_str()), |
| | | CString(row.buddyId.c_str()), row.aoiResult, |
| | | CString(row.path.c_str())); |
| | | csvContent += baseInfoRow; |
| | | } |
| | | |
| | | void CPageGlassList::ExportProcessParams(CString& csvContent, const GlassLogDb::Row& row) |
| | | { |
| | | csvContent += _T("\n=== 工艺参数 ===\n"); |
| | | |
| | | // 如果有 pretty 字段,解析工艺参数 |
| | | if (!row.pretty.empty()) { |
| | | SERVO::CGlass tempGlass; |
| | | if (GlassJson::FromString(row.pretty, tempGlass)) { |
| | | auto& params = tempGlass.getParams(); |
| | | if (!params.empty()) { |
| | | // 工艺参数表头 |
| | | csvContent += _T("参数名称,参数ID,数值,机器单元\n"); |
| | | |
| | | // 工艺参数数据 |
| | | for (auto& param : params) { |
| | | CString paramRow; |
| | | CString valueStr; |
| | | |
| | | // 根据参数类型格式化数值 |
| | | if (param.getValueType() == PVT_INT) { |
| | | valueStr.Format(_T("%d"), param.getIntValue()); |
| | | } |
| | | else { |
| | | valueStr.Format(_T("%.3f"), param.getDoubleValue()); |
| | | } |
| | | |
| | | paramRow.Format(_T("%s,%s,%s,%s\n"), |
| | | CString(param.getName().c_str()), |
| | | CString(param.getId().c_str()), |
| | | valueStr, |
| | | CString(param.getUnit().c_str())); |
| | | |
| | | csvContent += paramRow; |
| | | } |
| | | } |
| | | else { |
| | | csvContent += _T("无工艺参数数据\n"); |
| | | } |
| | | } |
| | | else { |
| | | csvContent += _T("无法解析工艺参数\n"); |
| | | } |
| | | } |
| | | else { |
| | | csvContent += _T("无工艺参数数据\n"); |
| | | } |
| | | } |
| | | |
| | | void CPageGlassList::ExportSensorData(CString& csvContent, const GlassLogDb::Row& row) |
| | | { |
| | | csvContent += _T("\n=== 传感器数据详情 ===\n"); |
| | | |
| | | // 如果有 pretty 字段,解析传感器数据 |
| | | if (!row.pretty.empty()) { |
| | | SERVO::CGlass tempGlass; |
| | | if (GlassJson::FromString(row.pretty, tempGlass)) { |
| | | #if USE_MOCK_SENSOR_DATA |
| | | // 生成模拟的SVData用于测试 |
| | | GenerateMockSVData(tempGlass); |
| | | #endif |
| | | // 对每个机器生成表格 |
| | | for (const auto& machinePair : tempGlass.getAllSVData()) { |
| | | int machineId = machinePair.first; |
| | | const auto& dataByType = machinePair.second; |
| | | CString machineName = CString(SERVO::CServoUtilsTool::getEqName(machineId).c_str()); |
| | | |
| | | csvContent += _T("\n[") + machineName + _T("]\n"); |
| | | |
| | | if (dataByType.empty()) { |
| | | csvContent += _T("No sensor data\n"); |
| | | continue; |
| | | } |
| | | |
| | | auto columnOrder = getMachineColumnOrder(machineId, &dataByType); |
| | | if (columnOrder.empty()) { |
| | | csvContent += _T("No exportable columns\n"); |
| | | continue; |
| | | } |
| | | |
| | | CString header = _T("Timestamp(ms),LocalTime"); |
| | | for (const auto& dataType : columnOrder) { |
| | | header += _T(","); |
| | | header += CString(dataType.c_str()); |
| | | } |
| | | header += _T("\n"); |
| | | csvContent += header; |
| | | |
| | | auto baselineIt = std::find_if(columnOrder.begin(), columnOrder.end(), |
| | | [&](const std::string& type) { |
| | | auto dataIt = dataByType.find(type); |
| | | return dataIt != dataByType.end() && !dataIt->second.empty(); |
| | | }); |
| | | if (baselineIt == columnOrder.end()) { |
| | | csvContent += _T("No usable time series\n"); |
| | | continue; |
| | | } |
| | | |
| | | const auto& timeSeries = dataByType.at(*baselineIt); |
| | | for (size_t i = 0; i < timeSeries.size(); ++i) { |
| | | auto timestamp = timeSeries[i].timestamp; |
| | | auto ms = timePointToMs(timestamp); |
| | | CString row; |
| | | row.Format(_T("%lld,"), ms); |
| | | |
| | | CString localTime = CString(timePointToString(timestamp).c_str()); |
| | | row += localTime; |
| | | |
| | | for (const auto& dataType : columnOrder) { |
| | | row += _T(","); |
| | | auto dataTypeIt = dataByType.find(dataType); |
| | | if (dataTypeIt != dataByType.end() && i < dataTypeIt->second.size()) { |
| | | CString valueStr; |
| | | valueStr.Format(_T("%.3f"), dataTypeIt->second[i].value); |
| | | row += valueStr; |
| | | } |
| | | else { |
| | | row += _T("N/A"); |
| | | } |
| | | } |
| | | row += _T("\n"); |
| | | csvContent += row; |
| | | } |
| | | } |
| | | } |
| | | else { |
| | | csvContent += _T("无法解析传感器数据\n"); |
| | | } |
| | | } |
| | | else { |
| | | csvContent += _T("无传感器数据\n"); |
| | | } |
| | | } |
| | | |
| | | void CPageGlassList::OnBnClickedButtonPrevPage() |
| | | { |
| | | if (m_nCurPage > 1) { |
| | |
| | | { |
| | | auto* p = reinterpret_cast<NMC_ELC_SHOWFULLTEXT*>(pNMHDR); |
| | | |
| | | // 这里暂时用消息框显示;后续可换成你的详情页 |
| | | CString strNewMsg = p->text; |
| | | strNewMsg.Replace(_T(","), _T("\n")); |
| | | MessageBox(strNewMsg, _T("详细信息"), MB_OK | MB_ICONINFORMATION); |
| | | // 对话框显示工艺参数 |
| | | if (p->iSubItem == 12) { |
| | | CProcessDataListDlg dlg; |
| | | dlg.setRawText(p->text); |
| | | dlg.DoModal(); |
| | | } |
| | | else { |
| | | AfxMessageBox(p->text); |
| | | } |
| | | |
| | | *pResult = 0; |
| | | } |
| | | |
| | |
| | | CExpandableListCtrl::Node* savedTop = nullptr; |
| | | |
| | | // 3) 逐个处理 WIP:已存在 -> 就地更新;必要时“只对根补子项” |
| | | // 不存在 -> 优先挂到 buddy 容器;否则触发整页重建(新根保持顶部) |
| | | // 不存在 -> 挂到 buddy 容器;若 buddy 不在可见表,触发全量重建(保证 WIP 顶部) |
| | | for (auto* g : wipGlasses) { |
| | | if (!GlassMatchesFilters(*g, m_filters)) continue; |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | // —— 只对“根节点”补子项,且仅当 buddy 尚未出现在可见表,且根下也没有该 buddy —— |
| | | // —— 只对“根节点”补子项 —— |
| | | SERVO::CGlass* b = g->getBuddy(); |
| | | if (b) { |
| | | auto itRoot = wipRootById.find(cid); |
| | |
| | | bool buddyExistsAnywhere = (wipRowById.find(newBid) != wipRowById.end()); |
| | | bool hasChildAlready = NodeHasChildWithClassId(container, newBuddyCid); |
| | | |
| | | // 关系是否发生变化?(oldChildCid 与 newBuddyCid 不同,或有子但现在没 buddy) |
| | | // 关系是否发生变化? |
| | | bool relationChanged = |
| | | (!oldChildCid.IsEmpty() && newBuddyCid.IsEmpty()) || |
| | | (oldChildCid.IsEmpty() && !newBuddyCid.IsEmpty()) || |
| | |
| | | } |
| | | } |
| | | } |
| | | // 若当前是“子节点”,不在这里调整父子关系;让“关系变化”走全量重建 |
| | | } |
| | | else { |
| | | // 没有 buddy:如果容器下现在有子,也算关系变化,触发重建 |
| | | // 没 buddy 但容器下有子 -> 关系变化,触发全量重建 |
| | | auto itRoot = wipRootById.find(cid); |
| | | if (itRoot != wipRootById.end()) { |
| | | CExpandableListCtrl::Node* container = itRoot->second; |
| | |
| | | } |
| | | else { |
| | | // (B) 不存在:新增 |
| | | // 先尝试“挂到 buddy 的容器根”下面; |
| | | // 若 buddy 不在当前可见表,则触发全量重建(保证 WIP 顶部)。 |
| | | SERVO::CGlass* b = g->getBuddy(); |
| | | CExpandableListCtrl::Node* container = nullptr; |
| | | |
| | |
| | | } |
| | | |
| | | if (container) { |
| | | // buddy 容器存在:把 g 作为“子行”挂上去(避免重复) |
| | | CString cidCs = g->getID().c_str(); |
| | | if (!NodeHasChildWithClassId(container, cidCs)) { |
| | | if (!needRebuildChildren) { CaptureUiState(m_listCtrl, savedSel, savedTop); } |
| | |
| | | |
| | | return true; |
| | | } |
| | | |
| | | BOOL CPageGlassList::PreTranslateMessage(MSG* pMsg) |
| | | { |
| | | if (pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE) { |
| | | return TRUE; |
| | | } |
| | | |
| | | return CDialogEx::PreTranslateMessage(pMsg); |
| | | } |
| | | |
| | | // 获取机器预定义的列顺序 |
| | | std::vector<std::string> CPageGlassList::getMachineColumnOrder(int machineId, |
| | | const std::unordered_map<std::string, std::vector<SERVO::SVDataItem>>* actualData) |
| | | { |
| | | std::vector<std::string> columnOrder; |
| | | auto dataTypes = SERVO::CServoUtilsTool::getEqDataTypes(); |
| | | auto it = dataTypes.find(machineId); |
| | | |
| | | if (actualData != nullptr) { |
| | | if (it != dataTypes.end()) { |
| | | for (const auto& name : it->second) { |
| | | if (actualData->find(name) != actualData->end()) { |
| | | columnOrder.push_back(name); |
| | | } |
| | | } |
| | | } |
| | | for (const auto& kv : *actualData) { |
| | | if (std::find(columnOrder.begin(), columnOrder.end(), kv.first) == columnOrder.end()) { |
| | | columnOrder.push_back(kv.first); |
| | | } |
| | | } |
| | | return columnOrder; |
| | | } |
| | | |
| | | if (it != dataTypes.end()) { |
| | | columnOrder = it->second; |
| | | } |
| | | return columnOrder; |
| | | } |
| | | |
| | | // 时间戳转换为字符串 |
| | | std::string CPageGlassList::timePointToString(const std::chrono::system_clock::time_point& tp) |
| | | { |
| | | auto time_t = std::chrono::system_clock::to_time_t(tp); |
| | | std::tm tm; |
| | | localtime_s(&tm, &time_t); |
| | | char buffer[20]; |
| | | std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm); |
| | | return buffer; |
| | | } |
| | | |
| | | // 时间戳转换为毫秒 |
| | | int64_t CPageGlassList::timePointToMs(const std::chrono::system_clock::time_point& tp) |
| | | { |
| | | return std::chrono::duration_cast<std::chrono::milliseconds>(tp.time_since_epoch()).count(); |
| | | } |
| | | |
| | | // 生成模拟的SVData用于测试 |
| | | void CPageGlassList::GenerateMockSVData(SERVO::CGlass& glass) |
| | | { |
| | | // 获取设备数据类型配置 |
| | | auto& dataTypes = SERVO::CServoUtilsTool::getEqDataTypes(); |
| | | |
| | | // 为每个设备生成模拟数据 |
| | | for (const auto& machinePair : dataTypes) { |
| | | int machineId = machinePair.first; |
| | | const auto& dataTypeList = machinePair.second; |
| | | std::vector<std::string> filteredTypes; |
| | | |
| | | if (machineId == EQ_ID_VACUUMBAKE || machineId == EQ_ID_BAKE_COOLING) { |
| | | const char activePrefix = 'A'; |
| | | for (const auto& dataType : dataTypeList) { |
| | | if (!dataType.empty() && dataType[0] == activePrefix) { |
| | | filteredTypes.push_back(dataType); |
| | | } |
| | | } |
| | | } |
| | | |
| | | const auto& typeList = filteredTypes.empty() ? dataTypeList : filteredTypes; |
| | | |
| | | // 生成时间序列:从当前时间往前推10分钟,每1秒一个数据点 |
| | | auto now = std::chrono::system_clock::now(); |
| | | auto startTime = now - std::chrono::minutes(10); |
| | | |
| | | // 为每个数据类型生成模拟数据 |
| | | for (const auto& dataType : typeList) { |
| | | std::vector<SERVO::SVDataItem> mockData; |
| | | |
| | | // 生成600个数据点(10分钟 * 60个点/分钟) |
| | | for (int i = 0; i < 600; ++i) { |
| | | auto timestamp = startTime + std::chrono::seconds(i * 1); |
| | | |
| | | // 根据设备类型和数据类型生成不同的模拟值 |
| | | double value = GenerateMockValue(machineId, dataType, i); |
| | | |
| | | mockData.emplace_back(timestamp, value); |
| | | } |
| | | |
| | | // 将模拟数据添加到glass对象中 |
| | | glass.addSVData(machineId, dataType, mockData); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 根据设备类型和数据类型生成模拟数值 |
| | | double CPageGlassList::GenerateMockValue(int machineId, const std::string& dataType, int index) |
| | | { |
| | | // 基础值范围 |
| | | double baseValue = 0.0; |
| | | double variation = 0.0; |
| | | |
| | | // 根据设备类型设置基础值 |
| | | switch (machineId) { |
| | | case EQ_ID_Bonder1: |
| | | case EQ_ID_Bonder2: |
| | | if (dataType.find("压力") != std::string::npos) { |
| | | baseValue = 50.0; // 压力基础值 |
| | | variation = 10.0; // 压力变化范围 |
| | | } else if (dataType.find("温度") != std::string::npos) { |
| | | baseValue = 180.0; // 温度基础值 |
| | | variation = 5.0; // 温度变化范围 |
| | | } else if (dataType.find("扩展值") != std::string::npos) { |
| | | baseValue = 100.0; // 扩展值基础值 |
| | | variation = 15.0; // 扩展值变化范围 |
| | | } |
| | | break; |
| | | |
| | | case EQ_ID_VACUUMBAKE: |
| | | if (dataType.find("扩展值") != std::string::npos) { |
| | | baseValue = 80.0; |
| | | variation = 12.0; |
| | | } else if (dataType.find("温度") != std::string::npos) { |
| | | baseValue = 200.0; |
| | | variation = 8.0; |
| | | } |
| | | break; |
| | | |
| | | case EQ_ID_BAKE_COOLING: |
| | | if (dataType.find("温度") != std::string::npos) { |
| | | baseValue = 25.0; // 冷却温度 |
| | | variation = 3.0; |
| | | } |
| | | break; |
| | | |
| | | default: |
| | | baseValue = 50.0; |
| | | variation = 5.0; |
| | | break; |
| | | } |
| | | |
| | | // 添加时间相关的趋势和随机变化 |
| | | double timeTrend = sin(index * 0.1) * 2.0; // 正弦波趋势 |
| | | double randomNoise = (rand() % 100 - 50) / 100.0 * variation * 0.3; // 随机噪声 |
| | | |
| | | return baseValue + timeTrend + randomNoise; |
| | | } |