已添加11个文件
已修改22个文件
2290 ■■■■■ 文件已修改
.gitignore 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/BLControlsSDK/include/BLLabel.h 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CHMPropertyDlg.cpp 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CHMPropertyDlg.h 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CHMPropertyPage.cpp 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CHMPropertyPage.h 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageGraph1.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageVarialbles.cpp 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CPageVarialbles.h 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CUserEdit2Dlg.cpp 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CUserEdit2Dlg.h 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CUserManager2.cpp 231 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CUserManager2.h 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CUserManager2Dlg.cpp 322 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CUserManager2Dlg.h 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CUserXLogDlg.cpp 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CUserXLogDlg.h 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/HsmsPassive.cpp 426 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/HsmsPassive.h 53 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/LoginDlg2.cpp 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/LoginDlg2.h 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Model.cpp 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/PageRecipe.cpp 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.cpp 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.rc 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj.filters 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ServoDlg.cpp 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ToolUnits.cpp 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ToolUnits.h 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/resource.h 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/stdafx.h 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/USERXLibrary/UserXAPI.h 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -63,3 +63,5 @@
SourceCode/Bond/x64/Debug/HsmsPassive.cache
SourceCode/Bond/x64/Debug/MasterState.dat
SourceCode/Bond/x64/Debug/Recipe/EQ10_Unit0.recipelist
SourceCode/Bond/UserX/
Document/~$Panel Bonder八零联合 SecsTest CheckList_v3.0.xlsx
SourceCode/Bond/BLControlsSDK/include/BLLabel.h
@@ -1,4 +1,4 @@
#if !defined(AFX_BLLABEL_H__A4EABEC5_2E8C_11D1_B79F_00805F9ECE10__INCLUDED_)
#if !defined(AFX_BLLABEL_H__A4EABEC5_2E8C_11D1_B79F_00805F9ECE10__INCLUDED_)
#define AFX_BLLABEL_H__A4EABEC5_2E8C_11D1_B79F_00805F9ECE10__INCLUDED_
#if _MSC_VER >= 1000
SourceCode/Bond/Servo/CHMPropertyDlg.cpp
@@ -5,7 +5,7 @@
#include "Servo.h"
#include "CHMPropertyDlg.h"
#include "afxdialogex.h"
#include "HmTab.h"
#include <algorithm>
// CEquipmentDlg å¯¹è¯æ¡†
@@ -19,6 +19,7 @@
    m_hbrBkgnd = nullptr;
    m_nWndWidth = 0;
    m_nWndHeight = 0;
    m_pTab = nullptr;
}
CHMPropertyDlg::CHMPropertyDlg(const char* pszTitle, int width, int height)
@@ -29,6 +30,7 @@
    m_nWndWidth = width;
    m_nWndHeight = height;
    m_strTitle = pszTitle;
    m_pTab = nullptr;
}
CHMPropertyDlg::~CHMPropertyDlg()
@@ -74,12 +76,13 @@
    // Tab
    CString strTitle;
    CHmTab* m_pTab = CHmTab::Hook(GetDlgItem(IDC_TAB1)->m_hWnd);
    m_pTab = CHmTab::Hook(GetDlgItem(IDC_TAB1)->m_hWnd);
    m_pTab->SetPaddingLeft(20);
    m_pTab->SetItemMarginLeft(18);
    for (int i = 0; i < m_pages.size(); i++) {
        m_pages[i]->SetParent(this);
        m_pages[i]->GetWindowText(strTitle);
        m_pages[i]->OnCreateBtns();
        m_pTab->AddItem(strTitle, i == m_pages.size() - 1);
    }
    m_pTab->SetCurSel(0);
@@ -125,7 +128,7 @@
void CHMPropertyDlg::OnSize(UINT nType, int cx, int cy)
{
    CDialogEx::OnSize(nType, cx, cy);
    if (GetDlgItem(IDC_TAB1) == nullptr) return;
    if (m_pTab == nullptr) return;
    Resize();
}
@@ -157,8 +160,36 @@
    pItem->GetWindowRect(&rcItem);
    pItem->MoveWindow(x2 - rcItem.Width(), y2 - rcItem.Height(),
        rcItem.Width(), rcItem.Height());
    y2 -= rcItem.Height();
    y2 -= 12;
    // å½“前子页按钮(如果有)
    int btnY = y2 - rcItem.Height();
    int btnX = 12;
    y2 -= rcItem.Height() + 12;
    int curIndex = (m_pTab != nullptr) ? m_pTab->GetCurSel() : 0;
    if (curIndex >= 0 && curIndex < (int)m_pages.size()) {
        auto& btnMap = m_pages[curIndex]->getBtns();
        // æŒ‰ BTN_ORDER æŽ’序
        std::vector<std::pair<int, CButton*>> ordered;
        for (auto& kv : btnMap) {
            CButton* btn = kv.second;
            if (btn == nullptr || !::IsWindow(btn->GetSafeHwnd())) continue;
            int order = (int)(INT_PTR)::GetProp(btn->GetSafeHwnd(), _T("BTN_ORDER"));
            ordered.emplace_back(order, btn);
        }
        std::sort(ordered.begin(), ordered.end(), [](const auto& a, const auto& b) {
            return a.first < b.first;
        });
        for (auto& item : ordered) {
            CButton* btn = item.second;
            CRect rcBtn;
            btn->GetWindowRect(&rcBtn);
            if (rcBtn.Width() <= 0 || rcBtn.Height() <= 0) {
                rcBtn.SetRect(0, 0, 80, 28);
            }
            btn->MoveWindow(btnX, btnY, rcBtn.Width(), rcBtn.Height());
            btnX += rcBtn.Width() + 8;
        }
    }
    // åˆ†éš”线
    pItem = GetDlgItem(IDC_LINE1);
@@ -185,6 +216,50 @@
    for (int i = 0; i < m_pages.size(); i++) {
        m_pages[i]->ShowWindow(i == index ? SW_SHOW : SW_HIDE);
    }
    // éšè—æ‰€æœ‰é¡µé¢çš„æŒ‰é’®
    for (auto page : m_pages) {
        for (auto& kv : page->getBtns()) {
            CButton* btn = kv.second;
            if (btn != nullptr && ::IsWindow(btn->GetSafeHwnd())) {
                btn->ShowWindow(SW_HIDE);
            }
        }
    }
    // åˆ›å»ºå¹¶æ˜¾ç¤ºå½“前页面的按钮
    auto& btns = m_pages[index]->getBtns();
    if (!btns.empty()) {
        CRect rcClient;
        GetClientRect(&rcClient);
        const int margin = 12;
        const int spacing = 8;
        int x = margin;
        int y = rcClient.bottom - 40; // é¢„留底部区域
        // æŒ‰ BTN_ORDER æŽ’序
        std::vector<std::pair<int, CButton*>> ordered;
        for (auto& kv : btns) {
            CButton* btn = kv.second;
            if (btn == nullptr || !::IsWindow(btn->GetSafeHwnd())) continue;
            int order = (int)(INT_PTR)::GetProp(btn->GetSafeHwnd(), _T("BTN_ORDER"));
            ordered.emplace_back(order, btn);
        }
        std::sort(ordered.begin(), ordered.end(), [](const auto& a, const auto& b) {
            return a.first < b.first;
        });
        for (auto& item : ordered) {
            CButton* btn = item.second;
            CRect rc;
            btn->GetWindowRect(&rc);
            if (rc.Width() <= 0 || rc.Height() <= 0) {
                rc.SetRect(0, 0, 80, 28);
            }
            btn->MoveWindow(x, y, rc.Width(), rc.Height());
            btn->ShowWindow(SW_SHOW);
            x += rc.Width() + spacing;
        }
    }
}
void CHMPropertyDlg::SetWindowSize(int width, int height)
@@ -210,3 +285,22 @@
        }
    }
}
BOOL CHMPropertyDlg::OnCommand(WPARAM wParam, LPARAM lParam)
{
    UINT code = HIWORD(wParam);
    HWND hCtrl = (HWND)lParam;
    if (code == BN_CLICKED && hCtrl != nullptr) {
        for (auto page : m_pages) {
            for (auto& kv : page->getBtns()) {
                if (kv.second != nullptr && kv.second->GetSafeHwnd() == hCtrl) {
                    page->HandleBtnClick(hCtrl);
                    return TRUE;
                }
            }
        }
    }
    return CDialogEx::OnCommand(wParam, lParam);
}
SourceCode/Bond/Servo/CHMPropertyDlg.h
@@ -1,6 +1,7 @@
#pragma once
#include <vector>
#include "CHMPropertyPage.h"
#include "HmTab.h"
// CEquipmentDlg å¯¹è¯æ¡†
@@ -30,6 +31,7 @@
    std::vector<CHMPropertyPage*> m_pages;
    int m_nWndWidth;
    int m_nWndHeight;
    CHmTab* m_pTab;
// å¯¹è¯æ¡†æ•°æ®
@@ -49,4 +51,5 @@
    afx_msg void OnTabSelChanged(NMHDR* nmhdr, LRESULT* result);
    afx_msg void OnBnClickedOk();
    afx_msg void OnBnClickedButtonApply();
    virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
};
SourceCode/Bond/Servo/CHMPropertyPage.cpp
@@ -18,3 +18,75 @@
{
}
void CHMPropertyPage::OnCreateBtns()
{
}
CButton* CHMPropertyPage::CreateBtn(const char* name, int w, int h, const UINT id)
{
    std::string key = std::string(name);
    auto it = m_btns.find(key);
    if (it != m_btns.end()) {
        return it->second;
    }
    CButton* pBtn = new CButton();
    pBtn->Create(name, WS_CHILD, CRect(0, 0, w, h), GetParent(), id);
    // ä½¿ç”¨é»˜è®¤GUI字体
    HFONT hFont = (HFONT)::GetStockObject(DEFAULT_GUI_FONT);
    if (hFont != nullptr) {
        pBtn->SetFont(CFont::FromHandle(hFont), FALSE);
    }
    ::SetProp(pBtn->GetSafeHwnd(), _T("BTN_ORDER"), (HANDLE)(INT_PTR)m_btnOrderSeq++);
    m_btns[key] = pBtn;
    return pBtn;
}
CButton* CHMPropertyPage::GetBtnByName(const char* name)
{
    auto it = m_btns.find(std::string(name));
    if (it != m_btns.end()) {
        return it->second;
    }
    return nullptr;
}
std::map<std::string, CButton*>& CHMPropertyPage::getBtns()
{
    return m_btns;
}
void CHMPropertyPage::HandleBtnClick(HWND hBtn)
{
    for (auto& kv : m_btns) {
        if (kv.second != nullptr && kv.second->GetSafeHwnd() == hBtn) {
            OnClickedBtn(kv.first.c_str());
            break;
        }
    }
}
BEGIN_MESSAGE_MAP(CHMPropertyPage, CDialogEx)
    ON_WM_DESTROY()
END_MESSAGE_MAP()
void CHMPropertyPage::OnDestroy()
{
    CDialogEx::OnDestroy();
    for (auto& kv : m_btns) {
        CButton* btn = kv.second;
        if (btn != nullptr) {
            if (::IsWindow(btn->GetSafeHwnd())) {
                ::RemoveProp(btn->GetSafeHwnd(), _T("BTN_ORDER"));
            }
            if (::IsWindow(btn->GetSafeHwnd())) {
                btn->DestroyWindow();
            }
            delete btn;
        }
    }
    m_btns.clear();
}
SourceCode/Bond/Servo/CHMPropertyPage.h
@@ -1,14 +1,29 @@
#pragma once
#pragma once
#include <map>
#include <string>
class CHMPropertyPage : public CDialogEx
{
    DECLARE_DYNAMIC(CHMPropertyPage)
public:
    CHMPropertyPage(UINT nID, CWnd* pPage);            // æ ‡å‡†æž„造函数
    virtual ~CHMPropertyPage();                        // æžæž„函数
    CHMPropertyPage(UINT nID, CWnd* pPage);            // æ ‡å‡†æž„造函数
    virtual ~CHMPropertyPage();                        // æžæž„函数
    virtual void OnApply();
    virtual void OnCreateBtns();
    afx_msg void OnDestroy();
    std::map<std::string, CButton*>& getBtns();
    CButton* GetBtnByName(const char* name);
    void HandleBtnClick(HWND hBtn);
protected:
    // å­ç±»å¯é‡å†™ï¼šæ–°å¢ž/删除/编辑按钮点击处理
    virtual void OnClickedBtn(const char* btnName) {};
protected:
    CButton* CreateBtn(const char* name, int w, int h, const UINT id);
    std::map<std::string, CButton*> m_btns;
    int m_btnOrderSeq{ 0 };
    DECLARE_MESSAGE_MAP()
};
SourceCode/Bond/Servo/CPageGraph1.cpp
@@ -180,7 +180,7 @@
{
    CDialogEx::OnInitDialog();
    InitRxWindows();
    SetTimer(TIMER_ID_DEVICE_STATUS, 3000, nullptr);
    SetTimer(TIMER_ID_DEVICE_STATUS, 800, nullptr);
    SetTimer(TIMER_ID_ROBOT_STATUS, 1000, nullptr); // æ¯ 1000ms æ›´æ–°ä¸€æ¬¡çŠ¶æ€
    // å›¾ç¤º
SourceCode/Bond/Servo/CPageVarialbles.cpp
@@ -32,6 +32,7 @@
    ON_WM_CTLCOLOR()
    ON_WM_DESTROY()
    ON_WM_SIZE()
    ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST1, &CPageVarialbles::OnLvnItemchangedList1)
END_MESSAGE_MAP()
@@ -130,3 +131,54 @@
        m_listCtrl.SetItemText(index, 4, item->getRemark().c_str());
    }
}
void CPageVarialbles::OnCreateBtns()
{
    const int BTN_W = 80;
    const int BTN_H = 28;
    CreateBtn(_T("新增"), BTN_W, BTN_H, 1001);
    CreateBtn(_T("删除"), BTN_W, BTN_H, 1002)->EnableWindow(FALSE);
    CreateBtn(_T("编辑"), BTN_W, BTN_H, 1003)->EnableWindow(FALSE);
}
void CPageVarialbles::OnLvnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult)
{
    LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
    int nSelCount = m_listCtrl.GetSelectedCount();
    // æ ¹æ®é€‰ä¸­çŠ¶æ€å¯ç”¨/禁用按钮
    if (CButton* pDel = GetBtnByName("删除")) {
        pDel->EnableWindow(nSelCount > 0);
    }
    if (CButton* pEdit = GetBtnByName("编辑")) {
        pEdit->EnableWindow(nSelCount > 0);
    }
    *pResult = 0;
}
void CPageVarialbles::OnClickedBtn(const char* btnName)
{
    ASSERT(btnName);
    if (_strcmpi(btnName, "新增") == 0) {
        // TODO: æ–°å¢žé€»è¾‘
    }
    else if (_strcmpi(btnName, "删除") == 0) {
        POSITION pos = m_listCtrl.GetFirstSelectedItemPosition();
        if (pos == nullptr) return;
        int nItem = m_listCtrl.GetNextSelectedItem(pos);
        auto pVar = reinterpret_cast<SERVO::CVariable*>(m_listCtrl.GetItemData(nItem));
        if (pVar == nullptr) return;
        int ret = theApp.m_model.m_hsmsPassive.deleteVariable(pVar->getVarialbleId());
        if (ret == 0) {
            m_listCtrl.DeleteAllItems();
            loadVariables();
            if (CButton* pDel = GetBtnByName("删除")) pDel->EnableWindow(FALSE);
            if (CButton* pEdit = GetBtnByName("编辑")) pEdit->EnableWindow(FALSE);
        }
    }
    else if (_strcmpi(btnName, "编辑") == 0) {
        // TODO: ç¼–辑逻辑
    }
}
SourceCode/Bond/Servo/CPageVarialbles.h
@@ -14,9 +14,13 @@
    virtual ~CPageVarialbles();
    virtual void OnApply();
    void loadVariables();
    virtual void OnCreateBtns();
private:
    CListCtrlEx m_listCtrl;
protected:
    virtual void OnClickedBtn(const char* btnName) override;
// å¯¹è¯æ¡†æ•°æ®
@@ -33,4 +37,5 @@
    afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
    afx_msg void OnDestroy();
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnLvnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult);
};
SourceCode/Bond/Servo/CUserEdit2Dlg.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,125 @@
#include "stdafx.h"
#include "CUserEdit2Dlg.h"
#include "CUserManager2.h"
#include "resource.h"
IMPLEMENT_DYNAMIC(CUserEdit2Dlg, CDialogEx)
CUserEdit2Dlg::CUserEdit2Dlg(bool editMode, CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_DIALOG_USER_EDIT2, pParent)
{
    m_bEditMode = editMode;
}
CUserEdit2Dlg::~CUserEdit2Dlg()
{
}
void CUserEdit2Dlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Text(pDX, IDC_EDIT_USER_ACCOUNT, m_strUsername);
    DDX_Text(pDX, IDC_EDIT_USER_DISPLAY, m_strDisplayName);
    DDX_Text(pDX, IDC_EDIT_USER_PASSWORD, m_strPassword);
    DDX_CBString(pDX, IDC_COMBO_USER_ROLE, m_strRole);
    DDX_Check(pDX, IDC_CHECK_USER_ENABLED, m_bEnabled);
}
BEGIN_MESSAGE_MAP(CUserEdit2Dlg, CDialogEx)
END_MESSAGE_MAP()
BOOL CUserEdit2Dlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();
    if (m_bEditMode) {
        if (auto pEdit = GetDlgItem(IDC_EDIT_USER_ACCOUNT)) {
            pEdit->EnableWindow(FALSE);
        }
    }
    UpdateData(FALSE);
    auto roles = CUserManager2::getInstance().getRoles();
    CComboBox* pCombo = (CComboBox*)GetDlgItem(IDC_COMBO_USER_ROLE);
    if (pCombo) {
        int selected = -1;
        for (const auto& role : roles) {
            CString text(role.name.c_str());
            int idx = pCombo->AddString(text);
            if (selected == -1 && m_strRole.CompareNoCase(text) == 0) {
                selected = idx;
            }
        }
        if (selected >= 0) {
            pCombo->SetCurSel(selected);
        }
        else if (pCombo->GetCount() > 0) {
            pCombo->SetCurSel(0);
            CString text;
            pCombo->GetLBText(0, text);
            if (m_strRole.IsEmpty()) {
                m_strRole = text;
            }
        }
    }
    if (auto pPwd = GetDlgItem(IDC_EDIT_USER_PASSWORD)) {
        pPwd->EnableWindow(!m_bEditMode);
        if (m_bEditMode) {
            pPwd->SetWindowText(_T(""));
        }
    }
    return TRUE;
}
void CUserEdit2Dlg::OnOK()
{
    UpdateData(TRUE);
    CString user = m_strUsername;
    user.Trim();
    CString role = m_strRole;
    role.Trim();
    CString password = m_strPassword;
    password.Trim();
    if (m_bEditMode) {
        password.Empty();
    }
    if (!m_bEditMode) {
        if (user.IsEmpty()) {
            AfxMessageBox(_T("请输入账号"));
            return;
        }
        if (password.IsEmpty()) {
            AfxMessageBox(_T("请输入密码"));
            return;
        }
    }
    if (role.IsEmpty()) {
        AfxMessageBox(_T("请选择角色"));
        return;
    }
    if (auto pCombo = (CComboBox*)GetDlgItem(IDC_COMBO_USER_ROLE)) {
        int sel = pCombo->GetCurSel();
        if (sel != CB_ERR) {
            CString text;
            pCombo->GetLBText(sel, text);
            if (!text.IsEmpty()) {
                m_strRole = text;
            }
        }
    }
    m_strUsername = user;
    m_strPassword = password;
    CDialogEx::OnOK();
}
SourceCode/Bond/Servo/CUserEdit2Dlg.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
#pragma once
class CUserEdit2Dlg : public CDialogEx
{
    DECLARE_DYNAMIC(CUserEdit2Dlg)
public:
    CUserEdit2Dlg(bool editMode = false, CWnd* pParent = nullptr);
    virtual ~CUserEdit2Dlg();
    CString m_strUsername;
    CString m_strDisplayName;
    CString m_strPassword;
    CString m_strRole;
    BOOL m_bEnabled = TRUE;
    bool m_bEditMode = false;
protected:
    virtual void DoDataExchange(CDataExchange* pDX);
    virtual BOOL OnInitDialog();
    afx_msg void OnOK();
    DECLARE_MESSAGE_MAP()
};
SourceCode/Bond/Servo/CUserManager2.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,231 @@
#include "stdafx.h"
#include "CUserManager2.h"
#include "ToolUnits.h"
#include <vector>
#include <map>
#include <utility>
#include <algorithm>
#include <sstream>
#include <cwchar>
std::vector<std::wstring> SplitLines(const std::wstring& text)
{
    std::wstringstream ss(text); std::vector<std::wstring> v; std::wstring line; while (std::getline(ss, line)) v.push_back(line); return v;
}
std::vector<std::wstring> SplitByDelimiter(const std::wstring& text, wchar_t delimiter)
{
    std::vector<std::wstring> parts;
    size_t start = 0;
    while (start <= text.length()) {
        size_t pos = text.find(delimiter, start);
        if (pos == std::wstring::npos) {
            parts.push_back(text.substr(start));
            break;
        }
        parts.push_back(text.substr(start, pos - start));
        start = pos + 1;
    }
    return parts;
}
template<typename Fn>
std::wstring ReadBufferVia(Fn fn)
{
    int need = fn(nullptr, 0); if (need <= 0) return L"";
    std::wstring buf; buf.resize((size_t)need);
    int rc = fn(buf.data(), need);
    if (rc == 0) { if (!buf.empty() && buf.back() == L'\0') buf.pop_back(); return buf; }
    return L"";
}
// èŽ·å–å•ä¾‹å®žä¾‹
CUserManager2& CUserManager2::getInstance() {
    static CUserManager2 instance;
    return instance;
}
CUserManager2::CUserManager2()
{
}
CUserManager2::~CUserManager2()
{
}
void CUserManager2::init(const char* pszDir)
{
    std::wstring dir = CToolUnits::AnsiToWString(std::string(pszDir));
    UX_Init(dir.c_str());
    wchar_t buffer[1024];
    UX_GetUsers(buffer, 1024);
    bool hasAny = false;
    for (auto& ln : SplitLines(buffer)) { if (!ln.empty()) { hasAny = true; break; } }
    if (!hasAny) {
        const wchar_t* roles = L"Admin:100\nEE:80\nPE:50\nOperator:10\n";
        (void)UX_SetRoleDefinitions(roles);
        (void)UX_AddUser(L"admin", L"Administrator", L"admin123", L"Admin");
        UX_DefineAction(L"start", L"启动机台", L"Operator");
        UX_DefineAction(L"stop", L"停机", L"Operator");
        UX_DefineAction(L"recipe", L"编辑配方", L"Engineer");
    }
}
bool CUserManager2::login(const char* pszAccount, const char* pszPwd)
{
    std::wstring strUser, strPwd;
    strUser = CToolUnits::AnsiToWString(std::string(pszAccount));
    strPwd = CToolUnits::AnsiToWString(std::string(pszPwd));
    int rc = UX_Login(strUser.c_str(), strPwd.c_str());
    return rc == UX_OK;
}
bool CUserManager2::isLoggedIn()
{
    return UX_IsLoggedIn();
}
std::string CUserManager2::getCurrentUserName()
{
    std::string strName;
    int need = UX_GetCurrentUser(nullptr, 0);
    std::wstring buf; buf.resize((size_t)need);
    if (UX_GetCurrentUser(buf.data(), need) == UX_OK) {
        if (!buf.empty() && buf.back() == L'\0')
            buf.pop_back();
        strName = CToolUnits::WStringToAnsi(buf);
    }
    return strName;
}
bool CUserManager2::IsAdminCurrent()
{
    if (UX_IsLoggedIn() != 1) return false;
    int need = UX_GetCurrentUser(nullptr, 0); if (need <= 0) return false;
    std::wstring user; user.resize((size_t)need);
    if (UX_GetCurrentUser(user.data(), need) != 0) return false;
    if (!user.empty() && user.back() == L'\0') user.pop_back();
    int maxLvl = 0; auto rolesTxt = ReadBufferVia([](wchar_t* b, int n) { return UX_GetRoles(b, n); });
    for (auto& ln : SplitLines(rolesTxt)) { size_t p = ln.find(L':'); if (p != std::wstring::npos) { int lvl = _wtoi(ln.substr(p + 1).c_str()); if (lvl > maxLvl) maxLvl = lvl; } }
    int myLvl = 0; auto usersTxt = ReadBufferVia([](wchar_t* b, int n) { return UX_GetUsers(b, n); });
    for (auto& ln : SplitLines(usersTxt)) {
        if (ln.empty()) continue; size_t p1 = ln.find(L','), p2 = ln.find(L',', p1 == std::wstring::npos ? 0 : p1 + 1), p3 = ln.find(L',', p2 == std::wstring::npos ? 0 : p2 + 1);
        std::wstring name = (p1 == std::wstring::npos ? ln : ln.substr(0, p1)); if (name == user) { if (p2 != std::wstring::npos) { std::wstring lvlS = ln.substr(p2 + 1, (p3 == std::wstring::npos ? ln.size() : p3) - (p2 + 1)); myLvl = _wtoi(lvlS.c_str()); } break; }
    }
    return (maxLvl > 0) && (myLvl >= maxLvl);
}
std::vector<CUserManager2::RoleInfo> CUserManager2::getRoles()
{
    std::vector<RoleInfo> roles;
    auto txt = ReadBufferVia([](wchar_t* b, int n) { return UX_GetRoles(b, n); });
    if (txt.empty()) {
        return roles;
    }
    for (auto& line : SplitLines(txt)) {
        if (line.empty()) continue;
        size_t pos = line.find(L':');
        RoleInfo info;
        info.name = (pos == std::wstring::npos) ? line : line.substr(0, pos);
        if (pos != std::wstring::npos) {
            info.level = _wtoi(line.substr(pos + 1).c_str());
        }
        if (!info.name.empty()) {
            roles.push_back(std::move(info));
        }
    }
    std::sort(roles.begin(), roles.end(), [](const RoleInfo& a, const RoleInfo& b) {
        if (a.level == b.level) {
            return a.name < b.name;
        }
        return a.level > b.level;
    });
    return roles;
}
std::vector<CUserManager2::UserInfo> CUserManager2::getUsers()
{
    std::vector<UserInfo> users;
    auto txt = ReadBufferVia([](wchar_t* b, int n) { return UX_GetUsers(b, n); });
    if (txt.empty()) {
        return users;
    }
    std::map<int, std::wstring> roleMap;
    for (auto& role : getRoles()) {
        roleMap[role.level] = role.name;
    }
    for (auto& line : SplitLines(txt)) {
        if (line.empty()) continue;
        auto parts = SplitByDelimiter(line, L',');
        UserInfo info;
        if (!parts.empty()) info.userName = parts[0];
        if (parts.size() > 1) info.displayName = parts[1];
        if (parts.size() > 2) info.roleLevel = _wtoi(parts[2].c_str());
        if (parts.size() > 3) info.enabled = (_wtoi(parts[3].c_str()) != 0);
        auto it = roleMap.find(info.roleLevel);
        if (it != roleMap.end()) {
            info.roleName = it->second;
        }
        users.push_back(std::move(info));
    }
    return users;
}
int CUserManager2::addUser(const std::wstring& userName, const std::wstring& displayName,
    const std::wstring& password, const std::wstring& roleName, bool enabled)
{
    int rc = UX_AddUser(userName.c_str(), displayName.c_str(), password.c_str(), roleName.c_str());
    if (rc == UX_OK && !enabled) {
        UX_EnableUser(userName.c_str(), 0);
    }
    return rc;
}
int CUserManager2::updateUser(const std::wstring& userName, const std::wstring& displayName,
    const std::wstring& password, const std::wstring& roleName, bool enabled)
{
    const wchar_t* disp = displayName.empty() ? nullptr : displayName.c_str();
    const wchar_t* pwd = password.empty() ? nullptr : password.c_str();
    const wchar_t* role = roleName.empty() ? nullptr : roleName.c_str();
    return UX_UpdateUser(userName.c_str(), disp, pwd, role, enabled ? 1 : 0);
}
int CUserManager2::deleteUser(const std::wstring& userName)
{
    return UX_DeleteUser(userName.c_str());
}
int CUserManager2::setUserEnabled(const std::wstring& userName, bool enabled)
{
    return UX_EnableUser(userName.c_str(), enabled ? 1 : 0);
}
int CUserManager2::resetPassword(const std::wstring& userName, const std::wstring& password)
{
    return UX_ResetPassword(userName.c_str(), password.c_str());
}
SourceCode/Bond/Servo/CUserManager2.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
#pragma once
#include <string>
#include <vector>
class CUserManager2
{
public:
    static CUserManager2& getInstance();
    CUserManager2(const CUserManager2&) = delete;
    CUserManager2& operator=(const CUserManager2&) = delete;
    struct RoleInfo
    {
        std::wstring name;
        int level = 0;
    };
    struct UserInfo
    {
        std::wstring userName;
        std::wstring displayName;
        std::wstring roleName;
        int roleLevel = 0;
        bool enabled = false;
    };
public:
    void init(const char* pszDir);
    bool login(const char* pszAccount, const char* pszPwd);
    bool isLoggedIn();
    std::string getCurrentUserName();
    bool IsAdminCurrent();
    std::vector<RoleInfo> getRoles();
    std::vector<UserInfo> getUsers();
    int addUser(const std::wstring& userName, const std::wstring& displayName,
        const std::wstring& password, const std::wstring& roleName, bool enabled);
    int updateUser(const std::wstring& userName, const std::wstring& displayName,
        const std::wstring& password, const std::wstring& roleName, bool enabled);
    int deleteUser(const std::wstring& userName);
    int setUserEnabled(const std::wstring& userName, bool enabled);
    int resetPassword(const std::wstring& userName, const std::wstring& password);
private:
    CUserManager2();
    ~CUserManager2();
};
SourceCode/Bond/Servo/CUserManager2Dlg.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,322 @@
// CUserManager2Dlg.cpp
//
#include "stdafx.h"
#include "Servo.h"
#include "CUserManager2Dlg.h"
#include "afxdialogex.h"
#include "CUserEdit2Dlg.h"
#include "InputDialog.h"
#include "resource.h"
#include "ToolUnits.h"
IMPLEMENT_DYNAMIC(CUserManager2Dlg, CDialogEx)
CUserManager2Dlg::CUserManager2Dlg(CWnd* pParent)
    : CDialogEx(IDD_DIALOG_USER_MANAGER2, pParent)
{
}
CUserManager2Dlg::~CUserManager2Dlg()
{
}
void CUserManager2Dlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_LIST1, m_listUsers);
}
BEGIN_MESSAGE_MAP(CUserManager2Dlg, CDialogEx)
    ON_WM_SIZE()
    ON_BN_CLICKED(IDC_BUTTON_ADD, &CUserManager2Dlg::OnBnClickedButtonAdd)
    ON_BN_CLICKED(IDC_BUTTON_EDIT, &CUserManager2Dlg::OnBnClickedButtonEdit)
    ON_BN_CLICKED(IDC_BUTTON_DEL, &CUserManager2Dlg::OnBnClickedButtonDel)
    ON_BN_CLICKED(IDC_BUTTON_RESET_PWD, &CUserManager2Dlg::OnBnClickedButtonResetPwd)
    ON_BN_CLICKED(IDC_BUTTON_ENABLE, &CUserManager2Dlg::OnBnClickedButtonEnable)
    ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST1, &CUserManager2Dlg::OnLvnItemchangedUsers)
END_MESSAGE_MAP()
BOOL CUserManager2Dlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();
    InitList();
    RefreshUserList();
    UpdateButtonState();
    return TRUE;
}
void CUserManager2Dlg::OnSize(UINT nType, int cx, int cy)
{
    CDialogEx::OnSize(nType, cx, cy);
}
void CUserManager2Dlg::InitList()
{
    DWORD dwStyle = m_listUsers.GetExtendedStyle();
    m_listUsers.SetExtendedStyle(dwStyle | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
    m_listUsers.InsertColumn(0, _T("账号"), LVCFMT_LEFT, 90);
    m_listUsers.InsertColumn(1, _T("显示名"), LVCFMT_LEFT, 100);
    m_listUsers.InsertColumn(2, _T("角色"), LVCFMT_LEFT, 80);
    m_listUsers.InsertColumn(3, _T("级别"), LVCFMT_LEFT, 60);
    m_listUsers.InsertColumn(4, _T("状态"), LVCFMT_LEFT, 70);
}
void CUserManager2Dlg::RefreshUserList()
{
    CString selectedName;
    int currentIndex = GetSelectedIndex();
    if (currentIndex >= 0 && currentIndex < static_cast<int>(m_users.size())) {
        selectedName = m_users[currentIndex].userName.c_str();
    }
    m_users = CUserManager2::getInstance().getUsers();
    m_listUsers.DeleteAllItems();
    for (size_t i = 0; i < m_users.size(); ++i) {
        const auto& user = m_users[i];
        CString account(user.userName.c_str());
        CString display(user.displayName.c_str());
        CString role(user.roleName.empty() ? L"-" : user.roleName.c_str());
        CString level;
        level.Format(_T("%d"), user.roleLevel);
        CString state = user.enabled ? _T("启用") : _T("禁用");
        int row = m_listUsers.InsertItem(static_cast<int>(i), account);
        m_listUsers.SetItemText(row, 1, display);
        m_listUsers.SetItemText(row, 2, role);
        m_listUsers.SetItemText(row, 3, level);
        m_listUsers.SetItemText(row, 4, state);
    }
    if (!selectedName.IsEmpty()) {
        for (int i = 0; i < m_listUsers.GetItemCount(); ++i) {
            if (selectedName.CompareNoCase(m_listUsers.GetItemText(i, 0)) == 0) {
                m_listUsers.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
                m_listUsers.EnsureVisible(i, FALSE);
                break;
            }
        }
    }
    UpdateButtonState();
}
void CUserManager2Dlg::UpdateButtonState()
{
    int index = GetSelectedIndex();
    BOOL hasSelection = (index >= 0);
    auto enable = [&](int id, BOOL enableFlag) {
        if (CWnd* p = GetDlgItem(id)) {
            p->EnableWindow(enableFlag);
        }
    };
    enable(IDC_BUTTON_EDIT, hasSelection);
    enable(IDC_BUTTON_DEL, hasSelection);
    enable(IDC_BUTTON_RESET_PWD, hasSelection);
    enable(IDC_BUTTON_ENABLE, hasSelection);
    CString toggleText = _T("禁用/启用");
    if (const auto* user = GetSelectedUser()) {
        toggleText = user->enabled ? _T("禁用") : _T("启用");
        if (IsCurrentUser(*user)) {
            enable(IDC_BUTTON_DEL, FALSE);
            enable(IDC_BUTTON_ENABLE, FALSE);
        }
    }
    SetDlgItemText(IDC_BUTTON_ENABLE, toggleText);
}
int CUserManager2Dlg::GetSelectedIndex() const
{
    if (!::IsWindow(m_listUsers.GetSafeHwnd())) {
        return -1;
    }
    return m_listUsers.GetNextItem(-1, LVNI_SELECTED);
}
const CUserManager2::UserInfo* CUserManager2Dlg::GetSelectedUser() const
{
    int index = GetSelectedIndex();
    if (index < 0 || index >= static_cast<int>(m_users.size())) {
        return nullptr;
    }
    return &m_users[index];
}
std::wstring CUserManager2Dlg::ToWString(const CString& text) const
{
    CString trimmed(text);
    trimmed.Trim();
    std::string str((LPTSTR)(LPCTSTR)trimmed);
    return CToolUnits::AnsiToWString(str);
}
void CUserManager2Dlg::ShowErrorMessage(const CString& action, int code)
{
    const wchar_t* detail = UX_ErrorMessage(code);
    CString msg;
    msg.Format(_T("%s: %s"), action.GetString(), detail ? detail : L"Unknown");
    AfxMessageBox(msg, MB_ICONERROR);
}
bool CUserManager2Dlg::IsCurrentUser(const CUserManager2::UserInfo& info) const
{
    CString current(CUserManager2::getInstance().getCurrentUserName().c_str());
    CString account(info.userName.c_str());
    current.Trim();
    account.Trim();
    return !current.IsEmpty() && current.CompareNoCase(account) == 0;
}
void CUserManager2Dlg::OnBnClickedButtonAdd()
{
    CUserEdit2Dlg dlg(false, this);
    if (dlg.DoModal() != IDOK) {
        return;
    }
    CString account = dlg.m_strUsername;
    account.Trim();
    CString display = dlg.m_strDisplayName;
    display.Trim();
    if (display.IsEmpty()) {
        display = account;
    }
    CString role = dlg.m_strRole;
    role.Trim();
    CString password = dlg.m_strPassword;
    password.Trim();
    int rc = CUserManager2::getInstance().addUser(ToWString(account), ToWString(display), ToWString(password), ToWString(role), dlg.m_bEnabled == TRUE);
    if (rc == UX_OK) {
        RefreshUserList();
        AfxMessageBox(_T("新增用户成功"));
    }
    else {
        ShowErrorMessage(_T("新增用户失败"), rc);
    }
}
void CUserManager2Dlg::OnBnClickedButtonEdit()
{
    const auto* user = GetSelectedUser();
    if (!user) {
        AfxMessageBox(_T("请选择用户"));
        return;
    }
    CUserEdit2Dlg dlg(true, this);
    dlg.m_strUsername = user->userName.c_str();
    dlg.m_strDisplayName = user->displayName.c_str();
    dlg.m_strRole = user->roleName.c_str();
    dlg.m_bEnabled = user->enabled ? TRUE : FALSE;
    if (dlg.DoModal() != IDOK) {
        return;
    }
    CString display = dlg.m_strDisplayName;
    display.Trim();
    CString password = dlg.m_strPassword;
    password.Trim();
    CString role = dlg.m_strRole;
    role.Trim();
    int rc = CUserManager2::getInstance().updateUser(user->userName, ToWString(display), ToWString(password), ToWString(role), dlg.m_bEnabled == TRUE);
    if (rc == UX_OK) {
        RefreshUserList();
        AfxMessageBox(_T("保存成功"));
    }
    else {
        ShowErrorMessage(_T("保存失败"), rc);
    }
}
void CUserManager2Dlg::OnBnClickedButtonDel()
{
    const auto* user = GetSelectedUser();
    if (!user) {
        AfxMessageBox(_T("请选择用户"));
        return;
    }
    if (IsCurrentUser(*user)) {
        AfxMessageBox(_T("不能删除当前登录用户"));
        return;
    }
    CString prompt;
    prompt.Format(_T("确定删除用户 %s ?"), CString(user->userName.c_str()));
    if (AfxMessageBox(prompt, MB_ICONQUESTION | MB_YESNO) != IDYES) {
        return;
    }
    int rc = CUserManager2::getInstance().deleteUser(user->userName);
    if (rc == UX_OK) {
        RefreshUserList();
        AfxMessageBox(_T("删除成功"));
    }
    else {
        ShowErrorMessage(_T("删除失败"), rc);
    }
}
void CUserManager2Dlg::OnBnClickedButtonResetPwd()
{
    const auto* user = GetSelectedUser();
    if (!user) {
        AfxMessageBox(_T("请选择用户"));
        return;
    }
    CInputDialog dlg(_T("重置密码"), _T("请输入新密码:"), this);
    if (dlg.DoModal() != IDOK) {
        return;
    }
    CString password = dlg.GetInputText();
    password.Trim();
    if (password.IsEmpty()) {
        AfxMessageBox(_T("密码不能为空"));
        return;
    }
    int rc = CUserManager2::getInstance().resetPassword(user->userName, ToWString(password));
    if (rc == UX_OK) {
        AfxMessageBox(_T("密码已重置"));
    }
    else {
        ShowErrorMessage(_T("重置失败"), rc);
    }
}
void CUserManager2Dlg::OnBnClickedButtonEnable()
{
    const auto* user = GetSelectedUser();
    if (!user) {
        AfxMessageBox(_T("请选择用户"));
        return;
    }
    bool enable = !user->enabled;
    int rc = CUserManager2::getInstance().setUserEnabled(user->userName, enable);
    if (rc == UX_OK) {
        RefreshUserList();
        CString msg = enable ? _T("用户已启用") : _T("用户已禁用");
        AfxMessageBox(msg);
    }
    else {
        ShowErrorMessage(_T("操作失败"), rc);
    }
}
void CUserManager2Dlg::OnLvnItemchangedUsers(NMHDR* /*pNMHDR*/, LRESULT* pResult)
{
    UpdateButtonState();
    *pResult = 0;
}
SourceCode/Bond/Servo/CUserManager2Dlg.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
#pragma once
#include "CUserManager2.h"
#include <string>
#include <vector>
class CUserManager2Dlg : public CDialogEx
{
    DECLARE_DYNAMIC(CUserManager2Dlg)
public:
    CUserManager2Dlg(CWnd* pParent = nullptr);
    virtual ~CUserManager2Dlg();
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_DIALOG_USER_MANAGER2 };
#endif
protected:
    virtual void DoDataExchange(CDataExchange* pDX);
    DECLARE_MESSAGE_MAP()
public:
    virtual BOOL OnInitDialog();
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnBnClickedButtonAdd();
    afx_msg void OnBnClickedButtonEdit();
    afx_msg void OnBnClickedButtonDel();
    afx_msg void OnBnClickedButtonResetPwd();
    afx_msg void OnBnClickedButtonEnable();
    afx_msg void OnLvnItemchangedUsers(NMHDR* pNMHDR, LRESULT* pResult);
private:
    CListCtrl m_listUsers;
    std::vector<CUserManager2::UserInfo> m_users;
    void InitList();
    void RefreshUserList();
    void UpdateButtonState();
    int GetSelectedIndex() const;
    const CUserManager2::UserInfo* GetSelectedUser() const;
    std::wstring ToWString(const CString& text) const;
    void ShowErrorMessage(const CString& action, int code);
    bool IsCurrentUser(const CUserManager2::UserInfo& info) const;
};
SourceCode/Bond/Servo/CUserXLogDlg.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,178 @@
#include "stdafx.h"
#include "Servo.h"
#include "CUserXLogDlg.h"
#include "afxdialogex.h"
#include <functional>
#include <vector>
#include <sstream>
namespace
{
    std::wstring ReadBufferVia(const std::function<int(wchar_t*, int)>& fn)
    {
        int need = fn(nullptr, 0);
        if (need <= 0) {
            return L"";
        }
        std::wstring buffer;
        buffer.resize(static_cast<size_t>(need));
        if (fn(buffer.data(), need) == UX_OK) {
            if (!buffer.empty() && buffer.back() == L'\0') {
                buffer.pop_back();
            }
            return buffer;
        }
        return L"";
    }
    std::vector<std::wstring> SplitLines(const std::wstring& text)
    {
        std::vector<std::wstring> lines;
        std::wstringstream ss(text);
        std::wstring line;
        while (std::getline(ss, line)) {
            lines.push_back(line);
        }
        return lines;
    }
}
IMPLEMENT_DYNAMIC(CUserXLogDlg, CDialogEx)
CUserXLogDlg::CUserXLogDlg(CWnd* pParent)
    : CDialogEx(IDD_DIALOG_USERX_LOG, pParent)
{
}
CUserXLogDlg::~CUserXLogDlg()
{
}
void CUserXLogDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_LIST1, m_listLogs);
}
BEGIN_MESSAGE_MAP(CUserXLogDlg, CDialogEx)
    ON_WM_SIZE()
    ON_WM_DESTROY()
END_MESSAGE_MAP()
BOOL CUserXLogDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();
    InitListCtrl();
    RefreshLogs();
    AdjustLayout();
    return TRUE;
}
void CUserXLogDlg::InitListCtrl()
{
    DWORD dwStyle = m_listLogs.GetExtendedStyle();
    m_listLogs.SetExtendedStyle(dwStyle | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
    m_listLogs.InsertColumn(0, _T("时间"), LVCFMT_LEFT, 180);
    m_listLogs.InsertColumn(1, _T("用户"), LVCFMT_LEFT, 120);
    m_listLogs.InsertColumn(2, _T("动作"), LVCFMT_LEFT, 120);
    m_listLogs.InsertColumn(3, _T("描述"), LVCFMT_LEFT, 200);
}
void CUserXLogDlg::RefreshLogs()
{
    m_logs.clear();
    m_listLogs.DeleteAllItems();
    auto allLogs = ReadBufferVia([](wchar_t* buffer, int size) {
        return UX_QueryLogs(200, buffer, size);
    });
    for (auto& rawLine : SplitLines(allLogs)) {
        if (rawLine.empty()) continue;
        auto trim = [](std::wstring value) {
            size_t first = value.find_first_not_of(L" \t\r\n");
            size_t last = value.find_last_not_of(L" \t\r\n");
            if (first == std::wstring::npos || last == std::wstring::npos) {
                return std::wstring();
            }
            return value.substr(first, last - first + 1);
        };
        auto takeField = [&](size_t& cursor) {
            if (cursor == std::wstring::npos || cursor >= rawLine.length()) {
                return std::wstring();
            }
            size_t pos = rawLine.find(L',', cursor);
            std::wstring part = (pos == std::wstring::npos) ? rawLine.substr(cursor) : rawLine.substr(cursor, pos - cursor);
            cursor = (pos == std::wstring::npos) ? std::wstring::npos : pos + 1;
            return trim(part);
        };
        size_t cursor = 0;
        LogItem item;
        item.time = takeField(cursor).c_str();
        item.user = takeField(cursor).c_str();
        item.action = takeField(cursor).c_str();
        if (cursor != std::wstring::npos && cursor < rawLine.length()) {
            item.detail = trim(rawLine.substr(cursor)).c_str();
        }
        m_logs.push_back(item);
    }
    for (size_t i = 0; i < m_logs.size(); ++i) {
        const auto& log = m_logs[i];
        int row = m_listLogs.InsertItem(static_cast<int>(i), log.time);
        m_listLogs.SetItemText(row, 1, log.user);
        m_listLogs.SetItemText(row, 2, log.action);
        m_listLogs.SetItemText(row, 3, log.detail);
    }
}
void CUserXLogDlg::AdjustLayout()
{
    if (!::IsWindow(m_listLogs.GetSafeHwnd())) {
        return;
    }
    CRect rcClient;
    GetClientRect(&rcClient);
    const int margin = 7;
    CRect rcList(margin, margin, rcClient.right - margin, rcClient.bottom - 40);
    m_listLogs.MoveWindow(rcList);
    auto moveButton = [&](int id, int order) {
        if (CWnd* pBtn = GetDlgItem(id)) {
            CRect rc;
            pBtn->GetWindowRect(&rc);
            ScreenToClient(&rc);
            int width = rc.Width();
            int height = rc.Height();
            rc.left = rcClient.right - margin - width - order * (width + margin);
            rc.right = rc.left + width;
            rc.top = rcClient.bottom - margin - height;
            rc.bottom = rc.top + height;
            pBtn->MoveWindow(rc);
        }
    };
    moveButton(IDOK, 1);
    moveButton(IDCANCEL, 0);
}
void CUserXLogDlg::OnSize(UINT nType, int cx, int cy)
{
    CDialogEx::OnSize(nType, cx, cy);
    AdjustLayout();
}
void CUserXLogDlg::OnDestroy()
{
    CDialogEx::OnDestroy();
}
SourceCode/Bond/Servo/CUserXLogDlg.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
#pragma once
#include <vector>
#include <string>
class CUserXLogDlg : public CDialogEx
{
    DECLARE_DYNAMIC(CUserXLogDlg)
public:
    CUserXLogDlg(CWnd* pParent = nullptr);
    virtual ~CUserXLogDlg();
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_DIALOG_USERX_LOG };
#endif
protected:
    virtual void DoDataExchange(CDataExchange* pDX);
    DECLARE_MESSAGE_MAP()
public:
    virtual BOOL OnInitDialog();
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnDestroy();
private:
    struct LogItem
    {
        CString time;
        CString user;
        CString action;
        CString detail;
    };
    CListCtrl m_listLogs;
    std::vector<LogItem> m_logs;
    void InitListCtrl();
    void RefreshLogs();
    void AdjustLayout();
};
SourceCode/Bond/Servo/HsmsPassive.cpp
@@ -228,40 +228,133 @@
int CHsmsPassive::loadVarialbles(const char* pszFilepath)
{
    CStdioFile file;
    if (!file.Open(pszFilepath, CFile::modeRead)) {
    m_strVariableFilepath = pszFilepath;
    m_bVariableUtf8 = false;
    m_bVariableUtf8Bom = false;
    // å…ˆè¯»åŽŸå§‹å­—èŠ‚ï¼ŒåŽç»­å†æŒ‰ UTF-8/BOM æˆ–本地编码转换
    CFile file;
    if (!file.Open(pszFilepath, CFile::modeRead | CFile::shareDenyNone)) {
        return -1;
    }
    std::regex pattern("^\\d+,.*");  // åŒ¹é…ä»¥æ•°å­—+逗号开头的字符串
    const ULONGLONG nLen = file.GetLength();
    if (nLen == 0) {
        return -1;
    }
    std::string buffer;
    buffer.resize(static_cast<size_t>(nLen));
    file.Read(buffer.data(), static_cast<UINT>(nLen));
    file.Close();
    const unsigned char* bytes = reinterpret_cast<const unsigned char*>(buffer.data());
    size_t offset = 0;
    CStringW content;
    // UTF-8 BOM
    if (nLen >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) {
        offset = 3;
        m_bVariableUtf8 = true;
        m_bVariableUtf8Bom = true;
    }
    // UTF-16 LE BOM
    if (nLen >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE) {
        const wchar_t* wdata = reinterpret_cast<const wchar_t*>(buffer.data() + 2);
        const size_t wlen = (nLen - 2) / sizeof(wchar_t);
        content.SetString(wdata, static_cast<int>(wlen));
    }
    // UTF-16 BE BOM
    else if (nLen >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF) {
        const size_t wlen = (nLen - 2) / sizeof(wchar_t);
        std::wstring temp;
        temp.reserve(wlen);
        for (size_t i = 0; i < wlen; ++i) {
            wchar_t ch = static_cast<wchar_t>(bytes[2 + i * 2] << 8 | bytes[2 + i * 2 + 1]);
            temp.push_back(ch);
        }
        content = temp.c_str();
    }
    // å°è¯• UTF-8(含无 BOM)
    else {
        auto tryUtf8 = [&](size_t off) -> bool {
            int need = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, buffer.data() + off,
                static_cast<int>(buffer.size() - off), nullptr, 0);
            if (need <= 0) return false;
            std::wstring temp;
            temp.resize(need);
            MultiByteToWideChar(CP_UTF8, 0, buffer.data() + off,
                static_cast<int>(buffer.size() - off), temp.data(), need);
            content = temp.c_str();
            m_bVariableUtf8 = true;
            return true;
        };
        if (!tryUtf8(offset)) {
            // å›žé€€åˆ°æœ¬åœ°ä»£ç é¡µ
            int need = MultiByteToWideChar(CP_ACP, 0, buffer.data(), static_cast<int>(buffer.size()), nullptr, 0);
            if (need > 0) {
                std::wstring temp;
                temp.resize(need);
                MultiByteToWideChar(CP_ACP, 0, buffer.data(), static_cast<int>(buffer.size()), temp.data(), need);
                content = temp.c_str();
            }
        }
    }
    if (content.IsEmpty()) {
        return -1;
    }
    std::wregex pattern(L"^\\d+,.+");  // åŒ¹é…ä»¥æ•°å­—+逗号开头的字符串
    std::vector<SERVO::CVariable*> variables;
    int index, last;
    CString strLine;
    CString strId, strName, strFormat, strRemark;
    while (file.ReadString(strLine)) {
        if (!std::regex_match((LPTSTR)(LPCTSTR)strLine, pattern)) {
    CStringW strLine;
    CStringW strId, strName, strFormat, strRemark;
    std::wstringstream ss(content.GetString());
    auto narrowFromW = [](const CStringW& s) -> std::string {
        int need = WideCharToMultiByte(CP_ACP, 0, s, -1, nullptr, 0, nullptr, nullptr);
        if (need <= 0) return {};
        std::string out(static_cast<size_t>(need - 1), '\0');
        WideCharToMultiByte(CP_ACP, 0, s, -1, out.data(), need, nullptr, nullptr);
        return out;
    };
    std::wstring line;
    while (std::getline(ss, line, L'\n')) {
        strLine = line.c_str();
        strLine.Trim();
        if (strLine.IsEmpty()) continue;
        if (!std::regex_match(static_cast<LPCWSTR>(strLine), pattern)) {
            continue;
        }
        last = 0;
        index = strLine.Find(",", last);
        index = strLine.Find(L",", last);
        if (index < 0) continue;
        strId = strLine.Left(index);
        last = index + 1;
        index = strLine.Find(",", last);
        index = strLine.Find(L",", last);
        if (index < 0) continue;
        strName = strLine.Mid(last, index - last);
        last = index + 1;
        index = strLine.Find(",", last);
        index = strLine.Find(L",", last);
        if (index < 0) continue;
        strFormat = strLine.Mid(last, index - last);
        strRemark = strLine.Right(strLine.GetLength() - index - 1);
        strRemark.Replace(_T("\\r\\n"), _T("\r\n"));
        strRemark.Replace(L"\\r\\n", L"\r\n");
        std::string sId = narrowFromW(strId);
        std::string sName = narrowFromW(strName);
        std::string sFormat = narrowFromW(strFormat);
        std::string sRemark = narrowFromW(strRemark);
        SERVO::CVariable* pVarialble = new SERVO::CVariable(
            (LPTSTR)(LPCTSTR)strId, (LPTSTR)(LPCTSTR)strName, (LPTSTR)(LPCTSTR)strFormat, (LPTSTR)(LPCTSTR)strRemark);
            sId.c_str(),
            sName.c_str(),
            sFormat.c_str(),
            sRemark.c_str());
        variables.push_back(pVarialble);
    }
@@ -272,8 +365,6 @@
        }
    }
    file.Close();
    return 0;
}
@@ -312,6 +403,102 @@
    m_variabels.clear();
}
CStringA WideToUtf8(const CStringW& ws)
{
    int need = WideCharToMultiByte(CP_UTF8, 0, ws, -1, nullptr, 0, nullptr, nullptr);
    if (need <= 0) return "";
    CStringA out;
    LPSTR buf = out.GetBufferSetLength(need - 1);
    WideCharToMultiByte(CP_UTF8, 0, ws, -1, buf, need, nullptr, nullptr);
    out.ReleaseBuffer();
    return out;
}
CStringA WideToAnsi(const CStringW& ws)
{
    int need = WideCharToMultiByte(CP_ACP, 0, ws, -1, nullptr, 0, nullptr, nullptr);
    if (need <= 0) return "";
    CStringA out;
    LPSTR buf = out.GetBufferSetLength(need - 1);
    WideCharToMultiByte(CP_ACP, 0, ws, -1, buf, need, nullptr, nullptr);
    out.ReleaseBuffer();
    return out;
}
static CStringA AnsiToUtf8(const std::string& s)
{
    int wlen = MultiByteToWideChar(CP_ACP, 0, s.c_str(), -1, nullptr, 0);
    if (wlen <= 0) return "";
    CStringW ws;
    LPWSTR wbuf = ws.GetBufferSetLength(wlen - 1);
    MultiByteToWideChar(CP_ACP, 0, s.c_str(), -1, wbuf, wlen);
    ws.ReleaseBuffer();
    return WideToUtf8(ws);
}
int CHsmsPassive::deleteVariable(int variableId)
{
    Lock();
    auto it = std::find_if(m_variabels.begin(), m_variabels.end(), [=](SERVO::CVariable* v) {
        return v != nullptr && v->getVarialbleId() == variableId;
        });
    if (it == m_variabels.end()) {
        Unlock();
        return -1;
    }
    delete *it;
    m_variabels.erase(it);
    auto filepath = m_strVariableFilepath;
    Unlock();
    if (filepath.empty()) return -2;
    // å†™å›žæ–‡ä»¶ï¼Œä¿æŒåŽŸç¼–ç ï¼ˆUTF-8 æˆ–本地编码)
    CFile file;
    if (!file.Open(filepath.c_str(), CFile::modeCreate | CFile::modeWrite | CFile::shareDenyNone)) {
        return -3;
    }
    // header
    const std::string headerAnsi = "SVID,SV Name,SV Format,SV Remark\r\n";
    if (m_bVariableUtf8) {
        if (m_bVariableUtf8Bom) {
            const BYTE bom[3] = { 0xEF, 0xBB, 0xBF };
            file.Write(bom, 3);
        }
        CStringA header = AnsiToUtf8(headerAnsi);
        file.Write(header.GetString(), header.GetLength());
    }
    else {
        file.Write(headerAnsi.data(), (UINT)headerAnsi.size());
    }
    for (auto v : m_variabels) {
        if (v == nullptr) continue;
        std::string lineAnsi;
        lineAnsi.reserve(256);
        lineAnsi += std::to_string(v->getVarialbleId());
        lineAnsi.push_back(',');
        lineAnsi += v->getName();
        lineAnsi.push_back(',');
        lineAnsi += SERVO::CVariable::formatToString(v->getFormat());
        lineAnsi.push_back(',');
        lineAnsi += v->getRemark();
        lineAnsi.append("\r\n");
        if (m_bVariableUtf8) {
            CStringA outLine = AnsiToUtf8(lineAnsi);
            file.Write(outLine.GetString(), outLine.GetLength());
        }
        else {
            file.Write(lineAnsi.data(), (UINT)lineAnsi.size());
        }
    }
    file.Close();
    return 0;
}
void CHsmsPassive::setVariableValue(const char* pszName, __int64 value)
{
    auto v = getVariable(pszName);
@@ -338,30 +525,108 @@
int CHsmsPassive::loadReports(const char* pszFilepath)
{
    CStdioFile file;
    if (!file.Open(pszFilepath, CFile::modeRead)) {
    // å…¼å®¹ UTF-8/BOM ä¸Žæœ¬åœ°ç¼–码读取
    CFile file;
    if (!file.Open(pszFilepath, CFile::modeRead | CFile::shareDenyNone)) {
        return -1;
    }
    std::regex pattern("^\\d+,\\(\\d+(,\\d+)*\\).*");  // åŒ¹é…ä»¥æ•°å­—+逗号开头的字符串
    const ULONGLONG nLen = file.GetLength();
    if (nLen == 0) {
        return -1;
    }
    std::string buffer;
    buffer.resize(static_cast<size_t>(nLen));
    file.Read(buffer.data(), static_cast<UINT>(nLen));
    file.Close();
    const unsigned char* bytes = reinterpret_cast<const unsigned char*>(buffer.data());
    size_t offset = 0;
    CStringW content;
    // UTF-8 BOM
    if (nLen >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) {
        offset = 3;
    }
    // UTF-16 LE BOM
    if (nLen >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE) {
        const wchar_t* wdata = reinterpret_cast<const wchar_t*>(buffer.data() + 2);
        const size_t wlen = (nLen - 2) / sizeof(wchar_t);
        content.SetString(wdata, static_cast<int>(wlen));
    }
    // UTF-16 BE BOM
    else if (nLen >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF) {
        const size_t wlen = (nLen - 2) / sizeof(wchar_t);
        std::wstring temp;
        temp.reserve(wlen);
        for (size_t i = 0; i < wlen; ++i) {
            wchar_t ch = static_cast<wchar_t>(bytes[2 + i * 2] << 8 | bytes[2 + i * 2 + 1]);
            temp.push_back(ch);
        }
        content = temp.c_str();
    }
    else {
        auto tryUtf8 = [&](size_t off) -> bool {
            int need = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, buffer.data() + off,
                static_cast<int>(buffer.size() - off), nullptr, 0);
            if (need <= 0) return false;
            std::wstring temp;
            temp.resize(need);
            MultiByteToWideChar(CP_UTF8, 0, buffer.data() + off,
                static_cast<int>(buffer.size() - off), temp.data(), need);
            content = temp.c_str();
            return true;
        };
        if (!tryUtf8(offset)) {
            int need = MultiByteToWideChar(CP_ACP, 0, buffer.data(), static_cast<int>(buffer.size()), nullptr, 0);
            if (need > 0) {
                std::wstring temp;
                temp.resize(need);
                MultiByteToWideChar(CP_ACP, 0, buffer.data(), static_cast<int>(buffer.size()), temp.data(), need);
                content = temp.c_str();
            }
        }
    }
    if (content.IsEmpty()) {
        return -1;
    }
    std::wregex pattern(L"^\\d+,\\(\\d+(,\\d+)*\\).*");  // åŒ¹é…ä»¥æ•°å­—+逗号开头的字符串
    std::vector<SERVO::CReport*> reports;
    int index;
    CString strLine, strVariable;
    CString strId;
    while (file.ReadString(strLine)) {
        if (!std::regex_match((LPTSTR)(LPCTSTR)strLine, pattern)) {
    CStringW strLine, strVariable;
    CStringW strId;
    std::wstringstream ss(content.GetString());
    auto narrowFromW = [](const CStringW& s) -> std::string {
        int need = WideCharToMultiByte(CP_ACP, 0, s, -1, nullptr, 0, nullptr, nullptr);
        if (need <= 0) return {};
        std::string out(static_cast<size_t>(need - 1), '\0');
        WideCharToMultiByte(CP_ACP, 0, s, -1, out.data(), need, nullptr, nullptr);
        return out;
    };
    std::wstring line;
    while (std::getline(ss, line, L'\n')) {
        strLine = line.c_str();
        strLine.Trim();
        if (strLine.IsEmpty()) continue;
        if (!std::regex_match(static_cast<LPCWSTR>(strLine), pattern)) {
            continue;
        }
        index = strLine.Find(",", 0);
        index = strLine.Find(L",", 0);
        if (index < 0) continue;
        strId = strLine.Left(index);
        strVariable = strLine.Right(strLine.GetLength() - index - 1);
        strVariable.Delete(0);
        strVariable.Delete(strVariable.GetLength() - 1);
        auto vids = parseVidList(strVariable);
        CString strVariableA(narrowFromW(strVariable).c_str());
        auto vids = parseVidList(strVariableA);
        SERVO::CReport* pReport = new SERVO::CReport(atoi((LPTSTR)(LPCTSTR)strId), vids);
        SERVO::CReport* pReport = new SERVO::CReport(_wtoi(strId), vids);
        for (auto vid : vids) {
            SERVO::CVariable* pVariable = getVariable(vid);
            if (pVariable != nullptr) {
@@ -380,7 +645,6 @@
    }
    
    file.Close();
    return 0;
}
@@ -423,42 +687,122 @@
int CHsmsPassive::loadCollectionEvents(const char* pszFilepath)
{
    CStdioFile file;
    if (!file.Open(pszFilepath, CFile::modeRead)) {
    CFile file;
    if (!file.Open(pszFilepath, CFile::modeRead | CFile::shareDenyNone)) {
        return -1;
    }
    std::regex pattern("^\\d+,[^,]*,[^,]*,\\(\\d+(,\\d+)*\\).*");  // åŒ¹é…ä»¥æ•°å­—+逗号开头的字符串
    const ULONGLONG nLen = file.GetLength();
    if (nLen == 0) {
        return -1;
    }
    std::string buffer;
    buffer.resize(static_cast<size_t>(nLen));
    file.Read(buffer.data(), static_cast<UINT>(nLen));
    file.Close();
    const unsigned char* bytes = reinterpret_cast<const unsigned char*>(buffer.data());
    size_t offset = 0;
    CStringW content;
    // UTF-8 BOM
    if (nLen >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) {
        offset = 3;
    }
    // UTF-16 LE BOM
    if (nLen >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE) {
        const wchar_t* wdata = reinterpret_cast<const wchar_t*>(buffer.data() + 2);
        const size_t wlen = (nLen - 2) / sizeof(wchar_t);
        content.SetString(wdata, static_cast<int>(wlen));
    }
    // UTF-16 BE BOM
    else if (nLen >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF) {
        const size_t wlen = (nLen - 2) / sizeof(wchar_t);
        std::wstring temp;
        temp.reserve(wlen);
        for (size_t i = 0; i < wlen; ++i) {
            wchar_t ch = static_cast<wchar_t>(bytes[2 + i * 2] << 8 | bytes[2 + i * 2 + 1]);
            temp.push_back(ch);
        }
        content = temp.c_str();
    }
    else {
        auto tryUtf8 = [&](size_t off) -> bool {
            int need = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, buffer.data() + off,
                static_cast<int>(buffer.size() - off), nullptr, 0);
            if (need <= 0) return false;
            std::wstring temp;
            temp.resize(need);
            MultiByteToWideChar(CP_UTF8, 0, buffer.data() + off,
                static_cast<int>(buffer.size() - off), temp.data(), need);
            content = temp.c_str();
            return true;
        };
        if (!tryUtf8(offset)) {
            int need = MultiByteToWideChar(CP_ACP, 0, buffer.data(), static_cast<int>(buffer.size()), nullptr, 0);
            if (need > 0) {
                std::wstring temp;
                temp.resize(need);
                MultiByteToWideChar(CP_ACP, 0, buffer.data(), static_cast<int>(buffer.size()), temp.data(), need);
                content = temp.c_str();
            }
        }
    }
    if (content.IsEmpty()) {
        return -1;
    }
    std::wregex pattern(L"^\\d+,[^,]*,[^,]*,\\(\\d+(,\\d+)*\\).*");  // åŒ¹é…ä»¥æ•°å­—+逗号开头的字符串
    std::vector<SERVO::CCollectionEvent*> events;
    int index, last;
    CString strLine, strRPTIDs;
    CString strId, strName, strDescription;
    while (file.ReadString(strLine)) {
        if (!std::regex_match((LPTSTR)(LPCTSTR)strLine, pattern)) {
    CStringW strLine, strRPTIDs;
    CStringW strId, strName, strDescription;
    std::wstringstream ss(content.GetString());
    auto narrowFromW = [](const CStringW& s) -> std::string {
        int need = WideCharToMultiByte(CP_ACP, 0, s, -1, nullptr, 0, nullptr, nullptr);
        if (need <= 0) return {};
        std::string out(static_cast<size_t>(need - 1), '\0');
        WideCharToMultiByte(CP_ACP, 0, s, -1, out.data(), need, nullptr, nullptr);
        return out;
    };
    std::wstring line;
    while (std::getline(ss, line, L'\n')) {
        strLine = line.c_str();
        strLine.Trim();
        if (strLine.IsEmpty()) continue;
        if (!std::regex_match(static_cast<LPCWSTR>(strLine), pattern)) {
            continue;
        }
        last = 0;
        index = strLine.Find(",", last);
        index = strLine.Find(L",", last);
        if (index < 0) continue;
        strId = strLine.Left(index);
        last = index + 1;
        index = strLine.Find(",", last);
        index = strLine.Find(L",", last);
        if (index < 0) continue;
        strName = strLine.Mid(last, index - last);
        last = index + 1;
        index = strLine.Find(",", last);
        index = strLine.Find(L",", last);
        if (index < 0) continue;
        strDescription = strLine.Mid(last, index - last);
        strRPTIDs = strLine.Right(strLine.GetLength() - index - 1);
        strRPTIDs.Delete(0);
        strRPTIDs.Delete(strRPTIDs.GetLength() - 1);
        auto prtids = parseVidList(strRPTIDs);
        CString strRPTIDsA(narrowFromW(strRPTIDs).c_str());
        auto prtids = parseVidList(strRPTIDsA);
        std::string sName = narrowFromW(strName);
        std::string sDesc = narrowFromW(strDescription);
        SERVO::CCollectionEvent* pEvent = new SERVO::CCollectionEvent(
            atoi(strId), (LPTSTR)(LPCTSTR)strName, (LPTSTR)(LPCTSTR)strDescription, prtids);
            _wtoi(strId), sName.c_str(), sDesc.c_str(), prtids);
        for (auto rptid : prtids) {
            SERVO::CReport* pReport = getReport(rptid);
            if (pReport != nullptr) {
@@ -474,9 +818,6 @@
            m_collectionEvents.push_back(item);
        }
    }
    file.Close();
    return 0;
}
@@ -1924,5 +2265,10 @@
    return requestEventReportSend("Panel_End");
}
int CHsmsPassive::requestEventReportSend_OCR_PanelID_Read_OK()
{
    return requestEventReportSend("OCR_PanelID_Read_OK");
}
SourceCode/Bond/Servo/HsmsPassive.h
@@ -1,4 +1,4 @@
#pragma once
#pragma once
#include <string>
#include <list>
#include "HsmsAction.h"
@@ -39,7 +39,7 @@
#define CAACK_6                    6        /* command performed with errors */
/*
 * å¸¸é‡æ•°æ®ç»“æž„
 * å¸¸é‡æ•°æ®ç»“æž„
 */
typedef struct _EQConstant
{
@@ -48,7 +48,7 @@
} EQConstant;
/*
 * Command æ•°æ®ç»“æž„
 * Command æ•°æ®ç»“æž„
 */
typedef struct _CommandParameter
{
@@ -57,7 +57,7 @@
} CommandParameter;
/*
 * Report æ•°æ®ç»“æž„
 * Report æ•°æ®ç»“æž„
 */
typedef struct _REPORT
{
@@ -66,7 +66,7 @@
} REPORT;
/*
 * Value æ•°æ®ç»“æž„
 * Value æ•°æ®ç»“æž„
 */
typedef struct _VALUE
{
@@ -115,62 +115,63 @@
    ~CHsmsPassive();
public:
    /* è®¾ç½®æœºå™¨åž‹å· æœ€å¤§é•¿åº¦ 20 bytes */
    /* è®¾ç½®æœºå™¨åž‹å· æœ€å¤§é•¿åº¦ 20 bytes */
    void setEquipmentModelType(const char* pszMode);
    /* è®¾ç½®è½¯ä»¶ç‰ˆæœ¬å· æœ€å¤§é•¿åº¦ 20 bytes */
    /* è®¾ç½®è½¯ä»¶ç‰ˆæœ¬å· æœ€å¤§é•¿åº¦ 20 bytes */
    void setSoftRev(const char* pszRev);
    /* æ·»åŠ å˜é‡å€¼åˆ°ISECS2Item */
    /* æ·»åŠ å˜é‡å€¼åˆ°ISECS2Item */
    void addVariableValueToItem(ISECS2Item* pParent, SERVO::CVariable* pVariable);
    // è¿žæŽ¥Report
    // è¿žæŽ¥Report
    void linkEventReport(unsigned int CEID, unsigned int RPTID);
    // å–消连接report
    // å–消连接report
    void unlinkEventReport(unsigned int CEID);
    // define Report
    SERVO::CReport* defineReport(unsigned int RPTID, std::vector<unsigned int>& vids);
    // å–消 define report
    // å–消 define report
    bool removeReport(int rptid);
    void clearAllReport();
    // ä»Žæ–‡ä»¶ä¸­åŠ è½½CVariable列表
    // ä»Žæ–‡ä»¶ä¸­åŠ è½½CVariable列表
    int loadVarialbles(const char* pszFilepath);
    // å–å¾—CVariable列表
    // å–å¾—CVariable列表
    std::vector<SERVO::CVariable*>& getVariables();
    // å–得指定Variable
    // å–得指定Variable
    SERVO::CVariable* getVariable(int variableId);
    SERVO::CVariable* getVariable(const char* pszName);
    int deleteVariable(int variableId);
    // è®¾ç½®å˜é‡å€¼
    // è®¾ç½®å˜é‡å€¼
    void setVariableValue(const char* pszName, __int64 value);
    void setVariableValue(const char* pszName, const char* value);
    void setVariableValue(const char* pszName, std::vector<SERVO::CVariable>& vars);
    // ä»Žæ–‡ä»¶ä¸­åŠ è½½CReport列表
    // ä»Žæ–‡ä»¶ä¸­åŠ è½½CReport列表
    int loadReports(const char* pszFilepath);
    // å–å¾—Report列表
    // å–å¾—Report列表
    std::vector<SERVO::CReport*>& getReports();
    // ä»Žæ–‡ä»¶ä¸­åŠ è½½CCollectionEvent列表
    // ä»Žæ–‡ä»¶ä¸­åŠ è½½CCollectionEvent列表
    int loadCollectionEvents(const char* pszFilepath);
    // å–å¾—CCollectionEvent列表
    // å–å¾—CCollectionEvent列表
    std::vector<SERVO::CCollectionEvent*>& getCollectionEvents();
    // å–消/删除所有CollectionEvent
    // å–消/删除所有CollectionEvent
    void clearAllCollectionEvent();
    // å–å¾—CCollectionEvent
    // å–å¾—CCollectionEvent
    SERVO::CCollectionEvent* getEvent(unsigned short CEID);
    // å–å¾—Report
    // å–å¾—Report
    SERVO::CReport* getReport(int rptid);
    void setListener(SECSListener listener);
@@ -185,7 +186,7 @@
    int unserialize(const char* pszBuffer, int nBufferSize);
public:
    /* request开头的函数为主动发送数据的函数 */
    /* request开头的函数为主动发送数据的函数 */
    int requestAreYouThere();
    int requestAlarmReport(int ALCD, int ALID, const char* ALTX);
    int requestEventReportSend(unsigned int CEID);
@@ -201,11 +202,12 @@
    int requestEventReportSend_CJ_End();
    int requestEventReportSend_Panel_Start();
    int requestEventReportSend_Panel_End();
    int requestEventReportSend_OCR_PanelID_Read_OK();
private:
    void replyAck(int s, int f, unsigned int systemBytes, BYTE ack, const char* pszAckName);
    /* reply开头的函数为回复函数 */
    /* reply开头的函数为回复函数 */
    int replyAreYouThere(IMessage* pRecv);
    int replyEstablishCommunications(IMessage* pRecv);
    int replySelectedEquipmentStatusData(IMessage* pRecv);
@@ -250,6 +252,9 @@
private:
    SECSListener m_listener;
    std::string m_strVariableFilepath;
    bool m_bVariableUtf8{ false };
    bool m_bVariableUtf8Bom{ false };
    BOOL m_bCimWorking;
    HANDLE m_hCimWorkEvent;
    HANDLE m_hCimWorkThreadHandle;
SourceCode/Bond/Servo/LoginDlg2.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,103 @@
// LoginDlg.cpp: å®žçŽ°æ–‡ä»¶
//
#include "stdafx.h"
#include "Servo.h"
#include "afxdialogex.h"
#include "LoginDlg2.h"
// CLoginDlg å¯¹è¯æ¡†
IMPLEMENT_DYNAMIC(CLoginDlg2, CDialogEx)
CLoginDlg2::CLoginDlg2(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_DIALOG_LOGIN, pParent)
{
}
CLoginDlg2::~CLoginDlg2()
{
}
void CLoginDlg2::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CLoginDlg2, CDialogEx)
    ON_BN_CLICKED(IDC_BUTTON_LOGIN, &CLoginDlg2::OnBnClickedLogin)
    ON_STN_CLICKED(IDC_STATIC_CHANGE_PASSWORD, &CLoginDlg2::OnBnClickedChangePassword)
END_MESSAGE_MAP()
// CLoginDlg æ¶ˆæ¯å¤„理程序
BOOL CLoginDlg2::OnInitDialog()
{
    CDialog::OnInitDialog();
    // è®¾ç½®çª—口标题和初始值
    SetWindowText(_T("登录"));
    CStatic* pStaticImage = (CStatic*)GetDlgItem(IDC_STATIC_IMAGE);
    ASSERT(pStaticImage);
    CString strIconPath;
    strIconPath.Format(_T("%s\\Res\\Operator_High_32.ico"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
    HICON hIcon = (HICON)::LoadImage(
        nullptr,
        strIconPath,
        IMAGE_ICON,
        32, // å›¾æ ‡å®½åº¦
        32, // å›¾æ ‡é«˜åº¦
        LR_LOADFROMFILE);
    if (hIcon) {
        // è®¾ç½® CStatic æŽ§ä»¶ä¸ºå›¾æ ‡æ ·å¼
        pStaticImage->ModifyStyle(0xF, SS_ICON);
        pStaticImage->SetIcon(hIcon);
    }
    // æ·»åŠ SS_NOTIFY样式
    CStatic* pStatic = (CStatic*)GetDlgItem(IDC_STATIC_CHANGE_PASSWORD);
    if (pStatic != nullptr) {
        pStatic->ModifyStyle(0, SS_NOTIFY);
    }
    GetDlgItem(IDC_CHECK_REMEMBER_PASSWORD)->ShowWindow(SW_HIDE);
    // test
    SetDlgItemText(IDC_EDIT_USERNAME, _T("admin"));
    SetDlgItemText(IDC_EDIT_PASSWORD, _T("admin123"));
    return TRUE;
}
void CLoginDlg2::OnBnClickedLogin()
{
    GetDlgItemText(IDC_EDIT_USERNAME, m_strUsername);
    GetDlgItemText(IDC_EDIT_PASSWORD, m_strPassword);
    if (m_strUsername.IsEmpty()) {
        AfxMessageBox(_T("请输入用户名"));
        GetDlgItem(IDC_EDIT_USERNAME)->SetFocus();
        return;
    }
    if (m_strPassword.IsEmpty()) {
        AfxMessageBox(_T("请输入密码"));
        GetDlgItem(IDC_EDIT_PASSWORD)->SetFocus();
        return;
}
    EndDialog(IDOK);
}
void CLoginDlg2::OnBnClickedChangePassword()
{
}
SourceCode/Bond/Servo/LoginDlg2.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
#pragma once
#include "afxdialogex.h"
// CLoginDlg å¯¹è¯æ¡†
class CLoginDlg2 : public CDialogEx
{
    DECLARE_DYNAMIC(CLoginDlg2)
public:
    CLoginDlg2(CWnd* pParent = nullptr);   // æ ‡å‡†æž„造函数
    virtual ~CLoginDlg2();
// å¯¹è¯æ¡†æ•°æ®
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_DIALOG_LOGIN };
#endif
public:
    CString m_strUsername;
    CString m_strPassword;
protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV æ”¯æŒ
    virtual BOOL OnInitDialog();
    afx_msg void OnBnClickedLogin();
    afx_msg void OnBnClickedChangePassword();
    DECLARE_MESSAGE_MAP()
};
SourceCode/Bond/Servo/Model.cpp
@@ -1,4 +1,4 @@
#include "stdafx.h"
#include "stdafx.h"
#include "Model.h"
#include "Log.h"
#include "Common.h"
@@ -25,7 +25,7 @@
{
    if (m_pObservable == nullptr) {
        m_pObservable = RX_AllocaObservable([&](IObservableEmitter* e) -> void {
            m_pObservableEmitter = e;            // ä¿å­˜å‘射器
            m_pObservableEmitter = e;            // ä¿å­˜å‘射器
        });
    }
@@ -83,7 +83,7 @@
    m_configuration.setFilepath((LPTSTR)(LPCTSTR)strIniFile);
    m_configuration.getUnitId(strUnitId);
    // æœºå™¨åž‹å·å’Œè½¯ä»¶ç‰ˆæœ¬å·åº”从配置中读取,当前先固定值
    // æœºå™¨åž‹å·å’Œè½¯ä»¶ç‰ˆæœ¬å·åº”从配置中读取,当前先固定值
    CString strModeType = _T("Master");
    CString strSoftRev = _T("1.0.2");
@@ -108,10 +108,10 @@
    SECSListener listener;
    listener.onEQOffLine = [&](void* pFrom) -> void {
        LOGI("远程请求OffLine");
        LOGI("远程请求OffLine");
    };
    listener.onEQOnLine = [&](void* pFrom) -> void {
        LOGI("远程请求OnLine");
        LOGI("远程请求OnLine");
    };
    listener.onCommand = [&](void* pFrom, const char* pszName, std::vector<CommandParameter>& params) -> void {
        LOGI("onCommand:%s", pszName);
@@ -120,13 +120,13 @@
        }
    };
    listener.onEQConstantRequest = [&](void* pFrom, std::vector<EQConstant>& eqcs) -> void {
        // åœ¨æ­¤å¡«å……常量值,目前仅是加1后返回
        // åœ¨æ­¤å¡«å……常量值,目前仅是加1后返回
        for (auto& item : eqcs) {
            sprintf_s(item.szValue, 256, "Test%d", item.id + 1);
        }
    };
    listener.onEQConstantSend = [&](void* pFrom, std::vector<EQConstant>& eqcs) -> void {
        // åœ¨æ­¤ä¿å­˜å’Œè®¾ç½®æœºå™¨å¸¸é‡å€¼
        // åœ¨æ­¤ä¿å­˜å’Œè®¾ç½®æœºå™¨å¸¸é‡å€¼
        for (auto& item : eqcs) {
            LOGI("onEQConstantRequest: %d, %s", item.id, item.szValue);
        }
@@ -296,6 +296,13 @@
    };
    masterListener.onEqVcrEventReport = [&](void* pMaster, SERVO::CEquipment* pEquipment, SERVO::CVcrEventReport* pReport) {
        LOGE("<CModel>onEqVcrEventReport.");
        if (pReport != nullptr) {
            m_hsmsPassive.setVariableValue("PanelStartID", pReport->getGlassId().c_str());
            int nRet = m_hsmsPassive.requestEventReportSend_OCR_PanelID_Read_OK();
            if (nRet != ER_NOERROR) {
                LOGE("<CModel>requestEventReportSend_OCR_PanelID_Read_OK failed, ret=%d", nRet);
            }
        }
    };
    masterListener.onEqDataChanged = [&](void* pMaster, SERVO::CEquipment* pEquipment, int code) {
        LOGE("<CModel>onEqDataChanged.");
@@ -303,11 +310,11 @@
    };
    masterListener.onRobotTaskEvent = [&](void* pMaster, SERVO::CRobotTask* pTask, int code) {
        if (pTask == nullptr) {
            LOGE("<CModel>onRobotTaskEvent: ç©ºä»»åŠ¡æŒ‡é’ˆï¼Œå¿½ç•¥äº‹ä»¶ code=%d", code);
            LOGE("<CModel>onRobotTaskEvent: ç©ºä»»åŠ¡æŒ‡é’ˆï¼Œå¿½ç•¥äº‹ä»¶ code=%d", code);
            return;
        }
        // ä»»åŠ¡æè¿°ä¸Ž ID ç”¨äºŽæ—¥å¿—
        // ä»»åŠ¡æè¿°ä¸Ž ID ç”¨äºŽæ—¥å¿—
        SERVO::CGlass* pGlass = (SERVO::CGlass*)pTask->getContext();
        const std::string& strDesc = pTask->getDescription();
        std::string strClassID;
@@ -319,48 +326,48 @@
            }
        }
        // æ—¥å¿—输出与状态处理
        // æ—¥å¿—输出与状态处理
        switch (code) {
        case ROBOT_EVENT_CREATE:
            LOGI("<CModel>onRobotTaskEvent: æ–°ä»»åŠ¡åˆ›å»º(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
            LOGI("<CModel>onRobotTaskEvent: æ–°ä»»åŠ¡åˆ›å»º(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
            break;
        case ROBOT_EVENT_FINISH:
            LOGI("<CModel>onRobotTaskEvent: ä»»åŠ¡å®Œæˆ(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
            LOGI("<CModel>onRobotTaskEvent: ä»»åŠ¡å®Œæˆ(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
            break;
        case ROBOT_EVENT_ERROR:
            LOGE("<CModel>onRobotTaskEvent: ä»»åŠ¡é”™è¯¯(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
            LOGE("<CModel>onRobotTaskEvent: ä»»åŠ¡é”™è¯¯(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
            break;
        case ROBOT_EVENT_ABORT:
            LOGE("<CModel>onRobotTaskEvent: ä»»åŠ¡åœæ­¢(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
            LOGE("<CModel>onRobotTaskEvent: ä»»åŠ¡åœæ­¢(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
            break;
        case ROBOT_EVENT_RESTORE:
            LOGE("<CModel>onRobotTaskEvent: ä»»åŠ¡å›žæ’¤(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
            LOGE("<CModel>onRobotTaskEvent: ä»»åŠ¡å›žæ’¤(%s, ClassID=%s).", strDesc.c_str(), strClassID.c_str());
            break;
        default:
            LOGE("<CModel>onRobotTaskEvent: æœªçŸ¥äº‹ä»¶ code=%d, ä»»åŠ¡=%s", code, strDesc.c_str());
            LOGE("<CModel>onRobotTaskEvent: æœªçŸ¥äº‹ä»¶ code=%d, ä»»åŠ¡=%s", code, strDesc.c_str());
            break;
        }
        // å®‰å…¨æ ¼å¼åŒ–æ—¶é—´
        // å®‰å…¨æ ¼å¼åŒ–æ—¶é—´
        auto format_time = [](time_t t) -> std::string {
            if (t <= 0 || t == _I64_MIN || t == _I64_MAX) { 
                return "";
            }
            // ä½¿ç”¨ localtime_s ç¡®ä¿çº¿ç¨‹å®‰å…¨
            // ä½¿ç”¨ localtime_s ç¡®ä¿çº¿ç¨‹å®‰å…¨
            tm tmBuf{};
            errno_t err = localtime_s(&tmBuf, &t);
            if (err != 0 || tmBuf.tm_mon < 0 || tmBuf.tm_mon > 11) {
                return "";
            }
            // æ ¼å¼åŒ–时间字符串
            // æ ¼å¼åŒ–时间字符串
            char buf[64] = {};
            strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tmBuf);
            return std::string(buf);
        };
        // æž„造 TransferData æ•°æ®ç»“æž„
        // æž„造 TransferData æ•°æ®ç»“æž„
        TransferData data;
        data.strClassID = strClassID;
        data.strCreateTime = format_time(pTask->getCreateTime());
@@ -369,7 +376,7 @@
        data.strEndTime = format_time(pTask->getFinishTime());
        data.strDescription = pTask->getSimpleDescription();
        // çŠ¶æ€æ˜ å°„
        // çŠ¶æ€æ˜ å°„
        static const char* STATUS_STR[] = {
            "Ready", "Running", "Picking", "Picked", "Placing", "Restoring", "Error", "Abort", "Restored", "Completed"
        };
@@ -382,12 +389,12 @@
            data.strStatus = "Unknown";
        }
        // å†™å…¥æ•°æ®åº“
        // å†™å…¥æ•°æ®åº“
        if (code == ROBOT_EVENT_FINISH || code == ROBOT_EVENT_ERROR
            || code == ROBOT_EVENT_ABORT || code == ROBOT_EVENT_RESTORE) {
            int nRecordId = 0;
            TransferManager::getInstance().addTransferRecord(data, nRecordId);
            LOGI("<CModel>onRobotTaskEvent: ä»»åŠ¡è®°å½•å·²ä¿å­˜ï¼ŒRecordID=%d", nRecordId);
            LOGI("<CModel>onRobotTaskEvent: ä»»åŠ¡è®°å½•å·²ä¿å­˜ï¼ŒRecordID=%d", nRecordId);
        }
        notifyPtrAndInt(RX_CODE_EQ_ROBOT_TASK, pTask, nullptr, code);
@@ -435,7 +442,7 @@
        m_hsmsPassive.setVariableValue("CJEndID", ((SERVO::CControlJob*)pj)->id().c_str());
        m_hsmsPassive.requestEventReportSend_CJ_End();
        // ç»“批,保存ControlJob
        // ç»“批,保存ControlJob
        // 
    };
    masterListener.onPjStart = [&](void* pMaster, void* pj) {
@@ -464,28 +471,28 @@
    m_master.setContinuousTransferCount(m_configuration.getContinuousTransferCount());
    // master è®¾ç½®ç¼“存文件
    // master è®¾ç½®ç¼“存文件
    CString strMasterDataFile;
    strMasterDataFile.Format(_T("%s\\Master.dat"), (LPTSTR)(LPCTSTR)m_strWorkDir);
    m_master.setCacheFilepath((LPTSTR)(LPCTSTR)strMasterDataFile);
    m_master.setCompareMapsBeforeProceeding(m_configuration.isCompareMapsBeforeProceeding());
    m_master.setJobMode(m_configuration.isJobMode());
    // åŠ æˆªJob
    // åŠ æˆªJob
    strMasterDataFile.Format(_T("%s\\MasterState.dat"), (LPTSTR)(LPCTSTR)m_strWorkDir);
    std::string strPath = std::string((LPTSTR)(LPCTSTR)strMasterDataFile);
    m_master.setStateFile(strPath);
    // åŠ è½½è­¦å‘Šä¿¡æ¯
    // åŠ è½½è­¦å‘Šä¿¡æ¯
    AlarmManager& alarmManager = AlarmManager::getInstance();
    char szBuffer[MAX_PATH];
    sprintf_s(szBuffer, MAX_PATH, "%s\\AlarmList.csv", (LPTSTR)(LPCTSTR)m_strWorkDir);
    alarmManager.readAlarmFile(szBuffer);
    // Glass数据库
    // Glass数据库
    strLogDir.Format(_T("%s\\db\\process.db"), (LPTSTR)(LPCTSTR)m_strWorkDir);
    std::string path((LPTSTR)(LPCTSTR)strLogDir);
    GlassLogDb::Init(path);
SourceCode/Bond/Servo/PageRecipe.cpp
@@ -386,13 +386,12 @@
void CPageRecipe::OnBnClickedButtonNew()
{
    // TODO: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    //CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_EQUIPMENT);
    //int nSel = pComboBox->GetCurSel();
    //SERVO::CEquipment* pEq = (SERVO::CEquipment*)pComboBox->GetItemDataPtr(nSel);
    //if (pEq == nullptr) {
    //    return;
    //}
    int rc = UX_CanExecute(L"recipe");
    if (rc != 1) {
        AfxMessageBox("操作权限不足,请联系管理人员!");
        return;
    }
    UX_RecordAction(L"recipe");
    CRecipeDeviceBindDlg dlg(this);
    if (dlg.DoModal() == IDOK) {
@@ -452,6 +451,13 @@
void CPageRecipe::OnBnClickedButtonModify()
{
    int rc = UX_CanExecute(L"recipe");
    if (rc != 1) {
        AfxMessageBox("操作权限不足,请联系管理人员!");
        return;
    }
    UX_RecordAction(L"recipe");
    // TODO: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_EQUIPMENT);
    if (pComboBox == nullptr || !::IsWindow(pComboBox->m_hWnd)) {
@@ -492,6 +498,13 @@
void CPageRecipe::OnBnClickedButtonDelete()
{
    int rc = UX_CanExecute(L"recipe");
    if (rc != 1) {
        AfxMessageBox("操作权限不足,请联系管理人员!");
        return;
    }
    UX_RecordAction(L"recipe");
    // TODO: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    POSITION pos = m_listPPID.GetFirstSelectedItemPosition();
    if (!pos) { 
@@ -518,6 +531,13 @@
void CPageRecipe::OnBnClickedButtonDeleteAll()
{
    int rc = UX_CanExecute(L"recipe");
    if (rc != 1) {
        AfxMessageBox("操作权限不足,请联系管理人员!");
        return;
    }
    UX_RecordAction(L"recipe");
    // TODO: åœ¨æ­¤æ·»åŠ æŽ§ä»¶é€šçŸ¥å¤„ç†ç¨‹åºä»£ç 
    if (IDYES != AfxMessageBox(_T("确定要删除全部配方记录吗?"), MB_YESNO | MB_ICONWARNING)) {
        return;
SourceCode/Bond/Servo/Servo.cpp
@@ -17,6 +17,8 @@
#include "MapPosWnd.h"
#include "HmTab.h"
#include "CControlJobManagerDlg.h"
#include "ToolUnits.h"
#include "CUserManager2.h"
// å£°æ˜Žå…¨å±€å˜é‡ï¼Œç”¨äºŽç®¡ç† GDI+ åˆå§‹åŒ–
@@ -100,6 +102,11 @@
    m_strAppDir = CString(sDrive) + CString(sDir);
    m_strAppFile = CString(sFilename);
    m_model.setWorkDir((LPTSTR)(LPCTSTR)m_strAppDir);
    // ç”¨æˆ·æ•°æ®åº“管理
    CString strDir = m_strAppDir + _T("\\DB");
    CUserManager2::getInstance().init((LPTSTR)(LPCTSTR)strDir);
    // æ³¨å†ŒæŽ§ä»¶
@@ -233,6 +240,7 @@
    m_model.term();
    HSMS_Term();
    RX_Term();
    UX_Shutdown();
    // æ¸…理 GDI+
    TermGDIPlus();
SourceCode/Bond/Servo/Servo.rc
Binary files differ
SourceCode/Bond/Servo/Servo.vcxproj
@@ -255,6 +255,10 @@
    <ClInclude Include="CRobotTaskDlg.h" />
    <ClInclude Include="CSVData.h" />
    <ClInclude Include="CServoUtilsTool.h" />
    <ClInclude Include="CUserManager2.h" />
    <ClInclude Include="CUserManager2Dlg.h" />
    <ClInclude Include="CUserEdit2Dlg.h" />
    <ClInclude Include="CUserXLogDlg.h" />
    <ClInclude Include="CVariable.h" />
    <ClInclude Include="DeviceRecipeParamDlg.h" />
    <ClInclude Include="GlassJson.h" />
@@ -280,6 +284,7 @@
    <ClInclude Include="InputDialog.h" />
    <ClInclude Include="JobSlotGrid.h" />
    <ClInclude Include="LoginDlg.h" />
    <ClInclude Include="LoginDlg2.h" />
    <ClInclude Include="MsgDlg.h" />
    <ClInclude Include="PageRecipe.h" />
    <ClInclude Include="CDoubleGlass.h" />
@@ -468,6 +473,10 @@
    <ClCompile Include="CRobotTaskDlg.cpp" />
    <ClCompile Include="CSVData.cpp" />
    <ClCompile Include="CServoUtilsTool.cpp" />
    <ClCompile Include="CUserManager2.cpp" />
    <ClCompile Include="CUserManager2Dlg.cpp" />
    <ClCompile Include="CUserEdit2Dlg.cpp" />
    <ClCompile Include="CUserXLogDlg.cpp" />
    <ClCompile Include="CVariable.cpp" />
    <ClCompile Include="DeviceRecipeParamDlg.cpp" />
    <ClCompile Include="GlassJson.cpp" />
@@ -491,6 +500,7 @@
    <ClCompile Include="InputDialog.cpp" />
    <ClCompile Include="JobSlotGrid.cpp" />
    <ClCompile Include="LoginDlg.cpp" />
    <ClCompile Include="LoginDlg2.cpp" />
    <ClCompile Include="MsgDlg.cpp" />
    <ClCompile Include="PageRecipe.cpp" />
    <ClCompile Include="CDoubleGlass.cpp" />
SourceCode/Bond/Servo/Servo.vcxproj.filters
@@ -227,6 +227,12 @@
    <ClCompile Include="..\DAQBridge\proto\ProtocolCodec.cpp">
      <Filter>DAQBridge</Filter>
    </ClCompile>
    <ClCompile Include="ClientListDlg.cpp" />
    <ClCompile Include="LoginDlg2.cpp" />
    <ClCompile Include="CUserManager2.cpp" />
    <ClCompile Include="CUserManager2Dlg.cpp" />
    <ClCompile Include="CUserEdit2Dlg.cpp" />
    <ClCompile Include="CUserXLogDlg.cpp" />
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="AlarmManager.h" />
@@ -497,6 +503,12 @@
    <ClInclude Include="..\DAQBridge\DAQConfig.h">
      <Filter>DAQBridge</Filter>
    </ClInclude>
    <ClInclude Include="ClientListDlg.h" />
    <ClInclude Include="LoginDlg2.h" />
    <ClInclude Include="CUserManager2.h" />
    <ClInclude Include="CUserManager2Dlg.h" />
    <ClInclude Include="CUserEdit2Dlg.h" />
    <ClInclude Include="CUserXLogDlg.h" />
  </ItemGroup>
  <ItemGroup>
    <ResourceCompile Include="Servo.rc" />
SourceCode/Bond/Servo/ServoDlg.cpp
@@ -16,6 +16,7 @@
#include "CRobotCmdContainerDlg.h"
#include "CRobotCmdTestDlg.h"
#include "LoginDlg.h"
#include "LoginDlg2.h"
#include "ChangePasswordDlg.h"
#include "UserManagerDlg.h"
#include "SystemLogManagerDlg.h"
@@ -30,6 +31,9 @@
#include "InputDialog.h"
#include "ClientListDlg.h"
#include "CControlJobManagerDlg.h"
#include "CUserManager2.h"
#include "CUserManager2Dlg.h"
#include "CUserXLogDlg.h"
#ifdef _DEBUG
@@ -44,7 +48,7 @@
#define TIMER_ID_UPDATE_RUMTIME            2
/* Test */
#define TIMER_ID_TEST                    3
#define TIMER_ID_LOGIN                    3
// ç”¨äºŽåº”用程序“关于”菜单项的 CAboutDlg å¯¹è¯æ¡†
@@ -197,7 +201,7 @@
            else if (RX_CODE_MASTER_STATE_CHANGED == code) {
                SERVO::MASTERSTATE state = theApp.m_model.getMaster().getState();
                if (state == SERVO::MASTERSTATE::READY) {
                    m_pTopToolbar->GetBtn(IDC_BUTTON_RUN)->EnableWindow(TRUE);
                    m_pTopToolbar ->GetBtn(IDC_BUTTON_RUN)->EnableWindow(TRUE);
                    m_pTopToolbar->GetBtn(IDC_BUTTON_RUN_BATCH)->EnableWindow(TRUE);
                    m_pTopToolbar->GetBtn(IDC_BUTTON_RUN_CT)->EnableWindow(TRUE);
                    m_pTopToolbar->GetBtn(IDC_BUTTON_STOP)->EnableWindow(FALSE);
@@ -368,7 +372,7 @@
    // model init
    theApp.m_model.init();
    SetTimer(TIMER_ID_TEST, 1000, nullptr);
    SetTimer(TIMER_ID_LOGIN, 1500, nullptr);
    // èœå•
    CMenu menu;
@@ -935,11 +939,23 @@
        m_pMyStatusbar->setRunTimeText((LPTSTR)(LPCTSTR)strText);
    }
    else if(TIMER_ID_TEST == nIDEvent){
        static __int64 tttt = 0;
        tttt++;
        theApp.m_model.m_hsmsPassive.setVariableValue("CJobSpace", tttt % 10);
        theApp.m_model.m_hsmsPassive.setVariableValue("PJobSpace", tttt % 5);
    else if(TIMER_ID_LOGIN == nIDEvent){
        KillTimer(TIMER_ID_LOGIN);
        if (!CUserManager2::getInstance().isLoggedIn()) {
            CLoginDlg2 dlg;
            if (dlg.DoModal() != IDOK) {
                PostMessage(WM_CLOSE);
            }
            else {
                bool bRet = CUserManager2::getInstance().login((LPTSTR)(LPCTSTR)dlg.m_strUsername,
                    (LPTSTR)(LPCTSTR)dlg.m_strPassword);
                if (!bRet) {
                    AfxMessageBox("登录失败,请检查用户名或密码是否正确!");
                    PostMessage(WM_CLOSE);
                }
                UpdateLoginStatus();
            }
        }
    }
@@ -977,32 +993,24 @@
void CServoDlg::UpdateLoginStatus()
{
    HMENU hMenu = m_pTopToolbar->GetOperatorMenu();
    UserManager& userManager = UserManager::getInstance();
    if (userManager.isLoggedIn())
    {
        ::EnableMenuItem(hMenu, ID_OPEATOR_LOGIN, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
        ::EnableMenuItem(hMenu, ID_OPERATOR_CHANGE_PASSWORD, MF_BYCOMMAND | MF_ENABLED);
    CUserManager2& userManager = CUserManager2::getInstance();
    if (userManager.isLoggedIn()) {
        ::EnableMenuItem(hMenu, ID_OPERATOR_SYSTEM_LOG, MF_BYCOMMAND | MF_ENABLED);
        ::EnableMenuItem(hMenu, ID_OPEATOR_SWITCH, MF_BYCOMMAND | MF_ENABLED);
        ::EnableMenuItem(hMenu, ID_OPERATOR_LOGOUT, MF_BYCOMMAND | MF_ENABLED);
        if (userManager.getCurrentUserRole() == UserRole::SuperAdmin) {
        if (userManager.IsAdminCurrent()) {
            ::EnableMenuItem(hMenu, ID_OPEATOR_USER_MANAGER, MF_BYCOMMAND | MF_ENABLED);
        }
        else {
            ::EnableMenuItem(hMenu, ID_OPEATOR_USER_MANAGER, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
        }
        m_pTopToolbar->SetOperatorBtnText(userManager.getCurrentUser().c_str());
        m_pTopToolbar->SetOperatorBtnText(userManager.getCurrentUserName().c_str());
    }
    else {
        ::EnableMenuItem(hMenu, ID_OPEATOR_LOGIN, MF_BYCOMMAND | MF_ENABLED);
        ::EnableMenuItem(hMenu, ID_OPERATOR_CHANGE_PASSWORD, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
        ::EnableMenuItem(hMenu, ID_OPEATOR_USER_MANAGER, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
        ::EnableMenuItem(hMenu, ID_OPERATOR_SYSTEM_LOG, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
        ::EnableMenuItem(hMenu, ID_OPEATOR_SWITCH, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
        ::EnableMenuItem(hMenu, ID_OPERATOR_LOGOUT, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
        m_pTopToolbar->SetOperatorBtnText(_T("未登录"));
    }
}
@@ -1010,15 +1018,14 @@
LRESULT CServoDlg::OnToolbarBtnClicked(WPARAM wParam, LPARAM lParam)
{
    int id = (int)lParam;
    if (id == IDC_BUTTON_RUN || id == IDC_BUTTON_STOP) {
        UserRole emRole = UserManager::getInstance().getCurrentUserRole();
        if (emRole != UserRole::SuperAdmin) {
            AfxMessageBox(_T("当前用户并非管理员!!!"));
            return 1;
        }
    }
    if (id == IDC_BUTTON_RUN) {
        int rc = UX_CanExecute(L"start");
        if (rc != 1) {
            AfxMessageBox("操作权限不足,请联系管理人员!");
            return 0;
        }
        UX_RecordAction(L"start");
        if (theApp.m_model.getMaster().getState() == SERVO::MASTERSTATE::MSERROR) {
            AfxMessageBox("当前有机台发生错误,不能启动,请确认解决问题后再尝试重新启动!");
        }
@@ -1055,6 +1062,13 @@
        }
    }
    else if (id == IDC_BUTTON_STOP) {
        int rc = UX_CanExecute(L"stop");
        if (rc != 1) {
            AfxMessageBox("操作权限不足,请联系管理人员!");
            return 0;
        }
        UX_RecordAction(L"stop");
        if (theApp.m_model.getMaster().stop() == 0) {
            m_pTopToolbar->GetBtn(IDC_BUTTON_STOP)->EnableWindow(FALSE);
        }
@@ -1090,6 +1104,27 @@
    }
    else if (id == IDC_BUTTON_OPERATOR) {
        int menuId = (int)wParam;
        if (menuId == 0) {
            CUserManager2Dlg dlg;
            dlg.DoModal();
        }
        else if (menuId == 1) {
            CUserXLogDlg dlg;
            dlg.DoModal();
        }
        else if (menuId == 2) {
            CLoginDlg2 dlg;
            if (dlg.DoModal() == IDOK) {
                bool bRet = CUserManager2::getInstance().login((LPTSTR)(LPCTSTR)dlg.m_strUsername,
                    (LPTSTR)(LPCTSTR)dlg.m_strPassword);
                if (!bRet) {
                    AfxMessageBox("登录失败,请检查用户名或密码是否正确!");
                }
                UpdateLoginStatus();
            }
        }
        /*
        SystemLogManager& logManager = SystemLogManager::getInstance();
        UserManager& userManager = UserManager::getInstance();
        if (menuId == 0) {
@@ -1138,6 +1173,7 @@
        }
        UpdateLoginStatus();
        */
    }
    return 0;
SourceCode/Bond/Servo/ToolUnits.cpp
@@ -574,4 +574,44 @@
    std::ostringstream oss;
    oss << std::put_time(&tm, "%Y%m%d%H%M%S"); // ä¾‹ï¼š2025-09-15 08:23:07
    return oss.str();
}
std::wstring CToolUnits::AnsiToWString(const std::string& str)
{
    if (str.empty()) return std::wstring();
    int len = ::MultiByteToWideChar(CP_ACP, 0,
        str.c_str(), -1,
        nullptr, 0);
    if (len <= 0) return std::wstring();
    std::wstring ws;
    ws.resize(len - 1);
    ::MultiByteToWideChar(CP_ACP, 0,
        str.c_str(), -1,
        &ws[0], len);
    return ws;
}
std::string CToolUnits::WStringToAnsi(const std::wstring& wstr)
{
    if (wstr.empty()) return std::string();
    int len = ::WideCharToMultiByte(CP_ACP, 0,
        wstr.c_str(), -1,
        nullptr, 0,
        nullptr, nullptr);
    if (len <= 0) return std::string();
    std::string str;
    str.resize(len - 1);
    ::WideCharToMultiByte(CP_ACP, 0,
        wstr.c_str(), -1,
        &str[0], len,
        nullptr, nullptr);
    return str;
}
SourceCode/Bond/Servo/ToolUnits.h
@@ -60,5 +60,7 @@
        const char* fmt = "%Y-%m-%d %H:%M:%S");
    static std::string TimePointToLocalStringMs(const std::optional<TP>& tp);
    static std::string NowStrSec();
    static std::wstring AnsiToWString(const std::string& str);
    static std::string WStringToAnsi(const std::wstring& wstr);
};
SourceCode/Bond/Servo/resource.h
Binary files differ
SourceCode/Bond/Servo/stdafx.h
@@ -75,7 +75,7 @@
#include "..\RxWindows1.0\include\RxWindowsLib.h"
#include "..\HSMSSDK\Include\HSMSSDK.h"
#include "..\UserXLibrary\UserXAPI.h"
#ifdef _UNICODE
SourceCode/Bond/USERXLibrary/UserXAPI.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,136 @@
// UserX ç”¨æˆ·ç®¡ç†åº“(C æŽ¥å£ï¼‰/ UserX user management library (C API)
// è¦†ç›–功能 / Features: users, roles, actions, auth, logs(固定 SQLite3 æŒä¹…化 / SQLite3 persistence)
#pragma once
#if defined(_WIN32)
#  ifdef USERXLIBRARY_EXPORTS
#    define UX_API extern "C" __declspec(dllexport)
#  else
#    define UX_API extern "C" __declspec(dllimport)
#  endif
#else
#  define UX_API extern "C"
#endif
#include <wchar.h>
#ifdef _COMPILE_AS_LIB
#warning "compiling as lib!"
#else
#ifdef _DEBUG
#ifndef _WIN64
#pragma comment(lib, "../USERXLibrary/lib/Win32/Debug/UserXLibrary.lib")
#else
#pragma comment(lib, "../USERXLibrary/lib/x64/Debug/UserXLibrary.lib")
#endif
#else
#ifndef _WIN64
#pragma comment(lib, "../USERXLibrary/lib/Win32/Release/UserXLibrary.lib")
#else
#pragma comment(lib, "../USERXLibrary/lib/x64/Release/UserXLibrary.lib")
#endif
#endif
#endif // !BUILD_AS_LIB
// é¢å¤–错误码 / Extra error code
#ifndef UX_ERR_BAD_PASSWORD
#define UX_ERR_BAD_PASSWORD        -10    // å¯†ç é”™è¯¯ / bad password
#endif
// é”™è¯¯ç å®å®šä¹‰ / Error code macros
#define UX_OK                        0    // æˆåŠŸ / success
#define UX_ERR_INVALID_ARGS         -1    // å‚数错误 / invalid arguments
#define UX_ERR_NOT_FOUND            -2    // æœªæ‰¾åˆ° / not found
#define UX_ERR_EXISTS               -3    // å·²å­˜åœ¨ / already exists
#define UX_ERR_PERMISSION           -4    // æƒé™ä¸è¶³ / permission denied
#define UX_ERR_NOT_LOGGED_IN        -5    // æœªç™»å½• / not logged in
#define UX_ERR_BUFFER_TOO_SMALL     -6    // ç¼“冲区不足 / buffer too small
#define UX_ERR_DB                   -7    // I/O æˆ–数据库错误 / I/O or database error
#define UX_ERR_NOT_DEFINED          -8    // æœªå®šä¹‰ï¼ˆå¦‚动作不存在)/ not defined
#define UX_ERR_DB_NOT_EMPTY         -9    // æ•°æ®åº“非空(已初始化)/ database not empty
// è¿”回码 / Return codes(详见上方宏)
// UX_OK, UX_ERR_INVALID_ARGS, UX_ERR_NOT_FOUND, UX_ERR_EXISTS,
// UX_ERR_PERMISSION, UX_ERR_NOT_LOGGED_IN, UX_ERR_BUFFER_TOO_SMALL,
// UX_ERR_DB, UX_ERR_NOT_DEFINED, UX_ERR_DB_NOT_EMPTY
// åˆå§‹åŒ–:指定数据目录,内部将打开/创建 SQLite æ•°æ®åº“文件 UserX.db
// Init: provide data directory; opens/creates SQLite DB file UserX.db
UX_API int UX_Init(const wchar_t* storage_dir);
// å¯é€‰ï¼šè¦†ç›–数据库路径(仅使用第一个参数作为 .db æ–‡ä»¶è·¯å¾„,其他忽略)
// Optional: override DB path (use first arg as .db path; others ignored)
UX_API int UX_SetStorage(const wchar_t* users_db_path,
                         const wchar_t* /*unused_logs*/,
                         const wchar_t* /*unused_roles*/,
                         const wchar_t* /*unused_actions*/);
// é…ç½®è§’色(name:level æŒ‰è¡Œï¼‰/ Configure roles from lines "name:level"
// ä»…允许在“空数据库”(roles/users/actions/logs å››è¡¨å‡æ— æ•°æ®ï¼‰æ—¶è°ƒç”¨ï¼›å¦åˆ™è¿”回 -9。
// Only allowed when DB is empty (roles/users/actions/logs all zero rows); otherwise returns -9.
// e.g. L"Admin:100\nEngineer:50\nOperator:10\n"
UX_API int UX_SetRoleDefinitions(const wchar_t* roles_text);
// èŽ·å–è§’è‰²åˆ—è¡¨ï¼ˆname:level æŒ‰è¡Œï¼‰ï¼›buffer ä¸ºç©ºæˆ–不足时返回所需大小
// Get roles list as lines; returns required size if buffer is null/too small
UX_API int UX_GetRoles(wchar_t* buffer, int buffer_chars);
// ç”¨æˆ·ç®¡ç† / User management
UX_API int UX_AddUser(const wchar_t* username,
                      const wchar_t* display_name,
                      const wchar_t* password,
                      const wchar_t* role_name);
UX_API int UX_DeleteUser(const wchar_t* username);
// æ›´æ–°ä»»æ„å­—段(传 nullptr è¡¨ç¤ºä¿æŒä¸å˜ï¼‰/ Update subset; nullptr keeps field
UX_API int UX_UpdateUser(const wchar_t* username,
                         const wchar_t* new_display_name,
                         const wchar_t* new_password,
                         const wchar_t* new_role_name,
                         int enabled /* -1 keep, 0 disable, 1 enable */);
UX_API int UX_EnableUser(const wchar_t* username, int enabled /*0/1*/);
UX_API int UX_ResetPassword(const wchar_t* username, const wchar_t* new_password);
UX_API int UX_RenameUser(const wchar_t* username, const wchar_t* new_display_name);
// ç®¡ç†å‘˜æƒé™ï¼šåˆ é™¤æ‰€æœ‰ç”¨æˆ· / Admin-only
UX_API int UX_DeleteAllUsers();
// èŽ·å–ç”¨æˆ·åˆ—è¡¨ï¼ˆæ¯è¡Œï¼šusername,display,level,enabled)/ Query users
UX_API int UX_GetUsers(wchar_t* buffer, int buffer_chars);
// è®¤è¯ / Authentication
UX_API int UX_Login(const wchar_t* username, const wchar_t* password);
UX_API void UX_Logout();
UX_API int UX_IsLoggedIn();
UX_API void UX_Shutdown(); // é‡Šæ”¾èµ„源 / shutdown and free resources
UX_API int UX_GetCurrentUser(wchar_t* buffer, int buffer_chars);
// åŠ¨ä½œä¸Žæƒé™ / Actions & permissions
// å®šä¹‰/覆盖动作(名称、描述、最低角色)/ Define/overwrite action
UX_API int UX_DefineAction(const wchar_t* action_name,
                           const wchar_t* description,
                           const wchar_t* min_role_name);
// å¯æ‰§è¡Œè¿”回1,不可执行返回0;负数为错误 / 1 allowed, 0 denied
UX_API int UX_CanExecute(const wchar_t* action_name);
// è®°å½•动作到日志(时间、用户、动作、描述)/ Record action to logs
UX_API int UX_RecordAction(const wchar_t* action_name);
// èŽ·å–åŠ¨ä½œåˆ—è¡¨ï¼ˆæ¯è¡Œï¼šname,desc,minlevel)/ Get actions list
UX_API int UX_GetActions(wchar_t* buffer, int buffer_chars);
// è®°å½•尝试(允许/拒绝)/ record an attempt with allowed flag (1 allowed, 0 denied)
UX_API int UX_RecordAttempt(const wchar_t* action_name, int allowed /*1/0*/);
// æŸ¥è¯¢æœ€è¿‘ N æ¡æ—¥å¿—(每行:ISO8601时间,用户名,动作,描述)/ Query logs
UX_API int UX_QueryLogs(int last_n, wchar_t* buffer, int buffer_chars);
// æ ¹æ®é”™è¯¯ç è¿”回文字描述(中文/English)
// Return a human-readable message for an error code
UX_API const wchar_t* UX_ErrorMessage(int code);