LAPTOP-SNT8I5JK\Boounion
2025-10-13 047c7cbd047e11fba8d7872e69a11a13e463aec4
1.保存单条记录。
已修改10个文件
733 ■■■■ 文件已修改
SourceCode/Bond/Servo/CGlass.cpp 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CGlass.h 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.cpp 199 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageGlassList.cpp 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageGlassList.h 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/GlassJson.cpp 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/GlassLogDb.cpp 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/GlassLogDb.h 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.rc 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/resource.h 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CGlass.cpp
@@ -431,4 +431,147 @@
        return strOut;
    }
    // ========== SV数据管理接口实现 ==========
    void CGlass::addSVData(int machineId, const std::string& dataType, const SVDataItem& dataItem) {
        m_svDatas[machineId][dataType].push_back(dataItem);
    }
    void CGlass::addSVData(int machineId, const std::string& dataType, double value) {
        auto now = std::chrono::system_clock::now();
        m_svDatas[machineId][dataType].emplace_back(now, value);
    }
    void CGlass::addSVData(int machineId, const std::string& dataType, int64_t timestamp, double value) {
        // 将int64_t时间戳转换为system_clock::time_point
        std::chrono::system_clock::time_point timePoint{
            std::chrono::milliseconds(timestamp)  // 假设timestamp是毫秒
            // 如果是秒,使用:std::chrono::seconds(timestamp)
        };
        m_svDatas[machineId][dataType].emplace_back(timePoint, value);
    }
    void CGlass::addSVData(int machineId, const std::string& dataType, const std::vector<SVDataItem>& dataItems) {
        auto& dataList = m_svDatas[machineId][dataType];
        dataList.insert(dataList.end(), dataItems.begin(), dataItems.end());
    }
    std::vector<SVDataItem> CGlass::getSVData(int machineId, const std::string& dataType) const {
        auto machineIt = m_svDatas.find(machineId);
        if (machineIt != m_svDatas.end()) {
            auto dataIt = machineIt->second.find(dataType);
            if (dataIt != machineIt->second.end()) {
                return dataIt->second;
            }
        }
        return std::vector<SVDataItem>();
    }
    std::vector<std::string> CGlass::getSVDataTypes(int machineId) const {
        std::vector<std::string> types;
        auto machineIt = m_svDatas.find(machineId);
        if (machineIt != m_svDatas.end()) {
            for (const auto& pair : machineIt->second) {
                types.push_back(pair.first);
            }
        }
        return types;
    }
    std::unordered_map<std::string, std::vector<SVDataItem>> CGlass::getMachineSVData(int machineId) const {
        auto it = m_svDatas.find(machineId);
        if (it != m_svDatas.end()) {
            return it->second;
        }
        return std::unordered_map<std::string, std::vector<SVDataItem>>();
    }
    const std::unordered_map<int, std::unordered_map<std::string, std::vector<SVDataItem>>>& CGlass::getAllSVData() const {
        return m_svDatas;
    }
    bool CGlass::hasSVData(int machineId, const std::string& dataType) const {
        auto machineIt = m_svDatas.find(machineId);
        if (machineIt != m_svDatas.end()) {
            return machineIt->second.find(dataType) != machineIt->second.end();
        }
        return false;
    }
    bool CGlass::hasMachineSVData(int machineId) const {
        return m_svDatas.find(machineId) != m_svDatas.end();
    }
    std::vector<int> CGlass::getMachineIdsWithSVData() const {
        std::vector<int> machineIds;
        for (const auto& pair : m_svDatas) {
            machineIds.push_back(pair.first);
        }
        return machineIds;
    }
    void CGlass::clearSVData(int machineId, const std::string& dataType) {
        auto machineIt = m_svDatas.find(machineId);
        if (machineIt != m_svDatas.end()) {
            machineIt->second.erase(dataType);
            // 如果该机器没有其他数据了,也清除机器条目
            if (machineIt->second.empty()) {
                m_svDatas.erase(machineIt);
            }
        }
    }
    void CGlass::clearMachineSVData(int machineId) {
        m_svDatas.erase(machineId);
    }
    void CGlass::clearAllSVData() {
        m_svDatas.clear();
    }
    size_t CGlass::getSVDataCount(int machineId, const std::string& dataType) const {
        auto machineIt = m_svDatas.find(machineId);
        if (machineIt != m_svDatas.end()) {
            auto dataIt = machineIt->second.find(dataType);
            if (dataIt != machineIt->second.end()) {
                return dataIt->second.size();
            }
        }
        return 0;
    }
    size_t CGlass::getMachineSVDataCount(int machineId) const {
        size_t total = 0;
        auto machineIt = m_svDatas.find(machineId);
        if (machineIt != m_svDatas.end()) {
            for (const auto& pair : machineIt->second) {
                total += pair.second.size();
            }
        }
        return total;
    }
    size_t CGlass::getTotalSVDataCount() const {
        size_t total = 0;
        for (const auto& machinePair : m_svDatas) {
            for (const auto& dataPair : machinePair.second) {
                total += dataPair.second.size();
            }
        }
        return total;
    }
    std::vector<std::pair<int, SVDataItem>> CGlass::findSVDataByType(const std::string& dataType) const {
        std::vector<std::pair<int, SVDataItem>> result;
        for (const auto& machinePair : m_svDatas) {
            auto dataIt = machinePair.second.find(dataType);
            if (dataIt != machinePair.second.end()) {
                for (const auto& item : dataIt->second) {
                    result.emplace_back(machinePair.first, item);
                }
            }
        }
        return result;
    }
}
SourceCode/Bond/Servo/CGlass.h
@@ -12,6 +12,15 @@
namespace SERVO {
    /// 数据项:时间戳 + 数值
    struct SVDataItem {
        std::chrono::system_clock::time_point timestamp;
        double value;  // 或者根据实际情况使用其他类型
        SVDataItem(std::chrono::system_clock::time_point ts, double val)
            : timestamp(ts), value(val) {}
    };
    /// PJ 生命周期(贴近 E40 常见状态)
    enum class GlsState : uint8_t {
        NoState = 0,
@@ -100,6 +109,58 @@
        void addParams(std::vector<CParam>& params);
        std::vector<CParam>& getParams();
        // ========== SV数据管理接口(新设计)==========
        // 添加数据到指定机器的指定数据类型
        void addSVData(int machineId, const std::string& dataType, const SVDataItem& dataItem);
        void addSVData(int machineId, const std::string& dataType, double value); // 自动使用当前时间
        void addSVData(int machineId, const std::string& dataType, int64_t timestamp, double value);
        // 批量添加数据到指定机器的指定数据类型
        void addSVData(int machineId, const std::string& dataType, const std::vector<SVDataItem>& dataItems);
        // 获取指定机器的指定数据类型的所有数据
        std::vector<SVDataItem> getSVData(int machineId, const std::string& dataType) const;
        // 获取指定机器的所有数据类型
        std::vector<std::string> getSVDataTypes(int machineId) const;
        // 获取指定机器的所有数据(按数据类型组织)
        std::unordered_map<std::string, std::vector<SVDataItem>> getMachineSVData(int machineId) const;
        // 获取所有机器的数据
        const std::unordered_map<int, std::unordered_map<std::string, std::vector<SVDataItem>>>& getAllSVData() const;
        // 检查指定机器是否有指定类型的数据
        bool hasSVData(int machineId, const std::string& dataType) const;
        // 检查指定机器是否有任何数据
        bool hasMachineSVData(int machineId) const;
        // 获取所有有SV数据的机器ID
        std::vector<int> getMachineIdsWithSVData() const;
        // 清空指定机器的指定数据类型的数据
        void clearSVData(int machineId, const std::string& dataType);
        // 清空指定机器的所有数据
        void clearMachineSVData(int machineId);
        // 清空所有机器的所有数据
        void clearAllSVData();
        // 获取指定机器的指定数据类型的数据数量
        size_t getSVDataCount(int machineId, const std::string& dataType) const;
        // 获取指定机器的总数据数量
        size_t getMachineSVDataCount(int machineId) const;
        // 获取所有机器的总数据数量
        size_t getTotalSVDataCount() const;
        // 获取指定数据类型在所有机器中的数据
        std::vector<std::pair<int, SVDataItem>> findSVDataByType(const std::string& dataType) const;
    private:
        MaterialsType m_type;
        std::string m_strID;
@@ -112,6 +173,9 @@
        BOOL m_bScheduledForProcessing;            /* 是否将加工处理 */
        CProcessJob* m_pProcessJob;
        std::vector<CParam> m_params;            // 工艺参数
        // 新的三层数据结构:机器ID -> 数据类型 -> 数据列表
        std::unordered_map<int, std::unordered_map<std::string, std::vector<SVDataItem>>> m_svDatas;
    };
}
SourceCode/Bond/Servo/CMaster.cpp
@@ -9,6 +9,32 @@
namespace SERVO {
    static std::unordered_map<int, std::vector<std::string>> MACHINE_DATA_TYPES = {
    {EQ_ID_Bonder1, {
        "气囊压力", "上腔压力", "管道真空规值", "腔体真空规值",
        "上腔温度1", "上腔温度2", "上腔温度3", "上腔温度4",
        "上腔温度5", "上腔温度6", "下腔温度1", "下腔温度2",
        "下腔温度3", "下腔温度4", "下腔温度5", "下腔温度6"
    }},
    {EQ_ID_Bonder2, {
        "气囊压力", "上腔压力", "管道真空规值", "腔体真空规值",
        "上腔温度1", "上腔温度2", "上腔温度3", "上腔温度4",
        "上腔温度5", "上腔温度6", "下腔温度1", "下腔温度2",
        "下腔温度3", "下腔温度4", "下腔温度5", "下腔温度6"
    }},
    {EQ_ID_VACUUMBAKE, {
        "A腔真空规值", "A腔温控1", "A腔温控2", "A腔温控4",
        "A腔温控5", "A腔温控6", "A腔温控7", "B腔真空规值",
        "B腔温控1", "B腔温控2", "B腔温控4", "B腔温控5",
        "B腔温控6", "B腔温控7"
    }},
    {EQ_ID_BAKE_COOLING, {
        "A烘烤温控1", "A烘烤温控2", "A烘烤温控4", "A烘烤温控5",
        "A烘烤温控6", "A烘烤温控7", "B烘烤温控1", "B烘烤温控2",
        "B烘烤温控4", "B烘烤温控5", "B烘烤温控6", "B烘烤温控7"
    }}
    };
    static inline int64_t now_ms_epoch() {
        using namespace std::chrono;
        return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
@@ -1624,55 +1650,72 @@
        
            // 以下加入到曲线数据中
            const int64_t ts = now_ms_epoch();
            int eqid = ((CEquipment*)pEquipment)->getID();
            if (eqid == EQ_ID_Bonder1 || eqid == EQ_ID_Bonder2) {
                m_pCollector->buffersPush(eqid, 1, ts, params.at(1).getDoubleValue());
                m_pCollector->buffersPush(eqid, 2, ts, params.at(2).getDoubleValue());
                m_pCollector->buffersPush(eqid, 3, ts, params.at(3).getDoubleValue());
                m_pCollector->buffersPush(eqid, 4, ts, params.at(4).getDoubleValue());
                m_pCollector->buffersPush(eqid, 5, ts, params.at(5).getDoubleValue());
                m_pCollector->buffersPush(eqid, 6, ts, params.at(6).getDoubleValue());
                m_pCollector->buffersPush(eqid, 7, ts, params.at(7).getDoubleValue());
                m_pCollector->buffersPush(eqid, 8, ts, params.at(8).getDoubleValue());
                m_pCollector->buffersPush(eqid, 9, ts, params.at(9).getDoubleValue());
                m_pCollector->buffersPush(eqid, 10, ts, params.at(10).getDoubleValue());
                m_pCollector->buffersPush(eqid, 11, ts, params.at(11).getDoubleValue());
                m_pCollector->buffersPush(eqid, 12, ts, params.at(12).getDoubleValue());
                m_pCollector->buffersPush(eqid, 13, ts, params.at(13).getDoubleValue());
                m_pCollector->buffersPush(eqid, 14, ts, params.at(14).getDoubleValue());
                m_pCollector->buffersPush(eqid, 15, ts, params.at(15).getDoubleValue());
                m_pCollector->buffersPush(eqid, 16, ts, params.at(16).getDoubleValue());
                // 定义 Bonder 的特定映射
                std::vector<std::pair<int, int>> bonderMapping = {
                    {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7},
                    {8, 8}, {9, 9}, {10, 10}, {11, 11}, {12, 12}, {13, 13}, {14, 14}, {15, 15}, {16, 16}
                };
                CGlass* pGlass = ((CEquipment*)pEquipment)->getGlassFromSlot(0);
                auto& bonderTypes = MACHINE_DATA_TYPES[eqid];
                for (const auto& mapping : bonderMapping) {
                    int paramIndex = mapping.first;
                    int channel = mapping.second;
                    if (paramIndex < params.size() && channel - 1 < bonderTypes.size()) {
                        if(m_pCollector != nullptr)
                            m_pCollector->buffersPush(eqid, channel, ts, params.at(paramIndex).getDoubleValue());
                        if(pGlass != nullptr)
                            pGlass->addSVData(eqid, bonderTypes[channel], ts, params.at(paramIndex).getDoubleValue());
                    }
                }
            }
            else if (eqid == EQ_ID_VACUUMBAKE) {
                m_pCollector->buffersPush(eqid, 1, ts, params.at(1).getDoubleValue());
                m_pCollector->buffersPush(eqid, 2, ts, params.at(2).getDoubleValue());
                m_pCollector->buffersPush(eqid, 3, ts, params.at(3).getDoubleValue());
                m_pCollector->buffersPush(eqid, 4, ts, params.at(4).getDoubleValue());
                m_pCollector->buffersPush(eqid, 5, ts, params.at(5).getDoubleValue());
                m_pCollector->buffersPush(eqid, 6, ts, params.at(6).getDoubleValue());
                m_pCollector->buffersPush(eqid, 7, ts, params.at(7).getDoubleValue());
                m_pCollector->buffersPush(eqid, 8, ts, params.at(10).getDoubleValue());
                m_pCollector->buffersPush(eqid, 9, ts, params.at(11).getDoubleValue());
                m_pCollector->buffersPush(eqid, 10, ts, params.at(12).getDoubleValue());
                m_pCollector->buffersPush(eqid, 11, ts, params.at(13).getDoubleValue());
                m_pCollector->buffersPush(eqid, 12, ts, params.at(14).getDoubleValue());
                m_pCollector->buffersPush(eqid, 13, ts, params.at(15).getDoubleValue());
                m_pCollector->buffersPush(eqid, 14, ts, params.at(16).getDoubleValue());
                // 定义 VACUUMBAKE 的特定映射
                std::vector<std::pair<int, int>> vacuumMapping = {
                    {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7},
                    {10, 8}, {11, 9}, {12, 10}, {13, 11}, {14, 12}, {15, 13}, {16, 14}
                };
                CGlass* pGlass = ((CEquipment*)pEquipment)->getGlassFromSlot(0);
                auto& bonderTypes = MACHINE_DATA_TYPES[eqid];
                for (const auto& mapping : vacuumMapping) {
                    int paramIndex = mapping.first;
                    int channel = mapping.second;
                    if (paramIndex < params.size() && channel - 1 < bonderTypes.size()) {
                        if (m_pCollector != nullptr)
                            m_pCollector->buffersPush(eqid, channel, ts, params.at(paramIndex).getDoubleValue());
                        if (pGlass != nullptr)
                            pGlass->addSVData(eqid, bonderTypes[channel], ts, params.at(paramIndex).getDoubleValue());
                    }
                }
            }
            else if (eqid == EQ_ID_BAKE_COOLING) {
                m_pCollector->buffersPush(eqid, 1, ts, params.at(1).getDoubleValue());
                m_pCollector->buffersPush(eqid, 2, ts, params.at(2).getDoubleValue());
                m_pCollector->buffersPush(eqid, 3, ts, params.at(3).getDoubleValue());
                m_pCollector->buffersPush(eqid, 4, ts, params.at(4).getDoubleValue());
                m_pCollector->buffersPush(eqid, 5, ts, params.at(5).getDoubleValue());
                m_pCollector->buffersPush(eqid, 6, ts, params.at(6).getDoubleValue());
                m_pCollector->buffersPush(eqid, 7, ts, params.at(11).getDoubleValue());
                m_pCollector->buffersPush(eqid, 8, ts, params.at(12).getDoubleValue());
                m_pCollector->buffersPush(eqid, 9, ts, params.at(13).getDoubleValue());
                m_pCollector->buffersPush(eqid, 10, ts, params.at(14).getDoubleValue());
                m_pCollector->buffersPush(eqid, 11, ts, params.at(15).getDoubleValue());
                m_pCollector->buffersPush(eqid, 12, ts, params.at(16).getDoubleValue());
                // 定义 BAKE_COOLING 的特定映射
                std::vector<std::pair<int, int>> coolingMapping = {
                    {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6},
                    {11, 7}, {12, 8}, {13, 9}, {14, 10}, {15, 11}, {16, 12}
                };
                CGlass* pGlass = ((CEquipment*)pEquipment)->getGlassFromSlot(0);
                auto& coolingTypes = MACHINE_DATA_TYPES[eqid];
                for (const auto& mapping : coolingMapping) {
                    int paramIndex = mapping.first;
                    int channel = mapping.second;
                    if (paramIndex < params.size() && channel - 1 < coolingTypes.size()) {
                        if (m_pCollector != nullptr)
                            m_pCollector->buffersPush(eqid, channel, ts, params.at(paramIndex).getDoubleValue());
                        if (pGlass != nullptr)
                            pGlass->addSVData(eqid, coolingTypes[channel], ts, params.at(paramIndex).getDoubleValue());
                    }
                }
            }
@@ -3118,67 +3161,21 @@
            // 2) 为通道设置“曲线名称”
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 1, "气囊压力");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 2, "上腔压力");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 3, "管道真空规值");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 4, "腔体真空规值");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 5, "上腔温度1");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 6, "上腔温度2");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 7, "上腔温度3");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 8, "上腔温度4");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 9, "上腔温度5");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 10, "上腔温度6");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 11, "下腔温度1");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 12, "下腔温度2");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 13, "下腔温度3");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 14, "下腔温度4");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 15, "下腔温度5");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, 16, "下腔温度6");
            auto& bonderTypes = MACHINE_DATA_TYPES[EQ_ID_Bonder1];
            for (size_t i = 0; i < bonderTypes.size(); ++i) {
                m_pCollector->buffersSetChannelName(EQ_ID_Bonder1, i + 1, bonderTypes[i].c_str());
                m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, i + 1, bonderTypes[i].c_str());
            }
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 1, "气囊压力");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 2, "上腔压力");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 3, "管道真空规值");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 4, "腔体真空规值");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 5, "上腔温度1");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 6, "上腔温度2");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 7, "上腔温度3");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 8, "上腔温度4");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 9, "上腔温度5");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 10, "上腔温度6");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 11, "下腔温度1");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 12, "下腔温度2");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 13, "下腔温度3");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 14, "下腔温度4");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 15, "下腔温度5");
            m_pCollector->buffersSetChannelName(EQ_ID_Bonder2, 16, "下腔温度6");
            auto& vacuumbakeTypes = MACHINE_DATA_TYPES[EQ_ID_VACUUMBAKE];
            for (size_t i = 0; i < bonderTypes.size(); ++i) {
                m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, i + 1, vacuumbakeTypes[i].c_str());
            }
            m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 1, "A腔真空规值");
            m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 2, "A腔温控1");
            m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 3, "A腔温控2");
            m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 4, "A腔温控4");
            m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 5, "A腔温控5");
            m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 6, "A腔温控6");
            m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 7, "A腔温控7");
            m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 8, "B腔真空规值");
            m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 9, "B腔温控1");
            m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 10, "B腔温控2");
            m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 11, "B腔温控4");
            m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 12, "B腔温控5");
            m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 13, "B腔温控6");
            m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, 14, "B腔温控7");
            m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 1, "A烘烤温控1");
            m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 2, "A烘烤温控2");
            m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 3, "A烘烤温控4");
            m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 4, "A烘烤温控5");
            m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 5, "A烘烤温控6");
            m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 6, "A烘烤温控7");
            m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 7, "B烘烤温控1");
            m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 8, "B烘烤温控2");
            m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 9, "B烘烤温控4");
            m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 10, "B烘烤温控5");
            m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 11, "B烘烤温控6");
            m_pCollector->buffersSetChannelName(EQ_ID_BAKE_COOLING, 12, "B烘烤温控7");
            auto& coolingTypes = MACHINE_DATA_TYPES[EQ_ID_BAKE_COOLING];
            for (size_t i = 0; i < bonderTypes.size(); ++i) {
                m_pCollector->buffersSetChannelName(EQ_ID_VACUUMBAKE, i + 1, coolingTypes[i].c_str());
            }
        }
    }
}
SourceCode/Bond/Servo/CPageGlassList.cpp
@@ -372,6 +372,55 @@
    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 对话框
@@ -426,6 +475,7 @@
    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()
// ===== 私有小工具 =====
@@ -1240,6 +1290,158 @@
    }
}
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.json"), strSanitizedGlassId);
    // 文件保存对话框,设置默认文件名
    CFileDialog fileDialog(FALSE, _T("json"), strDefaultFileName,
        OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
        _T("JSON Files (*.json)|*.json|CSV Files (*.csv)|*.csv||"));
    if (fileDialog.DoModal() != IDOK) return;
    CString filePath = fileDialog.GetPathName();
    CString fileExt = fileDialog.GetFileExt();
    if (fileExt.CompareNoCase(_T("json")) == 0) {
        // 保存为 JSON
        if (!row->pretty.empty()) {
            CFile file;
            if (file.Open(filePath, CFile::modeCreate | CFile::modeWrite)) {
                file.Write(row->pretty.c_str(), row->pretty.length());
                file.Close();
                CString strSuccess;
                strSuccess.Format(_T("记录已保存为JSON文件:\n%s"), filePath);
                AfxMessageBox(strSuccess);
            }
            else {
                AfxMessageBox(_T("保存文件失败"));
            }
        }
        else {
            AfxMessageBox(_T("该记录没有JSON数据"));
        }
    }
    else {
        // 保存为 CSV 格式 - 分段式
        CString csvContent;
        // === 第一部分:基础信息 ===
        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;
        // === 第二部分:工艺参数 ===
        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");
        }
        // 使用辅助函数保存为 UTF-8 编码
        if (WriteAnsiStringAsUtf8ToFile(csvContent, filePath)) {
            CString strSuccess;
            strSuccess.Format(_T("记录已保存为CSV文件:\n%s"), filePath);
            AfxMessageBox(strSuccess);
        }
        else {
            AfxMessageBox(_T("保存文件失败"));
        }
    }
}
void CPageGlassList::OnBnClickedButtonPrevPage()
{
    if (m_nCurPage > 1) {
@@ -1595,4 +1797,4 @@
    }
    return CDialogEx::PreTranslateMessage(pMsg);
}
}
SourceCode/Bond/Servo/CPageGlassList.h
@@ -55,6 +55,7 @@
    void UpdateWipData();
    bool eraseGlassInVector(SERVO::CGlass* pGlass, std::vector<SERVO::CGlass*>& glasses);
    void UpdateWipRow(unsigned int index, SERVO::CGlass* pGlass);
    bool WriteAnsiStringAsUtf8ToFile(const CString& ansiContent, const CString& filePath);
// 对话框数据
#ifdef AFX_DESIGN_TIME
@@ -77,4 +78,6 @@
    afx_msg void OnShowFullText(NMHDR* pNMHDR, LRESULT* pResult);
    virtual BOOL PreTranslateMessage(MSG* pMsg);
    DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnBnClickedButtonExportRow();
};
SourceCode/Bond/Servo/GlassJson.cpp
@@ -190,7 +190,38 @@
        root["path"] = std::move(arr);
    }
    root["payload_version"] = 1;
    // SV数据:三层结构序列化
    {
        Json::Value svDataObj(Json::objectValue);
        const auto& allSVData = g.getAllSVData();
        for (const auto& machinePair : allSVData) {
            int machineId = machinePair.first;
            const auto& dataTypes = machinePair.second;
            Json::Value machineObj(Json::objectValue);
            for (const auto& dataTypePair : dataTypes) {
                const std::string& dataType = dataTypePair.first;
                const auto& dataItems = dataTypePair.second;
                Json::Value dataArray(Json::arrayValue);
                for (const auto& item : dataItems) {
                    Json::Value itemObj(Json::objectValue);
                    // 时间戳转换为毫秒字符串
                    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
                        item.timestamp.time_since_epoch()).count();
                    itemObj["t"] = std::to_string(ms);
                    itemObj["v"] = item.value;
                    dataArray.append(itemObj);
                }
                machineObj[dataType] = dataArray;
            }
            svDataObj[std::to_string(machineId)] = machineObj;
        }
        root["sv_datas"] = svDataObj;
    }
    root["payload_version"] = 2; // 版本升级,因为新增了sv_datas字段
    return root;
}
@@ -308,6 +339,36 @@
            if (JBool(n, "processed", false)) tail->processEnd();
        }
    }
    // SV数据:反序列化三层结构
    if (root.isMember("sv_datas") && root["sv_datas"].isObject()) {
        g.clearAllSVData(); // 清空现有数据
        const auto& svDataObj = root["sv_datas"];
        auto memberNames = svDataObj.getMemberNames();
        for (const auto& machineIdStr : memberNames) {
            int machineId = std::stoi(machineIdStr);
            const auto& machineObj = svDataObj[machineIdStr];
            auto dataTypeNames = machineObj.getMemberNames();
            for (const auto& dataType : dataTypeNames) {
                const auto& dataArray = machineObj[dataType];
                if (dataArray.isArray()) {
                    for (const auto& itemObj : dataArray) {
                        long long timestampMs = 0;
                        double value = 0.0;
                        if (get_ll_from_json(itemObj, "t", timestampMs)) {
                            value = JDouble(itemObj, "v", 0.0);
                            // 添加SV数据
                            g.addSVData(machineId, dataType, timestampMs, value);
                        }
                    }
                }
            }
        }
    }
}
// ==================== 便捷封装 ====================
@@ -333,4 +394,4 @@
    }
    FromJson(j, g);
    return true;
}
}
SourceCode/Bond/Servo/GlassLogDb.cpp
@@ -524,3 +524,55 @@
    ofs.flush();
    return rows;
}
std::optional<GlassLogDb::Row> GlassLogDb::queryById(long long id) {
    std::lock_guard<std::mutex> lk(mtx_);
    const char* sql =
        "SELECT id, cassette_seq_no, job_seq_no, class_id, material_type, state,"
        " IFNULL(strftime('%Y-%m-%d %H:%M:%S', t_start, 'localtime'), ''),"
        " IFNULL(strftime('%Y-%m-%d %H:%M:%S', t_end,   'localtime'), ''),"
        " buddy_id, aoi_result, path, params, pretty"
        " FROM " GLASS_LOG_TABLE
        " WHERE id = ?";
    sqlite3_stmt* stmt = nullptr;
    try {
        throwIf(sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr), db_, "prepare queryById");
        throwIf(sqlite3_bind_int64(stmt, 1, id), db_, "bind id");
        std::optional<Row> result;
        int rc = sqlite3_step(stmt);
        if (rc == SQLITE_ROW) {
            Row row;
            row.id = sqlite3_column_int64(stmt, 0);
            row.cassetteSeqNo = sqlite3_column_int(stmt, 1);
            row.jobSeqNo = sqlite3_column_int(stmt, 2);
            row.classId = safe_text(stmt, 3);
            row.materialType = sqlite3_column_int(stmt, 4);
            row.state = sqlite3_column_int(stmt, 5);
            row.tStart = safe_text(stmt, 6);
            row.tEnd = safe_text(stmt, 7);
            row.buddyId = safe_text(stmt, 8);
            row.aoiResult = sqlite3_column_int(stmt, 9);
            row.path = safe_text(stmt, 10);
            row.params = safe_text(stmt, 11);
            row.pretty = safe_text(stmt, 12);
            result = std::move(row);
        }
        else if (rc != SQLITE_DONE) {
            throwIf(rc, db_, "queryById step");
        }
        sqlite3_finalize(stmt);
        return result;
    }
    catch (const std::exception& e) {
        if (stmt) sqlite3_finalize(stmt);
        TRACE("GlassLogDb::queryById exception: %s\n", e.what());
        return std::nullopt;
    }
}
SourceCode/Bond/Servo/GlassLogDb.h
@@ -96,6 +96,9 @@
        int limit = 50,
        int offset = 0);
    // 按 ID 查询单条记录(包含 pretty 字段)
    std::optional<Row> queryById(long long id);
    // 统计与 filters 相同条件的总数
    long long count(const Filters& filters = {});
SourceCode/Bond/Servo/Servo.rc
Binary files differ
SourceCode/Bond/Servo/resource.h
Binary files differ