feat: 更新Swift助手样式规则和应用结构

- 在swift-assistant-style.mdc中添加项目背景、代码结构、命名规范、Swift最佳实践、UI开发、性能、安全性、测试与质量、核心功能、开发流程、App Store指南等详细规则。
- 在yanaApp.swift中将SplashView替换为Splash,简化应用结构。
This commit is contained in:
edwinQQQ
2025-08-06 14:12:20 +08:00
parent 86fcb96d50
commit 428aa95c5e
18 changed files with 2390 additions and 229 deletions

230
yana/MVVM/IDLoginPage.swift Normal file
View File

@@ -0,0 +1,230 @@
import SwiftUI
import Combine
// MARK: - IDLogin ViewModel
@MainActor
class IDLoginViewModel: ObservableObject {
// MARK: - Published Properties
@Published var userID: String = ""
@Published var password: String = ""
@Published var isPasswordVisible: Bool = false
@Published var isLoading: Bool = false
@Published var errorMessage: String?
@Published var showRecoverPassword: Bool = false
@Published var loginStep: LoginStep = .input
// MARK: - Callbacks
var onBack: (() -> Void)?
var onLoginSuccess: (() -> Void)?
// MARK: - Private Properties
private var cancellables = Set<AnyCancellable>()
// MARK: - Enums
enum LoginStep: Equatable {
case input
case completed
}
// MARK: - Computed Properties
var isLoginButtonEnabled: Bool {
return !isLoading && !userID.isEmpty && !password.isEmpty
}
// MARK: - Public Methods
func onBackTapped() {
onBack?()
}
func onLoginTapped() {
guard isLoginButtonEnabled else { return }
isLoading = true
errorMessage = nil
Task {
do {
let result = try await performLogin()
await MainActor.run {
self.handleLoginResult(result)
}
} catch {
await MainActor.run {
self.handleLoginError(error)
}
}
}
}
func onRecoverPasswordTapped() {
showRecoverPassword = true
}
func onRecoverPasswordBack() {
showRecoverPassword = false
}
// MARK: - Private Methods
private func performLogin() async throws -> Bool {
// 使LoginHelperDES
guard let loginRequest = await LoginHelper.createIDLoginRequest(
userID: userID,
password: password
) else {
throw APIError.custom("DES加密失败")
}
let apiService = LiveAPIService()
let response: IDLoginResponse = try await apiService.request(loginRequest)
if response.code == 200, let data = response.data {
// API
if let userInfo = data.userInfo {
await UserInfoManager.saveUserInfo(userInfo)
}
//
guard let accountModel = AccountModel.from(loginData: data) else {
throw APIError.custom("账户信息无效")
}
await UserInfoManager.saveAccountModel(accountModel)
// API
if data.userInfo == nil, let userInfo = await UserInfoManager.fetchUserInfoFromServer(
uid: String(data.uid ?? 0),
apiService: apiService
) {
await UserInfoManager.saveUserInfo(userInfo)
}
return true
} else {
throw APIError.custom(response.message ?? "Login failed")
}
}
private func handleLoginResult(_ success: Bool) {
isLoading = false
if success {
loginStep = .completed
onLoginSuccess?()
}
}
private func handleLoginError(_ error: Error) {
isLoading = false
errorMessage = error.localizedDescription
}
}
// MARK: - IDLogin View
struct IDLoginPage: View {
@StateObject private var viewModel = IDLoginViewModel()
let onBack: () -> Void
let onLoginSuccess: () -> Void
var body: some View {
GeometryReader { geometry in
ZStack {
//
LoginBackgroundView()
VStack(spacing: 0) {
//
LoginHeaderView(onBack: {
viewModel.onBackTapped()
})
Spacer()
.frame(height: 60)
//
Text("ID Login")
.font(.system(size: 28, weight: .bold))
.foregroundColor(.white)
.padding(.bottom, 60)
//
VStack(spacing: 24) {
// ID
CustomInputField(
type: .number,
placeholder: "Please enter ID",
text: $viewModel.userID
)
//
CustomInputField(
type: .password,
placeholder: "Please enter password",
text: $viewModel.password,
isPasswordVisible: $viewModel.isPasswordVisible
)
}
.padding(.horizontal, 32)
Spacer()
.frame(height: 80)
//
HStack {
Spacer()
Button(action: {
viewModel.onRecoverPasswordTapped()
}) {
Text("Forgot Password?")
.font(.system(size: 14))
.foregroundColor(.white.opacity(0.8))
}
}
.padding(.horizontal, 32)
.padding(.bottom, 20)
//
LoginButtonView(
isLoading: viewModel.isLoading,
isEnabled: viewModel.isLoginButtonEnabled,
onTap: {
viewModel.onLoginTapped()
}
)
.padding(.horizontal, 32)
Spacer()
}
}
}
.navigationBarHidden(true)
.navigationDestination(isPresented: $viewModel.showRecoverPassword) {
RecoverPasswordPage(
onBack: {
viewModel.onRecoverPasswordBack()
}
)
.navigationBarHidden(true)
}
.onAppear {
viewModel.onBack = onBack
viewModel.onLoginSuccess = onLoginSuccess
#if DEBUG
debugInfoSync("🐛 Debug模式: 已移除硬编码测试凭据")
#endif
}
.onChange(of: viewModel.loginStep) { _, newStep in
debugInfoSync("🔄 IDLoginView: loginStep 变化为 \(newStep)")
if newStep == .completed {
debugInfoSync("✅ IDLoginView: 登录成功,准备关闭自身")
}
}
}
}
#Preview {
IDLoginPage(
onBack: {},
onLoginSuccess: {}
)
}