Browse Source

登录页面总算改好了

张洋 1 month ago
parent
commit
ddd11701f0
64 changed files with 1758 additions and 303 deletions
  1. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/DuiLib_d.dll
  2. 254 0
      bin/Win32/Debug/zhipuzi_pos_windows/login_debug.log
  3. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/brand_close_hot.png
  4. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/brand_close_normal.png
  5. 205 74
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login.xml
  6. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_arrow_hot.png
  7. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_arrow_normal.png
  8. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_btn_hot.png
  9. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_btn_loading.png
  10. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_btn_normal.png
  11. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_btn_pushed.png
  12. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_cb_checked.png
  13. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_cb_checked_hot.png
  14. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_cb_hot.png
  15. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_cb_normal.png
  16. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_cb_pushed.png
  17. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_deco_box.png
  18. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_err_icon.png
  19. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_err_tip.png
  20. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_icon_lock.png
  21. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_icon_user.png
  22. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_err.png
  23. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_focus.png
  24. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_hot.png
  25. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_lock_err.png
  26. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_lock_focus.png
  27. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_lock_hot.png
  28. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_lock_normal.png
  29. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_normal.png
  30. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_user_editbox_err.png
  31. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_user_editbox_focus.png
  32. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_user_editbox_hot.png
  33. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_user_editbox_normal.png
  34. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_user_err.png
  35. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_user_focus.png
  36. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_user_hot.png
  37. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_user_normal.png
  38. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_left_bg.png
  39. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_spinner_16.gif
  40. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_spinner_40.gif
  41. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_sync_bar.gif
  42. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_sync_dot.png
  43. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_sync_track.png
  44. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/login_window_mask.png
  45. 86 25
      bin/Win32/Debug/zhipuzi_pos_windows/skin/update.xml
  46. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/update_bar_fill.png
  47. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/update_bar_fill_done.png
  48. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/update_bar_fill_fail.png
  49. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/update_bar_track.png
  50. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/update_deco_line.png
  51. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/update_footer_line.png
  52. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/update_icon_bg.png
  53. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/update_icon_done.png
  54. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/update_icon_fail.png
  55. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/update_left_bg.png
  56. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/update_retry_hot.png
  57. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/update_retry_normal.png
  58. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/update_retry_pushed.png
  59. BIN
      bin/Win32/Debug/zhipuzi_pos_windows/skin/update_spinner_30.gif
  60. BIN
      dll/debug/DuiLib_d.dll
  61. 533 101
      zhipuzi_pos_windows/wnd/CLoginWnd.cpp
  62. 32 1
      zhipuzi_pos_windows/wnd/CLoginWnd.h
  63. 592 89
      zhipuzi_pos_windows/wnd/CUpdateWnd.cpp
  64. 56 13
      zhipuzi_pos_windows/wnd/CUpdateWnd.h

BIN
bin/Win32/Debug/zhipuzi_pos_windows/DuiLib_d.dll


+ 254 - 0
bin/Win32/Debug/zhipuzi_pos_windows/login_debug.log

@@ -0,0 +1,254 @@
+[INIT] m_bNoActivate(before fix)=1 -> SetNoActivate(false)
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x002D1448 cls=EditWnd parent=0x01340E8C | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(ControlUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(ControlUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x000A0A4C cls=EditWnd parent=0x01340E8C | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(VerticalLayoutUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(VerticalLayoutUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x000B0A4C cls=EditWnd parent=0x01340E8C | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(VerticalLayoutUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(VerticalLayoutUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x000C0A4C cls=EditWnd parent=0x01340E8C | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(VerticalLayoutUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(VerticalLayoutUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x000D0A4C cls=EditWnd parent=0x01340E8C | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(VerticalLayoutUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(VerticalLayoutUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=pwdedit(EditUI) | focus=0x000E0A4C cls=EditWnd parent=0x01340E8C | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=pwdedit(EditUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(VerticalLayoutUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(VerticalLayoutUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=pwdedit(EditUI) | focus=0x000F0A4C cls=EditWnd parent=0x01340E8C | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=pwdedit(EditUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=login_remember_password(CheckBoxUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=login_remember_password(CheckBoxUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=closebtn(ButtonUI) | focus=0x01340E8C cls=UILoginFrame parent=0x00000000 | hPaint=0x01340E8C(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[INIT] m_bNoActivate(before fix)=1 -> SetNoActivate(false)
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x000F0952 cls=EditWnd parent=0x01130CBC | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01130CBC cls=UILoginFrame parent=0x00000000 | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(VerticalLayoutUI) | focus=0x01130CBC cls=UILoginFrame parent=0x00000000 | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(VerticalLayoutUI) | focus=0x01130CBC cls=UILoginFrame parent=0x00000000 | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=pwdedit(EditUI) | focus=0x00180A0C cls=EditWnd parent=0x01130CBC | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=pwdedit(EditUI) | focus=0x01130CBC cls=UILoginFrame parent=0x00000000 | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(VerticalLayoutUI) | focus=0x01130CBC cls=UILoginFrame parent=0x00000000 | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(VerticalLayoutUI) | focus=0x01130CBC cls=UILoginFrame parent=0x00000000 | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x00190A0C cls=EditWnd parent=0x01130CBC | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01130CBC cls=UILoginFrame parent=0x00000000 | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(HorizontalLayoutUI) | focus=0x01130CBC cls=UILoginFrame parent=0x00000000 | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(HorizontalLayoutUI) | focus=0x01130CBC cls=UILoginFrame parent=0x00000000 | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x001B0A0C cls=EditWnd parent=0x01130CBC | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01130CBC cls=UILoginFrame parent=0x00000000 | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(VerticalLayoutUI) | focus=0x01130CBC cls=UILoginFrame parent=0x00000000 | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(VerticalLayoutUI) | focus=0x01130CBC cls=UILoginFrame parent=0x00000000 | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=closebtn(ButtonUI) | focus=0x01130CBC cls=UILoginFrame parent=0x00000000 | hPaint=0x01130CBC(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[INIT] m_bNoActivate(before fix)=1 -> SetNoActivate(false)
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x00401376 cls=EditWnd parent=0x01E407A6 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(VerticalLayoutUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(VerticalLayoutUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=pwdedit(EditUI) | focus=0x00421376 cls=EditWnd parent=0x01E407A6 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=pwdedit(EditUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(HorizontalLayoutUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(HorizontalLayoutUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=pwdedit(EditUI) | focus=0x007B137E cls=EditWnd parent=0x01E407A6 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=pwdedit(EditUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(HorizontalLayoutUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(HorizontalLayoutUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x007D137E cls=EditWnd parent=0x01E407A6 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(LabelUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(LabelUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=pwdedit(EditUI) | focus=0x0080137E cls=EditWnd parent=0x01E407A6 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=pwdedit(EditUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(HorizontalLayoutUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(HorizontalLayoutUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x0081137E cls=EditWnd parent=0x01E407A6 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(VerticalLayoutUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(VerticalLayoutUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=pwdedit(EditUI) | focus=0x0082137E cls=EditWnd parent=0x01E407A6 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=pwdedit(EditUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=loginBtn(ButtonUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=loginBtn(ButtonUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountcombo(ComboUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[itemselect] 进入: CurSel=0, 选中项=liantongxue, 写入前edit=liantongxue
+[itemselect] 写入后: edit=liantongxue
+[KILLFOCUS] sender=accountcombo(ComboUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x00491376 cls=EditWnd parent=0x01E407A6 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountcombo(ComboUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=accountcombo(ComboUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x004B1376 cls=EditWnd parent=0x01E407A6 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=pwdedit(EditUI) | focus=0x006C0B4C cls=EditWnd parent=0x01E407A6 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=pwdedit(EditUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x004D1376 cls=EditWnd parent=0x01E407A6 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountcombo(ComboUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[itemselect] 进入: CurSel=1, 选中项=liantongxue:hejia, 写入前edit=liantongxue
+[itemselect] 写入后: edit=liantongxue:hejia
+[KILLFOCUS] sender=accountcombo(ComboUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x00501376 cls=EditWnd parent=0x01E407A6 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountcombo(ComboUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[itemselect] 进入: CurSel=0, 选中项=liantongxue, 写入前edit=liantongxue:hejia
+[itemselect] 写入后: edit=liantongxue
+[KILLFOCUS] sender=accountcombo(ComboUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x006F0B4C cls=EditWnd parent=0x01E407A6 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountcombo(ComboUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=accountcombo(ComboUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(VerticalLayoutUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(VerticalLayoutUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountcombo(ComboUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[itemselect] 进入: CurSel=1, 选中项=liantongxue:hejia, 写入前edit=liantongxue
+[itemselect] 写入后: edit=liantongxue:hejia
+[KILLFOCUS] sender=accountcombo(ComboUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x00710B4C cls=EditWnd parent=0x01E407A6 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountcombo(ComboUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=accountcombo(ComboUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(VerticalLayoutUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(VerticalLayoutUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=closebtn(ButtonUI) | focus=0x01E407A6 cls=UILoginFrame parent=0x00000000 | hPaint=0x01E407A6(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[INIT] m_bNoActivate(before fix)=1 -> SetNoActivate(false)
+[SETFOCUS] sender=loginBtn(ButtonUI) | focus=0x007E120E cls=UILoginFrame parent=0x00000000 | hPaint=0x007E120E(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=loginBtn(ButtonUI) | focus=0x007E120E cls=UILoginFrame parent=0x00000000 | hPaint=0x007E120E(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[INIT] m_bNoActivate(before fix)=0 -> SetNoActivate(false)
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x000A16CE cls=EditWnd parent=0x0074109E | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x0074109E cls=UILoginFrame parent=0x00000000 | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(VerticalLayoutUI) | focus=0x0074109E cls=UILoginFrame parent=0x00000000 | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(VerticalLayoutUI) | focus=0x0074109E cls=UILoginFrame parent=0x00000000 | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=pwdedit(EditUI) | focus=0x000C16CE cls=EditWnd parent=0x0074109E | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=pwdedit(EditUI) | focus=0x0074109E cls=UILoginFrame parent=0x00000000 | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(HorizontalLayoutUI) | focus=0x0074109E cls=UILoginFrame parent=0x00000000 | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(HorizontalLayoutUI) | focus=0x0074109E cls=UILoginFrame parent=0x00000000 | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=accountedit(EditUI) | focus=0x003A07FE cls=EditWnd parent=0x0074109E | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=accountedit(EditUI) | focus=0x0074109E cls=UILoginFrame parent=0x00000000 | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(VerticalLayoutUI) | focus=0x0074109E cls=UILoginFrame parent=0x00000000 | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(VerticalLayoutUI) | focus=0x0074109E cls=UILoginFrame parent=0x00000000 | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=pwdedit(EditUI) | focus=0x003B07FE cls=EditWnd parent=0x0074109E | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=1 | popup=0 childStyle=1 layered=0 noactivate=0
+[KILLFOCUS] sender=pwdedit(EditUI) | focus=0x0074109E cls=UILoginFrame parent=0x00000000 | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=(VerticalLayoutUI) | focus=0x0074109E cls=UILoginFrame parent=0x00000000 | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=(VerticalLayoutUI) | focus=0x0074109E cls=UILoginFrame parent=0x00000000 | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[SETFOCUS] sender=loginBtn(ButtonUI) | focus=0x0074109E cls=UILoginFrame parent=0x00000000 | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[KILLFOCUS] sender=loginBtn(ButtonUI) | focus=0x0074109E cls=UILoginFrame parent=0x00000000 | hPaint=0x0074109E(UILoginFrame) isChildOfPaint=0 | popup=1 childStyle=0 layered=0 noactivate=0
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[HOVER] ENTER pt=(541,166) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=1
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=1
+[HOVER] LEAVE pt=(559,116) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[HOVER] ENTER pt=(620,117) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=1
+[HOVER] LEAVE pt=(634,116) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[HOVER] ENTER pt=(613,117) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=1
+[BOXVISUAL] err=0 focused=1(edit=0 combo=1) hovered=1
+[HOVER] LEAVE pt=(628,167) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=1(edit=0 combo=1) hovered=0
+[itemselect] 进入: CurSel=0, 选中项=liantongxue, 写入前edit=liantongxue
+[itemselect] 写入后: edit=liantongxue
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=1(edit=0 combo=1) hovered=0
+[HOVER] ENTER pt=(592,166) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=1(edit=0 combo=1) hovered=1
+[HOVER] LEAVE pt=(560,115) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=1(edit=0 combo=1) hovered=0
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[HOVER] ENTER pt=(595,117) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=1
+[BOXVISUAL] err=0 focused=1(edit=0 combo=1) hovered=1
+[HOVER] LEAVE pt=(624,167) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=1(edit=0 combo=1) hovered=0
+[itemselect] 进入: CurSel=1, 选中项=liantongxue:hejia, 写入前edit=liantongxue
+[itemselect] 写入后: edit=liantongxue:hejia
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=1(edit=0 combo=1) hovered=0
+[HOVER] ENTER pt=(648,166) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=1(edit=0 combo=1) hovered=1
+[HOVER] LEAVE pt=(624,167) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=1(edit=0 combo=1) hovered=0
+[itemselect] 进入: CurSel=2, 选中项=liantongxue:limin, 写入前edit=liantongxue:hejia
+[itemselect] 写入后: edit=liantongxue:limin
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=1(edit=0 combo=1) hovered=0
+[HOVER] ENTER pt=(543,166) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=1(edit=0 combo=1) hovered=1
+[HOVER] LEAVE pt=(524,113) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=1(edit=0 combo=1) hovered=0
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[HOVER] ENTER pt=(523,118) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=1
+[HOVER] LEAVE pt=(526,167) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[HOVER] ENTER pt=(523,166) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=1
+[HOVER] LEAVE pt=(515,116) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[HOVER] ENTER pt=(534,117) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=1
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=1
+[HOVER] LEAVE pt=(541,116) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[HOVER] ENTER pt=(543,117) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=1
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=1
+[HOVER] LEAVE pt=(533,116) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[HOVER] ENTER pt=(546,117) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=1
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=1
+[HOVER] LEAVE pt=(549,116) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[HOVER] ENTER pt=(549,117) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=1
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=1
+[HOVER] LEAVE pt=(627,167) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[HOVER] ENTER pt=(571,117) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=1
+[HOVER] LEAVE pt=(558,116) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[HOVER] ENTER pt=(567,117) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=1
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=1
+[HOVER] LEAVE pt=(575,116) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[HOVER] ENTER pt=(580,117) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=1
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=1
+[HOVER] LEAVE pt=(571,116) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=1(edit=1 combo=0) hovered=0
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[HOVER] ENTER pt=(552,117) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=1
+[HOVER] LEAVE pt=(548,167) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0
+[HOVER] ENTER pt=(548,165) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=1
+[HOVER] LEAVE pt=(542,116) rcBox=(346,117,674,167)
+[BOXVISUAL] err=0 focused=0(edit=0 combo=0) hovered=0

BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/brand_close_hot.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/brand_close_normal.png


+ 205 - 74
bin/Win32/Debug/zhipuzi_pos_windows/skin/login.xml

@@ -1,80 +1,211 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Window size="600,480" caption="0,0,0,36" roundcorner="4,4" shadowsize="3" showshadow="true">
-	<Font shared="true" id="0" name="微软雅黑" size="16" default="true" />
-	<Font id="1" name="微软雅黑" size="40" />
-	<Font id="2" name="微软雅黑" size="18" />
-	<Font id="3" name="微软雅黑" size="32" />
-
-	<VerticalLayout bkimage="file='Dialog_BG2.png' corner='4,48,4,4'" >		
-		<HorizontalLayout height="44" bkcolor="0xFF3CB371">
-			<Label name="login_version" text="" height="44" align="left" valign="center" padding="15,0,0,0" textcolor="#FFFFFFFF" font="0"/>
-			<Button name="closebtn" padding="0,13,15,0" width="16" height="16" tooltip="关闭" normalimage="file='close_normal.png'" hotimage="file='close_hover.png'" pushedimage="file='close_normal.png'"/>
-		</HorizontalLayout>
-		
-		<HorizontalLayout height="90" padding="0,30,0,0">
-			<Control></Control>
-			<HorizontalLayout bkimage="file='logo_guanwang.png'" width="248" height="57" padding="0,0,0,0"></HorizontalLayout>
-			<Control></Control>
-		</HorizontalLayout>
-		
-		<VerticalLayout name="login_input" height="200">
-			<HorizontalLayout height="50">
-				<Label text="账号:" float="true" pos="165,8,228,30"/>
-				<Combo name="accountcombo" float="true" pos="225, 0, 436, 38" tooltip="请点击这里选择您的登录账号" normalimage="file='Login_Combox_Normal.png' corner='2,2,30,2'" hotimage="file='Login_Combox_Hover.png' corner='2,2,30,2'" pushedimage="Login_Combox_Click.png' corner='2,2,30,2'" textpadding="10,1,1,1" >
-				</Combo>
-				<Edit name="accountedit" float="true" pos="225,0,398,38" normalimage="file='Chat_InputBox_BG.png' corner='4,4,4,4'" hotimage="file='Chat_InputBox_BG_Hover.png' corner='4,4,4,4'" />
+<?xml version="1.0" encoding="UTF-8"?>
+<Window size="720,460" caption="0,0,0,36" roundcorner="20,20" shadowsize="4" showshadow="true">
+	<Font shared="true" id="0" name="微软雅黑" size="13" default="true" />
+	<Font id="1" name="微软雅黑" size="21" bold="true" />
+	<Font id="2" name="微软雅黑" size="24" bold="true" />
+	<Font id="3" name="微软雅黑" size="12" />
+	<Font id="4" name="微软雅黑" size="15" bold="true" />
+	<Font id="5" name="微软雅黑" size="14" />
+
+	<!-- 只读说明:本页有两套「属性串」写法,不能混用。
+	     1) 普通图片属性  —— 直接由 SetAttribute 交给图片解析器,用单引号:
+	            bkimage="file='x.png' corner='4,4,4,4'"
+	     2) 会被二次解析的属性串(<Default value=...> / dropbox / style / innerstyle /
+	        vscrollbarstyle / itemattr 等)—— 走 CControlUI::ApplyAttributeList
+	        (DuiLib/Core/UIControl.cpp:1205),它【只认双引号】,且 UIMarkup 不做实体转义,
+	        所以每一对必须写成  key=&quot;value&quot; :
+	            dropbox="inset=&quot;1,1,1,1&quot;"
+	        写成 key='value' 时:Debug 在第 1245 行 ASSERT 中断,Release 静默吞掉整串属性。 -->
+
+	<!-- 下拉列表项统一行高 34。
+	     注意:本页下拉项是在 CLoginWnd::Init() 里 new 出来的,不经过 CDialogBuilder,
+	     拿不到这条 Default,所以行高在代码里另用 SetFixedHeight(34) 设了一遍,两处需保持一致。 -->
+	<Default name="ListLabelElement" value="height=&quot;34&quot;" />
+
+	<HorizontalLayout bkcolor="#FFFFFFFF">
+
+		<!-- ==================== 左:品牌区 300 x 460 ==================== -->
+		<VerticalLayout width="300" height="460" inset="30,36,30,0" bkimage="file='login_left_bg.png'">
+			<Label text="智铺子" height="28" font="1" textcolor="#FFFFFFFF" padding="0,0,0,37" />
+			<Label text="门店数字化服务商" height="37" font="2" textcolor="#FFFFFFFF" padding="0,0,0,16" />
+			<Label text="让每一次收银都更简单、" height="24" font="0" textcolor="#EBFFFFFF" />
+			<Label text="更智能、更高效。" height="24" font="0" textcolor="#EBFFFFFF" />
+
+			<!-- 装饰虚线框:距底 34,高 96,左右各 30 -->
+			<Container float="true" pos="30,330,270,426" bkimage="file='login_deco_box.png'">
+				<Label text="收银 · 会员 · 库存 · 报表" font="3" textcolor="#BFFFFFFF" align="center" valign="vcenter" />
+			</Container>
+		</VerticalLayout>
+
+		<!-- ==================== 右:表单区 420 x 460 ==================== -->
+		<VerticalLayout width="420" height="460" inset="46,96,46,0" bkcolor="#FFFFFFFF">
+
+			<!-- 关闭按钮:右上角,距右 16 / 距顶 14 -->
+			<Button name="closebtn" float="true" pos="388,14,404,30" tooltip="关闭"
+					normalimage="file='brand_close_normal.png'"
+					hotimage="file='brand_close_hot.png'"
+					pushedimage="file='brand_close_normal.png'" />
+
+		<!-- 【对齐修复】padding.left=3 让 Label 文字 anti-alias 左缘 = Container 框视觉外边。
+		     PNG 设计:x=0~3 全透明(圆角),x=4 是 1px 实色边线 → 框视觉外边 = Container.x + 4。
+		     Label 默认 padding.left=0 → 文字起点 = Container.x + 1(文字抗锯齿左缘),差 3px。
+		     只动 padding.left,不改 right/top/bottom,垂直居中不受影响。 -->
+		<Label text="账号" height="18" font="0" textcolor="#FF5F6368" padding="3,0,0,7" />
+
+<!-- 账号框:与旧版同构的「Combo 底层全宽 + Edit 上层左 280 + 右 48 交回 Combo 原生」结构。
+			  命中顺序:CContainerUI::FindControl 传 UIFIND_TOP_FIRST 是逆序遍历,声明在最后的控件优先命中;float 子控件不参与父裁剪判断。
+			  所以 Combo 声明在前(底层)、Edit 声明在后(上层),Edit 只盖住左 280,右 48(280~328)露出 Combo 本体,点那里走 DuiLib 原生 CComboUI::DoEvent(UIEVENT_BUTTONDOWN) 后 Activate(),与旧代码一致。
+			  之前改成 Combo mouse=false 全盖 + Edit 全盖 + 手动 WM_LBUTTONUP Activate,才引出「先点编辑框再点箭头没反应」(非原生路径的 owner/child 焦点竞争)。现已撤掉手动路径,回到原生触发。
+			  ① accountcombo —— 底层、全可见宽。pos="0,0,328,50"(宽 328,与 acct_box/pwdedit 外缘对齐在 346..674):
+				  自画整框底图(含右 48 的箭头),corner='4,4,48,4' 保证右 48 箭头区不被拉伸,1:1 绘制,箭头不模糊。
+				  dropboxsize="328,200":cx=328,弹层宽 328,与 Combo 外缘 1:1 对齐(弹层=(346,167,674,367))。
+				  不写 mouse="false":右 48 必须能被 Combo 命中,才能原生弹层。
+			  ② accountedit —— 上层、只盖左 280。pos="0,0,280,50":
+				  用专用 280x50 底图 login_input_user_editbox_*.png(左 280 含图标、不含右箭头),
+				  不写 source:CEditUI 会把整图压进 280 框、source 裁不干净,直接用恰好 280 宽的图 1:1 最稳,
+				  左框只有图标、右 48 箭头完全交给底层 Combo 自画,只出现一个箭头。
+				  textpadding 右边距 48,原生 EDIT 子窗口右界收在 280-48=232,不侵入右 48 箭头区。
+			  右 48 完全交给 Combo 原生处理,无挡板 Label、无手动光标/点击代码。 -->
+		<Container name="acct_box" width="328" height="50" padding="0,-4,0,16">
+			<Combo name="accountcombo" float="true" pos="0,0,328,50"
+				  font="0"
+				  dropboxsize="328,200"
+				  dropbox="bkcolor=&quot;#FFFFFFFF&quot;"
+				  normalimage="file='login_input_user_normal.png' corner='4,4,48,4'"
+				  hotimage="file='login_input_user_hot.png' corner='4,4,48,4'"
+				  pushedimage="file='login_input_user_focus.png' corner='4,4,48,4'"
+				  focusedimage="file='login_input_user_focus.png' corner='4,4,48,4'"
+				  itemfont="0" itemalign="left" itemvalign="vcenter"
+				  itemtextpadding="12,0,12,0"
+				  itemtextcolor="#FF1F2329"
+				  itemhottextcolor="#FF3CB371" itemhotbkcolor="#FFF2F7F4"
+				  itemselectedtextcolor="#FF3CB371" itemselectedbkcolor="#FFE8F5EE" />
+
+			<!-- 【不要给 Edit 加 bkcolor="#00000000"】
+			     CEditUI::CEditUI() 构造函数里有一句 SetBkColor(0xFFFFFFFF)(UIEdit.cpp:249),
+			     即 Edit 的背景色【默认就是不透明白色】,跟其他控件默认透明不一样。
+			     加 bkcolor="#00000000" 会把它设成 0,于是 PaintBkColor 直接 if(m_dwBackColor!=0)
+			     短路、整层白底不画 —— 每次重绘就没有东西去擦掉残留像素,
+			     原生 EDIT 子窗口销毁后遗留的 caret 就会一直显示在框里(那条黑线)。
+			     去掉这个属性即可恢复白底覆盖,残留被抹掉。
+			     白底会不会露馅?本页右侧面板本身就是 #FFFFFFFF,圆角外的白方块看不出来;
+			     但绘制顺序是 PaintBkColor → PaintStatusImage,白底在底图之下,中间区域不受影响。
+			     注意:如果以后把 Edit 放到非白底/渐变底上,需要改用 borderround 让圆角裁剪生效,
+			     而不是靠把 bkcolor 设成透明。
+			     accountedit 用【专用 280x50 底图 login_input_user_editbox_*.png】(左 280 含图标、不含右箭头),
+			     不写 source:CEditUI 会把整张图压进 280 框,写 source 也裁不干净,直接用恰好 280 宽的图 1:1 最稳,
+			     这样左框只有图标、右 48 的箭头完全交给底下 Combo 自画,只出现一个箭头。 -->
+			<Edit name="accountedit" float="true" pos="0,0,280,50"
+				  font="0" textcolor="#FF1F2329"
+				  align="left" valign="vcenter" textpadding="44,4,48,4"
+				  tipvalue="请输入登录账号" tipvaluecolor="#FFA8ADB3"
+				  nativetextcolor="#FF1F2329"
+				  tooltip="可直接输入新账号,也可以点右侧箭头选择历史账号"
+				  normalimage="file='login_input_user_editbox_normal.png'"
+				  hotimage="file='login_input_user_editbox_hot.png'"
+				  focusedimage="file='login_input_user_editbox_focus.png'" />
+
+		</Container>
+
+			<Label text="密码" height="18" font="0" textcolor="#FF5F6368" padding="3,0,0,7" />
+			<Edit name="pwdedit" width="328" height="50" padding="0,-4,0,16"
+				  password="true" font="0" textcolor="#FF1F2329"
+				  align="left" valign="vcenter" textpadding="44,4,16,4"
+				  nativetextcolor="#FF1F2329"
+				  normalimage="file='login_input_lock_normal.png'"
+				  hotimage="file='login_input_lock_hot.png'"
+				  focusedimage="file='login_input_lock_focus.png'" />
+
+			<!-- 【对齐修复】padding.left=5 让 CheckBox 起点 = 账号 label 文字起点(实测 x=351),
+			     修复前 CheckBox x=346 比 label 文字偏左 5px。 -->
+			<!-- 自动登录 / 记住密码 -->
+			<HorizontalLayout height="16" padding="0,0,0,28" childpadding="28">
+				<HorizontalLayout width="76" height="16" maxheight="16" padding="5,0,0,0">
+					<CheckBox name="login_auto_login" width="16" height="16" maxheight="16"
+							  normalimage="file='login_cb_normal.png'"
+							  hotimage="file='login_cb_hot.png'"
+							  pushedimage="file='login_cb_pushed.png'"
+							  selectedimage="file='login_cb_checked.png'"
+							  selectedhotimage="file='login_cb_checked_hot.png'" />
+					<Label text="自动登录" width="52" height="16" maxheight="16" padding="8,0,0,0"
+						   font="0" textcolor="#FF5F6368" align="left" valign="vcenter" />
+				</HorizontalLayout>
+				<HorizontalLayout width="76" height="16" maxheight="16" padding="5,0,0,0">
+					<CheckBox name="login_remember_password" width="16" height="16" maxheight="16"
+							  normalimage="file='login_cb_normal.png'"
+							  hotimage="file='login_cb_hot.png'"
+							  pushedimage="file='login_cb_pushed.png'"
+							  selectedimage="file='login_cb_checked.png'"
+							  selectedhotimage="file='login_cb_checked_hot.png'" />
+					<Label text="记住密码" width="52" height="16" maxheight="16" padding="8,0,0,0"
+						   font="0" textcolor="#FF5F6368" align="left" valign="vcenter" />
+				</HorizontalLayout>
 			</HorizontalLayout>
-			
-			<HorizontalLayout height="50">
-				<Label text="密码:" float="true" pos="165,8,228,30" />
-				<Edit name="pwdedit" float="true" pos="225,0,436,38" password="true" normalimage="file='Chat_InputBox_BG.png' corner='4,4,4,4'" hotimage="file='Chat_InputBox_BG_Hover.png' corner='4,4,4,4'" />
+
+			<!-- 登录按钮:整块 328x52,可视按钮 (4,0)-(324,44),下方 8px 留给投影(不占布局,故图内向下偏移抵消) -->
+			<Container width="328" height="52">
+				<Button name="loginBtn" text="登 录" font="4" textcolor="#FFFFFFFF"
+						align="center" valign="vcenter" textpadding="0,0,0,8" tooltip="点击这里登录"
+						normalimage="file='login_btn_normal.png'"
+						hotimage="file='login_btn_hot.png'"
+						pushedimage="file='login_btn_pushed.png'" />
+				<!-- 登录中:转圈(16) + 间距(9) + 文字(66) = 91,整体在 320 可视区内居中 → 起点 4+114=118 -->
+				<GifAnim name="login_spinner" float="true" pos="118,14,134,30"
+						 mouse="false" visible="false" mousechild="false"
+						 bkimage="file='login_spinner_16.gif'" autosize="true" />
+				<Label name="login_loading_text" float="true" pos="143,0,324,44" visible="false"
+					   text="登录中…" font="4" textcolor="#FFFFFFFF"
+					   align="left" valign="vcenter" mouse="false" />
+			</Container>
+
+			<Control />
+
+			<HorizontalLayout height="48">
+				<Control />
+				<Button name="guanwang" text="www.zhipuzi.com" width="160" height="48"
+						font="0" textcolor="#FF8A9099" hottextcolor="#FF3CB371"
+						align="center" valign="vcenter" />
+				<Control />
 			</HorizontalLayout>
-			
-			<HorizontalLayout height="32">
-				<Control></Control>
-				<CheckBox name="login_auto_login" padding="0,7,0,7" width="16" height="16" normalimage="file='Setting_CheckBox_Normal.png'" hotimage="file='Setting_CheckBox_Hover.png'" selectedimage="file='Setting_CheckBox_Click.png'"/>
-				<Label text="自动登录" width="85" padding="10,0,0,0"/>
-				<CheckBox name="login_remember_password" padding="56,7,0,7" width="16" height="16" normalimage="file='Setting_CheckBox_Normal.png'" hotimage="file='Setting_CheckBox_Hover.png'" selectedimage="file='Setting_CheckBox_Click.png'"/>
-				<Label text="记住密码" width="78" padding="10,0,0,0"/>
-				<Control></Control>
+
+		<!-- 登录失败提示条:浮动在按钮下方 14px,不参与流式布局(避免撑动页脚) -->
+		<!-- 图 328x36,可视区 (4,0)-(324,36) = 320x36。
+		     居中交给 HorizontalLayout 的 childalign=center:两个子项宽度都写死后
+		     nAdjustables==0,DuiLib 会按 (328 - 组宽)/2 整体偏移。
+		     文案宽度由代码 ShowLoginError 按实际文字量出来后 SetFixedWidth。 -->
+		<Container name="loginresult" float="true" pos="46,372,374,408" visible="false"
+				   bkimage="file='login_err_tip.png'">
+			<HorizontalLayout childalign="center" childvalign="vcenter" childpadding="6">
+				<Control name="loginresult_icon" width="14" height="14" maxheight="14"
+						 bkimage="file='login_err_icon.png'" />
+				<Label name="loginresult_text" width="156" height="36" maxheight="36" valign="vcenter"
+					   font="0" textcolor="#FFFF4500" align="left" />
 			</HorizontalLayout>
-			
-			<HorizontalLayout height="68">
-				<Control></Control>
-				<Button name="loginBtn" text="登录" height="44" width="270" padding="0,15,0,0" tooltip="点击这里登录" normalimage="file='btn_border_white.png' corner='5,5,5,5'" hotimage="file='btn_border_white_Hover.png' corner='5,5,5,5'" pushedimage="file='btn_border_white_Click.png' corner='5,5,5,5'" />
-				<Control></Control>
-			</HorizontalLayout>			
-		</VerticalLayout>
-		
-		<VerticalLayout name="login_loading" height="200">
-			<Label text="登录中..." align="center" padding="0,100,0,0" font="0"></Label>
-		</VerticalLayout>
-		
-		<VerticalLayout name="login_init_data_ing" height="200">
-			<HorizontalLayout height="120">
-				<Control></Control>
-				<VerticalLayout width="24" padding="0,80,0,0">
-					<Control></Control>
-					<HorizontalLayout height="24">
-						<GifAnim bkimage="loading.gif" autosize="true"></GifAnim>
+		</Container>
+
+			<!-- 数据同步遮罩:只盖右侧表单区,声明在最后所以绘制与命中都处于最上层 -->
+			<Container name="login_sync_mask" float="true" pos="0,0,420,460" visible="false"
+					   bkcolor="#F0FFFFFF">
+				<VerticalLayout childvalign="vcenter">
+					<HorizontalLayout height="40" padding="0,0,0,18">
+						<Control />
+						<GifAnim name="login_sync_spinner" bkimage="file='login_spinner_40.gif'" autosize="true" />
+						<Control />
+					</HorizontalLayout>
+					<HorizontalLayout height="22" padding="0,0,0,18">
+						<Control />
+						<Label name="login_sync_text" text="服务器数据同步中,请稍后…" width="260" height="22"
+							   font="5" textcolor="#FF5F6368" align="center" valign="vcenter" />
+						<Control />
+					</HorizontalLayout>
+					<HorizontalLayout height="4">
+						<Control />
+						<GifAnim name="login_sync_bar" bkimage="file='login_sync_bar.gif'" autosize="true" />
+						<Control />
 					</HorizontalLayout>
-					<Control></Control>
 				</VerticalLayout>
-				<Control></Control>
-			</HorizontalLayout>
-			<Label text="服务器数据同步中,请稍后..." align="center" font="0"></Label>
+			</Container>
+
 		</VerticalLayout>
-		
-		<HorizontalLayout height="50">
-			<Control></Control>
-			<Label name="loginresult" width="400" height="50" wordbreak="true" text="" visible="false" textcolor="#FF0000" align="center" />
-			<Control></Control>
-		</HorizontalLayout>
-		
-		<HorizontalLayout padding="0,0,0,15" height="30">
-			<Control></Control>
-			<Button name="guanwang" text="{u}www.zhipuzi.com{/u}" showhtml="true" width="200" align="center"/>
-			<Control></Control>
-		</HorizontalLayout>
-    </VerticalLayout>
-</Window>
+	</HorizontalLayout>
+</Window>

BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_arrow_hot.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_arrow_normal.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_btn_hot.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_btn_loading.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_btn_normal.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_btn_pushed.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_cb_checked.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_cb_checked_hot.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_cb_hot.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_cb_normal.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_cb_pushed.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_deco_box.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_err_icon.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_err_tip.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_icon_lock.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_icon_user.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_err.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_focus.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_hot.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_lock_err.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_lock_focus.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_lock_hot.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_lock_normal.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_normal.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_user_editbox_err.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_user_editbox_focus.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_user_editbox_hot.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_user_editbox_normal.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_user_err.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_user_focus.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_user_hot.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_input_user_normal.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_left_bg.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_spinner_16.gif


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_spinner_40.gif


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_sync_bar.gif


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_sync_dot.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_sync_track.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/login_window_mask.png


+ 86 - 25
bin/Win32/Debug/zhipuzi_pos_windows/skin/update.xml

@@ -1,26 +1,87 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<Window size="600,480" caption="0,0,0,36" roundcorner="4,4" shadowsize="3" showshadow="true">
-	<Font shared="true" id="0" name="微软雅黑" size="20" />
-	<VerticalLayout bkimage="file='Dialog_BG2.png' corner='4,48,4,4'" >		
-		<HorizontalLayout height="44" bkcolor="0xFF3CB371">
-			<Label text="自动更新" height="44" align="left" valign="center" padding="15,0,0,0" textcolor="#FFFFFFFF" font="0"/>
-			<Button name="closebtn" padding="0,13,15,0" width="16" height="16" tooltip="关闭" normalimage="file='close_normal.png'" hotimage="file='close_hover.png'" pushedimage="file='close_normal.png'"/>
-		</HorizontalLayout>
-		
-		<HorizontalLayout height="90" padding="0,30,0,0">
-			<Control></Control>
-			<HorizontalLayout bkimage="file='logo_guanwang.png'" width="248" height="57" padding="0,0,0,0"></HorizontalLayout>
-			<Control></Control>
-		</HorizontalLayout>
-		
-		<Control height="60" />
-	
-		<HorizontalLayout height="50">
-			<Control></Control>
-			<Progress name="update_progress" width="400" height="7" foreimage="file='progress_fore.png'" bkimage="file='progress_back.png'" min="0" max="100" value="0" maxheight="7" maxwidth="400"/>
-			<Control></Control>
-		</HorizontalLayout>
-		
-		<Label text="正在下载最新版本安装包..." align="center" padding="0,10,0,0"/>
-    </VerticalLayout>
-</Window>
+<Window size="720,460" caption="0,0,0,36" roundcorner="20,20" shadowsize="4" showshadow="true">
+	<Font shared="true" id="0" name="微软雅黑" size="13" default="true" />
+	<Font id="1" name="微软雅黑" size="20" bold="true" />
+	<Font id="2" name="微软雅黑" size="28" bold="true" />
+	<Font id="3" name="微软雅黑" size="18" bold="true" />
+	<Font id="4" name="微软雅黑" size="12" />
+	<Font id="5" name="微软雅黑" size="14" bold="true" />
+
+	<HorizontalLayout bkcolor="#FFFFFFFF">
+
+		<!-- ==================== 左:品牌区 300 x 460 ==================== -->
+		<!-- 与登录页同一套渐变;装饰圆已烘进 update_left_bg.png -->
+		<VerticalLayout width="300" height="460" inset="30,36,30,36" bkimage="file='update_left_bg.png'">
+			<Label text="智铺子" height="26" font="1" textcolor="#FFFFFFFF" padding="0,0,0,30" />
+
+			<!-- 弹性占位,等价于 CSS 的 margin-top:auto,把下方文案推到底部 -->
+			<Control />
+
+			<Label text="自动更新" height="37" font="2" textcolor="#FFFFFFFF" padding="0,0,0,14" />
+			<Label text="正在为您下载最新版本程序" height="25" font="0" textcolor="#EBFFFFFF" />
+			<Label text="完成后将自动启动安装" height="25" font="0" textcolor="#EBFFFFFF" padding="0,0,0,28" />
+			<Control width="44" height="3" maxheight="3" bkimage="file='update_deco_line.png'" />
+		</VerticalLayout>
+
+		<!-- ==================== 右:内容区 420 x 460 ==================== -->
+		<!-- inset 底 44 给通栏提示条让位;childalign=center 让内容水平居中;
+		     首尾各一个可伸缩 Control 实现垂直居中(等价于 justify-content:center) -->
+		<VerticalLayout width="420" height="460" inset="60,0,60,44" childalign="center" bkcolor="#FFFFFFFF">
+
+			<Control />
+
+			<!-- 状态图标 64x64:底图三态由代码切换;下载中叠一个 30x30 转圈 -->
+			<Container name="update_icon" width="64" height="64" maxheight="64" padding="0,0,0,24"
+					   bkimage="file='update_icon_bg.png'">
+				<GifAnim name="update_spinner" float="true" pos="17,17,47,47"
+						 bkimage="file='update_spinner_30.gif'" autosize="true" />
+			</Container>
+
+			<Label name="update_main_text" text="正在下载最新版本…" width="300" height="24"
+				   font="3" textcolor="#FF1F2329" align="center" valign="vcenter" padding="0,0,0,8" />
+			<Label name="update_sub_text" text="请保持网络连接" width="300" height="22"
+				   font="0" textcolor="#FF8A9099" align="center" valign="vcenter" padding="0,0,0,36" />
+
+			<!-- 进度条 300x6:isstretchfore=true(默认)使填充图随进度压缩,与 CSS 渐变一致 -->
+			<Progress name="update_progress" width="300" height="6" maxheight="6" padding="0,0,0,12"
+					  min="0" max="100" value="0" hor="true" isstretchfore="true"
+					  bkcolor="#00000000"
+					  bkimage="file='update_bar_track.png'"
+					  foreimage="file='update_bar_fill.png'" />
+
+			<!-- 进度信息行:左「已下载 / 总大小」,右百分比。不显示下载速度 -->
+			<HorizontalLayout width="300" height="16">
+				<Label name="update_size_text" text="0.0 MB / 0.0 MB" width="200" height="16" maxheight="16"
+					   font="4" textcolor="#FF8A9099" align="left" valign="vcenter" />
+				<Label name="update_percent_text" text="0%" width="100" height="16" maxheight="16"
+					   font="4" textcolor="#FF3CB371" align="right" valign="vcenter" />
+			</HorizontalLayout>
+
+			<!-- 重新下载:仅失败态显示。图 212x54,可视按钮 (6,0)-(206,40),
+			     下方 14px 为投影,用 -14 底边距抵消,避免多占布局空间 -->
+			<Button name="update_retry_btn" text="重新下载" width="212" height="54" maxheight="54"
+					padding="0,4,0,-14" visible="false"
+					font="5" textcolor="#FFFFFFFF" align="center" valign="vcenter"
+					textpadding="6,0,6,14" tooltip="从断点处继续下载"
+					normalimage="file='update_retry_normal.png'"
+					hotimage="file='update_retry_hot.png'"
+					pushedimage="file='update_retry_pushed.png'" />
+
+			<Control />
+
+			<!-- 关闭按钮:右上角,距右 16 / 距顶 14(更新允许关闭,不做弱化) -->
+			<Button name="closebtn" float="true" pos="388,14,404,30" tooltip="关闭"
+					normalimage="file='brand_close_normal.png'"
+					hotimage="file='brand_close_hot.png'"
+					pushedimage="file='brand_close_normal.png'" />
+
+			<!-- 底部提示条:通栏贴底 44 高,上边框线 + 居中文案;失败态由代码改红底红字 -->
+			<Container name="update_footer_box" float="true" pos="0,416,420,460">
+				<Control height="1" maxheight="1" bkimage="file='update_footer_line.png'" />
+				<Label name="update_footer_tip" text="更新过程中请勿断开网络"
+					   font="4" textcolor="#FF8A9099" align="center" valign="vcenter" />
+			</Container>
+
+		</VerticalLayout>
+	</HorizontalLayout>
+</Window>

BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/update_bar_fill.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/update_bar_fill_done.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/update_bar_fill_fail.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/update_bar_track.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/update_deco_line.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/update_footer_line.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/update_icon_bg.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/update_icon_done.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/update_icon_fail.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/update_left_bg.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/update_retry_hot.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/update_retry_normal.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/update_retry_pushed.png


BIN
bin/Win32/Debug/zhipuzi_pos_windows/skin/update_spinner_30.gif


BIN
dll/debug/DuiLib_d.dll


+ 533 - 101
zhipuzi_pos_windows/wnd/CLoginWnd.cpp

@@ -5,12 +5,38 @@
 #include "CMessageboxWnd.h"
 #include "../tool/CFoodHandle.h"
 
-void CLoginWnd::Init()
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>      // _countof
+
+// ---------------------------------------------------------------------------
+// 临时调试日志:定位「下拉里点第 2/3 个用户名,账号框仍显示第 1 个」用。
+// 输出到程序当前目录的 login_debug.log,同时 OutputDebugString(可用 DebugView 看)。
+// 第十四轮回退到原生 Combo 触发后,仍在 Init / itemselect 里留作校验;整体验收通过后删掉。
+// ---------------------------------------------------------------------------
+static void LoginLog(const wchar_t* fmt, ...)
 {
-	//刷新版本号
-	CLabelUI* version = static_cast<CLabelUI*>(m_pm.FindControl(_T("login_version")));
-	version->SetText((L"智铺子收银系统 " + CLewaimaiString::UTF8ToUnicode(CSystem::GetVersion())).c_str());
+	wchar_t buf[1024];
+
+	va_list ap;
+	va_start(ap, fmt);
+	_vsnwprintf_s(buf, _countof(buf), _TRUNCATE, fmt, ap);
+	va_end(ap);
+
+	FILE* fp = NULL;
+	if (_wfopen_s(&fp, L"login_debug.log", L"a, ccs=UTF-8") == 0 && fp != NULL)
+	{
+		fwprintf_s(fp, L"%s\n", buf);
+		fclose(fp);
+	}
+
+	OutputDebugStringW(buf);
+	OutputDebugStringW(L"\n");
+}
 
+
+void CLoginWnd::Init()
+{
 	//初始化下拉框的用户名
 	CComboUI* pCom = static_cast<CComboUI*>(m_pm.FindControl(_T("accountcombo")));
 
@@ -18,10 +44,16 @@ void CLoginWnd::Init()
 	for (std::map<std::string, std::string>::iterator it = users.begin(); it != users.end(); it++)
 	{
 		std::string username = it->first;
-		
+
 		CListLabelElementUI* elem = new CListLabelElementUI();
 		elem->SetText(CLewaimaiString::UTF8ToUnicode(username).c_str());
 
+		// 行高 34:下拉项是在代码里 new 出来的,不经过 CDialogBuilder,
+		// 拿不到 login.xml 里 <Default name="ListLabelElement" value="height=&quot;34&quot;">,
+		// 所以必须在这里显式设。(文字色/热态底色/padding 走 TListInfoUI,由 Combo 的
+		// item* 属性下发,那些不用在代码里管。)
+		elem->SetFixedHeight(34);
+
 		pCom->Add(elem);
 	}
 
@@ -54,29 +86,307 @@ void CLoginWnd::Init()
 	CEditUI* pAccountEdit = static_cast<CEditUI*>(m_pm.FindControl(_T("accountedit")));
 	CEditUI* pPasswordEdit = static_cast<CEditUI*>(m_pm.FindControl(_T("pwdedit")));
 
-	pAccountEdit->SetText(CLewaimaiString::UTF8ToUnicode(last_login_username).c_str());
-	pPasswordEdit->SetText(CLewaimaiString::UTF8ToUnicode(password).c_str());
+	// 回填上次登录账号。
+	// 显示的活儿现在全归 accountedit(Combo 退成只负责弹下拉、自己不画东西),
+	// 所以账号文字一律写进 Edit —— 历史列表里有没有都要写,
+	// 第一次登录时下拉是空的,也要让输入框里有上一次手动敲过的账号。
+	std::wstring wsLastUser = CLewaimaiString::UTF8ToUnicode(last_login_username);
+
+	if (pAccountEdit)
+	{
+		pAccountEdit->SetText(wsLastUser.c_str());
+	}
+
+	// ⚠️ 历史账号的「回填」统一走上面 accountedit / pwdedit 的直写(行 97、行 120 起):
+	//    账号文字 → accountedit,记住的密码 → pwdedit,二者都已写好,无需再借 SelectItem。
+	// 这里【刻意不调用】 pCom->SelectItem(nIndex):
+	//    Init() 在 OnCreate 阶段执行,此时窗口/焦点体系尚未完全就绪;SelectItem 会同步
+	//    派发 itemselect → Notify 里 pAccount->SetFocus()(会创建原生 EDIT 子窗口)+
+	//    SetInputError() 等一系列副作用,极易在启动期崩溃(即「系统启动就崩」)。
+	// 下拉项是否预高亮上次账号只是纯视觉细节,启动期没必要为此冒险;
+	// 用户后续手动点开下拉选历史项时,itemselect 仍正常派发、密码照常回填。
+
+	// 再兜底写一次密码,保证「没有历史项」和「历史项里没记住密码」两种情况都对
+	if (pPasswordEdit)
+	{
+		pPasswordEdit->SetText(CLewaimaiString::UTF8ToUnicode(password).c_str());
+	}
+
+	// 恢复到正常态:按钮可点、无转圈、无错误提示、无同步遮罩
+	SetLoginLoading(false);
+	SetInputError(false);
+
+	CContainerUI* pResult = static_cast<CContainerUI*>(m_pm.FindControl(_T("loginresult")));
+
+	if (pResult)
+	{
+		pResult->SetVisible(false);
+	}
+
+	CContainerUI* pMask = static_cast<CContainerUI*>(m_pm.FindControl(_T("login_sync_mask")));
+
+	if (pMask)
+	{
+		pMask->SetVisible(false);
+	}
 
 	if (CSetting::GetInstance()->GetParam("setting_is_auto_login") == "1")
 	{
 		StartLogin();
 	}
+
+	if (m_mode == 2)
+	{
+		PostMessage(WM_LOGIN_AGAIN_OUT);
+	}
+}
+
+// ---------------------------------------------------------------------------
+// 正常 / 登录中 两态切换
+// 对照设计稿:登录中按钮内换成「16px 转圈 + 9px 间距 + '登录中…'」,
+// 按钮底图不动,所以只需要换文案和两个浮层控件的可见性。
+// ---------------------------------------------------------------------------
+void CLoginWnd::SetLoginLoading(bool bLoading)
+{
+	CButtonUI* pBtn = static_cast<CButtonUI*>(m_pm.FindControl(_T("loginBtn")));
+
+	if (pBtn)
+	{
+		// 转圈期间清掉「登 录」,否则会和浮层上的「登录中…」叠在一起
+		pBtn->SetText(bLoading ? _T("") : _T("登 录"));
+	}
+
+	CGifAnimUI* pSpinner = static_cast<CGifAnimUI*>(m_pm.FindControl(_T("login_spinner")));
+
+	if (pSpinner)
+	{
+		pSpinner->SetVisible(bLoading);
+	}
+
+	CLabelUI* pLoadingText = static_cast<CLabelUI*>(m_pm.FindControl(_T("login_loading_text")));
+
+	if (pLoadingText)
+	{
+		pLoadingText->SetVisible(bLoading);
+	}
+}
+
+// ---------------------------------------------------------------------------
+// 输入框错误态:把两个输入框的 normal/hot/focus 三态图整组换成红色版本
+// ---------------------------------------------------------------------------
+void CLoginWnd::SetInputError(bool bError)
+{
+	m_bInputError = bError;
+
+	// 原生编辑窗口的背景是【实体画刷】,跟 DuiLib 画的底图是两层,必须把颜色对上,
+	// 否则聚焦时会在错误态的浅红底(#FFF8F6)上糊一块白的。
+	// 画刷在 CEditWnd 里按实例缓存(WM_CTLCOLOREDIT 中 m_hBkBrush 为空才建),
+	// 而 CEditWnd 每次聚焦都会新建,所以这次改的颜色在【下一次聚焦】生效 ——
+	// 实际调用点(登录失败 / 重新登录)此时输入框都不是焦点,没问题。
+	const DWORD NATIVE_BK_NORMAL = 0xFFFFFFFF;   // 三态图填充色 #FFFFFF
+	const DWORD NATIVE_BK_ERROR  = 0xFFFFF8F6;   // 错误态填充色 #FFF8F6
+
+	CEditUI* pAccount = static_cast<CEditUI*>(m_pm.FindControl(_T("accountedit")));
+	if (pAccount)
+	{
+		// 原生 EDIT 子窗口背景色必须和 DuiLib 底图对齐
+		pAccount->SetNativeEditBkColor(bError ? NATIVE_BK_ERROR : NATIVE_BK_NORMAL);
+	}
+
+	// 账号框(edit + combo 箭头)的视觉统一由 UpdateAccountBoxVisual 处理
+	UpdateAccountBoxVisual();
+
+	CEditUI* pPwd = static_cast<CEditUI*>(m_pm.FindControl(_T("pwdedit")));
+
+	if (pPwd)
+	{
+		LPCTSTR lpNormal = bError ? _T("file='login_input_lock_err.png'") : _T("file='login_input_lock_normal.png'");
+		LPCTSTR lpHot    = bError ? _T("file='login_input_lock_err.png'") : _T("file='login_input_lock_hot.png'");
+		LPCTSTR lpFocus  = bError ? _T("file='login_input_lock_err.png'") : _T("file='login_input_lock_focus.png'");
+
+		pPwd->SetNormalImage(lpNormal);
+		pPwd->SetHotImage(lpHot);
+		pPwd->SetFocusedImage(lpFocus);
+		pPwd->SetNativeEditBkColor(bError ? NATIVE_BK_ERROR : NATIVE_BK_NORMAL);
+	}
+}
+
+// ---------------------------------------------------------------------------
+// 账号框(accountedit + accountcombo)视觉统一:按「错误 > 聚焦 > 悬浮 > 正常」同步四态图。
+// 由于 Combo 与 Edit 是独立控件,不手动同步的话,鼠标悬停箭头时左边仍是 normal、
+// Edit 聚焦时箭头可能还是 hot/normal,导致边框颜色断裂。
+// ---------------------------------------------------------------------------
+void CLoginWnd::UpdateAccountBoxVisual()
+{
+	CEditUI* pAccount = static_cast<CEditUI*>(m_pm.FindControl(_T("accountedit")));
+	CComboUI* pAccountCombo = static_cast<CComboUI*>(m_pm.FindControl(_T("accountcombo")));
+	if (!pAccount || !pAccountCombo)
+	{
+		return;
+	}
+
+	const bool bFocused = pAccount->IsFocused() || pAccountCombo->IsFocused();
+
+	// 【必须带 corner='4,4,48,4'】login.xml 里 Combo 四态图就是这么声明的。
+	// CComboUI::PaintStatusImage 走 DrawImage 的九宫格逻辑,corner 决定右 48
+	// 的箭头区不被拉伸、1:1 绘制(login.xml 第 59-60 行注释有说明)。
+	// 之前这里漏写 corner,图会被整体拉伸,箭头尺寸/位置跟 XML 初值不一致。
+	const LPCTSTR kComboErr   = _T("file='login_input_user_err.png' corner='4,4,48,4'");
+	const LPCTSTR kComboFocus = _T("file='login_input_user_focus.png' corner='4,4,48,4'");
+	const LPCTSTR kComboHot   = _T("file='login_input_user_hot.png' corner='4,4,48,4'");
+	const LPCTSTR kComboNormal= _T("file='login_input_user_normal.png' corner='4,4,48,4'");
+
+	if (m_bInputError)
+	{
+		pAccount->SetNormalImage(_T("file='login_input_user_editbox_err.png'"));
+		pAccount->SetHotImage(_T("file='login_input_user_editbox_err.png'"));
+		pAccount->SetFocusedImage(_T("file='login_input_user_editbox_err.png'"));
+
+		pAccountCombo->SetNormalImage(kComboErr);
+		pAccountCombo->SetHotImage(kComboErr);
+		pAccountCombo->SetPushedImage(kComboErr);
+		pAccountCombo->SetFocusedImage(kComboErr);
+	}
+	else if (bFocused)
+	{
+		pAccount->SetNormalImage(_T("file='login_input_user_editbox_focus.png'"));
+		pAccount->SetHotImage(_T("file='login_input_user_editbox_focus.png'"));
+		pAccount->SetFocusedImage(_T("file='login_input_user_editbox_focus.png'"));
+
+		pAccountCombo->SetNormalImage(kComboFocus);
+		pAccountCombo->SetHotImage(kComboFocus);
+		pAccountCombo->SetPushedImage(kComboFocus);
+		pAccountCombo->SetFocusedImage(kComboFocus);
+	}
+	else if (m_bAccountBoxHovered)
+	{
+		pAccount->SetNormalImage(_T("file='login_input_user_editbox_hot.png'"));
+		pAccount->SetHotImage(_T("file='login_input_user_editbox_hot.png'"));
+		pAccount->SetFocusedImage(_T("file='login_input_user_editbox_hot.png'"));
+
+		pAccountCombo->SetNormalImage(kComboHot);
+		pAccountCombo->SetHotImage(kComboHot);
+		pAccountCombo->SetPushedImage(kComboFocus);
+		pAccountCombo->SetFocusedImage(kComboFocus);
+	}
 	else
 	{
-		CVerticalLayoutUI* pInput = static_cast<CVerticalLayoutUI*>(m_pm.FindControl(_T("login_input")));
-		pInput->SetVisible(true);
+		pAccount->SetNormalImage(_T("file='login_input_user_editbox_normal.png'"));
+		pAccount->SetHotImage(_T("file='login_input_user_editbox_hot.png'"));
+		pAccount->SetFocusedImage(_T("file='login_input_user_editbox_focus.png'"));
+
+		pAccountCombo->SetNormalImage(kComboNormal);
+		pAccountCombo->SetHotImage(kComboHot);
+		pAccountCombo->SetPushedImage(kComboFocus);
+		pAccountCombo->SetFocusedImage(kComboFocus);
+	}
 
-		CVerticalLayoutUI* pLoading = static_cast<CVerticalLayoutUI*>(m_pm.FindControl(_T("login_loading")));
-		pLoading->SetVisible(false);
+	LoginLog(L"[BOXVISUAL] err=%d focused=%d(edit=%d combo=%d) hovered=%d",
+		m_bInputError ? 1 : 0, bFocused ? 1 : 0,
+		pAccount->IsFocused() ? 1 : 0, pAccountCombo->IsFocused() ? 1 : 0,
+		m_bAccountBoxHovered ? 1 : 0);
+}
+
+// ---------------------------------------------------------------------------
+// 【账号框悬浮态联动 v5】读取鼠标真实位置,判断是否在 acct_box 整框内,
+// 状态变化时才刷新两边视觉。
+//
+// 为什么必须在 HandleMessage 里、m_pm.MessageHandler() 【之后】调用:
+//   ① 之前放在 MessageHandler(PreMessageFilter) 里,它由
+//      CPaintManagerUI::TranslateMessage 在 ::DispatchMessage 【之前】触发
+//      (UIManager.cpp:4211),DuiLib 自己那套 hover 分发(UIManager.cpp:1629
+//      FindControl -> UIEVENT_MOUSEENTER)还没跑,我们算完的状态会被它随后覆盖。
+//   ② PreMessageFilter 拿到的 lParam 是【消息目标窗口】的客户区坐标。鼠标停在
+//      原生 EDIT 子窗口(类名 "EditWnd",UIEdit.cpp:294 在 SETFOCUS 时创建)上时,
+//      消息目标是那个子窗口,坐标原点跟主窗口不同,PtInRect(acct_box) 必然算错。
+//   ③ 改用 ::GetCursorPos + ::ScreenToClient 取坐标,与消息发给哪个窗口无关,
+//      配合放在 DuiLib 处理之后,时序和坐标都稳。
+// ---------------------------------------------------------------------------
+void CLoginWnd::SyncAccountBoxHover()
+{
+	// 命中区用【accountedit + accountcombo 两个控件自身 rect 的并集】,
+	// 不用 acct_box 的 GetPos():acct_box 带 padding="0,-4,0,16",而 GetPos()
+	// 返回的是外框 m_rcItem;Combo/Edit 是 float 绝对定位,坐标基准是【内容区】,
+	// 实际可见框比 m_rcItem 上移了 4px(top padding = -4)。
+	// 两个子控件的 rect 才是真正画出来的那 328x50 视觉框,用它最准。
+	CControlUI* pEdit  = m_pm.FindControl(_T("accountedit"));
+	CControlUI* pCombo = m_pm.FindControl(_T("accountcombo"));
+	if (!pEdit && !pCombo)
+	{
+		// 只报一次,避免刷屏
+		static bool s_bWarned = false;
+		if (!s_bWarned)
+		{
+			s_bWarned = true;
+			LoginLog(L"[HOVER] !! accountedit / accountcombo 都找不到,悬浮联动失效");
+		}
+		return;
+	}
 
-		CVerticalLayoutUI* pIniting = static_cast<CVerticalLayoutUI*>(m_pm.FindControl(_T("login_init_data_ing")));
-		pIniting->SetVisible(false);
+	RECT rcBox = { 0, 0, 0, 0 };
+	if (pCombo)
+	{
+		rcBox = pCombo->GetPos();
+	}
+	if (pEdit)
+	{
+		const RECT rcEdit = pEdit->GetPos();
+		if (rcBox.right == rcBox.left && rcBox.bottom == rcBox.top)
+		{
+			rcBox = rcEdit;
+		}
+		else
+		{
+			::UnionRect(&rcBox, &rcBox, &rcEdit);
+		}
 	}
 
-	if (m_mode == 2)
+	POINT pt = { 0, 0 };
+	::GetCursorPos(&pt);
+	::ScreenToClient(m_pm.GetPaintWindow(), &pt);
+
+	const bool bInside = (::PtInRect(&rcBox, pt) != FALSE);
+
+	if (bInside == m_bAccountBoxHovered)
 	{
-		PostMessage(WM_LOGIN_AGAIN_OUT);
+		return; // 状态没变,不刷
 	}
+
+	m_bAccountBoxHovered = bInside;
+	LoginLog(L"[HOVER] %s pt=(%d,%d) rcBox=(%d,%d,%d,%d)",
+		bInside ? L"ENTER" : L"LEAVE",
+		pt.x, pt.y, rcBox.left, rcBox.top, rcBox.right, rcBox.bottom);
+	UpdateAccountBoxVisual();
+}
+
+// ---------------------------------------------------------------------------
+// 主线程快照界面输入,供登录子线程使用
+// ---------------------------------------------------------------------------
+void CLoginWnd::FetchLoginInput()
+{
+	m_wsAccount.clear();
+	m_wsPassword.clear();
+
+	// 账号从 accountedit 取:Combo 不显示文字,输入框里的才是用户真正要登录的账号
+	CEditUI* pAccount = static_cast<CEditUI*>(m_pm.FindControl(_T("accountedit")));
+
+	if (pAccount)
+	{
+		m_wsAccount = pAccount->GetText().GetData();
+	}
+
+	CEditUI* pPwd = static_cast<CEditUI*>(m_pm.FindControl(_T("pwdedit")));
+
+	if (pPwd)
+	{
+		m_wsPassword = pPwd->GetText().GetData();
+	}
+
+	CCheckBoxUI* pAuto = static_cast<CCheckBoxUI*>(m_pm.FindControl(_T("login_auto_login")));
+	CCheckBoxUI* pRemember = static_cast<CCheckBoxUI*>(m_pm.FindControl(_T("login_remember_password")));
+
+	m_bAutoLogin   = (pAuto != nullptr && pAuto->IsSelected());
+	m_bRememberPwd = (pRemember != nullptr && pRemember->IsSelected());
 }
 
 void CLoginWnd::Notify(TNotifyUI& msg)
@@ -128,26 +438,53 @@ void CLoginWnd::Notify(TNotifyUI& msg)
 		{
 			CComboUI* pCom = static_cast<CComboUI*>(m_pm.FindControl(_T("accountcombo")));
 
-			std::wstring name = pCom->GetItemAt(pCom->GetCurSel())->GetText();
-			std::string password = CSetting::GetInstance()->GetUser(CLewaimaiString::UnicodeToUTF8(name));
-			std::wstring wspassword = CLewaimaiString::UTF8ToUnicode(password);
+			if (pCom->GetCurSel() < 0)
+			{
+				return;
+			}
+
+			std::wstring username = pCom->GetItemAt(pCom->GetCurSel())->GetText().GetData();
+
+			// 选中项写回输入框:Combo 自己不显示文字了,显示归 accountedit
+			CEditUI* pAccount = static_cast<CEditUI*>(m_pm.FindControl(_T("accountedit")));
+
+			if (pAccount)
+			{
+				LoginLog(L"[itemselect] 进入: CurSel=%d, 选中项=%s, 写入前edit=%s",
+					pCom->GetCurSel(), username.c_str(), (LPCTSTR)pAccount->GetText());
+
+				pAccount->SetText(username.c_str());
 
-			CEditUI* pEdit = static_cast<CEditUI*>(m_pm.FindControl(_T("accountedit")));
-			pEdit->SetText(name.c_str());
+				LoginLog(L"[itemselect] 写入后: edit=%s", (LPCTSTR)pAccount->GetText());
+
+				// 焦点钉回账号框:弹层关闭时 CComboWnd::WM_CLOSE 会执行
+				// m_pOwner->SetFocus()(UICombo.cpp:173),把 DuiLib 的 logical focus
+				// 交给 Combo;Combo 没有原生子窗口,系统焦点就停在主窗口 hwnd 上,
+				// 用户看到「焦点跑到密码框」。这里显式抢回来。
+				// 只用公开接口,不碰 CEditUI 的内部成员(m_pWindow 等)。
+				pAccount->SetFocus();
+			}
+
+			std::string password = CSetting::GetInstance()->GetUser(CLewaimaiString::UnicodeToUTF8(username));
+			std::wstring wspassword = CLewaimaiString::UTF8ToUnicode(password);
 
 			CEditUI* pPassword = static_cast<CEditUI*>(m_pm.FindControl(_T("pwdedit")));
-			pPassword->SetText(wspassword.c_str());
-		}
-	}
-	else if (msg.sType == L"windowinit")
-	{
-		//m_pm.SetNextTabControl(false);
 
-		//CEditUI* pAccountEdit = static_cast<CEditUI*>(m_pm.FindControl(_T("accountedit")));
-		//if (pAccountEdit)
-		//{
-			//pAccountEdit->SetFocus();
-		//}
+			if (pPassword)
+			{
+				pPassword->SetText(wspassword.c_str());
+			}
+
+			// 换了账号就把上一次的红色错误态收掉
+			SetInputError(false);
+
+			CContainerUI* pResult = static_cast<CContainerUI*>(m_pm.FindControl(_T("loginresult")));
+
+			if (pResult)
+			{
+				pResult->SetVisible(false);
+			}
+		}
 	}
 	else if (msg.sType == _T("killfocus"))
 	{
@@ -155,6 +492,13 @@ void CLoginWnd::Notify(TNotifyUI& msg)
 		{
 			CSystem::HideSystemKeyboard();
 		}
+
+		CDuiString name = msg.pSender->GetName();
+		if (name == _T("accountedit") || name == _T("accountcombo"))
+		{
+			// 账号框任一子控件失焦,按「悬浮 > 正常」刷新整框视觉
+			UpdateAccountBoxVisual();
+		}
 	}
 	else if (msg.sType == _T("setfocus"))
 	{
@@ -165,6 +509,13 @@ void CLoginWnd::Notify(TNotifyUI& msg)
 				CSystem::ShowSystemKeyboard(m_hWnd);
 			}
 		}
+
+		CDuiString name = msg.pSender->GetName();
+		if (name == _T("accountedit") || name == _T("accountcombo"))
+		{
+			// 账号框任一子控件聚焦,强制把整框切到 focus 绿图,让边框连成一体
+			UpdateAccountBoxVisual();
+		}
 	}
 }
 
@@ -176,6 +527,19 @@ LRESULT CLoginWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHand
 
     // 把自己的窗口句柄与窗口绘制管理器挂接在一起
     m_pm.Init(m_hWnd);
+
+    // ---------------------------------------------------------------------------
+    // 【焦点修复】m_bNoActivate 必须为 false。
+    // DuiLib 的 CPaintManagerUI 原本不初始化该成员(UIManager.cpp 初始化列表缺项),
+    // 而它门控着三处 ::SetFocus(m_hWndPaint)(UIManager.cpp 1738 / 2442 / 2475)。
+    // 一旦读到垃圾值 true,原生 EDIT 子窗(CeditWnd)收不到 WM_KILLFOCUS 就永不销毁
+    // (其唯一销毁途径是 OnKillFocus 里的 PostMessage(WM_CLOSE),UIEdit.cpp:218),
+    // 表现为光标一直闪、点空白不丢焦点。
+    // 库侧已在构造函数初始化列表补 m_bNoActivate(false);这里再显式设一次作双保险,
+    // 幂等无害,确认库工程已重新编译后可删除本行。
+    // ---------------------------------------------------------------------------
+    //m_pm.SetNoActivate(false);
+
     m_pm.AddPreMessageFilter(this);
 
     CDialogBuilder builder;
@@ -308,38 +672,50 @@ LRESULT CLoginWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
     {
         return lRes;
     }
-    if(m_pm.MessageHandler(uMsg, wParam, lParam, lRes))
+    // 先让 DuiLib 处理,拿到它的返回值但先不 return:
+    // 账号框悬浮联动必须等 DuiLib 把 UISTATE_HOT 设好之后再算(顺序关键)。
+    const BOOL bDuiLibHandled = m_pm.MessageHandler(uMsg, wParam, lParam, lRes);
+
+    if (uMsg == WM_MOUSEMOVE || uMsg == WM_MOUSELEAVE ||
+        uMsg == WM_NCMOUSEMOVE || uMsg == WM_NCMOUSELEAVE)
+    {
+        SyncAccountBoxHover();
+    }
+
+    if(bDuiLibHandled)
     {
         return lRes;
     }
     return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
 }
 
+
 LRESULT CLoginWnd::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, bool& bHandled)
 {
+
+
     if(uMsg == WM_KEYDOWN)
     {
         if(wParam == VK_RETURN)
         {
-            CEditUI* pEdit = static_cast<CEditUI*>(m_pm.FindControl(_T("accountedit")));
-            if(pEdit->GetText().IsEmpty())
+			// 回车的行为和原来一致:账号空则聚焦账号,密码空则聚焦密码,都填了才登录
+			CEditUI* pAccount = static_cast<CEditUI*>(m_pm.FindControl(_T("accountedit")));
+			CEditUI* pPwd = static_cast<CEditUI*>(m_pm.FindControl(_T("pwdedit")));
+
+            if(pAccount && pAccount->GetText().IsEmpty())
             {
-                pEdit->SetFocus();
+                pAccount->SetFocus();
             }
+            else if(pPwd && pPwd->GetText().IsEmpty())
+            {
+				pPwd->SetFocus();
+			}
             else
             {
-                pEdit = static_cast<CEditUI*>(m_pm.FindControl(_T("pwdedit")));
-                if(pEdit->GetText().IsEmpty())
-                {
-                    pEdit->SetFocus();
-                }
-                else
-                {
-					//这里如果不把焦点设置为窗口,登录的时候会显示输入框(好像是个bug?获得焦点的控件无法隐藏?)
-					SetFocus(this->GetHWND());
-
-					this->StartLogin();
-                }
+				//这里如果不把焦点设置为窗口,登录的时候会显示输入框(好像是个bug?获得焦点的控件无法隐藏?)
+				SetFocus(this->GetHWND());
+
+				this->StartLogin();
             }
             return true;
         }
@@ -349,7 +725,12 @@ LRESULT CLoginWnd::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, bool&
             return true;
         }
 
-    }
+	}
+	// 【注意】账号框悬浮联动不在这里做。
+	// CEditUI / CComboUI 的 DoEvent 处理 UIEVENT_MOUSEENTER 时只设 UISTATE_HOT +
+	// Invalidate,根本不 SendNotify(UIEdit.cpp:346 / UICombo.cpp:565),所以 Notify
+	// 收不到 mouseenter;而 PreMessageFilter 又跑在 DuiLib 处理之前、坐标还是消息
+	// 目标窗口的。正确位置见 HandleMessage 里的 SyncAccountBoxHover()。
 	else if (uMsg == WM_LOGIN_SUCCESS)
 	{
 		ShowLoginSuccess();
@@ -383,18 +764,27 @@ LRESULT CLoginWnd::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, bool&
 
 void CLoginWnd::StartLogin()
 {
-	//隐藏密码输入框,显示进度条
-	CVerticalLayoutUI* pInput = static_cast<CVerticalLayoutUI*>(m_pm.FindControl(_T("login_input")));
-	pInput->SetVisible(false);
+	// 已经在登录中就忽略重复触发(回车连击、自动登录 + 手动点击)
+	if (m_is_login_ing)
+	{
+		return;
+	}
 
-	CVerticalLayoutUI* pLoading = static_cast<CVerticalLayoutUI*>(m_pm.FindControl(_T("login_loading")));
-	pLoading->SetVisible(true);
+	m_is_login_ing = true;
 
-	CVerticalLayoutUI* pIniting = static_cast<CVerticalLayoutUI*>(m_pm.FindControl(_T("login_init_data_ing")));
-	pIniting->SetVisible(false);
+	// 先把界面输入快照下来,后面子线程只用这份快照,不再碰控件
+	FetchLoginInput();
 
-	CLabelUI* pLoginResultLabel = static_cast<CLabelUI*>(m_pm.FindControl(_T("loginresult")));
-	pLoginResultLabel->SetVisible(false);
+	// 按钮切成「转圈 + 登录中…」,并收掉上一次的错误提示
+	SetLoginLoading(true);
+	SetInputError(false);
+
+	CContainerUI* pResult = static_cast<CContainerUI*>(m_pm.FindControl(_T("loginresult")));
+
+	if (pResult)
+	{
+		pResult->SetVisible(false);
+	}
 
 	//开启一个线程,开始处理登录
 	std::thread(&CLoginWnd::HandleLogin, this).detach();
@@ -402,8 +792,6 @@ void CLoginWnd::StartLogin()
 
 void CLoginWnd::HandleLogin()
 {
-	CLabelUI* pLoginResultLabel = static_cast<CLabelUI*>(m_pm.FindControl(_T("loginresult")));
-
 	//真正登录前,先检测是否有需要更新
 	std::map<std::string, std::string> params;
 
@@ -431,6 +819,7 @@ void CLoginWnd::HandleLogin()
 	{
 		if (!document.HasMember("errcode") || !document.HasMember("errmsg") || !document.HasMember("data"))
 		{
+			m_bInputError   = false;
 			m_wsLoginErrMsg = _T("服务器返回数据格式错误");
 			PostMessage(WM_LOGIN_ERROR);
 
@@ -442,6 +831,7 @@ void CLoginWnd::HandleLogin()
 		if (errcode != 0)
 		{
 			std::string errmsg = "response failed! message:" + std::string(document["errmsg"].GetString());
+			m_bInputError   = false;
 			m_wsLoginErrMsg = CLewaimaiString::UTF8ToUnicode(errmsg);
 			PostMessage(WM_LOGIN_ERROR);
 
@@ -462,18 +852,9 @@ void CLoginWnd::HandleLogin()
 	}
 
     //判断账号密码是否正确
-    std::wstring account, password;
-    CEditUI* pAccountEdit = static_cast<CEditUI*>(m_pm.FindControl(_T("accountedit")));
-    if(pAccountEdit)
-    {
-        account = pAccountEdit->GetText().GetData();
-    }
-
-    CEditUI* pPasswordEdit = static_cast<CEditUI*>(m_pm.FindControl(_T("pwdedit")));
-    if(pPasswordEdit)
-    {
-        password = pPasswordEdit->GetText().GetData();
-    }
+	//账号密码是 StartLogin 在主线程取好存进成员的,子线程不直接读 UI
+    std::wstring account  = m_wsAccount;
+    std::wstring password = m_wsPassword;
 
 	//把第1个中文冒号替换成英文冒号
 	CLewaimaiString::Replace(account, _T(":"), _T(":"), 1);
@@ -488,6 +869,7 @@ void CLoginWnd::HandleLogin()
 
 	if (s_account.compare("") == 0)
 	{
+		m_bInputError   = true;
 		m_wsLoginErrMsg = _T("用户名不能为空");
 		PostMessage(WM_LOGIN_ERROR);
 
@@ -496,6 +878,7 @@ void CLoginWnd::HandleLogin()
 
 	if (s_password.compare("") == 0)
 	{
+		m_bInputError   = true;
 		m_wsLoginErrMsg = _T("密码不能为空");
 		PostMessage(WM_LOGIN_ERROR);
 
@@ -512,16 +895,18 @@ void CLoginWnd::HandleLogin()
 		//判断当前shop_id是否为0,如果为0说明是主账号还没有绑定店铺,那么就跳转进行店铺绑定
 		if (CShopinfo::GetInstance()->m_is_shop == "0")
 		{
+			m_bInputError   = false;
 			m_wsLoginErrMsg = _T("主账号未绑定店铺");
 			PostMessage(WM_LOGIN_ERROR);
 
 			return;
 		}
 
-		CCheckBoxUI* pAuto = static_cast<CCheckBoxUI*>(m_pm.FindControl(_T("login_auto_login")));
-		CCheckBoxUI* pRemember = static_cast<CCheckBoxUI*>(m_pm.FindControl(_T("login_remember_password")));
+		// 两个勾选框的状态在 StartLogin 里已经快照到 m_bAutoLogin / m_bRememberPwd
+		bool bAutoLogin   = m_bAutoLogin;
+		bool bRememberPwd = m_bRememberPwd;
 
-		if (pAuto->IsSelected())
+		if (bAutoLogin)
 		{
 			CSetting::GetInstance()->SetParam("setting_is_auto_login", "1", false);
 			CSetting::GetInstance()->SetParam("setting_is_remember_password", "1", false);
@@ -533,25 +918,17 @@ void CLoginWnd::HandleLogin()
 		{
 			CSetting::GetInstance()->SetParam("setting_is_auto_login", "0", false);
 
-			if (pRemember->IsSelected())
+			if (bRememberPwd)
 			{
 				CSetting::GetInstance()->SetParam("setting_is_remember_password", "1", false);
-			}
-			else
-			{
-				CSetting::GetInstance()->SetParam("setting_is_remember_password", "0", false);
-			}
-
-
-			if (pRemember->IsSelected())
-			{
 				CSetting::GetInstance()->SetUser(s_account, s_password, true);
 			}
 			else
 			{
+				CSetting::GetInstance()->SetParam("setting_is_remember_password", "0", false);
 				CSetting::GetInstance()->SetUser(s_account, "", true);
 			}
-		}		
+		}
 
 		//在这里设置完参数后,统一保存到数据库
 		CSetting::GetInstance()->SetParam("last_login_username", s_account, true);
@@ -566,6 +943,7 @@ void CLoginWnd::HandleLogin()
 		bool ret = CFoodHandle::GetInstance()->InitAllFood();
 		if (!ret)
 		{
+			m_bInputError   = false;
 			m_wsLoginErrMsg = _T("同步云端商品数据失败!");
 			PostMessage(WM_LOGIN_ERROR);
 			return;
@@ -576,6 +954,7 @@ void CLoginWnd::HandleLogin()
 		ret = CShopinfo::GetInstance()->UpdateOneShopinfo(errmsg);
 		if (!ret)
 		{
+			m_bInputError   = false;
 			m_wsLoginErrMsg = errmsg;
 			PostMessage(WM_LOGIN_ERROR);
 			return;
@@ -587,6 +966,7 @@ void CLoginWnd::HandleLogin()
 			ret = CShopinfo::GetInstance()->GetTabletypeInfo();
 			if (!ret)
 			{
+				m_bInputError   = false;
 				m_wsLoginErrMsg = L"获取店铺桌子分类失败";
 				PostMessage(WM_LOGIN_ERROR);
 				return;
@@ -599,7 +979,8 @@ void CLoginWnd::HandleLogin()
     }
     else
     {
-        //登录失败了
+        //登录失败了:属于账号/密码填错,输入框要标红
+		m_bInputError   = true;
 		m_wsLoginErrMsg = std::wstring(_T("登录失败:") + CLewaimaiString::UTF8ToUnicode(errmsg));
 		PostMessage(WM_LOGIN_ERROR);
 
@@ -626,18 +1007,67 @@ void CLoginWnd::ShowLoginSuccess()
 
 void CLoginWnd::ShowLoginError()
 {
-	CVerticalLayoutUI* pInput = static_cast<CVerticalLayoutUI*>(m_pm.FindControl(_T("login_input")));
-	pInput->SetVisible(true);
+	// 结束登录中状态:按钮恢复「登 录」,转圈收起
+	m_is_login_ing = false;
+	SetLoginLoading(false);
 
-	CVerticalLayoutUI* pLoading = static_cast<CVerticalLayoutUI*>(m_pm.FindControl(_T("login_loading")));
-	pLoading->SetVisible(false);
+	// 账号/密码类错误才把输入框标红,网络类、同步类错误不标
+	SetInputError(m_bInputError);
 
-	CVerticalLayoutUI* pIniting = static_cast<CVerticalLayoutUI*>(m_pm.FindControl(_T("login_init_data_ing")));
-	pIniting->SetVisible(false);
+	// 同步阶段出错时遮罩可能还开着,这里一并收掉,否则挡住错误提示
+	CContainerUI* pMask = static_cast<CContainerUI*>(m_pm.FindControl(_T("login_sync_mask")));
 
-	CLabelUI* pLoginResultLabel = static_cast<CLabelUI*>(m_pm.FindControl(_T("loginresult")));
-	pLoginResultLabel->SetText(m_wsLoginErrMsg.c_str());
-	pLoginResultLabel->SetVisible(true);
+	if (pMask)
+	{
+		pMask->SetVisible(false);
+	}
+
+	CContainerUI* pResult = static_cast<CContainerUI*>(m_pm.FindControl(_T("loginresult")));
+	CLabelUI* pText = static_cast<CLabelUI*>(m_pm.FindControl(_T("loginresult_text")));
+
+	if (pText)
+	{
+		pText->SetText(m_wsLoginErrMsg.c_str());
+
+		// 提示条是「图标 14 + 间距 6 + 文字」水平居中的(HorizontalLayout childalign=center)。
+		// 文字宽度必须写死才能算出居中偏移,所以这里按实际文案量一次。
+		// 用 CRenderEngine 量真实宽度,比按字数估算稳;量不出来时退回字数估算。
+		int nTextW = 0;
+		HDC hDC = m_pm.GetPaintDC();
+
+		if (hDC != NULL)
+		{
+			SIZE szText = CRenderEngine::GetTextSize(hDC, &m_pm,
+			                                         m_wsLoginErrMsg.c_str(), pText->GetFont(), DT_SINGLELINE);
+
+			if (szText.cx > 0)
+			{
+				nTextW = szText.cx + 2;
+			}
+		}
+
+		if (nTextW <= 0)
+		{
+			for (size_t i = 0; i < m_wsLoginErrMsg.size(); i++)
+			{
+				// 中日韩字符按全宽 13px 算,其余按半宽 7px 算
+				nTextW += (m_wsLoginErrMsg[i] > 0x2E80) ? 13 : 7;
+			}
+		}
+
+		// 上限取提示条可视宽度 320 减去图标与间距,避免长文案把图标挤出可视区
+		if (nTextW > 300)
+		{
+			nTextW = 300;
+		}
+
+		pText->SetFixedWidth(nTextW);
+	}
+
+	if (pResult)
+	{
+		pResult->SetVisible(true);
+	}
 }
 
 void CLoginWnd::ShowUpdate()
@@ -675,12 +1105,14 @@ void CLoginWnd::ShowLoginAgainOut()
 
 void CLoginWnd::ShowInitData()
 {
-	CVerticalLayoutUI* pInput = static_cast<CVerticalLayoutUI*>(m_pm.FindControl(_T("login_input")));
-	pInput->SetVisible(false);
+	// 登录已经通过,接下来是拉服务器数据。
+	// 按钮恢复常态(反正会被遮罩盖住),然后把右侧表单区整块盖上半透明遮罩。
+	SetLoginLoading(false);
 
-	CVerticalLayoutUI* pLoading = static_cast<CVerticalLayoutUI*>(m_pm.FindControl(_T("login_loading")));
-	pLoading->SetVisible(false);
+	CContainerUI* pMask = static_cast<CContainerUI*>(m_pm.FindControl(_T("login_sync_mask")));
 
-	CVerticalLayoutUI* pIniting = static_cast<CVerticalLayoutUI*>(m_pm.FindControl(_T("login_init_data_ing")));
-	pIniting->SetVisible(true);
+	if (pMask)
+	{
+		pMask->SetVisible(true);
+	}
 }

+ 32 - 1
zhipuzi_pos_windows/wnd/CLoginWnd.h

@@ -58,6 +58,25 @@ public:
 
 	void StartLogin();
 
+	//「正常 / 登录中」两态切换:登录中把按钮文案换成转圈 + 「登录中…」
+	void SetLoginLoading(bool bLoading);
+
+	//输入框错误态:边框转红(对照设计稿状态 4),传 false 恢复常态
+	void SetInputError(bool bError);
+
+	//账号框视觉统一:按「错误 > 聚焦 > 悬浮 > 正常」同步 accountedit + accountcombo 的底图,
+	//让边框颜色(绿/红/灰)在输入区和箭头区连成一体;同时处理悬浮态与空白点击清焦点。
+	void UpdateAccountBoxVisual();
+
+	//检测鼠标是否落在账号框视觉范围内(accountedit + accountcombo 两个 rect 的并集),
+	//只在进出状态变化时才刷新两边视觉。
+	//必须在 HandleMessage 里、m_pm.MessageHandler() 之后调用,原因见 .cpp 里的注释。
+	void SyncAccountBoxHover();
+
+	//把界面上的账号、密码、两个勾选框状态快照到成员里。
+	//登录是 detach 的子线程,子线程不能直接读 UI 控件,所以统一在主线程先取好。
+	void FetchLoginInput();
+
 	void ShowLoginSuccess();
 
 	//登录失败时候的错误信息展示
@@ -69,7 +88,7 @@ public:
 	//被人挤下线了的处理
 	void ShowLoginAgainOut();
 
-	//展示开始同步数据的提示
+	//展示开始同步数据的遮罩(盖住右侧表单区)
 	void ShowInitData();
 
 	//登录的工作线程
@@ -87,5 +106,17 @@ private:
 	bool m_is_login_ing = false;
 
 	std::wstring m_wsLoginErrMsg;
+
+	// ---- 主线程取好的界面快照,供登录子线程使用 ----
+	std::wstring m_wsAccount;
+	std::wstring m_wsPassword;
+	bool m_bAutoLogin    = false;
+	bool m_bRememberPwd  = false;
+
+	//本次错误是否属于「账号/密码填错」,决定要不要把输入框标红
+	bool m_bInputError   = false;
+
+	//鼠标当前是否悬停在账号框整体区域(accountedit 或 accountcombo)上
+	bool m_bAccountBoxHovered = false;
 };
 

+ 592 - 89
zhipuzi_pos_windows/wnd/CUpdateWnd.cpp

@@ -1,46 +1,451 @@
 #include "pch.h"
 #include "CUpdateWnd.h"
 
-double CUpdateWnd::m_Percent;
+// _ftelli64 / _wfopen_s 的声明
+#include <stdio.h>
+
+// ============================================================================
+// 跨线程通信通道(全部为文件静态区数据)
+// ----------------------------------------------------------------------------
+// 旧实现用 FindWindow(NULL, L"自动更新") 让子线程找回窗口句柄来投递消息。
+// 这个写法有一串硬伤:
+//   1. 新设计去掉了标题栏,窗口标题只是"碰巧"还叫这个名字,
+//      任何一次改名都会让进度条不动、下载完成也不启动安装;
+//   2. FindWindow 按标题全局搜索,同名窗口会被误命中;
+//   3. 每次 curl 进度回调都做一次全局窗口枚举,高频且无谓。
+//
+// 现在改为:OnCreate 里把句柄记进 g_hUpdateWnd,子线程只跟这个静态变量打交道。
+// 之所以用静态区而不是成员变量,是因为下载线程是 detach 的,窗口可能先被关闭
+// 析构,静态区数据活到进程结束,天然免疫 Use-After-Free。
+// ============================================================================
+namespace
+{
+    std::atomic<HWND>   g_hUpdateWnd(NULL);   // 更新窗句柄,由主线程在 OnCreate / OnDestroy 维护
+    std::atomic<double> g_dlNow(0.0);         // 已下载字节(含断点续传已下部分)
+    std::atomic<double> g_dlTotal(0.0);       // 服务器文件总字节
+
+    // 字节数格式化:>=1MB 用 MB 保留 1 位小数,>=1KB 用 KB,否则用 B
+    // 对照设计稿「10.6 MB / 28.6 MB」
+    std::wstring FormatBytes(double bytes)
+    {
+        const double KB = 1024.0;
+        const double MB = KB * 1024.0;
+
+        wchar_t buf[64] = { 0 };
+
+        if(bytes >= MB)
+        {
+            _snwprintf_s(buf, _countof(buf), _TRUNCATE, L"%.1f MB", bytes / MB);
+        }
+
+        else if(bytes >= KB)
+        {
+            _snwprintf_s(buf, _countof(buf), _TRUNCATE, L"%.0f KB", bytes / KB);
+        }
+
+        else
+        {
+            _snwprintf_s(buf, _countof(buf), _TRUNCATE, L"%.0f B", bytes);
+        }
+
+        return std::wstring(buf);
+    }
+
+    // 窗口是否还活着(子线程判断要不要继续干活)
+    bool IsUpdateWndAlive()
+    {
+        HWND hWnd = g_hUpdateWnd.load();
+        return (hWnd != NULL && ::IsWindow(hWnd));
+    }
+}
 
+// ============================================================================
+// 生命周期
+// ============================================================================
 void CUpdateWnd::Init()
 {
-    std::thread(&CUpdateWnd::Run, this).detach();
+    m_nProgress    = 0;
+    m_bFailed      = false;
+    m_bFinished    = false;
+    m_bDownloading = false;
+
+    g_dlNow.store(0.0);
+    g_dlTotal.store(0.0);
+
+    // 必须在起线程【之前】登记句柄,否则子线程第一条消息就投不回来
+    g_hUpdateWnd.store(m_hWnd);
+
+    ShowDownloading();
+    StartDownload();
 }
 
-void CUpdateWnd::Notify(TNotifyUI& msg)
+void CUpdateWnd::StartDownload()
 {
-    if(msg.sType == _T("click"))
+    if(m_bDownloading)
+    {
+        return;
+    }
+
+    m_bDownloading = true;
+    m_bFailed      = false;
+
+    m_ctx      = std::make_shared<SUpdateCtx>();
+    m_ctx->url = m_url;
+    m_ctx->dir = m_dir;
+
+    // 传值捕获 shared_ptr:线程栈上多一份引用,ctx 一定活到线程结束
+    std::thread(&CUpdateWnd::Run, m_ctx).detach();
+}
+
+// ============================================================================
+// 三种状态切换(只在主线程调用,全部对照 update_design.html)
+// ============================================================================
+void CUpdateWnd::ShowDownloading()
+{
+    CContainerUI* pIcon = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_icon")));
+
+    if(pIcon)
+    {
+        pIcon->SetBkImage(_T("file='update_icon_bg.png'"));
+    }
+
+    CGifAnimUI* pSpinner = static_cast<CGifAnimUI*>(m_pm.FindControl(_T("update_spinner")));
+
+    if(pSpinner)
+    {
+        pSpinner->SetVisible(true);
+    }
+
+    CLabelUI* pMain = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_main_text")));
+
+    if(pMain)
+    {
+        pMain->SetText(_T("正在下载最新版本…"));
+        pMain->SetTextColor(0xFF1F2329);
+    }
+
+    CLabelUI* pSub = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_sub_text")));
+
+    if(pSub)
+    {
+        pSub->SetText(_T("请保持网络连接"));
+        pSub->SetVisible(true);
+    }
+
+    CProgressUI* pProgress = static_cast<CProgressUI*>(m_pm.FindControl(_T("update_progress")));
+
+    if(pProgress)
+    {
+        pProgress->SetVisible(true);
+        pProgress->SetForeImage(_T("file='update_bar_fill.png'"));
+    }
+
+    CLabelUI* pPercent = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_percent_text")));
+
+    if(pPercent)
+    {
+        pPercent->SetTextColor(0xFF3CB371);
+    }
+
+    CButtonUI* pRetry = static_cast<CButtonUI*>(m_pm.FindControl(_T("update_retry_btn")));
+
+    if(pRetry)
+    {
+        pRetry->SetVisible(false);
+    }
+
+    CContainerUI* pFooter = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_footer_box")));
+
+    if(pFooter)
+    {
+        pFooter->SetBkColor(0xFFFFFFFF);
+    }
+
+    CLabelUI* pTip = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_footer_tip")));
+
+    if(pTip)
+    {
+        pTip->SetText(_T("更新过程中请勿断开网络"));
+        pTip->SetTextColor(0xFF8A9099);
+    }
+
+    UpdateProgressInfo();
+}
+
+void CUpdateWnd::ShowFinish()
+{
+    m_bDownloading = false;
+
+    CContainerUI* pIcon = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_icon")));
+
+    if(pIcon)
+    {
+        pIcon->SetBkImage(_T("file='update_icon_done.png'"));
+    }
+
+    CGifAnimUI* pSpinner = static_cast<CGifAnimUI*>(m_pm.FindControl(_T("update_spinner")));
+
+    if(pSpinner)
+    {
+        pSpinner->SetVisible(false);
+    }
+
+    CLabelUI* pMain = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_main_text")));
+
+    if(pMain)
+    {
+        pMain->SetText(_T("下载完成,正在启动安装程序…"));
+        pMain->SetTextColor(0xFF1F2329);
+    }
+
+    CLabelUI* pSub = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_sub_text")));
+
+    if(pSub)
+    {
+        pSub->SetText(_T("请按安装向导提示完成更新"));
+        pSub->SetVisible(true);
+    }
+
+    CProgressUI* pProgress = static_cast<CProgressUI*>(m_pm.FindControl(_T("update_progress")));
+
+    if(pProgress)
+    {
+        pProgress->SetForeImage(_T("file='update_bar_fill_done.png'"));
+        pProgress->SetValue(100);
+    }
+
+    // 完成态把「已下载 / 总大小」拉平成同一个数
+    g_dlTotal.store(g_dlNow.load() > 0 ? g_dlNow.load() : g_dlTotal.load());
+    UpdateProgressInfo();
+
+    CLabelUI* pPercent = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_percent_text")));
+
+    if(pPercent)
+    {
+        pPercent->SetText(_T("100%"));
+        pPercent->SetTextColor(0xFF3CB371);
+    }
+
+    CButtonUI* pRetry = static_cast<CButtonUI*>(m_pm.FindControl(_T("update_retry_btn")));
+
+    if(pRetry)
+    {
+        pRetry->SetVisible(false);
+    }
+
+    CContainerUI* pFooter = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_footer_box")));
+
+    if(pFooter)
+    {
+        pFooter->SetBkColor(0xFFFFFFFF);
+    }
+
+    CLabelUI* pTip = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_footer_tip")));
+
+    if(pTip)
+    {
+        pTip->SetText(_T("本窗口即将自动关闭"));
+        pTip->SetTextColor(0xFF8A9099);
+    }
+}
+
+void CUpdateWnd::ShowFail(LPCTSTR lpReason)
+{
+    m_bDownloading = false;
+    m_bFailed      = true;
+
+    CContainerUI* pIcon = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_icon")));
+
+    if(pIcon)
+    {
+        pIcon->SetBkImage(_T("file='update_icon_fail.png'"));
+    }
+
+    CGifAnimUI* pSpinner = static_cast<CGifAnimUI*>(m_pm.FindControl(_T("update_spinner")));
+
+    if(pSpinner)
+    {
+        pSpinner->SetVisible(false);
+    }
+
+    CLabelUI* pMain = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_main_text")));
+
+    if(pMain)
+    {
+        pMain->SetText(_T("下载失败"));
+        pMain->SetTextColor(0xFF1F2329);
+    }
+
+    CLabelUI* pSub = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_sub_text")));
+
+    if(pSub)
+    {
+        pSub->SetText((lpReason && lpReason[0]) ? lpReason : _T("网络连接异常,请检查网络后重试"));
+        pSub->SetVisible(true);
+    }
+
+    // 进度条保留在当前位置,只把填充切成红色,让用户看到已经下了多少
+    CProgressUI* pProgress = static_cast<CProgressUI*>(m_pm.FindControl(_T("update_progress")));
+
+    if(pProgress)
+    {
+        pProgress->SetVisible(true);
+        pProgress->SetForeImage(_T("file='update_bar_fill_fail.png'"));
+    }
+
+    CLabelUI* pPercent = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_percent_text")));
+
+    if(pPercent)
+    {
+        pPercent->SetTextColor(0xFFFF4500);
+    }
+
+    CButtonUI* pRetry = static_cast<CButtonUI*>(m_pm.FindControl(_T("update_retry_btn")));
+
+    if(pRetry)
+    {
+        pRetry->SetVisible(true);
+    }
+
+    // 底部提示条转红底红字
+    CContainerUI* pFooter = static_cast<CContainerUI*>(m_pm.FindControl(_T("update_footer_box")));
+
+    if(pFooter)
+    {
+        pFooter->SetBkColor(0xFFFFF1ED);
+    }
+
+    CLabelUI* pTip = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_footer_tip")));
+
+    if(pTip)
+    {
+        pTip->SetText(_T("重新下载将从断点处继续,不会重新下完整包"));
+        pTip->SetTextColor(0xFFFF4500);
+    }
+
+    UpdateProgressInfo();
+}
+
+void CUpdateWnd::UpdateProgressInfo()
+{
+    double dlnow   = g_dlNow.load();
+    double dltotal = g_dlTotal.load();
+
+    int nPercent = 0;
+
+    if(dltotal > 0)
     {
-        if(msg.pSender->GetName() == _T("closebtn"))
+        nPercent = (int)(dlnow * 100.0 / dltotal);
+
+        if(nPercent < 0)
         {
-            PostQuitMessage(0);
-            return;
+            nPercent = 0;
         }
 
-        else if(msg.pSender->GetName() == _T("guanwang"))
+        if(nPercent > 100)
         {
-            ShellExecute(NULL, _T("open"), _T("explorer.exe"), _T("https://www.zhipuzi.com"), NULL, SW_SHOW);
+            nPercent = 100;
         }
     }
 
-    else if(msg.sType == _T("itemselect"))
+    m_nProgress = nPercent;
+
+    CProgressUI* pProgress = static_cast<CProgressUI*>(m_pm.FindControl(_T("update_progress")));
+
+    if(pProgress)
     {
-        CDuiString name = msg.pSender->GetName();
+        pProgress->SetValue(nPercent);
+    }
 
-        if(name == _T("accountcombo"))
-        {
-            CComboUI* pCom = static_cast<CComboUI*>(m_pm.FindControl(_T("accountcombo")));
+    CLabelUI* pSize = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_size_text")));
 
-            std::wstring name = pCom->GetItemAt(pCom->GetCurSel())->GetText();
-            std::string password = CSetting::GetInstance()->GetUser(CLewaimaiString::UnicodeToUTF8(name));
-            std::wstring wspassword = CLewaimaiString::UTF8ToUnicode(password);
+    if(pSize)
+    {
+        std::wstring sText = FormatBytes(dlnow) + L" / " + FormatBytes(dltotal);
+        pSize->SetText(sText.c_str());
+    }
+
+    CLabelUI* pPercent = static_cast<CLabelUI*>(m_pm.FindControl(_T("update_percent_text")));
 
-            CEditUI* pEdit = static_cast<CEditUI*>(m_pm.FindControl(_T("accountedit")));
-            pEdit->SetText(name.c_str());
+    if(pPercent)
+    {
+        wchar_t buf[16] = { 0 };
+        _snwprintf_s(buf, _countof(buf), _TRUNCATE, L"%d%%", nPercent);
+        pPercent->SetText(buf);
+    }
+}
+
+// ============================================================================
+// 子线程投递口(不访问任何 this 成员)
+// ============================================================================
+void CUpdateWnd::SetProgressSnapshot(double dlnow, double dltotal)
+{
+    g_dlNow.store(dlnow);
+    g_dlTotal.store(dltotal);
+}
 
-            CEditUI* pPassword = static_cast<CEditUI*>(m_pm.FindControl(_T("pwdedit")));
-            pPassword->SetText(wspassword.c_str());
+void CUpdateWnd::PostProgress()
+{
+    HWND hWnd = g_hUpdateWnd.load();
+
+    if(hWnd != NULL && ::IsWindow(hWnd))
+    {
+        ::PostMessage(hWnd, WM_UPDATEPROGRESS, 0, 0);
+    }
+}
+
+void CUpdateWnd::PostFinish()
+{
+    HWND hWnd = g_hUpdateWnd.load();
+
+    if(hWnd != NULL && ::IsWindow(hWnd))
+    {
+        ::PostMessage(hWnd, WM_DOWNLOAD_FINISH, 0, 0);
+    }
+}
+
+void CUpdateWnd::PostFail(LPCTSTR lpReason)
+{
+    HWND hWnd = g_hUpdateWnd.load();
+
+    // 窗口已经关了就不用提示了,同时省掉一次堆分配
+    if(hWnd == NULL || !::IsWindow(hWnd))
+    {
+        return;
+    }
+
+    // 失败原因随消息走:堆上分配,由主线程 OnDownloadFail 负责释放
+    std::wstring* pReason = new std::wstring((lpReason && lpReason[0])
+                                             ? lpReason
+                                             : L"网络连接异常,请检查网络后重试");
+
+    if(!::PostMessage(hWnd, WM_DOWNLOAD_FAIL, 0, (LPARAM)pReason))
+    {
+        delete pReason;    // 投递失败就地释放,避免泄漏
+    }
+}
+
+// ============================================================================
+// 消息与事件
+// ============================================================================
+void CUpdateWnd::Notify(TNotifyUI& msg)
+{
+    if(msg.sType == _T("click"))
+    {
+        CDuiString sName = msg.pSender->GetName();
+
+        if(sName == _T("closebtn"))
+        {
+            PostQuitMessage(0);
+            return;
+        }
+
+        else if(sName == _T("update_retry_btn"))
+        {
+            // 重新下载:Run() 会读本地已下文件的长度作为续传起点
+            ShowDownloading();
+            StartDownload();
+            return;
         }
     }
 }
@@ -83,6 +488,9 @@ LRESULT CUpdateWnd::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHand
  **/
 LRESULT CUpdateWnd::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
 {
+    // 断开回投通道:子线程下一次回调会发现窗口没了,随即中止下载
+    g_hUpdateWnd.store(NULL);
+
     bHandled = FALSE;
     return 0;
 }
@@ -104,14 +512,7 @@ LRESULT CUpdateWnd::OnNcCalcSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&
 
 LRESULT CUpdateWnd::OnNcPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
 {
-    //在这里设置焦点才有用
-    CEditUI* pAccountEdit = static_cast<CEditUI*>(m_pm.FindControl(_T("accountedit")));
-
-    if(pAccountEdit)
-    {
-        pAccountEdit->SetFocus();
-    }
-
+    // 更新页没有输入焦点,无需设置焦点(旧代码残留下来的 accountedit 已随旧版 XML 一起删除)
     return 0;
 }
 
@@ -203,9 +604,15 @@ LRESULT CUpdateWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
     case WM_UPDATEPROGRESS:
         lRes = OnRefresh(uMsg, wParam, lParam, bHandled);
         break;
+
 	case  WM_DOWNLOAD_FINISH:
 		lRes = OnDownloadFinish(uMsg, wParam, lParam, bHandled);
 		break;
+
+	case  WM_DOWNLOAD_FAIL:
+		lRes = OnDownloadFail(uMsg, wParam, lParam, bHandled);
+		break;
+
     default:
         bHandled = FALSE;
     }
@@ -251,7 +658,7 @@ double CUpdateWnd::getDownloadFileLength(std::string url)
 			ret |= curl_easy_setopt(easy_handle, CURLOPT_SSL_VERIFYPEER, false);
 			ret |= curl_easy_setopt(easy_handle, CURLOPT_SSL_VERIFYHOST, false);
 		}
-		
+
         ret |= curl_easy_setopt(easy_handle, CURLOPT_HEADER, 1L);
         ret |= curl_easy_setopt(easy_handle, CURLOPT_NOBODY, 1L);
         ret |= curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, nousecb);    // libcurl_a.lib will return error code 23 without this sentence on windows
@@ -288,69 +695,122 @@ double CUpdateWnd::getDownloadFileLength(std::string url)
     return size;
 }
 
-void CUpdateWnd::Run()
+// ---------------------------------------------------------------------------
+// 下载线程主体
+// 只读写 ctx(shared_ptr 保证生命周期)与文件静态区,绝不涉及窗口成员
+// ---------------------------------------------------------------------------
+void CUpdateWnd::Run(std::shared_ptr<SUpdateCtx> ctx)
 {
-	m_filepath = m_dir + CLewaimaiString::UTF8ToUnicode(CLewaimaiString::GetFilenameFromUrl(m_url));
+    ctx->filepath = ctx->dir + CLewaimaiString::UTF8ToUnicode(CLewaimaiString::GetFilenameFromUrl(ctx->url));
+
+    // 断点续传:本地已有半成品则从其末尾接着下
+    std::error_code ecSize;
+    uintmax_t nLocalSize = std::filesystem::file_size(ctx->filepath, ecSize);
+
+    ctx->resumeByte = (ecSize || nLocalSize == 0) ? 0L : (long)nLocalSize;
 
-    CURLcode res;
+    SetProgressSnapshot((double)ctx->resumeByte, 0.0);
+    PostProgress();
 
-    m_curl = curl_easy_init();
+    ctx->curl = curl_easy_init();
 
-    if(m_curl)
+    if(!ctx->curl)
     {
+        PostFail(L"网络组件初始化失败,请重启程序后重试");
+        return;
+    }
 
-        m_resumeByte = 0;
+    // 先探一次总大小,探不到就没必要开文件了
+    ctx->total = getDownloadFileLength(ctx->url);
 
-        m_downloadFileLength = getDownloadFileLength(m_url);
+    if(ctx->total <= 0)
+    {
+        curl_easy_cleanup(ctx->curl);
+        ctx->curl = nullptr;
+        PostFail(L"无法连接更新服务器,请检查网络后重试");
+        return;
+    }
 
-        if(m_resumeByte >= (int)m_downloadFileLength)
-        {
-            return;
-        }
+    SetProgressSnapshot((double)ctx->resumeByte, ctx->total);
+    PostProgress();
 
-        errno_t fopenErr = _wfopen_s(&m_outfile, m_filepath.c_str(), L"ab+");
+    // 上次已经下完(比如下完没装就退了),直接进完成态
+    if((double)ctx->resumeByte >= ctx->total)
+    {
+        curl_easy_cleanup(ctx->curl);
+        ctx->curl = nullptr;
+        SetProgressSnapshot(ctx->total, ctx->total);
+        PostFinish();
+        return;
+    }
+
+    errno_t fopenErr = _wfopen_s(&ctx->outfile, ctx->filepath.c_str(), L"ab+");
+
+    if(fopenErr != 0 || ctx->outfile == nullptr)
+    {
+        curl_easy_cleanup(ctx->curl);
+        ctx->curl = nullptr;
+        PostFail(L"无法写入本地文件,请检查磁盘空间与权限");
+        return;
+    }
+
+    curl_easy_setopt(ctx->curl, CURLOPT_URL, ctx->url.c_str());//在此要注意,此url必须是多字节
+
+    if (ctx->url.find("https://") == 0)
+    {
+        curl_easy_setopt(ctx->curl, CURLOPT_SSL_VERIFYPEER, false);
+        curl_easy_setopt(ctx->curl, CURLOPT_SSL_VERIFYHOST, false);
+    }
+
+    curl_easy_setopt(ctx->curl, CURLOPT_TIMEOUT, 0);
+    curl_easy_setopt(ctx->curl, CURLOPT_WRITEFUNCTION, my_write_func);
+    curl_easy_setopt(ctx->curl, CURLOPT_WRITEDATA, ctx->outfile);
+    curl_easy_setopt(ctx->curl, CURLOPT_NOPROGRESS, FALSE);
+    curl_easy_setopt(ctx->curl, CURLOPT_PROGRESSFUNCTION, my_progress_func);
+    curl_easy_setopt(ctx->curl, CURLOPT_PROGRESSDATA, ctx.get());//进度回调需要拿文件指针来算真实已下字节
+    curl_easy_setopt(ctx->curl, CURLOPT_RESUME_FROM, ctx->resumeByte);
+
+    CURLcode res = curl_easy_perform(ctx->curl);
 
-        curl_easy_setopt(m_curl, CURLOPT_URL, m_url.c_str());//在此要注意,此url必须是多字节
+    fclose(ctx->outfile);
+    ctx->outfile = nullptr;
 
-        if (m_url.find("https://") == 0)
+    curl_easy_cleanup(ctx->curl);
+    ctx->curl = nullptr;
+
+    if(res != CURLE_OK)
+    {
+        // 窗口已经关了就别再喊了,用户看不到
+        if(IsUpdateWndAlive())
         {
-            curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, false);
-            curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYHOST, false);
+            PostFail(L"网络连接中断,请检查网络后重试");
         }
 
-        curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, 0);
-        curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, my_write_func);
-        curl_easy_setopt(m_curl, CURLOPT_NOPROGRESS, FALSE);
-        curl_easy_setopt(m_curl, CURLOPT_PROGRESSFUNCTION, my_progress_func);
-        curl_easy_setopt(m_curl, CURLOPT_PROGRESSDATA, NULL);//给下载进度回调函数传参数,在此是指针
+        return;
+    }
 
-        if (fopenErr == 0 && m_outfile != nullptr)
-        {
-            curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, m_outfile);
-            res = curl_easy_perform(m_curl);
+    // 校验实际落盘大小,防止拿到一个残缺包就去安装
+    std::error_code ecDone;
+    uintmax_t nDoneSize = std::filesystem::file_size(ctx->filepath, ecDone);
 
-            fclose(m_outfile);
-            m_outfile = nullptr;
-        }
-        else
+    if(!ecDone)
+    {
+        SetProgressSnapshot((double)nDoneSize, ctx->total);
+
+        if((double)nDoneSize + 1.0 < ctx->total)
         {
-            // 无法打开文件时,清理 curl 并返回
-            curl_easy_cleanup(m_curl);
-            m_curl = nullptr;
+            if(IsUpdateWndAlive())
+            {
+                PostFail(L"下载内容不完整,请重新下载");
+            }
+
             return;
         }
-
-        curl_easy_cleanup(m_curl);
     }
 
-	//走到这里,下载完成了
-	HWND hWnd = FindWindow(NULL, L"自动更新");
-
-	if (hWnd)
-	{
-		::PostMessage(hWnd, WM_DOWNLOAD_FINISH, 0, 0);
-	}
+    PostFinish();
 }
+
 size_t CUpdateWnd::my_write_func(void *ptr, size_t size, size_t nmemb, FILE *stream)
 {
     size_t nWrite = fwrite(ptr, size, nmemb, stream);
@@ -372,22 +832,42 @@ int CUpdateWnd::my_progress_func(void *progress_data,
                                  double ultotal,
                                  double ulnow)
 {
-	if (t > 0)
-	{
-		CUpdateWnd::m_Percent = d * 100.0 / t;
-	}
-	else
-	{
-		CUpdateWnd::m_Percent = 0;
-	}
+    (void)t;
+    (void)d;
+    (void)ultotal;
+    (void)ulnow;
+
+    // 窗口已经关闭 → 返回非 0 让 curl 中止这次传输,避免留下僵尸下载线程
+    if(!IsUpdateWndAlive())
+    {
+        return 1;
+    }
 
-    HWND hWnd = FindWindow(NULL, L"自动更新");
+    const SUpdateCtx* pCtx = static_cast<const SUpdateCtx*>(progress_data);
 
-    if(hWnd)
+    if(!pCtx)
     {
-        ::PostMessage(hWnd, WM_UPDATEPROGRESS, 0, 0);
+        return 0;
     }
 
+    // 直接读文件当前长度作为已下载量:
+    // 一是天然包含断点续传已下的部分,不必猜 libcurl 对 dlnow 的口径;
+    // 二是 CURLOPT_RESUME_FROM 下 dltotal 的语义各版本不一致,统一用自己探到的总大小。
+    double dlnow = 0.0;
+
+    if(pCtx->outfile)
+    {
+        __int64 nPos = _ftelli64(pCtx->outfile);
+
+        if(nPos > 0)
+        {
+            dlnow = (double)nPos;
+        }
+    }
+
+    SetProgressSnapshot(dlnow, pCtx->total);
+    PostProgress();
+
     return 0;
 }
 
@@ -400,21 +880,44 @@ size_t CUpdateWnd::nousecb(char *buffer, size_t x, size_t y, void *userdata)
 
 LRESULT CUpdateWnd::OnRefresh(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
 {
-    CProgressUI* pProgress = static_cast<CProgressUI*>(m_pm.FindControl(_T("update_progress")));
+    UpdateProgressInfo();
 
-    if(pProgress)
+    return 0;
+}
+
+LRESULT CUpdateWnd::OnDownloadFinish(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+    // curl 完成与「本地文件已完整」都投过 WM_DOWNLOAD_FINISH,这里挡掉重复触发
+    if(m_bFinished)
     {
-        pProgress->SetValue((int)m_Percent);
+        return 0;
     }
 
+    m_bFinished = true;
+
+    ShowFinish();
+
+    if(m_ctx && !m_ctx->filepath.empty())
+    {
+        ShellExecute(NULL, L"open", m_ctx->filepath.c_str(), NULL, NULL, SW_SHOW);
+    }
+
+    PostQuitMessage(0);
+
     return 0;
 }
 
-LRESULT CUpdateWnd::OnDownloadFinish(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+LRESULT CUpdateWnd::OnDownloadFail(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
 {
-	HINSTANCE hNewExe = ShellExecute(NULL, L"open", m_filepath.c_str(), NULL, NULL, SW_SHOW);
+    // 失败原因由子线程在堆上分配并随消息传来,主线程负责释放
+    std::wstring* pReason = reinterpret_cast<std::wstring*>(lParam);
+
+    std::wstring sReason = pReason ? *pReason : L"";
 
-	PostQuitMessage(0);
+    delete pReason;
+    pReason = nullptr;
 
-	return 0;
+    ShowFail(sReason.c_str());
+
+    return 0;
 }

+ 56 - 13
zhipuzi_pos_windows/wnd/CUpdateWnd.h

@@ -3,6 +3,26 @@
 #include "pch.h"
 
 #include <curl/curl.h>
+#include <memory>
+
+// ============================================================================
+// 下载上下文
+// ----------------------------------------------------------------------------
+// 下载在 detach 的子线程里跑,而窗口可能在下载过程中被用户点右上角关闭并
+// delete this。为了让子线程不去碰已经释放的窗口对象,所有只有子线程会访问的
+// 字段全部收进这个结构,用 shared_ptr 同时被窗口与子线程持有:
+// 窗口析构只是释放它自己那份引用,子线程手里的那份依然有效。
+// ============================================================================
+struct SUpdateCtx
+{
+    std::string  url;        // 下载地址(curl 要求多字节)
+    std::wstring dir;        // 本地保存目录
+    std::wstring filepath;   // 完整保存路径
+    CURL*        curl     = nullptr;
+    FILE*        outfile  = nullptr;
+    double       total    = 0.0;   // 服务器文件总字节
+    long         resumeByte = 0;   // 断点续传起点
+};
 
 class CUpdateWnd : public CWindowWnd, public INotifyUI, public IMessageFilterUI
 {
@@ -30,8 +50,33 @@ public:
 		m_dir = dir;
 	}
 
+    // 建窗后由 OnCreate 调用:记录回投句柄并启动下载线程
     void Init();
 
+    // 起下载线程(首次进入与「重新下载」都走这里)
+    void StartDownload();
+
+    // 下载线程入口:静态 + 传值 shared_ptr,保证 ctx 活到线程结束
+    static void Run(std::shared_ptr<SUpdateCtx> ctx);
+
+    // ---- 三种状态切换,均只能在主线程调用 ----
+    void ShowDownloading();              // 状态 1:下载中
+    void ShowFinish();                   // 状态 2:下载完成,准备安装
+    void ShowFail(LPCTSTR lpReason);     // 状态 3:下载失败,显示「重新下载」
+
+    // 主线程:把当前已下载/总大小/百分比刷新到界面
+    void UpdateProgressInfo();
+
+    // ---- 子线程专用:所有 UI 切换都必须回到主线程 ----
+    // 下载线程是 detach 的,窗口可能先被销毁,因此这两个函数只依赖文件静态区数据,
+    // 不访问任何 this 成员,避免 Use-After-Free。
+    static void PostProgress();
+    static void PostFinish();
+    static void PostFail(LPCTSTR lpReason);
+
+    // 子线程:把 curl 的 dlnow/dltotal 写入静态快照(供主线程 UpdateProgressInfo 读取)
+    static void SetProgressSnapshot(double dlnow, double dltotal);
+
     void Notify(TNotifyUI& msg);
 
     LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
@@ -58,11 +103,11 @@ public:
 
 	LRESULT OnDownloadFinish(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
 
-	static size_t nousecb(char *buffer, size_t x, size_t y, void *userdata);
+	LRESULT OnDownloadFail(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
 
-    double getDownloadFileLength(std::string url);
+	static size_t nousecb(char *buffer, size_t x, size_t y, void *userdata);
 
-    void Run();
+    static double getDownloadFileLength(std::string url);
 
     static size_t my_write_func(void *ptr, size_t size, size_t nmemb, FILE *stream);
 
@@ -75,18 +120,16 @@ public:
     CPaintManagerUI m_pm;
 
     std::wstring m_dir;//目录名
-	std::wstring m_filepath; //完成文件在本地的路径名
-
-	CURL* m_curl;//libcurl句柄
-
-    double m_downloadFileLength;//服务器文件长度
-
-    int m_resumeByte;//本地已下载的文件长度
-
-    FILE* m_outfile;//文件指针
 
     std::string m_url;//下载地址
 
-    static double m_Percent;
+    // 当前一轮下载的上下文,窗口与下载线程各持一份引用
+    std::shared_ptr<SUpdateCtx> m_ctx;
+
+    // ---- 状态相关(全部只在主线程访问)----
+    int  m_nProgress;      // 当前百分比 0-100
+    bool m_bFailed;        // 是否处于失败态
+    bool m_bFinished;      // 是否已下载完成(避免重复 ShellExecute)
+    bool m_bDownloading;   // 下载线程是否仍在运行(防止重复启动)
 };