f621d57c by 柴进

feat(qml): task #13 登录页接 AuthBridge 真 db 认证

LoginScreen.qml wiring 改造:
- appState.login → auth.login (走 DatabaseManager.authenticate / pymysql)
- 删 || "demo" 兜底,密码空时 UI 阻断
- submitting 状态:登录中 username/password/checkbox/button 全 disabled,按钮文字 "登录中…"
- 监听 auth.loginFailed → errorLabel 红字展示后端返回的具体原因
  ("用户名或密码错误" / "无法连接到服务器" 等,DatabaseManager 已统一中文化)
- usernameField 不再硬编码 "chaijin",改 placeholder

冒烟测试(直接调 Python 测真 db):
- chaijin/wrongpwd → False,loginFailed 信号"用户名或密码错误"
- chaijin/a160827 → True,loggedIn=True,currentUser='chaijin'
QML 启动登录页渲染正常(task13_login.png 已验证)。

注:
- "记住用户名/密码" checkbox UI 保留但暂未持久化,task #18 系统集成时接 config_util 落盘
- pymysql 同步阻塞主线程最多 5s(connect_timeout),暂不影响 UX;
  若 db 慢成痛点 task #18 时再改 worker 异步

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4908db24
...@@ -9,15 +9,40 @@ Rectangle { ...@@ -9,15 +9,40 @@ Rectangle {
9 color: App.Theme.bgCanvas 9 color: App.Theme.bgCanvas
10 focus: true 10 focus: true
11 11
12 Keys.onReturnPressed: appState.login(usernameField.text, passwordField.text || "demo") 12 // 提交期间禁止重复点击 / 回车
13 Keys.onEnterPressed: appState.login(usernameField.text, passwordField.text || "demo") 13 property bool submitting: false
14
15 function doLogin() {
16 if (submitting) return
17 if (usernameField.text.length === 0 || passwordField.text.length === 0) {
18 errorLabel.text = "用户名和密码不能为空"
19 return
20 }
21 errorLabel.text = ""
22 submitting = true
23 // AuthBridge.login 是同步(pymysql 5s timeout),主线程会卡一下;
24 // task #18 时若 db 慢需求改异步再说
25 var ok = auth.login(usernameField.text, passwordField.text)
26 submitting = false
27 // 失败时 auth 会发 loginFailed 信号,由下面 Connections 接住
28 // 成功时 auth.loggedInChanged → AppState 转发 → Main.qml StackLayout 切换
29 }
30
31 Keys.onReturnPressed: doLogin()
32 Keys.onEnterPressed: doLogin()
33
34 Connections {
35 target: auth
36 function onLoginFailed(message) {
37 errorLabel.text = message
38 }
39 }
14 40
15 ColumnLayout { 41 ColumnLayout {
16 anchors.centerIn: parent 42 anchors.centerIn: parent
17 width: 360 43 width: 360
18 spacing: 0 44 spacing: 0
19 45
20 // 标题
21 Label { 46 Label {
22 text: "登录" 47 text: "登录"
23 font.family: App.Theme.fontFamily 48 font.family: App.Theme.fontFamily
...@@ -37,19 +62,19 @@ Rectangle { ...@@ -37,19 +62,19 @@ Rectangle {
37 Layout.bottomMargin: App.Theme.space5 62 Layout.bottomMargin: App.Theme.space5
38 } 63 }
39 64
40 // 用户名
41 CaptionLabel { 65 CaptionLabel {
42 text: "用户名" 66 text: "用户名"
43 Layout.bottomMargin: App.Theme.space2 67 Layout.bottomMargin: App.Theme.space2
44 } 68 }
45 ThemedTextField { 69 ThemedTextField {
46 id: usernameField 70 id: usernameField
47 text: "chaijin" 71 text: ""
72 placeholderText: "请输入用户名"
73 enabled: !root.submitting
48 Layout.fillWidth: true 74 Layout.fillWidth: true
49 Layout.bottomMargin: App.Theme.space4 75 Layout.bottomMargin: App.Theme.space4
50 } 76 }
51 77
52 // 密码
53 CaptionLabel { 78 CaptionLabel {
54 text: "密码" 79 text: "密码"
55 Layout.bottomMargin: App.Theme.space2 80 Layout.bottomMargin: App.Theme.space2
...@@ -58,19 +83,21 @@ Rectangle { ...@@ -58,19 +83,21 @@ Rectangle {
58 id: passwordField 83 id: passwordField
59 echoMode: TextInput.Password 84 echoMode: TextInput.Password
60 placeholderText: "••••••••" 85 placeholderText: "••••••••"
86 enabled: !root.submitting
61 Layout.fillWidth: true 87 Layout.fillWidth: true
62 Layout.bottomMargin: App.Theme.space2 88 Layout.bottomMargin: App.Theme.space2
63 } 89 }
64 90
65 // 复选行
66 RowLayout { 91 RowLayout {
67 spacing: App.Theme.space4 92 spacing: App.Theme.space4
68 Layout.fillWidth: true 93 Layout.fillWidth: true
69 Layout.bottomMargin: App.Theme.space5 94 Layout.bottomMargin: App.Theme.space5
70 95
71 CheckBox { 96 CheckBox {
97 id: rememberUser
72 text: "记住用户名" 98 text: "记住用户名"
73 checked: true 99 checked: true
100 enabled: !root.submitting
74 font.family: App.Theme.fontFamily 101 font.family: App.Theme.fontFamily
75 font.pointSize: App.Theme.fontSm 102 font.pointSize: App.Theme.fontSm
76 contentItem: Text { 103 contentItem: Text {
...@@ -100,8 +127,10 @@ Rectangle { ...@@ -100,8 +127,10 @@ Rectangle {
100 } 127 }
101 128
102 CheckBox { 129 CheckBox {
130 id: rememberPassword
103 text: "记住密码" 131 text: "记住密码"
104 checked: true 132 checked: true
133 enabled: !root.submitting
105 font.family: App.Theme.fontFamily 134 font.family: App.Theme.fontFamily
106 font.pointSize: App.Theme.fontSm 135 font.pointSize: App.Theme.fontSm
107 contentItem: Text { 136 contentItem: Text {
...@@ -133,20 +162,22 @@ Rectangle { ...@@ -133,20 +162,22 @@ Rectangle {
133 Item { Layout.fillWidth: true } 162 Item { Layout.fillWidth: true }
134 } 163 }
135 164
136 // 登录按钮
137 PrimaryButton { 165 PrimaryButton {
138 text: "登录" 166 text: root.submitting ? "登录中…" : "登录"
167 enabled: !root.submitting
139 Layout.fillWidth: true 168 Layout.fillWidth: true
140 onClicked: appState.login(usernameField.text, passwordField.text || "demo") 169 onClicked: doLogin()
141 } 170 }
142 171
143 Label { 172 Label {
144 id: hint 173 id: errorLabel
145 text: "" 174 text: ""
146 font.family: App.Theme.fontFamily 175 font.family: App.Theme.fontFamily
147 font.pointSize: App.Theme.fontXs 176 font.pointSize: App.Theme.fontXs
148 color: App.Theme.textTertiary 177 color: App.Theme.danger
149 Layout.alignment: Qt.AlignHCenter 178 wrapMode: Text.Wrap
179 horizontalAlignment: Text.AlignHCenter
180 Layout.fillWidth: true
150 Layout.topMargin: App.Theme.space3 181 Layout.topMargin: App.Theme.space3
151 } 182 }
152 } 183 }
......