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 {
color: App.Theme.bgCanvas
focus: true
Keys.onReturnPressed: appState.login(usernameField.text, passwordField.text || "demo")
Keys.onEnterPressed: appState.login(usernameField.text, passwordField.text || "demo")
// 提交期间禁止重复点击 / 回车
property bool submitting: false
function doLogin() {
if (submitting) return
if (usernameField.text.length === 0 || passwordField.text.length === 0) {
errorLabel.text = "用户名和密码不能为空"
return
}
errorLabel.text = ""
submitting = true
// AuthBridge.login 是同步(pymysql 5s timeout),主线程会卡一下;
// task #18 时若 db 慢需求改异步再说
var ok = auth.login(usernameField.text, passwordField.text)
submitting = false
// 失败时 auth 会发 loginFailed 信号,由下面 Connections 接住
// 成功时 auth.loggedInChanged → AppState 转发 → Main.qml StackLayout 切换
}
Keys.onReturnPressed: doLogin()
Keys.onEnterPressed: doLogin()
Connections {
target: auth
function onLoginFailed(message) {
errorLabel.text = message
}
}
ColumnLayout {
anchors.centerIn: parent
width: 360
spacing: 0
// 标题
Label {
text: "登录"
font.family: App.Theme.fontFamily
......@@ -37,19 +62,19 @@ Rectangle {
Layout.bottomMargin: App.Theme.space5
}
// 用户名
CaptionLabel {
text: "用户名"
Layout.bottomMargin: App.Theme.space2
}
ThemedTextField {
id: usernameField
text: "chaijin"
text: ""
placeholderText: "请输入用户名"
enabled: !root.submitting
Layout.fillWidth: true
Layout.bottomMargin: App.Theme.space4
}
// 密码
CaptionLabel {
text: "密码"
Layout.bottomMargin: App.Theme.space2
......@@ -58,19 +83,21 @@ Rectangle {
id: passwordField
echoMode: TextInput.Password
placeholderText: "••••••••"
enabled: !root.submitting
Layout.fillWidth: true
Layout.bottomMargin: App.Theme.space2
}
// 复选行
RowLayout {
spacing: App.Theme.space4
Layout.fillWidth: true
Layout.bottomMargin: App.Theme.space5
CheckBox {
id: rememberUser
text: "记住用户名"
checked: true
enabled: !root.submitting
font.family: App.Theme.fontFamily
font.pointSize: App.Theme.fontSm
contentItem: Text {
......@@ -100,8 +127,10 @@ Rectangle {
}
CheckBox {
id: rememberPassword
text: "记住密码"
checked: true
enabled: !root.submitting
font.family: App.Theme.fontFamily
font.pointSize: App.Theme.fontSm
contentItem: Text {
......@@ -133,20 +162,22 @@ Rectangle {
Item { Layout.fillWidth: true }
}
// 登录按钮
PrimaryButton {
text: "登录"
text: root.submitting ? "登录中…" : "登录"
enabled: !root.submitting
Layout.fillWidth: true
onClicked: appState.login(usernameField.text, passwordField.text || "demo")
onClicked: doLogin()
}
Label {
id: hint
id: errorLabel
text: ""
font.family: App.Theme.fontFamily
font.pointSize: App.Theme.fontXs
color: App.Theme.textTertiary
Layout.alignment: Qt.AlignHCenter
color: App.Theme.danger
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
Layout.topMargin: App.Theme.space3
}
}
......