Procházet zdrojové kódy

增加本地序列化 Defaults

openlockPPP před 1 rokem
rodič
revize
87b55bc23c

+ 16 - 0
JSJP_Student_sw.xcodeproj/project.pbxproj

@@ -21,6 +21,8 @@
 		904B25A62A289EE5001F7C9C /* NYArticleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 904B25A52A289EE5001F7C9C /* NYArticleViewController.swift */; };
 		904B25A82A289F55001F7C9C /* NYArticleNavBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 904B25A72A289F55001F7C9C /* NYArticleNavBar.swift */; };
 		904B25AA2A289F65001F7C9C /* NYArticleNavBar.xib in Resources */ = {isa = PBXBuildFile; fileRef = 904B25A92A289F65001F7C9C /* NYArticleNavBar.xib */; };
+		907AB3242A29BCAC009FB573 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907AB3232A29BCAC009FB573 /* LoginViewModel.swift */; };
+		907AB3262A29BCC5009FB573 /* LoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907AB3252A29BCC5009FB573 /* LoginService.swift */; };
 		908E44182A27225100DA2536 /* BaseCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 908E44172A27225100DA2536 /* BaseCollectionViewController.swift */; };
 		908E441D2A2749E100DA2536 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 908E441C2A2749E100DA2536 /* LoginViewController.swift */; };
 		908E441F2A274AAC00DA2536 /* Exam01_ParentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 908E441E2A274AAC00DA2536 /* Exam01_ParentViewController.swift */; };
@@ -52,6 +54,8 @@
 		90A64BD82A2604F600D8BED6 /* UILabel+GJRatioAutoLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 90A64BD12A2604F600D8BED6 /* UILabel+GJRatioAutoLayout.m */; };
 		90A64BD92A2604F600D8BED6 /* GJRatioAutoLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 90A64BD62A2604F600D8BED6 /* GJRatioAutoLayout.m */; };
 		90A64BDA2A2604F600D8BED6 /* UIView+GJRatioAutoLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 90A64BD72A2604F600D8BED6 /* UIView+GJRatioAutoLayout.m */; };
+		90E71CD42A2B81AB00E4086E /* LoginAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90E71CD32A2B81AB00E4086E /* LoginAPI.swift */; };
+		90E71CD62A2C7FBA00E4086E /* LocalManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90E71CD52A2C7FBA00E4086E /* LocalManager.swift */; };
 		B204266C2A1B0E50009FAC45 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B204266B2A1B0E50009FAC45 /* AppDelegate.swift */; };
 		B20426752A1B0E52009FAC45 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B20426742A1B0E52009FAC45 /* Assets.xcassets */; };
 		B20426782A1B0E52009FAC45 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B20426762A1B0E52009FAC45 /* LaunchScreen.storyboard */; };
@@ -147,6 +151,8 @@
 		904B25A52A289EE5001F7C9C /* NYArticleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NYArticleViewController.swift; sourceTree = "<group>"; };
 		904B25A72A289F55001F7C9C /* NYArticleNavBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NYArticleNavBar.swift; sourceTree = "<group>"; };
 		904B25A92A289F65001F7C9C /* NYArticleNavBar.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NYArticleNavBar.xib; sourceTree = "<group>"; };
+		907AB3232A29BCAC009FB573 /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = "<group>"; };
+		907AB3252A29BCC5009FB573 /* LoginService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginService.swift; sourceTree = "<group>"; };
 		908E44172A27225100DA2536 /* BaseCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseCollectionViewController.swift; sourceTree = "<group>"; };
 		908E441C2A2749E100DA2536 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = "<group>"; };
 		908E441E2A274AAC00DA2536 /* Exam01_ParentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Exam01_ParentViewController.swift; sourceTree = "<group>"; };
@@ -183,6 +189,8 @@
 		90A64BD62A2604F600D8BED6 /* GJRatioAutoLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GJRatioAutoLayout.m; sourceTree = "<group>"; };
 		90A64BD72A2604F600D8BED6 /* UIView+GJRatioAutoLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+GJRatioAutoLayout.m"; sourceTree = "<group>"; };
 		90A64BDB2A2605C500D8BED6 /* JSJP-Brigding-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JSJP-Brigding-Header.h"; sourceTree = "<group>"; };
+		90E71CD32A2B81AB00E4086E /* LoginAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginAPI.swift; sourceTree = "<group>"; };
+		90E71CD52A2C7FBA00E4086E /* LocalManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalManager.swift; sourceTree = "<group>"; };
 		B20426682A1B0E50009FAC45 /* JSJP_Student_sw.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JSJP_Student_sw.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		B204266B2A1B0E50009FAC45 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		B20426742A1B0E52009FAC45 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -309,6 +317,7 @@
 		904B259F2A28970C001F7C9C /* ViewModel */ = {
 			isa = PBXGroup;
 			children = (
+				907AB3232A29BCAC009FB573 /* LoginViewModel.swift */,
 			);
 			path = ViewModel;
 			sourceTree = "<group>";
@@ -560,6 +569,7 @@
 		B204269D2A1B45C1009FAC45 /* Utils */ = {
 			isa = PBXGroup;
 			children = (
+				90E71CD52A2C7FBA00E4086E /* LocalManager.swift */,
 				90A64B8F2A25A49500D8BED6 /* URLNavigationMap.swift */,
 				B20427422A1B5846009FAC45 /* Snap.swift */,
 				90A64B912A25A4DC00D8BED6 /* NYSwRouter.swift */,
@@ -570,8 +580,10 @@
 		B204269E2A1B45E6009FAC45 /* Services */ = {
 			isa = PBXGroup;
 			children = (
+				907AB3252A29BCC5009FB573 /* LoginService.swift */,
 				90A64B9A2A25A91B00D8BED6 /* HomeService.swift */,
 				90A64BA72A25BF8100D8BED6 /* HomeAPI.swift */,
+				90E71CD32A2B81AB00E4086E /* LoginAPI.swift */,
 			);
 			path = Services;
 			sourceTree = "<group>";
@@ -961,10 +973,12 @@
 				B204270F2A1B4ED2009FAC45 /* LWPlayer.swift in Sources */,
 				904B25A42A289C6E001F7C9C /* BaseTableViewController.swift in Sources */,
 				B20427162A1B4ED2009FAC45 /* LWPlayerUtils.swift in Sources */,
+				907AB3242A29BCAC009FB573 /* LoginViewModel.swift in Sources */,
 				90A64BB32A25D11800D8BED6 /* BaseViewController.swift in Sources */,
 				B20427362A1B5420009FAC45 /* NetEnvironment.swift in Sources */,
 				90A64B902A25A49500D8BED6 /* URLNavigationMap.swift in Sources */,
 				904B25A12A289C27001F7C9C /* NYWebViewController.swift in Sources */,
+				907AB3262A29BCC5009FB573 /* LoginService.swift in Sources */,
 				B204271F2A1B4ED2009FAC45 /* GesConflictCollectionView.swift in Sources */,
 				B20427132A1B4ED2009FAC45 /* LWPlayerSlider.swift in Sources */,
 				90A64BA62A25BD3A00D8BED6 /* Networking.swift in Sources */,
@@ -972,6 +986,7 @@
 				B204271E2A1B4ED2009FAC45 /* BilibiliCollectionViewLayout.swift in Sources */,
 				B20427102A1B4ED2009FAC45 /* LWPlayerView.swift in Sources */,
 				908E441F2A274AAC00DA2536 /* Exam01_ParentViewController.swift in Sources */,
+				90E71CD62A2C7FBA00E4086E /* LocalManager.swift in Sources */,
 				B20426C82A1B4DB6009FAC45 /* DefaultsKeys+Key.swift in Sources */,
 				B20427192A1B4ED2009FAC45 /* LWPlayerFullScreenViewController.swift in Sources */,
 				90A64BA82A25BF8100D8BED6 /* HomeAPI.swift in Sources */,
@@ -1004,6 +1019,7 @@
 				B20427382A1B549F009FAC45 /* RequestError.swift in Sources */,
 				B204272E2A1B4ED2009FAC45 /* PopOverView.swift in Sources */,
 				B20426D42A1B4DB6009FAC45 /* UIView+CornerRadius.swift in Sources */,
+				90E71CD42A2B81AB00E4086E /* LoginAPI.swift in Sources */,
 				904B25942A286072001F7C9C /* NYPasswordView.swift in Sources */,
 				B20426C92A1B4DB6009FAC45 /* UIColor+Hex.swift in Sources */,
 				904B25922A28605B001F7C9C /* NYPhoneView.swift in Sources */,

+ 1 - 1
JSJP_Student_sw/Sources/CompositionRoot.swift

@@ -85,7 +85,7 @@ final class CompositionRoot {
         let _ = Region(calendar: Calendars.gregorian, zone: Zones.asiaShanghai, locale: Locales.chinese)
 
         //打开app次数
-//        LocalManager.userInfo.openTimes += 1
+        LocalManager.userInfo.openTimes += 1
 
         //设置环境  --默认是线上环境
         Defaults.currentEnvironment = .res

+ 4 - 0
JSJP_Student_sw/Sources/Extensions/DefaultsKeys+Key.swift

@@ -18,5 +18,9 @@ extension DefaultsKeys {
     
     var avater: DefaultsKey<Data> { .init("avater", defaultValue: Data()) }
     
+    var userAccount:DefaultsKey<String?> { .init("userAccount", defaultValue:"") }
+    
+    var userPassword:DefaultsKey<String?> { .init("userPassword", defaultValue:"") }
+    
     var currentEnvironment: DefaultsKey<NetEnvironment> { .init("currentEnvironment", defaultValue: .res) }
 }

+ 1 - 0
JSJP_Student_sw/Sources/Main/Base/BaseViewController.swift

@@ -8,6 +8,7 @@
 import UIKit
 
 import RxSwift
+import RxCocoa
 import ReactorKit
 import RxDataSources
 

+ 118 - 9
JSJP_Student_sw/Sources/Modulars/Login/Controllers/LoginViewController.swift

@@ -5,12 +5,17 @@
 //  Created by Ning.ge on 2023/5/31.
 //
 import UIKit
+import RxSwift
+import RxCocoa
+import SwiftyUserDefaults
 
 final class LoginViewController: BaseViewController {
   
     
     // MARK: 私有属性
-  
+    
+    private var loginModel:LoginViewModel!
+    
     // MARK: UI
     
     @IBOutlet weak var welcomeLab: UILabel!
@@ -23,12 +28,18 @@ final class LoginViewController: BaseViewController {
         $0.backgroundColor = UIColor(242, 243, 245)
         $0.layer.cornerRadius = 20.f
         $0.layer.masksToBounds = true
+        if ((LocalManager.userInfo.userAccount?.isEmpty) != nil)  {
+            $0.textField.text = LocalManager.userInfo.userAccount
+        }
     }
     //密码
     let passwordView = NYPasswordView.loadFromNib().then {
         $0.backgroundColor = UIColor(242, 243, 245)
         $0.layer.cornerRadius = 20.f
         $0.layer.masksToBounds = true
+        if ((LocalManager.userInfo.userPassword?.count) != nil) {
+            $0.textField.text = LocalManager.userInfo.userPassword
+        }
     }
     //同意
     let agreementView = NYAgreementView.loadFromNib()
@@ -78,33 +89,131 @@ final class LoginViewController: BaseViewController {
     
     func biandViewModel(){
         
-        //验证账号
+//        loginModel = LoginViewModel(
+//            username: phoneView.textField.rx.text.orEmpty.asObservable(),
+//            password: passwordView.textField.rx.text.orEmpty.asObservable(),
+//            agree: agreementView.agreeBtn.rx.tap.asObservable())
+//
+//        //绑定按钮
+//        loginModel.everythingValid.bind(to: loginBtn.rx.isEnabled).disposed(by: disposeBag)
+//        loginModel.everythingValid.subscribe(onNext: { [unowned self] valid in
+//            NSLog("loginObserver")
+//            self.loginBtn.alpha = valid ? 1 : 0.5
+//        }).disposed(by: disposeBag)
+//
+//        //用户协议
+//        agreementView.user_protocol_btn.rx.tap.subscribe ({ [unowned self] (_)  in
+//            //跳web
+//            navigator.push("https://ys.zzxcx.net/xy_jsjp.html")
+//        }).disposed(by: disposeBag)
+//        //隐私
+//        agreementView.user_privacy_btn.rx.tap.subscribe ({ [unowned self] (_)  in
+//            //跳web
+//            navigator.push("https://ys.zzxcx.net/xy_jsjp.html")
+//        }).disposed(by: disposeBag)
+//
+//
+//        let tapBackground = UITapGestureRecognizer()
+//        tapBackground.rx.event
+//            .subscribe(onNext: { [weak self] _ in
+//                self?.view.endEditing(true)
+//            })
+//            .disposed(by: disposeBag)
+//        view.addGestureRecognizer(tapBackground)
+//        //登录
+//        loginBtn.rx.tap
+//                    .asObservable()
+//                    .withLatestFrom(loginModel.everythingValid)
+//                    .do(onNext: {
+//                        [unowned self]_ in
+//                        self.loginBtn.isEnabled = false
+//                        self.view.endEditing(true)
+//                    })
+//                    .subscribeOn(MainScheduler.instance)//主线程
+//                    .subscribe(onNext: {[unowned self]isLogin in
+//                        NSLog("登录 - 登录: login %@","123")
+//                        self.loginBtn.isEnabled = true
+//                    })
+//                    .disposed(by: disposeBag)
         
-        //验证密码
+        textBaind()
+    }
+    
+    
+    func textBaind() {
+        //判断账号的输入是否可用
+        let accountValid = phoneView.textField.rx.text.orEmpty.map{ value in
+            return value.count >= 6
+        }
+        //判断密码的输入是否可用
+        let passwordValid = passwordView.textField.rx.text.orEmpty.map{ value in
+            return value.count >= 6
+        }
         
+        //判断同意
+        let agreeValid = agreementView.agreeBtn.rx.tap.map { [unowned self] in
+            NSLog("agreeValid")
+                return self.agreementView.agreeBtn.isSelected
+        }
         //同意
         agreementView.agreeBtn.rx.tap.subscribe ({ [unowned self] (_)  in
+            NSLog("tap.subscribe")
             self.agreementView.agreeBtn.isSelected = !self.agreementView.agreeBtn.isSelected
-        }).disposed(by: rx.disposeBag)
+        }).disposed(by: disposeBag)
+        //登录按钮的可用与否
+        let loginObserver = Observable.combineLatest(accountValid,passwordValid,agreeValid){(account,password,agree) in
+            account && password && agree
+        }
+
+        //绑定按钮
+        loginObserver.bind(to: loginBtn.rx.isEnabled).disposed(by: disposeBag)
+        loginObserver.subscribe(onNext: { [unowned self] valid in
+            NSLog("loginObserver")
+            self.loginBtn.alpha = valid ? 1 : 0.5
+        }).disposed(by: disposeBag)
+        
+        
+        
         //用户协议
         agreementView.user_protocol_btn.rx.tap.subscribe ({ [unowned self] (_)  in
             //跳web
             navigator.push("https://ys.zzxcx.net/xy_jsjp.html")
-        }).disposed(by: rx.disposeBag)
+        }).disposed(by: disposeBag)
         //隐私
         agreementView.user_privacy_btn.rx.tap.subscribe ({ [unowned self] (_)  in
             //跳web
             navigator.push("https://ys.zzxcx.net/xy_jsjp.html")
-        }).disposed(by: rx.disposeBag)
+        }).disposed(by: disposeBag)
+        
+        //登录
+        loginBtn.rx.tap
+                    .asObservable()
+                    .withLatestFrom(loginObserver)
+                    .do(onNext: {
+                        [unowned self]_ in
+                        self.loginBtn.isEnabled = false
+                        self.view.endEditing(true)
+                    })
+                    .subscribeOn(MainScheduler.instance)//主线程
+                    .subscribe(onNext: {[unowned self]isLogin in
+                        NSLog("登录 - 登录: login %@","123")
+                        //保存用户信息
+                        LocalManager.userInfo.userAccount = self.phoneView.textField.text
+                        LocalManager.userInfo.userPassword = self.passwordView.textField.text
+                        
+                        self.loginBtn.isEnabled = true
+                    })
+                    .disposed(by: disposeBag)
     }
+    
     // MARK: 事件
     
     @IBAction func actionCloseClickdo(_ sender: UIButton) {
         self.navigationController?.popViewController(animated: true)
     }
     
-    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
-        self.view.endEditing(true)
-    }
+//    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
+//        self.view.endEditing(true)
+//    }
     
 }

+ 1 - 1
JSJP_Student_sw/Sources/Modulars/Login/Controllers/LoginViewController.xib

@@ -24,7 +24,7 @@
                 <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7MN-G5-6uG">
                     <rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
                     <subviews>
-                        <button opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="mkk-GL-u9a">
+                        <button opaque="NO" clipsSubviews="YES" alpha="0.5" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="mkk-GL-u9a">
                             <rect key="frame" x="24" y="474" width="327" height="40"/>
                             <color key="backgroundColor" red="0.28627450980392155" green="0.55686274509803924" blue="0.96078431372549022" alpha="1" colorSpace="calibratedRGB"/>
                             <constraints>

+ 58 - 0
JSJP_Student_sw/Sources/Modulars/Login/ViewModel/LoginViewModel.swift

@@ -0,0 +1,58 @@
+//
+//  LoginViewModel.swift
+//  JSJP_Student_sw
+//
+//  Created by Ning.ge on 2023/6/2.
+//
+
+import RxSwift
+import RxCocoa
+
+//不可重写class
+final class LoginViewModel
+{
+//    // 输出
+//    let usernameValid: Observable<Bool>
+//    let passwordValid: Observable<Bool>
+//    let everythingValid: Observable<Bool>  //组合
+//    let agreeValid: Observable<Bool>
+//    
+//    // 登录按钮是否可用
+//    let signupEnabled: Observable<Bool>
+//
+//    // 是否已登录
+//    let signedIn: Observable<Bool>
+//
+//    // 是否登录中
+//    let signingIn: Observable<Bool>
+//    
+//    
+//    static let minimalUsernameLength = 11
+//    static let minimalPasswordLength = 8
+//    // 输入 -> 输出
+//    init(input: (
+//        username: Observable<String>,
+//        password: Observable<String>,
+//        agree: Observable<Void>
+//        ),
+//        dependency :(
+//            API: LoginAPI,
+//            validationService: GitHubValidationService)
+//        ) {
+//        usernameValid = input.username
+//                .map { $0.count >= LoginViewModel.minimalUsernameLength }
+//            .share(replay: 1)
+//
+//        passwordValid = input.password
+//                .map { $0.count >= LoginViewModel.minimalPasswordLength }
+//            .share(replay: 1)
+//
+//        agreeValid = input.agree.map {
+//            return true
+//            }.share(replay: 1)
+//
+//        everythingValid = Observable.combineLatest(usernameValid, passwordValid,agreeValid) { $0 && $1 && $2 }
+//            .share(replay: 1)
+//    }
+    
+}

+ 39 - 12
JSJP_Student_sw/Sources/Modulars/Login/Views/NYAgreementView.xib

@@ -15,39 +15,54 @@
             <rect key="frame" x="0.0" y="0.0" width="375" height="30"/>
             <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
             <subviews>
-                <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2mp-Il-g5P">
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2mp-Il-g5P">
                     <rect key="frame" x="0.0" y="0.0" width="30" height="30"/>
-                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="30" id="DfJ-Ub-AOI"/>
+                        <constraint firstAttribute="width" constant="30" id="oWF-Kw-cLd"/>
+                    </constraints>
                     <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
                     <state key="normal" backgroundImage="选项框"/>
                     <state key="selected" backgroundImage="已勾选"/>
                 </button>
-                <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Gd4-hR-tap">
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Gd4-hR-tap">
                     <rect key="frame" x="82" y="1" width="92" height="28"/>
-                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="28" id="Y2w-g6-Oce"/>
+                        <constraint firstAttribute="width" constant="92" id="du1-DU-S2I"/>
+                    </constraints>
                     <fontDescription key="fontDescription" type="system" weight="medium" pointSize="15"/>
                     <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
                     <state key="normal" title="《用户协议》">
                         <color key="titleColor" red="0.28627450980000002" green="0.5568627451" blue="0.96078431369999995" alpha="1" colorSpace="deviceRGB"/>
                     </state>
                 </button>
-                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="和" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="m2D-Bh-JNY">
+                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="和" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="m2D-Bh-JNY">
                     <rect key="frame" x="174" y="7" width="15.666666666666666" height="16"/>
-                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="16" id="2jH-ie-IaC"/>
+                        <constraint firstAttribute="width" constant="15.67" id="H26-Tr-joT"/>
+                    </constraints>
                     <fontDescription key="fontDescription" type="system" weight="medium" pointSize="15"/>
                     <color key="textColor" red="0.54117647059999996" green="0.56470588239999997" blue="0.59999999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                     <nil key="highlightedColor"/>
                 </label>
-                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="已同意" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fK6-q7-2NY">
+                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="已同意" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fK6-q7-2NY">
                     <rect key="frame" x="36" y="7" width="46" height="16"/>
-                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                    <constraints>
+                        <constraint firstAttribute="width" constant="46" id="45a-6a-1gt"/>
+                        <constraint firstAttribute="height" constant="16" id="HmQ-Nk-jci"/>
+                    </constraints>
                     <fontDescription key="fontDescription" type="system" weight="medium" pointSize="15"/>
                     <color key="textColor" red="0.54117647059999996" green="0.56470588239999997" blue="0.59999999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                     <nil key="highlightedColor"/>
                 </label>
-                <button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" verticalHuggingPriority="249" horizontalCompressionResistancePriority="749" verticalCompressionResistancePriority="749" fixedFrame="YES" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="x94-oC-htT">
-                    <rect key="frame" x="189.66666666666666" y="1" width="185.66666666666666" height="28"/>
-                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                <button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" verticalHuggingPriority="249" horizontalCompressionResistancePriority="749" verticalCompressionResistancePriority="749" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="x94-oC-htT">
+                    <rect key="frame" x="189.66666666666666" y="1" width="98.999999999999972" height="28"/>
+                    <constraints>
+                        <constraint firstAttribute="width" constant="99" id="j2o-x7-uP2"/>
+                        <constraint firstAttribute="height" constant="28" id="kmh-eL-r6N"/>
+                    </constraints>
                     <fontDescription key="fontDescription" type="system" weight="medium" pointSize="15"/>
                     <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
                     <state key="normal" title="《隐私政策》">
@@ -57,13 +72,25 @@
             </subviews>
             <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
             <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+            <constraints>
+                <constraint firstItem="2mp-Il-g5P" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="2Go-yL-flY"/>
+                <constraint firstItem="x94-oC-htT" firstAttribute="leading" secondItem="m2D-Bh-JNY" secondAttribute="trailing" id="7cj-U0-40C"/>
+                <constraint firstItem="m2D-Bh-JNY" firstAttribute="leading" secondItem="Gd4-hR-tap" secondAttribute="trailing" id="Aeh-iG-Nq4"/>
+                <constraint firstItem="Gd4-hR-tap" firstAttribute="leading" secondItem="fK6-q7-2NY" secondAttribute="trailing" id="E0p-ts-EaC"/>
+                <constraint firstItem="Gd4-hR-tap" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="G4g-QQ-tGC"/>
+                <constraint firstItem="fK6-q7-2NY" firstAttribute="leading" secondItem="2mp-Il-g5P" secondAttribute="trailing" constant="6" id="Nh0-jB-rp6"/>
+                <constraint firstItem="x94-oC-htT" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="XxK-0t-mP8"/>
+                <constraint firstItem="2mp-Il-g5P" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="dya-Kd-bmQ"/>
+                <constraint firstItem="fK6-q7-2NY" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="rji-l9-Kcw"/>
+                <constraint firstItem="m2D-Bh-JNY" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="sx1-t5-FZJ"/>
+            </constraints>
             <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
             <connections>
                 <outlet property="agreeBtn" destination="2mp-Il-g5P" id="DXQ-jv-kYd"/>
                 <outlet property="user_privacy_btn" destination="x94-oC-htT" id="zgZ-ZH-Ehe"/>
                 <outlet property="user_protocol_btn" destination="Gd4-hR-tap" id="G24-CY-cEu"/>
             </connections>
-            <point key="canvasLocation" x="61" y="-11"/>
+            <point key="canvasLocation" x="60.305343511450381" y="-11.267605633802818"/>
         </view>
     </objects>
     <resources>

+ 8 - 0
JSJP_Student_sw/Sources/Services/LoginAPI.swift

@@ -0,0 +1,8 @@
+//
+//  LoginAPI.swift
+//  JSJP_Student_sw
+//
+//  Created by Ning.ge on 2023/6/3.
+//
+
+import Foundation

+ 41 - 0
JSJP_Student_sw/Sources/Services/LoginService.swift

@@ -0,0 +1,41 @@
+//
+//  LoginService.swift
+//  JSJP_Student_sw
+//
+//  Created by Ning.ge on 2023/6/2.
+//
+import RxSwift
+import RxCocoa
+
+enum ValidationResult {
+    case ok(message:String)//输入正确
+    case empty//输入为空
+    case validating
+    case failed(message:String)//输入不合法
+}
+
+enum SignupState {
+    case signedUp(signedUp: Bool)
+}
+
+protocol LoginAPI {
+    func usernameAvailable(_ username: String) -> Observable<Bool>
+    func signup(_ username: String, password: String) -> Observable<Bool>
+}
+
+protocol GitHubValidationService {
+    func validateUsername(_ username: String) -> Observable<ValidationResult>
+    func validatePassword(_ password: String) -> ValidationResult
+    func validateRepeatedPassword(_ password: String, repeatedPassword: String) -> ValidationResult
+}
+
+extension ValidationResult {
+    var isValid: Bool {
+        switch self {
+        case .ok:
+            return true
+        default:
+            return false
+        }
+    }
+}

+ 77 - 0
JSJP_Student_sw/Sources/Utils/LocalManager.swift

@@ -0,0 +1,77 @@
+//
+//  LocalManager.swift
+//  SwiftBilibili
+//
+//  Created by 罗文 on 2021/3/23.
+//  Copyright © 2021年 罗文. All rights reserved.
+//
+
+import UIKit
+import SwiftyUserDefaults
+
+class LocalManager {
+    
+    struct UserInfo {
+        var isLogin: Bool {
+            set {
+               Defaults.isLogin = newValue
+            }
+            get {
+               return Defaults.isLogin
+            }
+        }
+        
+        var openTimes: Int {
+            set {
+                Defaults.openTimes = newValue
+            }
+            get {
+                return Defaults.openTimes
+            }
+        }
+        
+        var avater: UIImage? {
+            set {
+                if newValue != nil {
+                    Defaults.avater = newValue!.pngData() ?? Data()
+                }
+            }
+            get {
+                return UIImage(data: Defaults.avater)
+            }
+        }
+        
+        
+        var userAccount:String? {
+            set {
+                if newValue != nil{
+                    Defaults.userAccount = newValue! ?? ""
+                }
+            }
+            get {
+                return Defaults.userAccount
+            }
+        }
+        
+        var userPassword:String? {
+            set {
+                if newValue != nil{
+                    Defaults.userPassword = newValue! ?? ""
+                }
+            }
+            get {
+                return Defaults.userPassword
+            }
+        }
+        
+    }
+    
+    static var userInfo: UserInfo = UserInfo()
+    
+    
+    class func clearUserInfo() {
+        
+        
+    }
+    
+}

+ 1 - 2
Podfile

@@ -39,6 +39,7 @@ target 'JSJP_Student_sw' do
   pod 'FSPagerView'
   pod 'Toaster'
   pod 'ManualLayout'
+  pod 'QMUIKit'                                     # 腾讯UI控件
   
   # Logging
   pod 'CocoaLumberjack/Swift'
@@ -62,8 +63,6 @@ target 'JSJP_Student_sw' do
   pod 'Dollar'
   pod 'SwiftyFitsize'   #适配-iphone-ipad等方案 支持 xib 和 storyboard
   
-  #OC 库
-  pod 'QMUIKit'                                     # 腾讯UI控件
   
 end