add
无故事王国
2024-06-03 3d8ce4866799bea7e66699acdeb86b60b0ba033c
add
34个文件已修改
9个文件已添加
2655 ■■■■■ 已修改文件
DolphinEnglishLearnStudent.xcodeproj/project.pbxproj 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Config/Config.swift 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Login/LoginVC.swift 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Login/LoginVC.xib 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Models/CommonModel.swift 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Home/CCell/AwardListCCell.swift 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Home/CCell/AwardListCCell.xib 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Home/HomeVC.swift 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Home/Listen/VC/HomeListenMenuVC.swift 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Home/View/AwardListView.swift 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Market/CCell/MarketCCell.swift 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Market/CCell/MarketTagCCell.swift 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Market/CCell/MarketTagCCell.xib 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Market/MarketVC.swift 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Market/MarketVC.xib 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Market/VC/MarketContentVC.swift 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Market/VC/MarketContentVC.xib 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Market/VC/MarketExchangeVC.swift 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Market/VC/MarketExchangeVC.xib 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Me/MeVC.swift 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Me/MeVC.xib 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Me/TCell/AddressManageTCell.swift 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Me/TCell/AddressManageTCell.xib 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Me/TCell/GoodsItemTCell.swift 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Me/TCell/GoodsItemTCell.xib 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Me/TCell/Home_1_TCell.swift 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Me/TCell/Home_1_TCell.xib 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Me/VC/AddressManageHandleVC.swift 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Me/VC/AddressManageHandleVC.xib 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Me/VC/AddressManageVC.swift 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Me/VC/CoinRecordHistoryVC.swift 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Me/VC/CoinRecordHistoryVC.xib 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Me/VC/ExchangeRecordHistoryVC.swift 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Moudle/Me/VC/StudyVC.swift 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Other/CommonWebVC.swift 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Other/UIView/CityAddressPickerView.swift 248 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Other/UIView/CommonBannerView.swift 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/SceneDelegate.swift 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Services/NetworkRequest.swift 278 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/Services/Services.swift 213 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/ViewModel/RefreshModel.swift 305 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent/ViewModel/UserViewModel.swift 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Podfile 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DolphinEnglishLearnStudent.xcodeproj/project.pbxproj
@@ -58,6 +58,13 @@
        1319B0292C0818540052F889 /* HomeListenStory_2_VC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1319B0282C0818540052F889 /* HomeListenStory_2_VC.swift */; };
        1319B02C2C081A320052F889 /* SimpleImageCCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1319B02A2C081A320052F889 /* SimpleImageCCell.swift */; };
        1319B02D2C081A320052F889 /* SimpleImageCCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1319B02B2C081A320052F889 /* SimpleImageCCell.xib */; };
        1319B0312C0859370052F889 /* Services.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1319B02F2C0859370052F889 /* Services.swift */; };
        1319B0322C0859370052F889 /* NetworkRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1319B0302C0859370052F889 /* NetworkRequest.swift */; };
        1319B0352C0859E20052F889 /* UserViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1319B0342C0859E20052F889 /* UserViewModel.swift */; };
        131C030A2C0D564000EA4C25 /* MarketTagCCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 131C03092C0D564000EA4C25 /* MarketTagCCell.xib */; };
        131C030B2C0D564000EA4C25 /* MarketTagCCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131C03082C0D564000EA4C25 /* MarketTagCCell.swift */; };
        131C030D2C0D6A4800EA4C25 /* CommonBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131C030C2C0D6A4800EA4C25 /* CommonBannerView.swift */; };
        131C03112C0DA5D500EA4C25 /* CityAddressPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 131C03102C0DA5D500EA4C25 /* CityAddressPickerView.swift */; };
        133386382C007E91002EE788 /* HomeListenFight_lesson_2_VC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133386372C007E91002EE788 /* HomeListenFight_lesson_2_VC.swift */; };
        13397D962C05EA9D003440F9 /* ListenFight_Game_CCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13397D942C05EA9D003440F9 /* ListenFight_Game_CCell.swift */; };
        13397D972C05EA9D003440F9 /* ListenFight_Game_CCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13397D952C05EA9D003440F9 /* ListenFight_Game_CCell.xib */; };
@@ -81,6 +88,8 @@
        13A830FA2C043A0600BB2F23 /* Lesson_3_AnswerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13A830F92C043A0600BB2F23 /* Lesson_3_AnswerView.xib */; };
        13AA25392C00759600F075B3 /* HomeStudyCompleteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AA25372C00759600F075B3 /* HomeStudyCompleteVC.swift */; };
        13AA253A2C00759600F075B3 /* HomeStudyCompleteVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13AA25382C00759600F075B3 /* HomeStudyCompleteVC.xib */; };
        13CD3AC72C0874B3007E1065 /* CommonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13CD3AC62C0874B3007E1065 /* CommonModel.swift */; };
        13CD3AC92C0886E5007E1065 /* RefreshModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13CD3AC82C0886E5007E1065 /* RefreshModel.swift */; };
        13CDF4492C0566E400E8D4FD /* HomeListenFight_lesson_4_VC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13CDF4482C0566E400E8D4FD /* HomeListenFight_lesson_4_VC.swift */; };
        13CDF44C2C056A6900E8D4FD /* ListenFight_lesson_4_CCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13CDF44B2C056A6900E8D4FD /* ListenFight_lesson_4_CCell.xib */; };
        13CDF44D2C056A6900E8D4FD /* ListenFight_lesson_4_CCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13CDF44A2C056A6900E8D4FD /* ListenFight_lesson_4_CCell.swift */; };
@@ -157,6 +166,13 @@
        1319B0282C0818540052F889 /* HomeListenStory_2_VC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeListenStory_2_VC.swift; sourceTree = "<group>"; };
        1319B02A2C081A320052F889 /* SimpleImageCCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleImageCCell.swift; sourceTree = "<group>"; };
        1319B02B2C081A320052F889 /* SimpleImageCCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SimpleImageCCell.xib; sourceTree = "<group>"; };
        1319B02F2C0859370052F889 /* Services.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Services.swift; sourceTree = "<group>"; };
        1319B0302C0859370052F889 /* NetworkRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkRequest.swift; sourceTree = "<group>"; };
        1319B0342C0859E20052F889 /* UserViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserViewModel.swift; sourceTree = "<group>"; };
        131C03082C0D564000EA4C25 /* MarketTagCCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketTagCCell.swift; sourceTree = "<group>"; };
        131C03092C0D564000EA4C25 /* MarketTagCCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MarketTagCCell.xib; sourceTree = "<group>"; };
        131C030C2C0D6A4800EA4C25 /* CommonBannerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommonBannerView.swift; sourceTree = "<group>"; };
        131C03102C0DA5D500EA4C25 /* CityAddressPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityAddressPickerView.swift; sourceTree = "<group>"; };
        133386372C007E91002EE788 /* HomeListenFight_lesson_2_VC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeListenFight_lesson_2_VC.swift; sourceTree = "<group>"; };
        13397D942C05EA9D003440F9 /* ListenFight_Game_CCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListenFight_Game_CCell.swift; sourceTree = "<group>"; };
        13397D952C05EA9D003440F9 /* ListenFight_Game_CCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ListenFight_Game_CCell.xib; sourceTree = "<group>"; };
@@ -180,6 +196,8 @@
        13A830F92C043A0600BB2F23 /* Lesson_3_AnswerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Lesson_3_AnswerView.xib; sourceTree = "<group>"; };
        13AA25372C00759600F075B3 /* HomeStudyCompleteVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeStudyCompleteVC.swift; sourceTree = "<group>"; };
        13AA25382C00759600F075B3 /* HomeStudyCompleteVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HomeStudyCompleteVC.xib; sourceTree = "<group>"; };
        13CD3AC62C0874B3007E1065 /* CommonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonModel.swift; sourceTree = "<group>"; };
        13CD3AC82C0886E5007E1065 /* RefreshModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshModel.swift; sourceTree = "<group>"; };
        13CDF4482C0566E400E8D4FD /* HomeListenFight_lesson_4_VC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeListenFight_lesson_4_VC.swift; sourceTree = "<group>"; };
        13CDF44A2C056A6900E8D4FD /* ListenFight_lesson_4_CCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListenFight_lesson_4_CCell.swift; sourceTree = "<group>"; };
        13CDF44B2C056A6900E8D4FD /* ListenFight_lesson_4_CCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ListenFight_lesson_4_CCell.xib; sourceTree = "<group>"; };
@@ -235,6 +253,9 @@
        130278282BFD957100DDCE81 /* DolphinEnglishLearnStudent */ = {
            isa = PBXGroup;
            children = (
                13CD3AC52C08749E007E1065 /* Models */,
                1319B0332C0859D70052F889 /* ViewModel */,
                1319B02E2C08592D0052F889 /* Services */,
                130278632BFD9E5D00DDCE81 /* Moudle */,
                130278582BFD985E00DDCE81 /* Other */,
                1302784E2BFD97EF00DDCE81 /* Login */,
@@ -275,6 +296,7 @@
        130278472BFD979200DDCE81 /* Base */ = {
            isa = PBXGroup;
            children = (
                1319B0362C085B040052F889 /* View */,
                130278432BFD979200DDCE81 /* BaseNav.swift */,
                130278442BFD979200DDCE81 /* BaseTabBarVC.swift */,
                130278452BFD979200DDCE81 /* BaseVC.swift */,
@@ -295,6 +317,7 @@
        130278562BFD985E00DDCE81 /* UIView */ = {
            isa = PBXGroup;
            children = (
                131C030C2C0D6A4800EA4C25 /* CommonBannerView.swift */,
                130278512BFD985E00DDCE81 /* BitrhdayPickerView.swift */,
                130278522BFD985E00DDCE81 /* CommonAlertView.swift */,
                130278532BFD985E00DDCE81 /* CommonAlertView.xib */,
@@ -302,6 +325,7 @@
                130278552BFD985E00DDCE81 /* CommonInputView.xib */,
                1362C75E2BFF4BA900BD7F73 /* StudyHandleView.swift */,
                1362C7602BFF4BB100BD7F73 /* StudyHandleView.xib */,
                131C03102C0DA5D500EA4C25 /* CityAddressPickerView.swift */,
            );
            path = UIView;
            sourceTree = "<group>";
@@ -352,6 +376,8 @@
            children = (
                1302786A2BFD9ED600DDCE81 /* MarketCCell.swift */,
                1302786B2BFD9ED600DDCE81 /* MarketCCell.xib */,
                131C03082C0D564000EA4C25 /* MarketTagCCell.swift */,
                131C03092C0D564000EA4C25 /* MarketTagCCell.xib */,
            );
            path = CCell;
            sourceTree = "<group>";
@@ -395,6 +421,31 @@
                130278902BFD9FBF00DDCE81 /* Home_1_TCell.xib */,
            );
            path = TCell;
            sourceTree = "<group>";
        };
        1319B02E2C08592D0052F889 /* Services */ = {
            isa = PBXGroup;
            children = (
                1319B0302C0859370052F889 /* NetworkRequest.swift */,
                1319B02F2C0859370052F889 /* Services.swift */,
            );
            path = Services;
            sourceTree = "<group>";
        };
        1319B0332C0859D70052F889 /* ViewModel */ = {
            isa = PBXGroup;
            children = (
                13CD3AC82C0886E5007E1065 /* RefreshModel.swift */,
                1319B0342C0859E20052F889 /* UserViewModel.swift */,
            );
            path = ViewModel;
            sourceTree = "<group>";
        };
        1319B0362C085B040052F889 /* View */ = {
            isa = PBXGroup;
            children = (
            );
            path = View;
            sourceTree = "<group>";
        };
        13649E992C002FDF001B04E2 /* CCell */ = {
@@ -456,6 +507,14 @@
                13AA25382C00759600F075B3 /* HomeStudyCompleteVC.xib */,
            );
            path = VC;
            sourceTree = "<group>";
        };
        13CD3AC52C08749E007E1065 /* Models */ = {
            isa = PBXGroup;
            children = (
                13CD3AC62C0874B3007E1065 /* CommonModel.swift */,
            );
            path = Models;
            sourceTree = "<group>";
        };
        13EEB88F2BFED3C6002996FC /* View */ = {
@@ -607,6 +666,7 @@
                13EEB8A92BFF354B002996FC /* HomeListen_item_TCell.xib in Resources */,
                13CDF4512C05757400E8D4FD /* Lesson_4_AnswerView.xib in Resources */,
                1319B02D2C081A320052F889 /* SimpleImageCCell.xib in Resources */,
                131C030A2C0D564000EA4C25 /* MarketTagCCell.xib in Resources */,
                130278982BFD9FBF00DDCE81 /* GoodsItemTCell.xib in Resources */,
                13CDF44C2C056A6900E8D4FD /* ListenFight_lesson_4_CCell.xib in Resources */,
                130278962BFD9FBF00DDCE81 /* Home_1_TCell.xib in Resources */,
@@ -669,12 +729,19 @@
                130278992BFD9FBF00DDCE81 /* Home_1_TCell.swift in Sources */,
                1302784B2BFD979200DDCE81 /* TapBtn.swift in Sources */,
                13CDF44F2C05756C00E8D4FD /* Lesson_4_AnswerView.swift in Sources */,
                1319B0312C0859370052F889 /* Services.swift in Sources */,
                13EEB8A82BFF354B002996FC /* HomeListen_item_TCell.swift in Sources */,
                13A830F12C04196400BB2F23 /* HomeListenFight_lesson_3_VC.swift in Sources */,
                13CDF44D2C056A6900E8D4FD /* ListenFight_lesson_4_CCell.swift in Sources */,
                131C030B2C0D564000EA4C25 /* MarketTagCCell.swift in Sources */,
                13CD3AC72C0874B3007E1065 /* CommonModel.swift in Sources */,
                131C030D2C0D6A4800EA4C25 /* CommonBannerView.swift in Sources */,
                1319B0322C0859370052F889 /* NetworkRequest.swift in Sources */,
                1362C75F2BFF4BA900BD7F73 /* StudyHandleView.swift in Sources */,
                130278672BFD9E8C00DDCE81 /* HomeVC.swift in Sources */,
                131C03112C0DA5D500EA4C25 /* CityAddressPickerView.swift in Sources */,
                130278482BFD979200DDCE81 /* BaseNav.swift in Sources */,
                1319B0352C0859E20052F889 /* UserViewModel.swift in Sources */,
                130278622BFD999200DDCE81 /* LoginVC.swift in Sources */,
                13EEB89C2BFF1C35002996FC /* HomeListenMenuVC.swift in Sources */,
                13CDF4492C0566E400E8D4FD /* HomeListenFight_lesson_4_VC.swift in Sources */,
@@ -703,6 +770,7 @@
                1302788D2BFD9F4200DDCE81 /* CoinRecordHistoryVC.swift in Sources */,
                13A049FF2C058B1400F1F52E /* HomeListenFight_lesson_5_VC.swift in Sources */,
                1302785A2BFD985E00DDCE81 /* CommonAlertView.swift in Sources */,
                13CD3AC92C0886E5007E1065 /* RefreshModel.swift in Sources */,
                1302782C2BFD957100DDCE81 /* SceneDelegate.swift in Sources */,
                1302787D2BFD9ED600DDCE81 /* MarketExchangeVC.swift in Sources */,
                1302789A2BFD9FBF00DDCE81 /* AddressManageTCell.swift in Sources */,
DolphinEnglishLearnStudent/Config/Config.swift
@@ -32,10 +32,35 @@
}
func LogSuccess(_ items:Any...,separator:String=" ",file:String=#file,function:String=#function,line:Int=#line){
#if DEBUG
                if #available(iOS 14.0, *) {
                                let logger = Logger(subsystem: "English", category: function)
                                logger.error("\(items)")
                }else{
                                let file = (file as NSString).lastPathComponent.split(separator: ".").first!;
                                print("✅✅✅ SUCCESS: \(file)  \(function) [Line: \(line)]: \(items)",separator);
                }
#endif
}
func LogError(_ items:Any...,separator:String=" ",file:String=#file,function:String=#function,line:Int=#line){
#if DEBUG
                if #available(iOS 14.0, *) {
                                let logger = Logger(subsystem: "English", category: function)
                                logger.error("\(items)")
                }else{
                                let file = (file as NSString).lastPathComponent.split(separator: ".").first!;
                                print("❌❌❌ ERROR: \(file)  \(function) [Line: \(line)]: \(items)",separator);
                }
#endif
}
func LogInfo(_ items:Any...,separator:String=" ",file:String=#file,function:String=#function,line:Int=#line){
#if DEBUG
                if #available(iOS 14.0, *) {
                                let logger = Logger(subsystem: "WanPai", category: function)
                                let logger = Logger(subsystem: "English", category: function)
                                logger.error("\(items)")
                }else{
                                let file = (file as NSString).lastPathComponent.split(separator: ".").first!;
@@ -44,6 +69,13 @@
#endif
}
func LogResponse(_ items:Any...,separator:String=" ",file:String=#file,function:String=#function,line:Int=#line){
#if DEBUG
                print("返回数据")
                print(items);
#endif
}
//提示框
func alert(msg: String) {
                SVProgressHUD.showInfo(withStatus: msg)
DolphinEnglishLearnStudent/Login/LoginVC.swift
@@ -41,7 +41,7 @@
                                                alert(msg: "请输入验证码");return false
                                }
                                guard tf_phone.text!.count != 6 else {
                                guard tf_authCode.text!.count == 6 else {
                                                alert(msg: "请输入6位验证码");return false
                                }
                                return true
@@ -54,14 +54,14 @@
                /// 隐私协议
                @IBAction func privacyAction(_ sender: UIButton) {
                                let vc = CommonWebVC(type: .privacyAgreement)
                                let vc = CommonWebVC(type: .PrivacyPolicy)
                                vc.title = "隐私协议"
                                self.navigationController?.pushViewController(vc, animated: true)
                }
                /// 用户协议
                @IBAction func privacyUserAction(_ sender: UIButton) {
                                let vc = CommonWebVC(type: .userAgreement)
                                let vc = CommonWebVC(type: .UserProtocol)
                                vc.title = "用户协议"
                                self.navigationController?.pushViewController(vc, animated: true)
                }
@@ -69,7 +69,9 @@
                @IBAction func getCodeAction(_ sender: UIButton) {
                                guard authInputPhone() else {return}
                                sender.openCountDown(60, defultTitle: "获取验证码", textColor:UIColor(hexStr: "#41A2EB"), unenableColor: .gray)
                                Services.sendPhoneCode(phone: tf_phone.text!).subscribe(onNext: {_ in
                                                sender.openCountDown(60, defultTitle: "获取验证码", textColor:UIColor(hexStr: "#41A2EB"), unenableColor: .gray)
                                }).disposed(by: disposeBag)
                }
                @IBAction func loginAction(_ sender: UIButton) {
@@ -81,6 +83,12 @@
                                                alert(msg: "请阅读并同意《隐私协议》《用户协议》");return
                                }
                                sceneDelegate?.loginSuccess()
                                Services.login(phone: tf_phone.text!, code: tf_authCode.text!).subscribe(onNext: {result in
                                                if var token = result.data?.token{
                                                                token.request_time = Int(Date().timeIntervalSince1970)
                                                                LoginTokenModel.saveToken(token)
                                                                sceneDelegate?.loginSuccess()
                                                }
                                }).disposed(by: disposeBag)
                }
}
DolphinEnglishLearnStudent/Login/LoginVC.xib
@@ -131,6 +131,7 @@
                    </constraints>
                    <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
                    <state key="normal" image="btn_choose_u"/>
                    <state key="selected" image="btn_choose"/>
                    <connections>
                        <action selector="chooseAction:" destination="-1" eventType="touchUpInside" id="taa-1D-MNJ"/>
                    </connections>
@@ -210,6 +211,7 @@
    </objects>
    <resources>
        <image name="bg_login" width="296" height="129"/>
        <image name="btn_choose" width="28" height="28"/>
        <image name="btn_choose_u" width="28" height="28"/>
        <image name="icon_input_code" width="15" height="18"/>
        <image name="icon_input_phone" width="16" height="18"/>
DolphinEnglishLearnStudent/Models/CommonModel.swift
New file
@@ -0,0 +1,237 @@
//
//  CommonModel.swift
//  DolphinEnglishLearnStudent
//
//  Created by 无故事王国 on 2024/5/30.
//
import Foundation
import HandyJSON
import UserDefaultsStore
struct LoginModel:HandyJSON{
                var token:LoginTokenModel?
}
struct LoginTokenModel:HandyJSON,Identifiable,Codable{
                static let idKey = \LoginTokenModel.id
                var id: Int = 0
                var access_token = ""
                var expires_in = 0
                var request_time = 0 //请求时间
                private static let userInfo = UserDefaultsStore<LoginTokenModel>(uniqueIdentifier: "LoginTokenModel")!
                static func saveToken(_ model:LoginTokenModel){
                                do{
                                                try LoginTokenModel.userInfo.save(model)
                                }catch{
                                }
                }
                static func isOverdue()->Bool{
                                if let token = LoginTokenModel.getToken(){
                                                //过期时间(秒)
                                                let overdueTimeval = token.expires_in * 60 + token.request_time
                                                if overdueTimeval < Int(Date().timeIntervalSince1970){
                                                                return true
                                                }
                                                return false
                                }
                                return true
                }
                static func getToken()->LoginTokenModel?{
                                return LoginTokenModel.userInfo.allObjects().first
                }
                static func clearToken(){
                                LoginTokenModel.userInfo.deleteAll()
                }
}
struct RecommendModel:HandyJSON{
                var basicCount: Int = 0
                var coverImg: String = ""
                var createBy: String = ""
                var createTime: String = ""
                var detail: String = ""
                var detailImg: String = ""
                var disabled: Bool = false
                var id: Int = 0
                var insertTime: String = ""
                var integral: Int = 0
                var inventory: Int = 0
                var isDelete: Int = 0
                var name: String = ""
                var price: Int = 0
                var surplus: Int = 0
                var total: Int = 0
                var type: Int = 0
                var typeIds: String = ""
                var updateBy: String = ""
                var updateTime: String = ""
                var userCount: Int = 0
}
struct MarketModel:HandyJSON{
                var basicCount: Int = 0
                var coverImg: String = ""
                var createBy: String = ""
                var createTime: String = ""
                var detail: String = ""
                var detailImg: String = ""
                var disabled: Bool = false
                var id: Int = 0
                var integral: Int = 0
                var inventory: Int = 0
                var isDelete: Int = 0
                var name: String = ""
                var price: Int = 0
                var surplus: Int = 0
                var total: Int = 0
                var type: Int = 0
                var typeIds: String = ""
                var updateBy: String = ""
                var updateTime: String = ""
                var userCount: Int = 0
}
struct MarketTypeModel:HandyJSON,Hashable{
                var id = 0
                var name = ""
}
struct MarketDetailModel:HandyJSON{
                var exchangeNumber: Int = 0
                var good: MarketModel?
                var goodTypes = [MarketTypeModel]()
                var orderNumber: String = ""
                var recipient: MarketRecipientModel?
}
struct MarketRecipientModel:HandyJSON,Hashable{
                var id = 0
                var name = ""
}
struct AddressModel:HandyJSON{
                var address: String = ""
                var city: String = ""
                var cityCode: String = ""
                var createBy: String = ""
                var createTime: String = ""
                var disabled: Bool = false
                var id: Int = 0
                var isDefault: Int = 0
                var province: String = ""
                var provinceCode: String = ""
                var recipient: String = ""
                var recipientPhone: String = ""
                var updateBy: String = ""
                var updateTime: String = ""
                var userId: Int = 0
}
struct AddressTreeModel:HandyJSON{
                var id = 0
                var name = ""
                var code = ""
                var parentId = 0
                var children:[AddressTreeModel]?
}
struct IntegralModel:HandyJSON{
                var createBy: String = ""
                var createTime: String = ""
                var disabled: Bool = false
                var gameId: Int = 0
                var id: Int = 0
                var integral: String = ""
                var method: String = ""
                var storyId: Int = 0
                var type: String = ""
                var updateBy: String = ""
                var updateTime: String = ""
                var userId: Int = 0
}
struct ExchangeRecordModel:HandyJSON{
                var completeTime: String = ""
                var consigneeAddress: String = ""
                var consigneeName: String = ""
                var consigneePhone: String = ""
                var count: Int = 0
                var createBy: String = ""
                var createTime: String = ""
                var disabled: Bool = false
                var express: String = ""
                var expressNumber: String = ""
                var expressTime: String = ""
                var goodsId: Int = 0
                var id: Int = 0
                var insertTime: String = ""
                var integral: Int = 0
                var orderNumber: String = ""
                var state: Int = 0
                var updateBy: String = ""
                var updateTime: String = ""
                var userId: Int = 0
}
struct StudyGamesModel:HandyJSON{
                var gameRecordList = [StudyGamesRecordModel]()
                var record:StudyDataRecordModel?
}
struct StudyGamesRecordModel:HandyJSON{
                var accuracy: Int = 0
                var createBy: String?
                var createTime: String?
                var disabled: Bool = false
                var gameDifficulty: Int = 0
                var gameId: Int = 0
                var gameName: String?
                var id: Int = 0
                var updateBy: String?
                var updateTime: String?
                var userId: Int = 0
                var useTime: Int = 0
}
struct StudyDataRecordModel:HandyJSON{
                var answer: Int = 0
                var createBy: String?
                var createTime: String?
                var day: Int = 0
                var disabled: Bool = false
                var id: Int = 0
                var induction: Int = 0
                var listen: Int = 0
                var look: Int = 0
                var monthStudy: Int = 0
                var pair: Int = 0
                var surplus: Int = 0
                var todayStudy: Int = 0
                var totalStudy: Int = 0
                var updateBy: String?
                var updateTime: String?
                var userId: Int = 0
                var week: Int = 0
                var weekStudy: Int = 0
}
struct ListenWeekModel:HandyJSON{
                var id = 0
                var day = 0
                var quarter = 0
                var title = ""
                var totalIntegral = 0
                var type = 0
                var week = 0
}
DolphinEnglishLearnStudent/Moudle/Home/CCell/AwardListCCell.swift
@@ -11,11 +11,16 @@
class AwardListCCell: UICollectionViewCell {
                @IBOutlet weak var image_awar: UIImageView!
                @IBOutlet weak var label_title: UILabel!
                override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
                func setModel(_ model:RecommendModel){
                                image_awar.sd_setImage(with: URL(string: model.coverImg))
                                image_awar.jq_cornerRadius = 8
                                label_title.text = model.name
                }
                override func layoutSubviews() {
                                super.layoutSubviews()
DolphinEnglishLearnStudent/Moudle/Home/CCell/AwardListCCell.xib
@@ -10,21 +10,30 @@
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
        <collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="_AwardListCCell" id="gTV-IL-0wX" customClass="AwardListCCell" customModule="DolphinEnglishLearnStudent" customModuleProvider="target">
            <rect key="frame" x="0.0" y="0.0" width="276" height="526"/>
            <rect key="frame" x="0.0" y="0.0" width="243" height="526"/>
            <autoresizingMask key="autoresizingMask"/>
            <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
                <rect key="frame" x="0.0" y="0.0" width="276" height="526"/>
                <rect key="frame" x="0.0" y="0.0" width="243" height="526"/>
                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                <subviews>
                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="YbH-L3-L1g">
                        <rect key="frame" x="0.0" y="0.0" width="276" height="359"/>
                        <rect key="frame" x="0.0" y="0.0" width="243" height="316"/>
                        <color key="backgroundColor" red="0.61960784310000006" green="0.54117647059999996" blue="0.85882352939999995" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstAttribute="width" secondItem="YbH-L3-L1g" secondAttribute="height" multiplier="1:1.3" id="K9A-Ng-kLe"/>
                        </constraints>
                        <userDefinedRuntimeAttributes>
                            <userDefinedRuntimeAttribute type="boolean" keyPath="ld_maskToBoundsXIB" value="YES"/>
                            <userDefinedRuntimeAttribute type="number" keyPath="ld_cornerRadiusXIB">
                                <real key="value" value="8"/>
                            </userDefinedRuntimeAttribute>
                        </userDefinedRuntimeAttributes>
                    </imageView>
                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wPP-pc-amm">
                        <rect key="frame" x="0.0" y="368" width="276" height="19.5"/>
                        <rect key="frame" x="0.0" y="325" width="243" height="44"/>
                        <constraints>
                            <constraint firstAttribute="height" constant="44" id="7Eh-PB-yFf"/>
                        </constraints>
                        <fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
                        <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
                        <nil key="highlightedColor"/>
@@ -39,11 +48,12 @@
                <constraint firstAttribute="trailing" secondItem="wPP-pc-amm" secondAttribute="trailing" id="UHp-wV-FRO"/>
                <constraint firstItem="YbH-L3-L1g" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="WD8-o4-ZS3"/>
            </constraints>
            <size key="customSize" width="276" height="526"/>
            <size key="customSize" width="243" height="526"/>
            <connections>
                <outlet property="image_awar" destination="YbH-L3-L1g" id="kuW-TO-4BH"/>
                <outlet property="label_title" destination="wPP-pc-amm" id="vw9-1s-r0k"/>
            </connections>
            <point key="canvasLocation" x="130.2439024390244" y="140.84745762711864"/>
            <point key="canvasLocation" x="116.70731707317073" y="140.84745762711864"/>
        </collectionViewCell>
    </objects>
</document>
DolphinEnglishLearnStudent/Moudle/Home/HomeVC.swift
@@ -11,10 +11,12 @@
    override func viewDidLoad() {
        super.viewDidLoad()
                                AwardListView.show {
                                }
                                Services.goodRecommend().subscribe(onNext: { data in
                                                AwardListView.show(items: data.data ?? []) {[weak self] model in
                                                                let vc = MarketContentVC(goodsId: model.id)
                                                                self?.push(vc: vc)
                                                }
                                }).disposed(by: disposeBag)
    }
                @IBAction func listenAction(_ sender: UIButton) {
DolphinEnglishLearnStudent/Moudle/Home/Listen/VC/HomeListenMenuVC.swift
@@ -9,15 +9,27 @@
class HomeListenMenuVC: BaseVC {
                @IBOutlet weak var tableView: UITableView!
                @IBOutlet weak var collectionView: UICollectionView!
                private var repeatColors = ["#F8A169","#92CADB","#9E8ADB","#6DD1BA","#37C06E","#DEB975","#C54A59","#5DA0D3","#F0C433","#DC4827"]
                private var titleItems = ["第一季","第二季","第三季","第四季"]
                private var selectIndexPath:IndexPath = IndexPath(row: 0, section: 0)
                private var dataItems = Array<[ListenWeekModel]>(repeating: [], count: 4)
                override func viewDidLoad() {
                                super.viewDidLoad()
                                getData()
                }
                private func getData(){
                                Services.weekList(quarter: selectIndexPath.row).subscribe(onNext: {result in
                                                self.dataItems[self.selectIndexPath.row] = result.data ?? []
                                                self.collectionView.reloadData()
                                }).disposed(by: disposeBag)
                }
                override func setUI() {
@@ -53,7 +65,7 @@
                }
                func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
                                return 20
                                return dataItems[selectIndexPath.row].count
                }
                func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
@@ -74,6 +86,10 @@
extension HomeListenMenuVC:UITableViewDataSource,UITableViewDelegate{
                func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
                                selectIndexPath = indexPath
                                if dataItems[indexPath.row].count == 0{
                                                getData()
                                }
                                tableView.reloadData()
                }
DolphinEnglishLearnStudent/Moudle/Home/View/AwardListView.swift
@@ -11,6 +11,8 @@
                @IBOutlet weak var collectionView: UICollectionView!
                @IBOutlet weak var view_container: UIView!
                private var items = [RecommendModel]()
                private var clickClouse:((RecommendModel)->Void)!
                override func awakeFromNib() {
                                super.awakeFromNib()
                                self.alpha = 0
@@ -22,8 +24,13 @@
                                layoutIfNeeded()
                }
                static func show(clouse:@escaping ()->Void){
                static func show(items:[RecommendModel],clouse:@escaping (RecommendModel)->Void){
                                if items.count == 0{return}
                                let awardListView = AwardListView.jq_loadNibView()
                                awardListView.items = items
                                awardListView.clickClouse = clouse
                                sceneDelegate?.window?.addSubview(awardListView)
                                awardListView.frame = sceneDelegate?.window?.frame ?? .zero
@@ -31,6 +38,8 @@
                                                awardListView.alpha = 1
                                                awardListView.view_container.transform = .init(translationX: 1.0, y: 1.0)
                                                awardListView.layoutIfNeeded()
                                }completion: { _ in
                                                awardListView.collectionView.reloadData()
                                }
                }
@@ -45,23 +54,36 @@
}
extension AwardListView:UICollectionViewDelegate{
                func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
                                UIView.animate(withDuration: 0.4) {
                                                self.alpha = 0
                                                self.view_container.transform = .init(scaleX: 0.1, y: 0.1)
                                } completion: { _ in
                                                self.removeFromSuperview()
                                                let item = self.items[indexPath.row]
                                                self.clickClouse(item)
                                }
                }
}
extension AwardListView:UICollectionViewDataSource{
                func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
                                let item = items[indexPath.row]
                                let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "_AwardListCCell", for: indexPath) as! AwardListCCell
                                cell.setModel(item)
                                return cell
                }
                func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
                                return 6
                                return items.count
                }
}
extension AwardListView:UICollectionViewDelegateFlowLayout{
                func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
                                let w = (self.jq_width - 74 - 190) / 3.0
                                let w = (JQ_ScreenW - 37 * 2 - 144 * 2 - 44 * 2) / 3.0
                                return CGSize(width: w, height: w * 1.552)
                }
DolphinEnglishLearnStudent/Moudle/Market/CCell/MarketCCell.swift
@@ -18,6 +18,16 @@
        
    }
                var marketModel:MarketModel?{
                                didSet{
                                                if let m = marketModel{
                                                                cover_imageView.sd_setImage(with: URL(string: m.coverImg))
                                                                label_title.text = m.name
                                                                label_coin.text = "\(m.integral)积分"
                                                }
                                }
                }
                override func layoutSubviews() {
                                super.layoutSubviews()
                                jq_addShadows(shadowColor: UIColor(hexStr: "#D9D9D9"), corner: 8, radius: 20, offset: CGSize(width: 0, height: 20), opacity: 1)
DolphinEnglishLearnStudent/Moudle/Market/CCell/MarketTagCCell.swift
New file
@@ -0,0 +1,20 @@
//
//  MarketTagCCell.swift
//  DolphinEnglishLearnStudent
//
//  Created by 无故事王国 on 2024/6/3.
//
import UIKit
class MarketTagCCell: UICollectionViewCell {
                @IBOutlet weak var view_container: UIView!
                @IBOutlet weak var label_name: UILabel!
                override func awakeFromNib() {
        super.awakeFromNib()
                                view_container.jq_borderColor = UIColor(hexString: "#41A2EB")
    }
}
DolphinEnglishLearnStudent/Moudle/Market/CCell/MarketTagCCell.xib
New file
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
    <device id="ipad10_9rounded" orientation="portrait" layout="fullscreen" appearance="light"/>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
        <capability name="System colors in document resources" minToolsVersion="11.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <objects>
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
        <collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="_MarketTagCCell" id="gTV-IL-0wX" customClass="MarketTagCCell" customModule="DolphinEnglishLearnStudent" customModuleProvider="target">
            <rect key="frame" x="0.0" y="0.0" width="106" height="66"/>
            <autoresizingMask key="autoresizingMask"/>
            <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
                <rect key="frame" x="0.0" y="0.0" width="106" height="66"/>
                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                <subviews>
                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ynz-c6-wvK">
                        <rect key="frame" x="0.0" y="0.0" width="106" height="66"/>
                        <subviews>
                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fam-fP-fIA">
                                <rect key="frame" x="17" y="10" width="72" height="47"/>
                                <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="16"/>
                                <color key="textColor" red="0.25490196079999999" green="0.63529411759999999" blue="0.92156862750000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                <nil key="highlightedColor"/>
                            </label>
                        </subviews>
                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                        <constraints>
                            <constraint firstAttribute="trailing" secondItem="fam-fP-fIA" secondAttribute="trailing" constant="17" id="Zzp-0t-bAM"/>
                            <constraint firstAttribute="bottom" secondItem="fam-fP-fIA" secondAttribute="bottom" constant="9" id="aCk-9L-SKS"/>
                            <constraint firstItem="fam-fP-fIA" firstAttribute="top" secondItem="ynz-c6-wvK" secondAttribute="top" constant="10" id="k72-Fv-BOv"/>
                            <constraint firstItem="fam-fP-fIA" firstAttribute="leading" secondItem="ynz-c6-wvK" secondAttribute="leading" constant="17" id="wC7-gL-7u1"/>
                        </constraints>
                        <userDefinedRuntimeAttributes>
                            <userDefinedRuntimeAttribute type="boolean" keyPath="ld_maskToBoundsXIB" value="YES"/>
                            <userDefinedRuntimeAttribute type="number" keyPath="ld_cornerRadiusXIB">
                                <real key="value" value="20.5"/>
                            </userDefinedRuntimeAttribute>
                        </userDefinedRuntimeAttributes>
                    </view>
                </subviews>
            </view>
            <constraints>
                <constraint firstAttribute="trailing" secondItem="ynz-c6-wvK" secondAttribute="trailing" id="QjW-oE-Tgj"/>
                <constraint firstAttribute="bottom" secondItem="ynz-c6-wvK" secondAttribute="bottom" id="e07-4t-A2U"/>
                <constraint firstItem="ynz-c6-wvK" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" id="ew2-Uv-ect"/>
                <constraint firstItem="ynz-c6-wvK" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="ohm-Eq-BKS"/>
            </constraints>
            <size key="customSize" width="106" height="66"/>
            <connections>
                <outlet property="label_name" destination="fam-fP-fIA" id="uca-7s-UDX"/>
                <outlet property="view_container" destination="ynz-c6-wvK" id="05W-u9-fnI"/>
            </connections>
            <point key="canvasLocation" x="68.048780487804876" y="23.898305084745765"/>
        </collectionViewCell>
    </objects>
    <resources>
        <systemColor name="systemBackgroundColor">
            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
        </systemColor>
    </resources>
</document>
DolphinEnglishLearnStudent/Moudle/Market/MarketVC.swift
@@ -7,17 +7,51 @@
import UIKit
import QMUIKit
import RxRelay
import RxSwift
class MarketListViewModel:RefreshInnerModel<MarketModel>{
                let keywords = BehaviorRelay<String>(value:"")
                let types = BehaviorRelay<[String]>(value:[])
                let menuTypes = BehaviorRelay<[MarketTypeModel]>(value:[])
                var selectMenuTypes = Set<MarketTypeModel>(){
                                didSet{
                                                types.accept(selectMenuTypes.map({$0.name}))
                                                beginRefresh()
                                }
                }
                override func api() -> (Observable<BaseResponse<BaseResponseList<MarketModel>>>)? {
                                Services.goodsList(keywords: keywords.value, page: page, type: types.value)
                }
}
class MarketVC: BaseVC {
                @IBOutlet weak var tf_search: QMUITextField!
                @IBOutlet weak var menu_collectView: UICollectionView!
                @IBOutlet weak var content_collectionView: UICollectionView!
                @IBOutlet weak var label_surplusCoin: UILabel!
                private let viewModel = MarketListViewModel()
                private var cellW = (JQ_ScreenW - 130 - 15) / 4.0
    override func viewDidLoad() {
        super.viewDidLoad()
                                viewModel.configure(content_collectionView,needMore: true)
                                viewModel.beginRefresh()
                                Services.getIntegral().subscribe(onNext: {[weak self] result in
                                                self?.label_surplusCoin.text = "\(result.data ?? 0)"
                                }).disposed(by: disposeBag)
                                Services.goodTypeStudy().subscribe(onNext: {[weak self] result in
                                                self?.viewModel.menuTypes.accept(result.data ?? [])
                                                self?.menu_collectView.reloadData()
                                }).disposed(by: disposeBag)
    }
                override func setUI() {
@@ -28,39 +62,93 @@
                                content_collectionView.backgroundColor = .clear
                                content_collectionView.contentInset = UIEdgeInsets(top: 0, left: 65, bottom: 0, right:65)
                                content_collectionView.register(UINib(nibName: "MarketCCell", bundle: nil), forCellWithReuseIdentifier: "_MarketCCell")
                                tf_search.returnKeyType = .search
                                tf_search.delegate = self
                                menu_collectView.backgroundColor = .clear
                                menu_collectView.dataSource = self
                                menu_collectView.delegate = self
                                menu_collectView.register(UINib(nibName: "MarketTagCCell", bundle: nil), forCellWithReuseIdentifier: "_MarketTagCCell")
                }
                @IBAction func searchAction(_ sender: UIButton) {
                                view.endEditing(true)
                                viewModel.keywords.accept(tf_search.text!)
                                viewModel.beginRefresh()
                }
}
extension MarketVC:UICollectionViewDelegate{
                func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
                                let vc = MarketContentVC()
                                vc.title = "商品详情"
                                push(vc: vc)
                                if collectionView == menu_collectView{
                                                let model = viewModel.menuTypes.value[indexPath.row]
                                                if viewModel.selectMenuTypes.contains(model){
                                                                viewModel.selectMenuTypes.remove(model)
                                                }else{
                                                                viewModel.selectMenuTypes.insert(model)
                                                }
                                                collectionView.reloadData()
                                }else{
                                                let v = viewModel.dataSource.value?.records[indexPath.row]
                                                let vc = MarketContentVC(goodsId: v?.id ?? 0)
                                                vc.title = "商品详情"
                                                push(vc: vc)
                                }
                }
}
extension MarketVC:UICollectionViewDataSource{
                func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
                                return 30
                                if collectionView == menu_collectView{
                                                return viewModel.menuTypes.value.count
                                }
                                return viewModel.dataSource.value?.records.count ?? 0
                }
                
                func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
                                if collectionView == menu_collectView{
                                                let cell    =  collectionView.dequeueReusableCell(withReuseIdentifier: "_MarketTagCCell", for: indexPath) as! MarketTagCCell
                                                let model = viewModel.menuTypes.value[indexPath.row]
                                                cell.label_name.text = model.name
                                                if viewModel.selectMenuTypes.contains(model){
                                                                cell.view_container.backgroundColor = UIColor(hexString: "#41A2EB")
                                                                cell.view_container.jq_borderWidth = 0
                                                                cell.label_name.textColor = .white
                                                }else{
                                                                cell.view_container.backgroundColor = UIColor.white
                                                                cell.view_container.jq_borderWidth = 1
                                                                cell.label_name.textColor = UIColor(hexString: "#41A2EB")
                                                }
                                                return cell
                                }
                            let cell    =  collectionView.dequeueReusableCell(withReuseIdentifier: "_MarketCCell", for: indexPath) as! MarketCCell
                                cell.backgroundColor = .gray.withAlphaComponent(0.5)
                                let model = viewModel.dataSource.value?.records[indexPath.row]
                                cell.marketModel = model
                                cell.backgroundColor = .white
                                return cell
                }
}
extension MarketVC:UICollectionViewDelegateFlowLayout{
                func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
                                if collectionView == menu_collectView{
                                                return CGSize(width: 100, height: 41)
                                }
                                return CGSize(width: cellW, height: cellW * 1.09)
                }
                func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
                                if collectionView == menu_collectView{
                                                return 29
                                }
                                return 24
                }
@@ -68,3 +156,12 @@
                                return 5
                }
}
extension MarketVC:QMUITextFieldDelegate{
                func textFieldShouldReturn(_ textField: UITextField) -> Bool {
                                view.endEditing(true)
                                viewModel.keywords.accept(textField.text!)
                                viewModel.beginRefresh()
                                return true
                }
}
DolphinEnglishLearnStudent/Moudle/Market/MarketVC.xib
@@ -12,6 +12,7 @@
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="MarketVC" customModule="DolphinEnglishLearnStudent" customModuleProvider="target">
            <connections>
                <outlet property="content_collectionView" destination="EPp-Vf-S4a" id="nYe-TY-YF6"/>
                <outlet property="label_surplusCoin" destination="82B-Dg-Sap" id="oRO-iM-jgF"/>
                <outlet property="menu_collectView" destination="OCn-Jg-gWg" id="v5E-bg-k90"/>
                <outlet property="tf_search" destination="Jv9-DX-cX8" id="Y6V-ux-8OL"/>
                <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
DolphinEnglishLearnStudent/Moudle/Market/VC/MarketContentVC.swift
@@ -7,11 +7,24 @@
import UIKit
import WebKit
import RxRelay
class MarketContentViewModel{
                //购买数量
                var number = BehaviorRelay<Int>(value:1)
                var detailModel = BehaviorRelay<MarketDetailModel?>(value:nil)
                var surplusCoin = BehaviorRelay<Int>(value:0)
                var goodsId = BehaviorRelay<Int>(value:0)
//                var orderNumber = BehaviorRelay<String>(value:"")
                //收获地址
                var address = BehaviorRelay<[AddressModel]>(value: [])
}
class MarketContentVC: BaseVC {
                @IBOutlet weak var scrollView: UIScrollView!
                @IBOutlet weak var image_cover: UIImageView!
                @IBOutlet weak var view_banner: CommonBannerView!
                @IBOutlet weak var view_footer: UIView!
                @IBOutlet weak var view_container: UIView!
                @IBOutlet weak var label_coin: UILabel!
@@ -22,10 +35,52 @@
                @IBOutlet weak var label_surplusCoin: UILabel!
                @IBOutlet weak var label_costCoin: UILabel!
                @IBOutlet weak var cons_footHei: NSLayoutConstraint!
                @IBOutlet weak var webViewHeiCons: NSLayoutConstraint!
                private var viewModel = MarketContentViewModel()
            required    init(goodsId:Int) {
                                super.init(nibName: nil, bundle: nil)
                            self.viewModel.goodsId.accept(goodsId)
                }
                required init?(coder: NSCoder) {
                                fatalError("init(coder:) has not been implemented")
                }
                
                override func viewDidLoad() {
        super.viewDidLoad()
                                Services.goodsDetail(goodsId: viewModel.goodsId.value).subscribe(onNext: {[weak self] result in
                                                if let m = result.data{
                                                                let banner = m.good?.detailImg.components(separatedBy: ",").map({ url in
                                                                                return CommonBannerModel(index: 0, id: 0, name: nil, resource: url, mediaType: .imageUrl)
                                                                })
                                                                self?.view_banner.setItems(items: banner ?? [], autoRoll: true, selectClouse: nil)
                                                                self?.label_coin.text = "\(m.good?.integral ?? 0)"
                                                                self?.label_costCoin.text = "\(m.good?.integral ?? 0)"
                                                                self?.label_title.text = m.good?.name ?? ""
                                                                self?.label_categry.text = m.goodTypes.map({$0.name}).joined(separator: "|")
                                                                var info_Array = Array<String>()
                                                                info_Array.append("剩余数量:\(m.good?.surplus ?? 0)")
                                                                info_Array.append("可换数量:\(m.good?.userCount ?? 0)")
                                                                info_Array.append("\(m.exchangeNumber)人兑换")
                                                                self?.label_info.text = info_Array.joined(separator: "|")
                                                                self?.webView.loadHTMLString(m.good?.detail.jq_wrapHtml() ?? "", baseURL: nil)
                                                                self?.viewModel.detailModel.accept(m)
                                                }
                                }).disposed(by: disposeBag)
                                Services.getIntegral().subscribe(onNext: {[weak self] result in
                                                self?.label_surplusCoin.text = "剩余积分:\(result.data ?? 0)"
                                                self?.viewModel.surplusCoin.accept(result.data ?? 0)
                                }).disposed(by: disposeBag)
    }
                override func setUI() {
@@ -34,20 +89,51 @@
                                cons_footHei.constant = 54 + UIDevice.jq_safeEdges.bottom
                }
                override func setRx() {
                                webView.scrollView.rx.observe(CGSize.self, "contentSize").map { (size) -> CGFloat? in
                                                if let size = size{
                                                                return size.height
                                                }
                                                return nil
                                }.subscribe(onNext: { [unowned self](height) in
                                                if let height = height{
                                                                self.webViewHeiCons.constant = height
                                                }
                                }).disposed(by: disposeBag)
                }
                @IBAction func exchangeAction(_ sender: UIButton) {
//                                CommonAlertView.show(isSinple: true, content: "兑换失败,当前剩余积分不足!") {
//
//                                }
                                let vc = MarketExchangeVC()
                                guard viewModel.detailModel.value?.good?.surplus != 0 else {
                                                CommonAlertView.show(isSinple: true, content: "兑换失败,当前剩余数量不足!") {
                                                }
                                                return
                                }
                                guard viewModel.detailModel.value?.good?.userCount != 0 else {
                                                CommonAlertView.show(isSinple: true, content: "兑换失败,当前可换数量不足!") {
                                                }
                                                return
                                }
                                guard (viewModel.detailModel.value?.good?.integral ?? 0) < viewModel.surplusCoin.value else {
                                                CommonAlertView.show(isSinple: true, content: "兑换失败,当前剩余积分不足!") {
                                                }
                                                return
                                }
                                let vc = MarketExchangeVC(viewModel: viewModel)
                                vc.title = "立即兑换"
                                push(vc: vc)
                }
                override func viewDidLayoutSubviews() {
                                super.viewDidLayoutSubviews()
                                image_cover.jq_addCorners(corner: [.topLeft,.topRight], radius: 8)
                                view_banner.jq_addCorners(corner: [.topLeft,.topRight], radius: 8)
                                view_footer.jq_addShadows(shadowColor: UIColor(hexStr: "#DEDEDE").withAlphaComponent(0.5), corner: 0, radius: 4, offset: CGSize(width: 0, height: -1), opacity: 1)
                                view_container.jq_addShadows(shadowColor: UIColor(hexStr: "#D9D9D9").withAlphaComponent(0.28), corner: 8, radius: 5, offset: CGSize(width: 0, height: 2), opacity: 1)
DolphinEnglishLearnStudent/Moudle/Market/VC/MarketContentVC.xib
@@ -12,7 +12,6 @@
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="MarketContentVC" customModule="DolphinEnglishLearnStudent" customModuleProvider="target">
            <connections>
                <outlet property="cons_footHei" destination="O1c-0y-Iw9" id="Oj2-S6-uVD"/>
                <outlet property="image_cover" destination="HQ8-qZ-zeA" id="0xr-UI-5I9"/>
                <outlet property="label_categry" destination="YLo-dD-PTM" id="kCG-k8-Baa"/>
                <outlet property="label_coin" destination="yrU-ab-vv9" id="4UT-Q9-CO3"/>
                <outlet property="label_costCoin" destination="Br9-rb-zWu" id="Qyp-x1-Ff0"/>
@@ -21,9 +20,11 @@
                <outlet property="label_title" destination="PgE-zX-EIu" id="FWi-qa-sK0"/>
                <outlet property="scrollView" destination="loc-rm-BZe" id="xqU-OW-GgJ"/>
                <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
                <outlet property="view_banner" destination="lUx-IV-YIU" id="9RA-3Q-ceF"/>
                <outlet property="view_container" destination="3V0-GL-Fmn" id="D6J-fr-4H2"/>
                <outlet property="view_footer" destination="mp9-Bg-Kez" id="41u-8O-co7"/>
                <outlet property="webView" destination="ZOY-ws-sjP" id="bXL-3r-nKs"/>
                <outlet property="webViewHeiCons" destination="xMb-sR-Ext" id="8RD-Be-b0n"/>
            </connections>
        </placeholder>
        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
@@ -93,13 +94,6 @@
                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3V0-GL-Fmn">
                            <rect key="frame" x="0.0" y="0.0" width="556" height="681"/>
                            <subviews>
                                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="HQ8-qZ-zeA">
                                    <rect key="frame" x="0.0" y="0.0" width="556" height="319.5"/>
                                    <color key="backgroundColor" systemColor="systemGray5Color"/>
                                    <constraints>
                                        <constraint firstAttribute="width" secondItem="HQ8-qZ-zeA" secondAttribute="height" multiplier="1:0.575" id="4sO-41-6AB"/>
                                    </constraints>
                                </imageView>
                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yrU-ab-vv9">
                                    <rect key="frame" x="14" y="329.5" width="10" height="25"/>
                                    <constraints>
@@ -171,30 +165,37 @@
                                        <wkPreferences key="preferences"/>
                                    </wkWebViewConfiguration>
                                </wkWebView>
                                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lUx-IV-YIU" customClass="CommonBannerView" customModule="DolphinEnglishLearnStudent" customModuleProvider="target">
                                    <rect key="frame" x="0.0" y="0.0" width="556" height="319.5"/>
                                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                                    <constraints>
                                        <constraint firstAttribute="width" secondItem="lUx-IV-YIU" secondAttribute="height" multiplier="1:0.575" id="Vfi-RM-kqs"/>
                                    </constraints>
                                </view>
                            </subviews>
                            <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                            <constraints>
                                <constraint firstItem="yrU-ab-vv9" firstAttribute="top" secondItem="lUx-IV-YIU" secondAttribute="bottom" constant="10" id="0Z9-Wg-moE"/>
                                <constraint firstItem="fkp-fX-eUo" firstAttribute="top" secondItem="3WZ-Jf-leh" secondAttribute="bottom" constant="15" id="1lu-mH-O3t"/>
                                <constraint firstItem="HQ8-qZ-zeA" firstAttribute="leading" secondItem="3V0-GL-Fmn" secondAttribute="leading" id="2L8-TY-eC1"/>
                                <constraint firstAttribute="trailing" secondItem="SHM-HW-3QJ" secondAttribute="trailing" constant="14" id="3fT-Df-Icu"/>
                                <constraint firstItem="ZOY-ws-sjP" firstAttribute="top" secondItem="fkp-fX-eUo" secondAttribute="bottom" constant="10" id="4Sb-GV-SMH"/>
                                <constraint firstItem="fkp-fX-eUo" firstAttribute="leading" secondItem="3V0-GL-Fmn" secondAttribute="leading" constant="14" id="CGr-W8-bYc"/>
                                <constraint firstItem="lUx-IV-YIU" firstAttribute="top" secondItem="3V0-GL-Fmn" secondAttribute="top" id="CJV-El-ct7"/>
                                <constraint firstItem="LtS-Mg-aeT" firstAttribute="centerY" secondItem="yrU-ab-vv9" secondAttribute="centerY" id="Eqy-fC-VXN"/>
                                <constraint firstItem="SHM-HW-3QJ" firstAttribute="leading" secondItem="3V0-GL-Fmn" secondAttribute="leading" constant="14" id="IcX-sV-UsY"/>
                                <constraint firstAttribute="trailing" secondItem="PgE-zX-EIu" secondAttribute="trailing" constant="14" id="KOH-43-2e0"/>
                                <constraint firstItem="lUx-IV-YIU" firstAttribute="leading" secondItem="3V0-GL-Fmn" secondAttribute="leading" id="OoB-ef-zF5"/>
                                <constraint firstItem="3WZ-Jf-leh" firstAttribute="top" secondItem="SHM-HW-3QJ" secondAttribute="bottom" constant="15" id="TZb-uR-n12"/>
                                <constraint firstItem="yrU-ab-vv9" firstAttribute="leading" secondItem="3V0-GL-Fmn" secondAttribute="leading" constant="14" id="bvf-qv-PXf"/>
                                <constraint firstItem="LtS-Mg-aeT" firstAttribute="leading" secondItem="yrU-ab-vv9" secondAttribute="trailing" id="ce9-HT-eYR"/>
                                <constraint firstItem="yrU-ab-vv9" firstAttribute="top" secondItem="HQ8-qZ-zeA" secondAttribute="bottom" constant="10" id="fcu-Lq-OG7"/>
                                <constraint firstAttribute="trailing" secondItem="3WZ-Jf-leh" secondAttribute="trailing" constant="13" id="gDs-iU-l4D"/>
                                <constraint firstItem="HQ8-qZ-zeA" firstAttribute="top" secondItem="3V0-GL-Fmn" secondAttribute="top" id="gge-7H-yMR"/>
                                <constraint firstAttribute="trailing" secondItem="lUx-IV-YIU" secondAttribute="trailing" id="i5N-By-Pa9"/>
                                <constraint firstItem="ZOY-ws-sjP" firstAttribute="leading" secondItem="3V0-GL-Fmn" secondAttribute="leading" constant="14" id="k11-lO-F1N"/>
                                <constraint firstItem="SHM-HW-3QJ" firstAttribute="top" secondItem="PgE-zX-EIu" secondAttribute="bottom" constant="7" id="kjs-SE-QJz"/>
                                <constraint firstItem="3WZ-Jf-leh" firstAttribute="leading" secondItem="3V0-GL-Fmn" secondAttribute="leading" constant="13" id="msq-Yj-Hkl"/>
                                <constraint firstItem="PgE-zX-EIu" firstAttribute="top" secondItem="yrU-ab-vv9" secondAttribute="bottom" constant="7" id="tro-c3-NxF"/>
                                <constraint firstItem="PgE-zX-EIu" firstAttribute="leading" secondItem="3V0-GL-Fmn" secondAttribute="leading" constant="14" id="upN-6F-Xy0"/>
                                <constraint firstAttribute="bottom" secondItem="ZOY-ws-sjP" secondAttribute="bottom" constant="10" id="x2O-1I-fif"/>
                                <constraint firstAttribute="trailing" secondItem="HQ8-qZ-zeA" secondAttribute="trailing" id="xc1-2D-IN1"/>
                                <constraint firstAttribute="trailing" secondItem="ZOY-ws-sjP" secondAttribute="trailing" constant="14" id="zdu-6P-uIG"/>
                            </constraints>
                        </view>
@@ -225,9 +226,6 @@
    <resources>
        <systemColor name="systemBackgroundColor">
            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
        </systemColor>
        <systemColor name="systemGray5Color">
            <color red="0.89803921568627454" green="0.89803921568627454" blue="0.91764705882352937" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
        </systemColor>
    </resources>
</document>
DolphinEnglishLearnStudent/Moudle/Market/VC/MarketExchangeVC.swift
@@ -7,10 +7,7 @@
import UIKit
import RxRelay
class MarketViewModel{
                var number = BehaviorRelay<Int>(value:1)
}
import QMUIKit
class MarketExchangeVC: BaseVC {
                @IBOutlet weak var scrollView: UIScrollView!
@@ -18,11 +15,57 @@
                @IBOutlet weak var view_container: UIView!
                @IBOutlet weak var field_number: UITextField!
                @IBOutlet weak var cons_footerHei: NSLayoutConstraint!
                @IBOutlet weak var label_address: UILabel!
                @IBOutlet weak var label_address_info: UILabel!
                @IBOutlet weak var img_cover: UIImageView!
                @IBOutlet weak var label_goodsName: UILabel!
                @IBOutlet weak var label_coin: UILabel!
                @IBOutlet weak var btn_add: UIButton!
                @IBOutlet weak var btn_reduce: UIButton!
                @IBOutlet weak var textView_remark: QMUITextView!
                @IBOutlet weak var label_num: UILabel!
                @IBOutlet weak var label_needCoin: UILabel!
                @IBOutlet weak var label_orderNum: UILabel!
                @IBOutlet weak var label_footNeedCoin: UILabel!
                
                private var viewModel = MarketViewModel()
                private var viewModel = MarketContentViewModel()
                required init(viewModel:MarketContentViewModel) {
                                super.init(nibName: nil, bundle: nil)
                                self.viewModel = viewModel
                }
                required init?(coder: NSCoder) {
                                fatalError("init(coder:) has not been implemented")
                }
    override func viewDidLoad() {
        super.viewDidLoad()
                                Services.redeemNow(goodId: viewModel.detailModel.value!.good!.id).subscribe(onNext: {result in
                                                self.label_orderNum.text = result.data?.orderNumber ?? ""
                                                self.viewModel.detailModel.accept(result.data)
                                }).disposed(by: disposeBag)
                                Services.addressList().subscribe(onNext: {[weak self]result in
                                                self?.viewModel.address.accept(result.data ?? [])
                                                if let first = result.data?.filter({$0.isDefault == 1}).first{
                                                                self?.label_address.text = first.address
                                                                self?.label_address_info.text = first.recipient + "|" + first.recipientPhone
                                                }else{
                                                                self?.label_address.text = "新建收货地址"
                                                                self?.label_address_info.isHidden = true
                                                }
                                }).disposed(by: disposeBag)
                                self.img_cover.sd_setImage(with: URL(string: viewModel.detailModel.value?.good?.coverImg))
                                self.label_goodsName.text = viewModel.detailModel.value?.good?.name ?? ""
                                self.label_coin.text = "\(viewModel.detailModel.value?.good?.integral ?? 0)"
                                if viewModel.detailModel.value?.good?.userCount == 1{
                                                btn_add.isEnabled = false
                                                btn_reduce.isEnabled = false
                                                field_number.isEnabled = false
                                }
    }
                override func setUI() {
@@ -35,25 +78,62 @@
                override func setRx() {
                                viewModel.number.subscribe(onNext: {[weak self] num in
                                                self?.field_number.text = "\(num)"
                                                self?.label_num.text = "\(num)"
                                                let totalCoin = num * (self?.viewModel.detailModel.value?.good?.integral ?? 0)
                                                self?.label_needCoin.text = "\(totalCoin)"
                                                self?.label_footNeedCoin.text = "\(totalCoin)"
                                }).disposed(by: disposeBag)
                }
                @IBAction func addressAction(_ sender: Any) {
                                if viewModel.address.value.filter({$0.isDefault == 1}).count > 0{
                                                let vc = AddressManageVC(type: .choose)
                                                vc.title = "地址管理"
                                                push(vc: vc)
                                }else{
                                                let vc = AddressManageVC(type: .handle)
                                                vc.title = "地址管理"
                                                push(vc: vc)
                                }
                }
                @IBAction func addNumAction(_ sender: UIButton) {
                                let num = viewModel.number.value + 1
                                var num = viewModel.number.value + 1
                                if  num >= viewModel.detailModel.value?.good?.userCount ?? 0{
                                                num = viewModel.detailModel.value?.good?.userCount ?? 0
                                                sender.isEnabled = false
                                }
                                viewModel.number.accept(num)
                }
                @IBAction func reduceAction(_ sender: UIButton) {
                                let num = max(1,viewModel.number.value - 1)
                                viewModel.number.accept(num)
                                btn_add.isEnabled = true
                }
                @IBAction func exchangeAction(_ sender: UIButton) {
                                guard viewModel.address.value.filter({$0.isDefault == 1}).count != 0 else{
                                                CommonAlertView.show(content: "请先设置收货地址") {
                                                                let vc = AddressManageVC(type: .handle)
                                                                vc.title = "地址管理"
                                                                self.push(vc: vc)
                                                }
                                                return
                                }
                                CommonAlertView.show(content: "确认兑换当前商品吗?") {
                                                let vc = ExchangeResultVC(resultType: .success)
                                                vc.title = "商品详情"
                                                self.push(vc: vc)
                                                let goodsId = self.viewModel.detailModel.value!.good!.id
                                                let num = self.viewModel.number.value
                                                let orderNumber = self.viewModel.detailModel.value?.orderNumber ?? ""
                                                let recipientId = self.viewModel.detailModel.value?.recipient?.id ?? 0
                                                Services.goodsExchangeStudy(goodsId: goodsId, number: num, orderNumber: orderNumber, recipientId: recipientId, remark: self.textView_remark.text!).subscribe(onNext: {_ in
                                                                let vc = ExchangeResultVC(resultType: .success)
                                                                vc.title = "商品详情"
                                                                self.push(vc: vc)
                                                }).disposed(by: self.disposeBag)
                                }
                }
}
DolphinEnglishLearnStudent/Moudle/Market/VC/MarketExchangeVC.xib
@@ -11,9 +11,21 @@
    <objects>
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="MarketExchangeVC" customModule="DolphinEnglishLearnStudent" customModuleProvider="target">
            <connections>
                <outlet property="btn_add" destination="wqg-IO-Jti" id="tqa-wd-rR2"/>
                <outlet property="btn_reduce" destination="H9G-ck-ezM" id="uPJ-2M-QEo"/>
                <outlet property="cons_footerHei" destination="71z-Eh-Ya8" id="V0f-Rh-ggi"/>
                <outlet property="field_number" destination="1nG-XY-fwB" id="xye-yR-VBc"/>
                <outlet property="img_cover" destination="QM5-dP-No2" id="xux-xq-ol5"/>
                <outlet property="label_address" destination="ph9-Ra-CxP" id="b0j-bK-UQV"/>
                <outlet property="label_address_info" destination="Hns-QV-baw" id="v5s-wf-nHN"/>
                <outlet property="label_coin" destination="Zy6-3Q-Lgi" id="WNc-y4-9Yn"/>
                <outlet property="label_footNeedCoin" destination="Nka-ZK-eeP" id="1wN-Kd-xoF"/>
                <outlet property="label_goodsName" destination="SZF-J0-byW" id="bMb-RR-rHM"/>
                <outlet property="label_needCoin" destination="vFF-je-HEk" id="XDA-D5-HO1"/>
                <outlet property="label_num" destination="p4X-ju-9r7" id="Cmx-HF-NKT"/>
                <outlet property="label_orderNum" destination="BaU-rt-9wh" id="fwg-Lq-bZB"/>
                <outlet property="scrollView" destination="hFz-gY-osL" id="xXh-5z-6XE"/>
                <outlet property="textView_remark" destination="Rq2-vc-D0Q" id="sDq-vQ-gM6"/>
                <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
                <outlet property="view_container" destination="4wG-oJ-eBG" id="8U4-Q9-4Ap"/>
                <outlet property="view_footer" destination="d3o-Ba-FIK" id="EMz-WT-ojz"/>
@@ -80,7 +92,7 @@
                                <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="HCf-Ym-o1O">
                                    <rect key="frame" x="0.0" y="0.0" width="556" height="600"/>
                                    <subviews>
                                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sfn-3Q-b7V">
                                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sfn-3Q-b7V" customClass="TapBtn" customModule="DolphinEnglishLearnStudent" customModuleProvider="target">
                                            <rect key="frame" x="0.0" y="0.0" width="556" height="90"/>
                                            <subviews>
                                                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5h7-8X-cXI">
@@ -90,44 +102,48 @@
                                                        <constraint firstAttribute="height" constant="3" id="7Bs-Y1-RKJ"/>
                                                    </constraints>
                                                </view>
                                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ph9-Ra-CxP">
                                                    <rect key="frame" x="14" y="28" width="492" height="20"/>
                                                    <constraints>
                                                        <constraint firstAttribute="height" constant="20" id="cgw-w2-dsN"/>
                                                    </constraints>
                                                    <fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
                                                    <color key="textColor" red="0.011764705882352941" green="0.015686274509803921" blue="0.019607843137254902" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                                    <nil key="highlightedColor"/>
                                                </label>
                                                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="icon_more_gray" translatesAutoresizingMaskIntoConstraints="NO" id="jKh-8m-ogE">
                                                    <rect key="frame" x="536" y="40" width="6" height="10"/>
                                                </imageView>
                                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Hns-QV-baw">
                                                    <rect key="frame" x="14" y="53" width="492" height="14.5"/>
                                                    <fontDescription key="fontDescription" type="system" pointSize="12"/>
                                                    <nil key="textColor"/>
                                                    <nil key="highlightedColor"/>
                                                </label>
                                                <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="ZAd-Ha-PgP">
                                                    <rect key="frame" x="14" y="25.5" width="36" height="39.5"/>
                                                    <subviews>
                                                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ph9-Ra-CxP">
                                                            <rect key="frame" x="0.0" y="0.0" width="36" height="20"/>
                                                            <constraints>
                                                                <constraint firstAttribute="height" constant="20" id="cgw-w2-dsN"/>
                                                            </constraints>
                                                            <fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
                                                            <color key="textColor" red="0.011764705882352941" green="0.015686274509803921" blue="0.019607843137254902" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                                            <nil key="highlightedColor"/>
                                                        </label>
                                                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Hns-QV-baw">
                                                            <rect key="frame" x="0.0" y="25" width="36" height="14.5"/>
                                                            <fontDescription key="fontDescription" type="system" pointSize="12"/>
                                                            <nil key="textColor"/>
                                                            <nil key="highlightedColor"/>
                                                        </label>
                                                    </subviews>
                                                </stackView>
                                            </subviews>
                                            <constraints>
                                                <constraint firstItem="jKh-8m-ogE" firstAttribute="centerY" secondItem="sfn-3Q-b7V" secondAttribute="centerY" id="AIT-eX-UYg"/>
                                                <constraint firstAttribute="trailing" secondItem="5h7-8X-cXI" secondAttribute="trailing" id="Byx-At-ZHn"/>
                                                <constraint firstAttribute="height" constant="90" id="Gtx-jC-H0V"/>
                                                <constraint firstItem="ph9-Ra-CxP" firstAttribute="leading" secondItem="sfn-3Q-b7V" secondAttribute="leading" constant="14" id="L6f-uW-gIs"/>
                                                <constraint firstItem="ph9-Ra-CxP" firstAttribute="top" secondItem="sfn-3Q-b7V" secondAttribute="top" constant="28" id="Pzt-wT-2Uz"/>
                                                <constraint firstItem="ZAd-Ha-PgP" firstAttribute="leading" secondItem="sfn-3Q-b7V" secondAttribute="leading" constant="14" id="KKV-Kz-UZ6"/>
                                                <constraint firstAttribute="bottom" secondItem="5h7-8X-cXI" secondAttribute="bottom" id="dS7-Gn-QOD"/>
                                                <constraint firstAttribute="trailing" secondItem="jKh-8m-ogE" secondAttribute="trailing" constant="14" id="fpD-Vw-mh2"/>
                                                <constraint firstItem="5h7-8X-cXI" firstAttribute="leading" secondItem="sfn-3Q-b7V" secondAttribute="leading" id="g86-ED-gAW"/>
                                                <constraint firstItem="Hns-QV-baw" firstAttribute="trailing" secondItem="ph9-Ra-CxP" secondAttribute="trailing" id="hFX-F5-1NJ"/>
                                                <constraint firstItem="Hns-QV-baw" firstAttribute="leading" secondItem="ph9-Ra-CxP" secondAttribute="leading" id="l9W-26-0ij"/>
                                                <constraint firstItem="Hns-QV-baw" firstAttribute="top" secondItem="ph9-Ra-CxP" secondAttribute="bottom" constant="5" id="mta-MA-VXe"/>
                                                <constraint firstAttribute="trailing" secondItem="ph9-Ra-CxP" secondAttribute="trailing" constant="50" id="oe6-gk-huK"/>
                                                <constraint firstAttribute="trailing" secondItem="jKh-8m-ogE" secondAttribute="trailing" constant="14" id="on7-ez-QYT"/>
                                                <constraint firstItem="ZAd-Ha-PgP" firstAttribute="centerY" secondItem="sfn-3Q-b7V" secondAttribute="centerY" id="qhc-bm-AqF"/>
                                            </constraints>
                                            <connections>
                                                <action selector="addressAction:" destination="-1" eventType="touchUpInside" id="fj4-mV-Wor"/>
                                            </connections>
                                        </view>
                                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Pcy-2J-bsd">
                                            <rect key="frame" x="0.0" y="90" width="556" height="366"/>
                                            <subviews>
                                                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="QM5-dP-No2">
                                                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="QM5-dP-No2">
                                                    <rect key="frame" x="14" y="15" width="170" height="170"/>
                                                    <constraints>
                                                        <constraint firstAttribute="width" constant="170" id="gTg-aH-0dy"/>
@@ -239,9 +255,8 @@
                                                        <action selector="addNumAction:" destination="-1" eventType="touchUpInside" id="N9f-5O-JZz"/>
                                                    </connections>
                                                </button>
                                                <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="H9G-ck-ezM">
                                                    <rect key="frame" x="702" y="206" width="32" height="22"/>
                                                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="H9G-ck-ezM">
                                                    <rect key="frame" x="440" y="207" width="32" height="22"/>
                                                    <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
                                                    <state key="normal" image="btn_reduce"/>
                                                    <state key="disabled" image="btn_reduce_un"/>
@@ -253,11 +268,13 @@
                                            <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                                            <constraints>
                                                <constraint firstAttribute="trailing" secondItem="SZF-J0-byW" secondAttribute="trailing" constant="13" id="2bc-jD-0jQ"/>
                                                <constraint firstItem="Y1j-mH-7vJ" firstAttribute="centerY" secondItem="H9G-ck-ezM" secondAttribute="centerY" id="3fZ-cn-vBb"/>
                                                <constraint firstItem="QM5-dP-No2" firstAttribute="top" secondItem="Pcy-2J-bsd" secondAttribute="top" constant="15" id="3oV-je-E0W"/>
                                                <constraint firstItem="SZF-J0-byW" firstAttribute="leading" secondItem="QM5-dP-No2" secondAttribute="trailing" constant="13" id="4nD-DW-K4G"/>
                                                <constraint firstItem="wqg-IO-Jti" firstAttribute="leading" secondItem="Y1j-mH-7vJ" secondAttribute="trailing" id="96J-NH-U7L"/>
                                                <constraint firstItem="MS7-3T-DWG" firstAttribute="top" secondItem="EeU-Wx-jBI" secondAttribute="bottom" constant="17" id="APQ-K4-sOj"/>
                                                <constraint firstAttribute="trailing" secondItem="xEk-ac-udO" secondAttribute="trailing" constant="14" id="AaJ-eg-xRI"/>
                                                <constraint firstItem="wqg-IO-Jti" firstAttribute="height" secondItem="H9G-ck-ezM" secondAttribute="height" id="BxV-pF-PDX"/>
                                                <constraint firstAttribute="trailing" secondItem="Y1j-mH-7vJ" secondAttribute="trailing" constant="42" id="Cnz-nI-ucx"/>
                                                <constraint firstItem="xEk-ac-udO" firstAttribute="leading" secondItem="Pcy-2J-bsd" secondAttribute="leading" constant="12" id="DSj-62-NMj"/>
                                                <constraint firstItem="MS7-3T-DWG" firstAttribute="leading" secondItem="EeU-Wx-jBI" secondAttribute="leading" id="EM3-jV-CSR"/>
@@ -269,6 +286,7 @@
                                                <constraint firstItem="Y1j-mH-7vJ" firstAttribute="top" secondItem="h4B-Ju-L2P" secondAttribute="bottom" constant="22" id="RcG-f4-yEi"/>
                                                <constraint firstItem="h4B-Ju-L2P" firstAttribute="centerY" secondItem="Zy6-3Q-Lgi" secondAttribute="centerY" id="ScA-Kv-FAY"/>
                                                <constraint firstItem="xEk-ac-udO" firstAttribute="top" secondItem="5zn-TC-ZAu" secondAttribute="bottom" constant="14" id="TRf-HU-Y0g"/>
                                                <constraint firstItem="wqg-IO-Jti" firstAttribute="width" secondItem="H9G-ck-ezM" secondAttribute="width" id="TaG-sx-WqG"/>
                                                <constraint firstAttribute="trailing" secondItem="5zn-TC-ZAu" secondAttribute="trailing" constant="14" id="XsR-S7-5se"/>
                                                <constraint firstItem="h4B-Ju-L2P" firstAttribute="leading" secondItem="Zy6-3Q-Lgi" secondAttribute="trailing" id="XyT-7a-Pzk"/>
                                                <constraint firstItem="5zn-TC-ZAu" firstAttribute="top" secondItem="MS7-3T-DWG" secondAttribute="bottom" constant="12" id="Zb6-DZ-qrC"/>
@@ -276,6 +294,7 @@
                                                <constraint firstItem="EeU-Wx-jBI" firstAttribute="top" secondItem="QM5-dP-No2" secondAttribute="bottom" constant="18" id="kWP-Z3-Uve"/>
                                                <constraint firstItem="QM5-dP-No2" firstAttribute="leading" secondItem="Pcy-2J-bsd" secondAttribute="leading" constant="14" id="lJI-a1-yyL"/>
                                                <constraint firstItem="EeU-Wx-jBI" firstAttribute="leading" secondItem="Pcy-2J-bsd" secondAttribute="leading" constant="14" id="pIg-OO-tpF"/>
                                                <constraint firstItem="Y1j-mH-7vJ" firstAttribute="leading" secondItem="H9G-ck-ezM" secondAttribute="trailing" id="v1W-TC-i1N"/>
                                                <constraint firstItem="h4B-Ju-L2P" firstAttribute="bottom" secondItem="QM5-dP-No2" secondAttribute="bottom" id="z2m-b7-4WH"/>
                                            </constraints>
                                        </view>
DolphinEnglishLearnStudent/Moudle/Me/MeVC.swift
@@ -14,11 +14,22 @@
                @IBOutlet weak var btn_exchangeRecord: QMUIButton!
                @IBOutlet weak var btn_share: QMUIButton!
                @IBOutlet weak var btn_etudyRecord: QMUIButton!
                @IBOutlet weak var imge_cover: UIImageView!
                @IBOutlet weak var label_name: UILabel!
                @IBOutlet weak var label_info: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
                                Services.userInfo().subscribe(onNext: {result in
                                                if let model = result.data{
                                                                self.imge_cover.sd_setImage(with: URL(string: model.headImg))
                                                                self.label_name.text = model.name
//                                                                var items = Array<String>()
//                                                                items.append("剩余积分:\(model.integral)")
//                                                                items.append("学习进度:\(model.integral)")
                                                }
                                }).disposed(by: disposeBag)
    }
@@ -69,7 +80,9 @@
                @IBAction func quitAction(_ sender: UIButton) {
                                CommonAlertView.show(content: "确认退出当前账户吗?") {
                                                Services.logoutStudy().subscribe(onNext: {result in
                                                                sceneDelegate?.needLogin()
                                                }).disposed(by: self.disposeBag)
                                }
                }
}
DolphinEnglishLearnStudent/Moudle/Me/MeVC.xib
@@ -16,6 +16,9 @@
                <outlet property="btn_etudyRecord" destination="0pk-gO-3Qh" id="SeS-bo-pn4"/>
                <outlet property="btn_exchangeRecord" destination="uH5-eT-V9Z" id="8gd-zT-JND"/>
                <outlet property="btn_share" destination="gtG-mF-MKi" id="ejw-y9-ugq"/>
                <outlet property="imge_cover" destination="qXF-FL-HEr" id="fG7-3a-cHy"/>
                <outlet property="label_info" destination="LJb-Ki-p3S" id="88F-Ay-1nv"/>
                <outlet property="label_name" destination="D0d-O7-Pt2" id="C7j-gd-GwB"/>
                <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
            </connections>
        </placeholder>
DolphinEnglishLearnStudent/Moudle/Me/TCell/AddressManageTCell.swift
@@ -6,6 +6,7 @@
//
import UIKit
import RxSwift
class AddressManageTCell: UITableViewCell {
@@ -15,7 +16,19 @@
                @IBOutlet weak var btn_delete: UIButton!
                @IBOutlet weak var btn_edit: UIButton!
                @IBOutlet weak var img_more: UIImageView!
                @IBOutlet weak var label_address: UILabel!
                @IBOutlet weak var label_addressInfo: UILabel!
                private var disposeBag = DisposeBag()
                var addressModel:AddressModel!{
                                didSet{
                                                label_address.text = addressModel.address
                                                label_addressInfo.text = addressModel.recipient + "|" + addressModel.recipientPhone
                                                isDefault(addressModel.isDefault == 1)
                                }
                }
    override func awakeFromNib() {
        super.awakeFromNib()
                                selectionStyle = .none
@@ -24,6 +37,10 @@
                func isDefault(_ state:Bool){
                                if state{
                                                let attribute = AttributedStringbuilder.build().add(string:"  默认 ", withFont: .systemFont(ofSize: 14, weight: .medium), withColor: UIColor.white).mutableAttributedString
                                                btn_default.setAttributedTitle(attribute, for: .normal)
                                                btn_default.setTitle(" 默认 ", for: .normal)
                                                btn_default.backgroundColor = UIColor(hexStr: "#F7462D")
                                                btn_default.setTitleColor(.white, for: .normal)
@@ -37,13 +54,23 @@
                }
                @IBAction func deleteAction(_ sender: UIButton) {
                                CommonAlertView.show(content:"确认删除所选信息吗?") {
                                CommonAlertView.show(content:"确认删除所选信息吗?") {[weak self] () in
                                                guard let weakSelf = self else { return }
                                                Services.deleteAddress(id: weakSelf.addressModel.id).subscribe(onNext: { _ in
                                                                NotificationCenter.default.post(name: AddressManage_Refresh_Noti, object: nil)
                                                }).disposed(by: weakSelf.disposeBag)
                                }
                }
                @IBAction func setDefaultAction(_ sender: UIButton) {
                                Services.setDefaultStudy(id: addressModel.id).subscribe(onNext: { _ in
                                                NotificationCenter.default.post(name: AddressManage_Refresh_Noti, object: nil)
                                }).disposed(by: disposeBag)
                }
                
                @IBAction func editAction(_ sender: UIButton) {
                                let vc = AddressManageHandleVC()
                                let vc = AddressManageHandleVC(addressModel)
                                vc.title = "编辑地址"
                                JQ_currentViewController().jq_push(vc: vc)
                }
@@ -57,5 +84,9 @@
                                                contentView.jq_addCorners(corner: [.bottomLeft,.bottomLeft], radius: 10)
                                }
                                if isFist && isLast{
                                                contentView.jq_addCorners(corner: .allCorners, radius: 10)
                                }
                }
}
DolphinEnglishLearnStudent/Moudle/Me/TCell/AddressManageTCell.xib
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
    <device id="retina6_12" orientation="portrait" appearance="light"/>
    <device id="ipad10_9rounded" orientation="portrait" layout="fullscreen" appearance="light"/>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
@@ -10,7 +10,7 @@
    <objects>
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
        <tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="95" id="KGk-i7-Jjw" customClass="AddressManageTCell" customModule="DolphinEnglishLearnManager" customModuleProvider="target">
        <tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="95" id="KGk-i7-Jjw" customClass="AddressManageTCell" customModule="DolphinEnglishLearnStudent" customModuleProvider="target">
            <rect key="frame" x="0.0" y="0.0" width="447" height="95"/>
            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
            <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
@@ -27,7 +27,7 @@
                        <nil key="highlightedColor"/>
                    </label>
                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ezc-re-P3r">
                        <rect key="frame" x="375" y="18.666666666666668" width="58" height="17.000000000000004"/>
                        <rect key="frame" x="375" y="18.5" width="58" height="17"/>
                        <constraints>
                            <constraint firstAttribute="height" constant="17" id="0Kf-PA-7Or"/>
                            <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="42" id="4RT-3H-dH2"/>
@@ -37,9 +37,12 @@
                        <state key="normal" title="设为默认">
                            <color key="titleColor" red="0.25490196079999999" green="0.63529411759999999" blue="0.92156862750000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        </state>
                        <connections>
                            <action selector="setDefaultAction:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="p3w-8e-C9z"/>
                        </connections>
                    </button>
                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-- | --" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="g9x-qe-0Me">
                        <rect key="frame" x="13.999999999999996" y="42" width="41.666666666666657" height="17"/>
                        <rect key="frame" x="14" y="42" width="42" height="17"/>
                        <constraints>
                            <constraint firstAttribute="height" constant="17" id="04w-Er-l9A"/>
                        </constraints>
@@ -77,7 +80,7 @@
                        </connections>
                    </button>
                    <imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="icon_more_gray" translatesAutoresizingMaskIntoConstraints="NO" id="Qav-bf-Mlo">
                        <rect key="frame" x="427" y="42.666666666666664" width="6" height="10"/>
                        <rect key="frame" x="427" y="42.5" width="6" height="10"/>
                    </imageView>
                </subviews>
                <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -106,6 +109,8 @@
                <outlet property="btn_delete" destination="a8n-tE-gqa" id="elJ-VF-A4k"/>
                <outlet property="btn_edit" destination="WFf-ch-GdK" id="Wfz-Oo-mbZ"/>
                <outlet property="img_more" destination="Qav-bf-Mlo" id="ykH-wg-Brt"/>
                <outlet property="label_address" destination="Qjx-I2-glv" id="iwX-fL-7LK"/>
                <outlet property="label_addressInfo" destination="g9x-qe-0Me" id="cZT-hR-szg"/>
            </connections>
            <point key="canvasLocation" x="202.29007633587784" y="49.647887323943664"/>
        </tableViewCell>
DolphinEnglishLearnStudent/Moudle/Me/TCell/GoodsItemTCell.swift
@@ -8,7 +8,16 @@
import UIKit
class GoodsItemTCell: UITableViewCell {
                @IBOutlet weak var label_state: UILabel!
                @IBOutlet weak var label_goodsName: UILabel!
                @IBOutlet weak var label_types: UILabel!
                @IBOutlet weak var label_goodsNum: UILabel!
                @IBOutlet weak var label_receiptInfo: UILabel!
                @IBOutlet weak var label_sendInfo: UILabel!
                @IBOutlet weak var btn_state: UIButton!
                @IBOutlet weak var label_coin: UILabel!
                @IBOutlet weak var view_container: UIView!
                override func awakeFromNib() {
        super.awakeFromNib()
@@ -17,6 +26,24 @@
                                view_container.jq_addShadows(shadowColor: UIColor(hexStr: "#D9D9D9").withAlphaComponent(0.28), corner: 8, radius: 3, offset: CGSize(width: 0, height: 2), opacity: 1)
    }
                func setModel(_ model:ExchangeRecordModel){
                                label_goodsNum.text = "商品数量:\(model.count)"
                                label_coin.text = "\(model.integral)积分"
                                var items_consignee = Array<String>()
                                items_consignee.append(model.consigneeName)
                                items_consignee.append(model.consigneePhone)
                                items_consignee.append(model.consigneeAddress)
                                label_receiptInfo.text = "收货信息:" + items_consignee.joined(separator: "|")
                                var items_express = Array<String>()
                                items_express.append(model.express)
                                items_express.append(model.expressNumber)
                                label_sendInfo.isHidden = items_express.filter({!$0.isEmpty}).count == 0
                                label_sendInfo.text = "发货信息:" + items_express.joined(separator: "|")
                }
                @IBAction func handleAction(_ sender: UIButton) {
                                let vc = AddressManageVC(type: .choose)
                                vc.title = "修改地址"
DolphinEnglishLearnStudent/Moudle/Me/TCell/GoodsItemTCell.xib
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
    <device id="retina6_12" orientation="portrait" appearance="light"/>
    <device id="ipad10_9rounded" orientation="portrait" layout="fullscreen" appearance="light"/>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
@@ -10,7 +10,7 @@
    <objects>
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
        <tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="_GoodsItemTCell" rowHeight="344" id="KGk-i7-Jjw" customClass="GoodsItemTCell" customModule="DolphinEnglishLearnManager" customModuleProvider="target">
        <tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="_GoodsItemTCell" rowHeight="344" id="KGk-i7-Jjw" customClass="GoodsItemTCell" customModule="DolphinEnglishLearnStudent" customModuleProvider="target">
            <rect key="frame" x="0.0" y="0.0" width="532" height="344"/>
            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
            <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
@@ -21,13 +21,13 @@
                        <rect key="frame" x="0.0" y="6" width="532" height="332"/>
                        <subviews>
                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9oA-7x-in6">
                                <rect key="frame" x="8" y="19" width="41.333333333333336" height="20.333333333333329"/>
                                <rect key="frame" x="8" y="19" width="41.5" height="20.5"/>
                                <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                <nil key="textColor"/>
                                <nil key="highlightedColor"/>
                            </label>
                            <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="HZS-Oe-5LR">
                                <rect key="frame" x="8" y="52.333333333333343" width="148" height="148"/>
                                <rect key="frame" x="8" y="52.5" width="148" height="148"/>
                                <constraints>
                                    <constraint firstAttribute="width" constant="148" id="D3I-iy-k6w"/>
                                    <constraint firstAttribute="width" secondItem="HZS-Oe-5LR" secondAttribute="height" multiplier="1:1" id="YQJ-eG-71N"/>
@@ -40,25 +40,25 @@
                                </userDefinedRuntimeAttributes>
                            </imageView>
                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UdI-oe-WSO">
                                <rect key="frame" x="173" y="52.333333333333336" width="234" height="19.333333333333336"/>
                                <rect key="frame" x="173" y="52.5" width="234" height="19.5"/>
                                <fontDescription key="fontDescription" type="system" pointSize="16"/>
                                <color key="textColor" red="0.082352941176470587" green="0.086274509803921567" blue="0.094117647058823528" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                <nil key="highlightedColor"/>
                            </label>
                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="0积分" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nMG-SO-H2Z">
                                <rect key="frame" x="481" y="52.666666666666664" width="40" height="18.666666666666664"/>
                                <rect key="frame" x="481" y="53" width="40" height="19"/>
                                <fontDescription key="fontDescription" name="DINAlternate-Bold" family="DIN Alternate" pointSize="16"/>
                                <color key="textColor" red="0.96862745100000003" green="0.27450980390000002" blue="0.1764705882" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                <nil key="highlightedColor"/>
                            </label>
                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="viA-yT-ZWk">
                                <rect key="frame" x="173" y="71.666666666666671" width="359" height="17"/>
                                <rect key="frame" x="173" y="72" width="359" height="17"/>
                                <fontDescription key="fontDescription" type="system" pointSize="14"/>
                                <nil key="textColor"/>
                                <nil key="highlightedColor"/>
                            </label>
                            <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="vd5-iK-Ujb">
                                <rect key="frame" x="173" y="95.666666666666686" width="303" height="70"/>
                                <rect key="frame" x="173" y="96" width="303" height="70"/>
                                <subviews>
                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="商品数量:-" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PlF-D7-xZD">
                                        <rect key="frame" x="0.0" y="0.0" width="303" height="20"/>
@@ -79,7 +79,7 @@
                                        <nil key="highlightedColor"/>
                                    </label>
                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="发货信息:-" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cSS-lo-Yba">
                                        <rect key="frame" x="0.0" y="49.999999999999986" width="303" height="20"/>
                                        <rect key="frame" x="0.0" y="50" width="303" height="20"/>
                                        <constraints>
                                            <constraint firstAttribute="height" constant="20" id="mDC-DF-Wc3"/>
                                        </constraints>
@@ -141,6 +141,14 @@
            </tableViewCellContentView>
            <viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
            <connections>
                <outlet property="btn_state" destination="xwt-lH-fyW" id="YNc-qk-0gB"/>
                <outlet property="label_coin" destination="nMG-SO-H2Z" id="weP-sJ-QLG"/>
                <outlet property="label_goodsName" destination="UdI-oe-WSO" id="o9d-iH-BCJ"/>
                <outlet property="label_goodsNum" destination="PlF-D7-xZD" id="1mj-HF-LGA"/>
                <outlet property="label_receiptInfo" destination="qnF-SM-ngM" id="me5-fe-eW2"/>
                <outlet property="label_sendInfo" destination="cSS-lo-Yba" id="4fa-fc-f69"/>
                <outlet property="label_state" destination="9oA-7x-in6" id="lBa-OB-S8W"/>
                <outlet property="label_types" destination="viA-yT-ZWk" id="3dU-h2-97f"/>
                <outlet property="view_container" destination="EWl-m5-3O9" id="vyU-l1-0CY"/>
            </connections>
            <point key="canvasLocation" x="259.5419847328244" y="126.05633802816902"/>
DolphinEnglishLearnStudent/Moudle/Me/TCell/Home_1_TCell.swift
@@ -9,6 +9,26 @@
class Home_1_TCell: UITableViewCell {
                @IBOutlet weak var label_1: UILabel!
                @IBOutlet weak var label_2: UILabel!
                @IBOutlet weak var label_3: UILabel!
                @IBOutlet weak var label_4: UILabel!
                var integralModel:IntegralModel!{
                                didSet{
                                                label_1.text = integralModel.createTime
                                                label_2.text = integralModel.integral
                                                label_3.text = integralModel.method
                                                label_4.text = integralModel.type
                                }
                }
                var studyGamesRecordModel:StudyGamesRecordModel!{
                                didSet{
                                }
                }
    override func awakeFromNib() {
        super.awakeFromNib()
                                selectionStyle = .none
DolphinEnglishLearnStudent/Moudle/Me/TCell/Home_1_TCell.xib
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
    <device id="retina6_12" orientation="portrait" appearance="light"/>
    <device id="ipad10_9rounded" orientation="portrait" layout="fullscreen" appearance="light"/>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
@@ -9,7 +9,7 @@
    <objects>
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
        <tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="_Home_1_TCell" id="KGk-i7-Jjw" customClass="Home_1_TCell" customModule="DolphinEnglishLearnManager" customModuleProvider="target">
        <tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="_Home_1_TCell" id="KGk-i7-Jjw" customClass="Home_1_TCell" customModule="DolphinEnglishLearnStudent" customModuleProvider="target">
            <rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
            <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
@@ -53,6 +53,12 @@
                    <constraint firstItem="xga-Br-Izz" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="dHd-ao-jYA"/>
                </constraints>
            </tableViewCellContentView>
            <connections>
                <outlet property="label_1" destination="LLg-mN-hnD" id="7UE-le-ZJZ"/>
                <outlet property="label_2" destination="bXA-iA-Sgc" id="4He-VH-alj"/>
                <outlet property="label_3" destination="tft-ra-5If" id="CLY-lE-hEm"/>
                <outlet property="label_4" destination="UQ2-uJ-GO9" id="nNZ-Gp-UJ9"/>
            </connections>
            <point key="canvasLocation" x="139" y="21"/>
        </tableViewCell>
    </objects>
DolphinEnglishLearnStudent/Moudle/Me/VC/AddressManageHandleVC.swift
@@ -6,17 +6,119 @@
//
import UIKit
import QMUIKit
import RxRelay
class AddressManageHandleViewModel{
                var userName = BehaviorRelay<String?>(value: nil)
                var phone = BehaviorRelay<String?>(value: nil)
                var provinceModel = BehaviorRelay<AddressTreeModel?>(value: nil)
                var cityModel = BehaviorRelay<AddressTreeModel?>(value: nil)
                var countryModel = BehaviorRelay<AddressTreeModel?>(value: nil)
                var detailAddress = BehaviorRelay<String?>(value: nil)
                var isDefault = BehaviorRelay<Bool>(value: false)
}
class AddressManageHandleVC: BaseVC {
                @IBOutlet weak var tf_name: UITextField!
                @IBOutlet weak var tf_phone: QMUITextField!
                @IBOutlet weak var tf_address: QMUITextField!
                @IBOutlet weak var tf_addressContent: QMUITextField!
                @IBOutlet weak var btn_isDefault: UIButton!
                let viewModel = AddressManageHandleViewModel()
                private var updateModel:AddressModel?
                @IBOutlet weak var view_container: UIView!
                override func viewDidLoad() {
        super.viewDidLoad()
                                if let updateModel{
                                                tf_name.text = updateModel.recipient
                                                tf_phone.text = updateModel.recipientPhone
                                                tf_address.text = updateModel.province + updateModel.city
                                                tf_addressContent.text = updateModel.address
                                                btn_isDefault.isSelected = updateModel.isDefault == 1
                                                viewModel.userName.accept(updateModel.recipient)
                                                viewModel.phone.accept(updateModel.recipientPhone)
                                                viewModel.detailAddress.accept(updateModel.address)
                                                viewModel.provinceModel.accept(AddressTreeModel(id: 0, name: updateModel.province, code: updateModel.provinceCode, parentId: 0, children: nil))
                                                viewModel.cityModel.accept(AddressTreeModel(id: 0, name: updateModel.city, code: updateModel.cityCode, parentId: 0, children: nil))
                                }
    }
                init(_ updateModel:AddressModel? = nil) {
                                super.init(nibName: nil, bundle: nil)
                                self.updateModel = updateModel
                }
                required init?(coder: NSCoder) {
                                fatalError("init(coder:) has not been implemented")
                }
                override func setUI() {
                                super.setUI()
                                view_container.jq_addShadows(shadowColor: UIColor(hexStr: "#D9D9D9").withAlphaComponent(0.28), corner: 8, radius: 20, offset: CGSize(width: 0, height: 2), opacity: 1)
                                tf_address.delegate = self
                }
                override func setRx() {
                                tf_name.rx.text.bind(to: viewModel.userName).disposed(by: disposeBag)
                                tf_phone.rx.text.bind(to: viewModel.phone).disposed(by: disposeBag)
                                tf_addressContent.rx.text.bind(to: viewModel.detailAddress).disposed(by: disposeBag)
                }
                @IBAction func chooseDefaultAction(_ sender: UIButton) {
                                sender.isSelected = !sender.isSelected
                }
                @IBAction func saveAction(_ sender: UIButton) {
                                guard !(viewModel.userName.value?.isEmpty ?? true) else {
                                                alert(msg: "请输入收件人姓名");return
                                }
                                guard !(viewModel.phone.value?.isEmpty ?? true) else {
                                                alert(msg: "请输入收件人电话");return
                                }
                                guard viewModel.phone.value!.jq_isPhone else {
                                                alert(msg: "请输入正确的手机号码");return
                                }
                                guard viewModel.cityModel.value != nil else {
                                                alert(msg: "请选择所在城市");return
                                }
                                guard !(viewModel.detailAddress.value?.isEmpty ?? true) else {
                                                alert(msg: "请输入详细地址");return
                                }
                                Services.addressSaveOrUpdate(id: updateModel?.id, province: viewModel.provinceModel.value, city: viewModel.cityModel.value, country: nil, userName: viewModel.userName.value, userPhone: viewModel.phone.value, isDefault: btn_isDefault.isSelected, detailAddress: viewModel.detailAddress.value ?? "").subscribe(onNext: {_ in
                                                if self.updateModel != nil{
                                                                alertSuccess(msg: "编辑成功")
                                                }else{
                                                                alertSuccess(msg: "添加成功")
                                                }
                                                self.navigationController?.popViewController()
                                                NotificationCenter.default.post(name: AddressManage_Refresh_Noti, object: nil)
                                }).disposed(by: disposeBag)
                }
}
extension AddressManageHandleVC:QMUITextFieldDelegate{
                func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
                                CityAddressPickerView.show(title: "选择省市区") {[weak self] province, city, country in
                                                self?.viewModel.provinceModel.accept(province)
                                                self?.viewModel.cityModel.accept(city)
                                                self?.viewModel.countryModel.accept(country)
                                                textField.text = "\(province?.name ?? "") \(city?.name ?? "") \(country?.name ?? "")"
                                }
                                return false
                }
}
DolphinEnglishLearnStudent/Moudle/Me/VC/AddressManageHandleVC.xib
@@ -11,6 +11,11 @@
    <objects>
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="AddressManageHandleVC" customModule="DolphinEnglishLearnStudent" customModuleProvider="target">
            <connections>
                <outlet property="btn_isDefault" destination="X5q-Za-IDi" id="6lr-qr-5nr"/>
                <outlet property="tf_address" destination="FIR-Ti-oMo" id="XyJ-kk-oGH"/>
                <outlet property="tf_addressContent" destination="9Rg-uR-zdR" id="sxn-jk-kYJ"/>
                <outlet property="tf_name" destination="oWU-h5-9OT" id="9Gb-Hg-ka1"/>
                <outlet property="tf_phone" destination="fLZ-09-hXt" id="YZs-at-Zeo"/>
                <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
                <outlet property="view_container" destination="qgb-Bj-4P0" id="eaY-Jn-MsE"/>
            </connections>
@@ -151,7 +156,7 @@
                                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="eNK-zb-eqQ">
                                    <rect key="frame" x="0.0" y="162" width="888" height="54"/>
                                    <subviews>
                                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="所在城市" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uwd-8E-QFK">
                                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="详细地址" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uwd-8E-QFK">
                                            <rect key="frame" x="14" y="18.5" width="57.5" height="17"/>
                                            <fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
                                            <nil key="textColor"/>
@@ -203,6 +208,9 @@
                                <color key="titleColor" red="0.0" green="0.0" blue="0.0" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
                            </state>
                            <state key="selected" title="设为默认" image="btn_choose"/>
                            <connections>
                                <action selector="chooseDefaultAction:" destination="-1" eventType="touchUpInside" id="6aM-GW-eHa"/>
                            </connections>
                        </button>
                    </subviews>
                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
@@ -232,6 +240,9 @@
                            <real key="value" value="8"/>
                        </userDefinedRuntimeAttribute>
                    </userDefinedRuntimeAttributes>
                    <connections>
                        <action selector="saveAction:" destination="-1" eventType="touchUpInside" id="Ml0-tI-QPP"/>
                    </connections>
                </button>
            </subviews>
            <viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
DolphinEnglishLearnStudent/Moudle/Me/VC/AddressManageVC.swift
@@ -6,6 +6,15 @@
//
import UIKit
import RxSwift
let AddressManage_Refresh_Noti = Notification.Name.init("AddressManage_Refresh_Noti")
class AddressManageViewModel:RefreshModel<AddressModel>{
                override func api() -> (Observable<BaseResponse<[AddressModel]>>)? {
                                Services.addressList()
                }
}
class AddressManageVC: BaseVC {
@@ -16,6 +25,7 @@
                private var tableView:UITableView!
                private var addressManageType:AddressManageType!
                private var viewModel = AddressManageViewModel()
                required init(type:AddressManageType) {
                                super.init(nibName: nil, bundle: nil)
@@ -28,6 +38,9 @@
                
    override func viewDidLoad() {
        super.viewDidLoad()
                                viewModel.configure(tableView)
                                viewModel.beginRefresh()
    }
@@ -73,6 +86,12 @@
                                }
                }
                override func setRx() {
                                NotificationCenter.default.rx.notification(AddressManage_Refresh_Noti).take(until: self.rx.deallocated).subscribe(onNext: {[weak self] _ in
                                                self?.viewModel.beginRefresh()
                                }).disposed(by: disposeBag)
                }
                @objc func handleAction(){
                                let vc = AddressManageHandleVC()
                                vc.title = "地址管理"
@@ -86,10 +105,11 @@
extension AddressManageVC:UITableViewDataSource{
                func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                                 let model = viewModel.dataSource.value[indexPath.row]
                                 let cell = tableView.dequeueReusableCell(withIdentifier: "_AddressManageTCell") as! AddressManageTCell
                                cell.addressModel = model
                                cell.isFist = indexPath.row == 0
                                cell.isLast = indexPath.row == 9
                                cell.isDefault(indexPath.row == 0)
                                cell.isLast = indexPath.row == viewModel.dataSource.value.count - 1
                                cell.btn_edit.isHidden = addressManageType == .choose
                                cell.btn_delete.isHidden = addressManageType == .choose
@@ -100,7 +120,7 @@
                }
                func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                                return 10
                                return viewModel.dataSource.value.count
                }
                func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
DolphinEnglishLearnStudent/Moudle/Me/VC/CoinRecordHistoryVC.swift
@@ -7,9 +7,15 @@
import UIKit
import RxRelay
import JQTools
import RxSwift
class CoinRecordHistoryViewModel{
class CoinRecordHistoryViewModel:RefreshInnerModel<IntegralModel>{
                var selectDate = BehaviorRelay<Date?>(value:nil)
                override func api() -> (Observable<BaseResponse<BaseResponseList<IntegralModel>>>)? {
                                return Services.integralDetail(pageNum: page, time: selectDate.value?.jq_format("yyyy-MM"))
                }
}
class CoinRecordHistoryVC: BaseVC {
@@ -23,6 +29,12 @@
                override func viewDidLoad() {
                                super.viewDidLoad()
                                viewModel.configure(tableView,needMore: true)
                                viewModel.beginRefresh()
                                Services.getIntegral().subscribe(onNext: {reault in
                                                self.label_coin.text = "\(reault.data ?? 0)"
                                }).disposed(by: disposeBag)
                }
                override func setUI() {
@@ -43,13 +55,19 @@
                                }).disposed(by: disposeBag)
                }
                @IBAction func chooseDateTimeAction(_ sender: UIButton) {
                                let year = viewModel.selectDate.value?.jq_nowYear() ?? Date().jq_nowYear()
                                let month = viewModel.selectDate.value?.jq_nowMonth() ?? Date().jq_nowMonth()
                                BitrhdayPickerView.show(title: "查询时间", type: .YM, defaultYear: year, defaultMonth: month, defaultDay: 0, minYear: 0) {[weak self] date in
                                                self?.viewModel.selectDate.accept(date)
                                                self?.viewModel.beginRefresh()
                                }
                }
                @IBAction func resetAction(_ sender: UIButton) {
                                viewModel.selectDate.accept(nil)
                                btn_selectDate.setTitle("请选择", for: .normal)
                                viewModel.beginRefresh()
                }
}
@@ -67,11 +85,13 @@
                                                cell.contentView.backgroundColor = .white
                                }
                                let m = viewModel.dataSource.value?.records[indexPath.row]
                                cell.integralModel = m
                                return cell
                }
                func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                                return 5
                                return viewModel.dataSource.value?.records.count ?? 0
                }
                func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
DolphinEnglishLearnStudent/Moudle/Me/VC/CoinRecordHistoryVC.xib
@@ -74,6 +74,9 @@
                            </constraints>
                            <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
                            <state key="normal" image="btn_refresh"/>
                            <connections>
                                <action selector="resetAction:" destination="-1" eventType="touchUpInside" id="UoC-8u-vu0"/>
                            </connections>
                        </button>
                    </subviews>
                    <constraints>
DolphinEnglishLearnStudent/Moudle/Me/VC/ExchangeRecordHistoryVC.swift
@@ -6,14 +6,25 @@
//
import UIKit
import RxSwift
class ExchangeRecordViewModel:RefreshModel<ExchangeRecordModel>{
                override func api() -> (Observable<BaseResponse<[ExchangeRecordModel]>>)? {
                                return Services.exchangeRecord()
                }
}
class ExchangeRecordHistoryVC: BaseVC {
                private  let viewModel = ExchangeRecordViewModel()
                private var tableView:UITableView!
    override func viewDidLoad() {
        super.viewDidLoad()
                                viewModel.configure(tableView)
                                viewModel.beginRefresh()
    }
                override func setUI() {
@@ -42,11 +53,12 @@
extension ExchangeRecordHistoryVC:UITableViewDataSource{
                func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                                let cell = tableView.dequeueReusableCell(withIdentifier: "_GoodsItemTCell") as! GoodsItemTCell
                                cell.setModel(viewModel.dataSource.value[indexPath.row])
                                return cell
                }
                func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                                return 10
                                return viewModel.dataSource.value.count
                }
                func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
DolphinEnglishLearnStudent/Moudle/Me/VC/StudyVC.swift
@@ -11,9 +11,21 @@
                @IBOutlet weak var view_menu: UIView!
                @IBOutlet weak var tableView: UITableView!
                
                private var gamesRecordModel:StudyGamesModel?{
                                didSet{
                                                self.tableView.reloadData()
                                }
                }
    override func viewDidLoad() {
        super.viewDidLoad()
                                Services.studyGamesRecord().subscribe(onNext: {result in
                                                if let m = result.data{
                                                                self.gamesRecordModel = m
                                                }
                                }).disposed(by: disposeBag)
    }
                override func setUI() {
@@ -44,12 +56,16 @@
                                }else{
                                                cell.contentView.backgroundColor = .white
                                }
                                if let m = gamesRecordModel?.gameRecordList[indexPath.row]{
                                                cell.studyGamesRecordModel = m
                                }
                                return cell
                }
                func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                                return 5
                                return gamesRecordModel?.gameRecordList.count ?? 0
                }
                func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
DolphinEnglishLearnStudent/Other/CommonWebVC.swift
@@ -10,16 +10,17 @@
class CommonWebVC: BaseVC {
                enum CommonWebType{
                                case logoff
                                case userAgreement
                                case privacyAgreement
                                case userGuide
                }
                private var type:AgreementType!
                private var type:CommonWebType!
                private lazy var webView:WKWebView = {
                                let webView = WKWebView()
                                webView.backgroundColor = .clear
                                webView.scrollView.backgroundColor = .clear
                                webView.isOpaque = false
                                return webView
                }()
                init(type:CommonWebType) {
                init(type:AgreementType) {
                                super.init(nibName: nil, bundle: nil)
                                self.type = type
                }
@@ -36,8 +37,14 @@
                override func setUI() {
                                super.setUI()
                                view.addSubview(webView)
                                webView.snp.makeConstraints { make in
                                                make.top.equalTo(self.view.safeAreaLayoutGuide.snp.top)
                                                make.left.right.equalToSuperview()
                                                make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom)
                                }
                                if type == .logoff{
                                if type == .logout{
                                                let completeBtn = UIButton(type: .custom)
                                                completeBtn.jq_cornerRadius = 8
                                                completeBtn.addTarget(self, action: #selector(handleAction), for: .touchUpInside)
@@ -76,6 +83,13 @@
                                                });
                                                timer.resume()
                                }
                                Services.getAgreement(type: type).subscribe(onNext: {[weak self]content in
                                                if let content = content.data{
                                                                self?.webView.loadHTMLString(content.jq_wrapHtml(), baseURL: nil)
                                                }
                                }).disposed(by: disposeBag)
                }
                @objc func handleAction(sender:UIButton){
DolphinEnglishLearnStudent/Other/UIView/CityAddressPickerView.swift
New file
@@ -0,0 +1,248 @@
//
//  CityAddressPickerView.swift
//  DolphinEnglishLearnStudent
//
//  Created by 无故事王国 on 2024/6/3.
//
import UIKit
import RxSwift
class CityAddressPickerView: UIView {
                private var disposeBag = DisposeBag()
                private var items = [AddressTreeModel]()
                private var clouse:((AddressTreeModel?,AddressTreeModel?,AddressTreeModel?)->Void)!
                private var view_content:UIView = {
                                let v = UIView()
                                v.backgroundColor = .white
                                return v
                }()
                private var label_title:UILabel = {
                                let label = UILabel()
                                label.font = .systemFont(ofSize: 18, weight: .medium)
                                label.textColor = .black.withAlphaComponent(0.8)
                                return label
                }()
                private var btn_close:UIButton = {
                                let btn = UIButton(type: .custom)
                                btn.setImage(UIImage(named: "btn_close_circle"), for: .normal)
                                return btn
                }()
                private var btn_complete:UIButton = {
                                let btn = UIButton(type: .custom)
                                btn.setTitle("确认", for: .normal)
                                btn.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .medium)
                                btn.setTitleColor(.white, for: .normal)
                                btn.backgroundColor = Config.ThemeColor
                                btn.jq_cornerRadius = 8
                                return btn
                }()
                private var picker:UIPickerView = {
                                let pickerView = UIPickerView()
                                return pickerView
                }()
                override init(frame: CGRect) {
                                super.init(frame: frame)
                                backgroundColor = UIColor.black.withAlphaComponent(0)
                                addSubview(view_content)
                                view_content.snp.makeConstraints { make in
                                                make.left.right.equalToSuperview()
                                                make.bottom.equalToSuperview().offset(JQ_ScreenW * 0.3278)
                                                make.height.equalTo(JQ_ScreenW * 0.3278)
                                }
                                view_content.addSubview(btn_close)
                                btn_close.addTarget(self, action: #selector(closeAction), for: .touchUpInside)
                                btn_close.snp.makeConstraints { make in
                                                make.top.equalTo(10)
                                                make.right.equalTo(-11)
                                                make.width.height.equalTo(23)
                                }
                                view_content.addSubview(label_title)
                                label_title.snp.makeConstraints { make in
                                                make.top.equalTo(30)
                                                make.centerX.equalToSuperview()
                                                make.height.equalTo(18)
                                }
                                layoutIfNeeded()
                }
                private func setUI(){
                                var items = Array<String>()
                                items.append("省")
                                items.append("市")
                                items.append("区")
                                let stackView =  UIStackView()
                                stackView.axis = .horizontal
                                stackView.distribution = .fillEqually
                                stackView.spacing = 133
                                view_content.addSubview(stackView)
                                stackView.snp.makeConstraints { make in
                                                make.top.equalTo(label_title.snp.bottom).offset(27)
                                                make.height.equalTo(25)
                                                make.centerX.equalToSuperview()
                                }
                                for (_,v) in items.enumerated(){
                                                let label     = UILabel()
                                                label.text = v
                                                label.font = UIFont.systemFont(ofSize: 18, weight: .medium)
                                                label.textColor = .black.withAlphaComponent(0.8)
                                                label.textAlignment = .center
                                                stackView.addArrangedSubview(label)
                                }
                                view_content.addSubview(btn_complete)
                                btn_complete.snp.makeConstraints { make in
                                                make.bottom.equalToSuperview().offset(-UIDevice.jq_safeEdges.bottom)
                                                make.centerX.equalToSuperview()
                                                make.width.equalTo(JQ_ScreenW * 0.1487)
                                                make.height.equalTo(47)
                                }
                                view_content.addSubview(picker)
                                picker.delegate = self
                                picker.dataSource = self
                                picker.snp.makeConstraints { make in
                                                make.top.equalTo(label_title.snp.bottom).offset(50)
                                                make.bottom.equalTo(btn_complete.snp.top)
                                                make.centerX.equalToSuperview()
                                                make.width.equalTo(500)
                                }
                                btn_complete.addTarget(self, action: #selector(completeAction), for: .touchUpInside)
                }
                required init?(coder: NSCoder) {
                                fatalError("init(coder:) has not been implemented")
                }
                static func show(title:String,clouse:@escaping (AddressTreeModel?,AddressTreeModel?,AddressTreeModel?)->Void){
                                let pickerView = CityAddressPickerView(frame: sceneDelegate?.window?.frame ?? .zero)
                                pickerView.clouse = clouse
                                sceneDelegate?.window?.addSubview(pickerView)
                                pickerView.setUI()
                                UIView.animate(withDuration: 0.35) {
                                                pickerView.backgroundColor = UIColor.black.withAlphaComponent(0.7)
                                                pickerView.view_content.snp.updateConstraints { make in
                                                                make.bottom.equalToSuperview()
                                                }
                                                pickerView.layoutIfNeeded()
                                }completion: { _ in
                                                Services.addressTree().subscribe(onNext: {data in
                                                                pickerView.items = data.data ?? []
                                                                pickerView.picker.reloadAllComponents()
                                                }).disposed(by: pickerView.disposeBag)
                                }
                }
                @objc func closeAction(){
                                UIView.animate(withDuration: 0.35) {
                                                self.backgroundColor = UIColor.black.withAlphaComponent(0)
                                                self.view_content.snp.updateConstraints { make in
                                                                make.bottom.equalToSuperview().offset(JQ_ScreenW * 0.3278)
                                                }
                                                self.layoutIfNeeded()
                                }completion: { _ in
                                                self.removeFromSuperview()
                                }
                }
                @objc func completeAction(){
                                let provinceIndex = picker.selectedRow(inComponent: 0)
                                let provinceModel = items[provinceIndex]
                                let cityModel = items[provinceIndex].children?[picker.selectedRow(inComponent: 1)]
                                let countryModel = items[provinceIndex].children?[picker.selectedRow(inComponent: 1)].children?[picker.selectedRow(inComponent: 2)]
                                clouse(provinceModel,cityModel,countryModel)
                                closeAction()
                }
}
extension CityAddressPickerView:UIPickerViewDelegate & UIPickerViewDataSource{
                func numberOfComponents(in pickerView: UIPickerView) -> Int {
                                if items.count > 0{
                                                return 3
                                }
                                return 0
                }
                func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
                                return 40
                }
                func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
                                var resultLabel:UILabel?
                                if let label = view as? UILabel{
                                                resultLabel = label
                                }else{
                                                resultLabel = UILabel()
                                                resultLabel!.font = UIFont.systemFont(ofSize: 18, weight: .semibold)
                                                resultLabel!.textColor = UIColor(hexStr: "#3C3C3C")
                                                resultLabel!.textAlignment = .center
                                }
                                if component == 0{
                                                resultLabel!.text = items[row].name
                                }
                                if component == 1{
                                                let v = items[pickerView.selectedRow(inComponent: 0)]
                                                resultLabel!.text = v.children?[row].name ?? ""
                                }
                                if component == 2{
                                                let v = items[pickerView.selectedRow(inComponent: 0)]
                                                let v1 = v.children?[pickerView.selectedRow(inComponent: 1)]
                                                resultLabel!.text = v1?.children?[row].name ?? ""
                                }
                                return resultLabel!
                }
                func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
                                if component == 0 {
                                                return items.count
                                }
                                if component == 1{
                                                let index = pickerView.selectedRow(inComponent: 0)
                                                return items[index].children?.count ?? 0
                                }
                                if component == 2{
                                                let indexI = pickerView.selectedRow(inComponent: 0)
                                                let indexJ = pickerView.selectedRow(inComponent: 1)
                                                return items[indexI].children?[indexJ].children?.count ?? 0
                                }
                                return 0
                }
                func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
                                if component == 0{
                                                pickerView.reloadComponent(1)
                                                pickerView.reloadComponent(2)
                                }
                                if component == 1{
                                                pickerView.reloadComponent(2)
                                }
                }
}
DolphinEnglishLearnStudent/Other/UIView/CommonBannerView.swift
New file
@@ -0,0 +1,208 @@
//
//  CommonBannerView.swift
//  WanPai
//
//  Created by 无故事王国 on 2023/6/30.
//
import UIKit
import SDWebImage
struct CommonBannerModel {
                var index = 0 //自定义索引
                var id:Int? //ID
                var name:String? //名称
                var resource:String? //数据源:URL等
                var mediaType:CommonBannerView.MediaType?
}
class CommonBannerView: UIView, UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
                enum MediaType {
                                case videoUrl,imageUrl,imageLocal,videoLocal
                }
                private lazy var collectionView:UICollectionView = {
                                var layout = UICollectionViewFlowLayout()
                                layout.minimumLineSpacing = 0
                                layout.minimumInteritemSpacing = 0
                                layout.scrollDirection = .horizontal
                                layout.sectionInset = .zero
                                layout.headerReferenceSize = .zero
                                layout.footerReferenceSize = .zero
                                layout.itemSize = CGSize(width: self.width, height: self.height)
                                let collectionView = UICollectionView(frame:CGRect(x: 0, y: 0, width: self.width, height: self.height), collectionViewLayout: layout)
                                collectionView.delegate = self
                                collectionView.dataSource = self
                                collectionView.isPagingEnabled = true
                                collectionView.showsHorizontalScrollIndicator = false
                                collectionView.register(CommonBannerViewCell.self, forCellWithReuseIdentifier: "BannerView")
                                collectionView.decelerationRate = .normal
                                collectionView.contentInsetAdjustmentBehavior = .never
                                collectionView.backgroundColor = .white
                                collectionView.bounces = false
                                return collectionView
                }()
                private lazy var pageControl:UIPageControl = {
                                let control = UIPageControl()
                                control.currentPageIndicatorTintColor = .white
                                control.pageIndicatorTintColor = .gray.withAlphaComponent(0.6)
                                return control
                }()
                private var timer:Timer?
                private var items = [CommonBannerModel]()
                private var selectClouse:((CommonBannerModel)->Void)?
                private var autoRoll:Bool = true
                private var currentPage:Int = 0
                private var timeInterval:Int = 5
                override func awakeFromNib() {
                                super.awakeFromNib()
                                setUI()
                }
                public func setItems(items:[CommonBannerModel],autoRoll:Bool = true,selectClouse:((CommonBannerModel)->Void)? = nil){
                                self.items = items
                                if items.count > 1{
                                                self.items.append(items.first!)
                                }
                                self.autoRoll = autoRoll
                                self.selectClouse = selectClouse
                                if items.count <= 1{self.autoRoll = false}
                                setUI()
                                collectionView.reloadData()
                                if self.autoRoll{
                                                DispatchQueue.main.asyncAfter(deadline: .now()+5) {
                                                                self.startTimer()
                                                }
                                }
                }
                private func setUI(){
                                addSubview(collectionView)
                                collectionView.snp.makeConstraints { make in
                                                make.edges.equalToSuperview()
                                }
                                if items.count > 1{
                                                pageControl.numberOfPages = items.count - 1
                                                addSubview(pageControl)
                                                pageControl.snp.makeConstraints { make in
                                                                make.centerX.equalToSuperview()
                                                                make.bottom.equalToSuperview().offset(-10)
                                                                make.height.equalTo(8)
                                                }
                                }
                }
                private func startTimer(){
                                timer = Timer(timeInterval: TimeInterval(timeInterval), repeats: true, block: {[weak self] t in
                                                guard let weakSelf = self else { return }
                                                var page = weakSelf.collectionView.contentOffset.x / weakSelf.collectionView.width
                                                weakSelf.currentPage = Int(page + 1)
                                                if weakSelf.currentPage >= weakSelf.pageControl.numberOfPages{
                                                                weakSelf.currentPage = 0
                                                                weakSelf.collectionView.setContentOffset(.zero, animated: false)
                                                                weakSelf.pageControl.currentPage = 0
                                                }
                                                weakSelf.collectionView.setContentOffset(CGPoint(x: weakSelf.currentPage * Int(weakSelf.width), y: 0), animated: true)
                                })
                                timer?.fire()
                                RunLoop.current.add(timer!, forMode: .common)
                }
                func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
                                return items.count
                }
                func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
                                let item = items[indexPath.row]
                                selectClouse?(item)
                }
                func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
                                return CGSize(width:collectionView.bounds.size.width,height:ceil(collectionView.bounds.size.height))
                }
                func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
                                print("--开始滑动")
                                timer?.fireDate = Date.distantFuture
                }
                func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
                                let page = scrollView.contentOffset.x / scrollView.width
                                if page.int >= pageControl.numberOfPages{
                                                pageControl.currentPage = 0
                                }else{
                                                pageControl.currentPage = page.int
                                }
                                print("--结束滑动")
                                timer?.fireDate = Date.init(timeIntervalSinceNow: 3.0) //3秒后开启
                }
                func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
                                let page = scrollView.contentOffset.x / scrollView.width
                                if page.int >= pageControl.numberOfPages{
                                                pageControl.currentPage = 0
                                }else{
                                                pageControl.currentPage = page.int
                                }
                }
                func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
                                let page = scrollView.contentOffset.x / scrollView.width
                                if page.int >= pageControl.numberOfPages{
                                                pageControl.currentPage = 0
                                                collectionView.setContentOffset(.zero, animated: false)
                                }else{
                                                pageControl.currentPage = page.int
                                }
                }
                func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
                                let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BannerView", for: indexPath) as! CommonBannerViewCell
                                let item = items[indexPath.row]
                                switch item.mediaType {
                                                case .imageUrl:
                                                                if let i = item.resource{
                                                                                cell.imgView.sd_setImage(with: URL(string: i))
                                                                }
                                                case .imageLocal:
                                                                if let i = item.resource{
                                                                                cell.imgView.image = UIImage(named: i)
                                                                }
                                                default:break
                                }
                                return cell
                }
}
class CommonBannerViewCell: UICollectionViewCell {
                lazy var imgView:UIImageView = {
                                let img = UIImageView()
                                img.contentMode = .scaleToFill
                                return img
                }()
                override init(frame: CGRect) {
                                super.init(frame: frame)
                                contentView.addSubview(imgView)
                                imgView.snp.makeConstraints { make in
                                                make.edges.equalToSuperview()
                                }
                }
                required init?(coder aDecoder: NSCoder) {
                                fatalError("init(coder:) has not been implemented")
                }
}
DolphinEnglishLearnStudent/SceneDelegate.swift
@@ -24,6 +24,10 @@
                                SVProgressHUD.setMaximumDismissTimeInterval(1.5)
                                SVProgressHUD.setDefaultMaskType(.custom)
                                guard let tokenModel = LoginTokenModel.getToken(),!LoginTokenModel.isOverdue() else {
                                                needLogin()
                                                return
                                }
                                loginSuccess()
                }
DolphinEnglishLearnStudent/Services/NetworkRequest.swift
New file
@@ -0,0 +1,278 @@
//
//  NetworkRequest.swift
//  HandyJSON
//
//  Created by Sweet on 2018/12/25.
//  Copyright © 2018 Sweet. All rights reserved.
//
import Foundation
import SwifterSwift
import Alamofire
import HandyJSON
import RxSwift
import SVProgressHUD
import JQTools
// 假设这是服务端返回的统一定义的response格式
struct BaseResponse<T :HandyJSON>: HandyJSON {
                var sysTime: Int = 0
                var code: Int = -1 // 服务端返回码
                var data: T? = nil // 具体的data的格式和业务相关,故用泛型定义
                var msg: String = ""
}
struct BaseData<T: HandyJSON>: HandyJSON {
                var records = [T]()
}
struct SimpleModel: HandyJSON {
}
struct HtmlModel: HandyJSON {
                var  content = ""
                var  content1 = ""
                var id = 0
                var type =  0
}
extension String: HandyJSON{
}
extension Array: HandyJSON{
}
extension Bool: HandyJSON{
}
extension Int: HandyJSON{
}
let SHAKEY = ""
class ParamsAppender: NSObject {
                var url: URL
                var params:Dictionary = [String: Any]()
                private init(url: String){
                                self.url = URL(string: url)!
                }
                @discardableResult
                func interface(url: String) -> ParamsAppender {
                                self.url.appendPathComponent(url)
                                return self
                }
                @discardableResult
                func append(key: String,value: Bool) -> ParamsAppender {
                                params += ["\(key)":"\(value)"]
                                return self
                }
                @discardableResult
                func append(key: String,value: String?) -> ParamsAppender {
                                if value != nil && value?.isEmpty == false {
                                                params += ["\(key)":"\(value!)"]
                                }
                                return self
                }
                @discardableResult
                func append(key: String,value: Array<String>) -> ParamsAppender {
                                if value.isEmpty == false {
                                                params += ["\(key)":value]
                                }
                                return self
                }
                @discardableResult
                func append(key: String, value: Int?) -> ParamsAppender {
                                if value != nil{
                                                params += ["\(key)":value!]
                                }
                                return self
                }
                @discardableResult
                func append(key: String, value: Int64) -> ParamsAppender {
                                params += ["\(key)":value]
                                return self
                }
                @discardableResult
                func append(key: String, value: Double?) -> ParamsAppender {
                                if value != nil{
                                                params += ["\(key)":value!]
                                }
                                return self
                }
                @discardableResult
                func append(key: String,data: Data?) -> ParamsAppender {
                                if data != nil{
                                                params += ["\(key)": data!]
                                }
                                return self
                }
                @discardableResult
                func append(key: String,url: URL) -> ParamsAppender {
                                params += ["\(key)":"\(url)"]
                                return self
                }
                @discardableResult
                func append(dic: [String : Any]) -> ParamsAppender {
                                params += dic
                                return self
                }
                /// 参数加密
                @discardableResult
                func done() -> Parameters {
                                var paramsArray: [String] = []
                                // 排序
                                let sortedArray: [String] = Array(params.keys).sorted()
                                //防止自签名而错误
                                if !sortedArray.contains("sign"){
                                                for item in sortedArray{
                                                                // 拼接字符串
                                                                if params.has(key: item){
                                                                                paramsArray.append("\(item)=\(params[item]!)")
                                                                }
                                                }
                                                let content = paramsArray.joined(separator: "&")
                                                params += ["sign": "\(content.jq_hmacBase64(algorithm: .SHA1, key: SHAKEY))"]
#if DEBUG
                                                LogInfo("签名:\(content) ----- \(content.jq_hmacBase64(algorithm: .SHA1, key: SHAKEY))")
#endif
                                }
                                return self.params
                }
                class func build(url: String) -> ParamsAppender {
                                return ParamsAppender(url: url)
                }
}
class NetworkRequest {
                static let sharedSessionManager: Alamofire.Session = {
                                let configuration = URLSessionConfiguration.default
                                configuration.timeoutIntervalForRequest = 10
                                return Alamofire.Session(configuration: configuration)
                }()
                enum NetRequestError: Error {
                                case Other(Int,String)
                                case URLNotFound
                                case DownloadFailed
                                case InvaildSession
                                case ModelError(String)
                                case DataAnalysis(String)
                }
                class func request<T: HandyJSON>(params: ParamsAppender, method: HTTPMethod,  encoding: ParameterEncoding? = nil, progress: Bool = true,ignoreAlert:Bool = false) -> Observable<BaseResponse<T>>{
                                return Observable<BaseResponse<T>>.create{ ob in
                                                guard NetworkReachabilityManager.init(host: All_Url)!.isReachable else {
                                                                alertError(msg: "当前网络不可用")
                                                                ob.onError(AFError.invalidURL(url: params.url))
                                                                return Disposables.create{}
                                                }
                                                if progress {showHUD()}
                                                var headers = HTTPHeaders()
                                                if let token = LoginTokenModel.getToken()?.access_token{
                                                                headers.add(name: "Authorization", value: "Bearer" + " " + token)
                                                                LogInfo("USER_token:Bearer \(token)")
                                                }
                                                if encoding is JSONEncoding {
                                                                headers.add(name: "Content-Type", value: "application/json;charset=UTF-8")
                                                }
                                                var newEncoding: ParameterEncoding
                                                if encoding != nil {
                                                                newEncoding = encoding!
                                                } else {
                                                                newEncoding = method == .post ? URLEncoding.httpBody : URLEncoding.queryString
                                                }
                                                sharedSessionManager.request(params.url.absoluteString, method: method, parameters:params.done(), encoding: newEncoding, headers:headers).validate().responseData{response in
                                                                LogInfo("请求地址:\(params.url)")
                                                                LogInfo("请求参数:\(params.params)")
                                                                hiddenHUD()
                                                                guard response.error == nil else {
                                                                                LogError("\(response.error!)")
                                                                                var errorString = ""
                                                                                errorString.append("服务器故障:\(response.error!.localizedDescription)")
                                                                                if let code = response.error?.responseCode{
                                                                                                errorString.append("\n【错误码:\(code)】")
                                                                                }
                                                                                if !ignoreAlert{
                                                                                                alert(msg: errorString)
                                                                                }
                                                                                ob.onError(response.error!)
                                                                                return
                                                                }
                                                                if let data = response.data,let jsonString = String(data: data, encoding: String.Encoding.utf8){
                                                                                LogResponse(try! JSONSerialization.jsonObject(with: data))
                                                                                if let next = BaseResponse<T>.deserialize(from: jsonString){
                                                                                                switch next.code{
                                                                                                                case 200:ob.onNext(next)
                                                                                                                case 504: //登录设备最大限制
                                                                                                                                let attribute = AttributedStringbuilder.build().add(string:next.msg, withFont: .systemFont(ofSize: 14, weight: .medium), withColor: .black.withAlphaComponent(0.6)).mutableAttributedString
//                                                                                                                                CommonAlertView.show(title: "提示", attribute: attribute, cancelStr: "关闭", completeStr: "关闭", isSingle: true, customBtnWidth: JQ_ScreenW - 170) { _ in
//
//                                                                                                                                }
                                                                                                                case 503: //登录被冻结
                                                                                                                                let attribute = AttributedStringbuilder.build().add(string:next.msg, withFont: .systemFont(ofSize: 14, weight: .medium), withColor: .black.withAlphaComponent(0.6)).mutableAttributedString
//                                                                                                                                CommonAlertView.show(title: "提示", attribute: attribute, cancelStr: "关闭", completeStr: "关闭", isSingle: true, customBtnWidth: JQ_ScreenW - 170) { _ in
//
//                                                                                                                                }
//                                                                                                                case 501:
//                                                                                                                                CommonAlertView.show(title: "提示", content: next.msg,isSingle: true) { _ in
//
//                                                                                                                                }
//                                                                                                                                ob.onError(NetRequestError.InvaildSession)
                                                                                                                case 401:
                                                                                                                                if !ignoreAlert{
                                                                                                                                                alertError(msg: "登录失效,请重新登录");ob.onError(NetRequestError.InvaildSession)
                                                                                                                                }
                                                                                                                                sceneDelegate?.needLogin()
                                                                                                                default:
                                                                                                                                if !ignoreAlert{
                                                                                                                                                alertError(msg: "\(next.msg)")
                                                                                                                                }
                                                                                                                                ob.onError(NetRequestError.Other(next.code,next.msg))
                                                                                                }
                                                                                }
                                                                }
                                                                ob.onCompleted()
                                                }
                                                return Disposables.create{}
                                }
                }
}
extension Dictionary {
                mutating func append(dict: Dictionary) {
                                dict.forEach { (key, value) in
                                                self.updateValue(value, forKey: key)
                                }
                }
}
func createError(text:String,code:Int)->AFError{
                return AFError.createURLRequestFailed(error: NSError(domain: text, code: code))
}
DolphinEnglishLearnStudent/Services/Services.swift
New file
@@ -0,0 +1,213 @@
//
//  Services.swift
//  YixiuShop
//
//  Created by Sweet on 2019/9/30.
//  Copyright © 2019 jackLove. All rights reserved.
//
import UIKit
import RxSwift
import Alamofire
import JQTools
#if DEBUG
let All_Url = "http://192.168.110.237:9000"
#else
let All_Url = "http://192.168.110.237:9000"
#endif
class Services: NSObject {
}
extension Services{
                class func weekList(quarter:Int)->Observable<BaseResponse<[ListenWeekModel]>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/study/base/study/weekList")
                                params.append(key: "quarter", value: quarter)
                                return NetworkRequest.request(params: params, method: .get, progress: false)
                }
}
// MARK: -- 登录部分
extension Services{
                class func sendPhoneCode(phone:String)->Observable<BaseResponse<SimpleModel>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/study/base/user/sendPhoneCode")
                                params.append(key: "phone", value: phone)
                                return NetworkRequest.request(params: params, method: .get, progress: true)
                }
                class func login(phone:String,code:String)->Observable<BaseResponse<LoginModel>>{
                                                let params = ParamsAppender.build(url: All_Url)
                                                params.interface(url: "/study/base/user/studyLogin")
                                params.append(key: "phone", value: phone)
                                params.append(key: "phoneCode", value: code)
                                return NetworkRequest.request(params: params, method: .post,encoding: JSONEncoding.default, progress: true)
                }
}
// MARK: -- 首页
extension Services{
}
// MARK: -- 商品
extension Services{
                class func goodRecommend()->Observable<BaseResponse<[RecommendModel]>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/study/base/study/goodRecommend")
                                return NetworkRequest.request(params: params, method: .get, progress: false)
                }
                class func goodsList(keywords:String,page:Int,pageSize:Int = 20,type:[String])->Observable<BaseResponse<BaseResponseList<MarketModel>>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/goods/base/goods/goodListStudy")
                                                .append(key: "keywords", value: keywords)
                                                .append(key: "pageNumber", value: page)
                                                .append(key: "pageSize", value: pageSize)
                                                .append(key: "type", value: type)
                                return NetworkRequest.request(params: params, method: .post,encoding: JSONEncoding.default, progress: false)
                }
                class func getIntegral()->Observable<BaseResponse<Int>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/study/base/study/getIntegralStudy")
                                return NetworkRequest.request(params: params, method: .get, progress: false)
                }
                class func goodTypeStudy()->Observable<BaseResponse<[MarketTypeModel]>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/goods/base/goods/goodTypeStudy")
                                return NetworkRequest.request(params: params, method: .get, progress: false)
                }
                class func goodsDetail(goodsId:Int)->Observable<BaseResponse<MarketDetailModel>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/goods/base/goods/goodDetail")
                                                .append(key: "goodId", value: goodsId)
                                return NetworkRequest.request(params: params, method: .get, progress: true)
                }
                class func addressList()->Observable<BaseResponse<[AddressModel]>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/goods/base/goods/shopAddress")
                                return NetworkRequest.request(params: params, method: .get, progress: false)
                }
                class func deleteAddress(id:Int)->Observable<BaseResponse<SimpleModel>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/goods/base/goods/addressDelete")
                                                .append(key: "id", value: id)
                                return NetworkRequest.request(params: params, method: .get, progress: true)
                }
                class func redeemNow(goodId:Int)->Observable<BaseResponse<MarketDetailModel>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/goods/base/goods/redeemNow")
                                                .append(key: "goodId", value: goodId)
                                return NetworkRequest.request(params: params, method: .get, progress: true)
                }
                class func addressTree()->Observable<BaseResponse<[AddressTreeModel]>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/goods/base/goods/addressTree")
                                return NetworkRequest.request(params: params, method: .get, progress: true)
                }
                class func addressSaveOrUpdate(id:Int?,province:AddressTreeModel?,city:AddressTreeModel?,country:AddressTreeModel?,userName:String?,userPhone:String?,isDefault:Bool,detailAddress:String)->Observable<BaseResponse<SimpleModel>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/goods/base/goods/addressSaveOrUpdate")
                                                .append(key: "id", value: id)
                                                .append(key: "address", value: detailAddress)
                                                .append(key: "city", value: city?.name)
                                                .append(key: "cityCode", value: city?.code)
                                                .append(key: "province", value: province?.name)
                                                .append(key: "provinceCode", value: province?.code)
                                                .append(key: "recipient", value: userName)
                                                .append(key: "recipientPhone", value: userPhone)
                                                .append(key: "isDefault", value: (isDefault == true) ? 1:0)
                                return NetworkRequest.request(params: params, method: .post,encoding: JSONEncoding.default, progress: true)
                }
                class func setDefaultStudy(id:Int)->Observable<BaseResponse<SimpleModel>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/goods/base/goods/setDefaultStudy")
                                                .append(key: "id", value: id)
                                return NetworkRequest.request(params: params, method: .get, progress: true)
                }
                class func goodsExchangeStudy(goodsId:Int,number:Int,orderNumber:String,recipientId:Int,remark:String)->Observable<BaseResponse<SimpleModel>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/goods/base/goods/goodExchangeStudy")
                                                .append(key: "goodId", value: goodsId)
                                                .append(key: "number", value: number)
                                                .append(key: "orderNumber", value: orderNumber)
                                                .append(key: "recipientId", value: recipientId)
                                                .append(key: "remark", value: remark)
                                return NetworkRequest.request(params: params, method: .post,encoding: JSONEncoding.default, progress: true)
                }
                class func userInfo()->Observable<BaseResponse<UserInfoModel>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/study/base/user/userInfo")
                                return NetworkRequest.request(params: params, method: .get, progress: false)
                }
                class func integralDetail(pageNum:Int,pageSize:Int = 20,time:String?)->Observable<BaseResponse<BaseResponseList<IntegralModel>>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/study/base/study/integralDetail")
                                                .append(key: "pageNum", value: pageNum)
                                                .append(key: "pageSize", value: pageSize)
                                                .append(key: "time", value: time)
                                return NetworkRequest.request(params: params, method: .get, progress: false)
                }
                class func exchangeRecord()->Observable<BaseResponse<[ExchangeRecordModel]>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/goods/base/goods/exchangeRecord")
                                return NetworkRequest.request(params: params, method: .get, progress: false)
                }
                class func studyGamesRecord()->Observable<BaseResponse<StudyGamesModel>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/study/base/study/record")
                                return NetworkRequest.request(params: params, method: .get, progress: true)
                }
                class func logoutStudy()->Observable<BaseResponse<SimpleModel>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/study/base/user/logoutStudy")
                                return NetworkRequest.request(params: params, method: .post,encoding: JSONEncoding.default, progress: true)
                }
}
extension Services{
                class func getAgreement(type:AgreementType)->Observable<BaseResponse<String>>{
                                let params = ParamsAppender.build(url: All_Url)
                                params.interface(url: "/study/base/user/getProtocol")
                                params.append(key: "type", value: type.rawValue)
                                return NetworkRequest.request(params: params, method: .post, progress: true)
                }
}
extension Services{
                static func startNetworkMonitor(){
                                let manager = NetworkReachabilityManager(host: All_Url)
                                manager?.startListening(onUpdatePerforming: { status in
                                                switch status {
                                                                case .notReachable:alertError(msg: "当前网络不可用")
                                                                case .reachable(let type):
                                                                                switch type{
                                                                                                case .ethernetOrWiFi:alert(msg: "当前为Wi-Fi网络")
                                                                                                case .cellular:alert(msg: "当前为移动网络")
                                                                                }
                                                                default:break
                                                }
                                })
                }
}
DolphinEnglishLearnStudent/ViewModel/RefreshModel.swift
New file
@@ -0,0 +1,305 @@
//
//  RefreshModel.swift
//  WanPai
//
//  Created by 无故事王国 on 2023/7/11.
//
import UIKit
import MJRefresh
import RxSwift
import HandyJSON
import RxRelay
enum RefreshState {
                case refreshing
                case completedRefresh
                case moreLoading
                case completedLoad
                case completedLoadWithNoMoreData
}
struct BaseResponseList<T :HandyJSON>: HandyJSON {
                var records: [T] = []
                var current:Int = 0
                var hasNextPage:Bool = false
                var hasPrevPage:Bool = false
                var pages:Int = 0
                var size:Int = 0
                var startIndex:Int = 0
                var total:Int = 0
}
protocol RefreshModelProctol {
                associatedtype T:HandyJSON
                func api()->(Observable<BaseResponse<[T]>>)?
}
protocol RefreshModelInnerProctol {
                associatedtype T:HandyJSON
                func api()->(Observable<BaseResponse<BaseResponseList<T>>>)?
}
class RefreshModel<T:HandyJSON>:RefreshModelProctol{
                func api() -> (RxSwift.Observable<BaseResponse<[T]>>)? {return nil}
                let disposeBag = DisposeBag()
                enum RefreshType {case refresh,load}
                private var handle:UIScrollView!
                lazy var refreshSubject = PublishSubject<RefreshState>()
                var page:Int = 1
                var pageSize:Int = 20
                private var needRefreshData:Bool = true
                lazy var dataSource = BehaviorRelay<[T]>(value: [])
                func resetPage(){
                                page = 1
                }
                func configure(_ scrollView:UITableView,needMore:Bool = true){
                                scrollView.mj_header = CustomRefreshHeaer.refreshing(with: refreshData())
                                if needMore{
                                                scrollView.mj_footer = CustomRefreshFooter.refreshing(with: loadMoreData())
                                }
                                refreshSubject.bind(to: scrollView.rx.handlestatus()).disposed(by: disposeBag)
                                handle = scrollView
                }
                func configure(_ scrollView:UICollectionView,needMore:Bool = true,needRefreshData:Bool = true){
                                self.needRefreshData = needRefreshData
                                scrollView.mj_header = CustomRefreshHeaer.refreshing(with: refreshData())
                                if needMore{
                                                scrollView.mj_footer = CustomRefreshFooter.refreshing(with: loadMoreData())
                                }
                                refreshSubject.bind(to: scrollView.rx.handlestatus()).disposed(by: disposeBag)
                                handle = scrollView
                }
                func beginRefresh(){
                                guard handle != nil else {return}
                                api()?.subscribe(onNext: { data in
                                                if let datas = data.data{
                                                                self.dataSource.accept(datas)
                                                                self.refreshSubject.onNext(.completedRefresh)
                                                }
                                }).disposed(by: disposeBag)
                }
                func refreshData() ->(()->Void) {
                                return {self.request(status: .refresh)}
                }
                func loadMoreData() ->(()->Void) {
                                return {self.request(status: .load)}
                }
                func request(status:RefreshType){
                                switch status {
                                                case .refresh:
                                                                self.page = 1
                                                                self.refreshSubject.onNext(.refreshing)
                                                case .load:
                                                                self.page += 1
                                                                self.refreshSubject.onNext(.moreLoading)
                                }
                                api()?.subscribe(onNext: { data in
                                                if let datas = data.data{
                                                                switch status{
                                                                                case .refresh:
                                                                                                self.dataSource.accept(datas)
                                                                                                self.refreshSubject.onNext(.completedRefresh)
                                                                                case .load:
                                                                                                self.dataSource.accept(self.dataSource.value + datas)
                                                                                                if datas.count == 0{
                                                                                                                self.refreshSubject.onNext(.completedLoadWithNoMoreData)
                                                                                                }else{
                                                                                                                self.refreshSubject.onNext(.completedLoad)
                                                                                                }
                                                                }
                                                }else{
                                                                self.refreshSubject.onNext(.completedLoadWithNoMoreData)
                                                }
                                }, onError: { error in
                                                self.refreshSubject.onNext(.completedLoad)
                                }).disposed(by: disposeBag)
                }
}
class RefreshInnerModel<T:HandyJSON>:RefreshModelInnerProctol{
                func api() -> (RxSwift.Observable<BaseResponse<BaseResponseList<T>>>)? {
                                return nil
                }
                let disposeBag = DisposeBag()
                enum RefreshType {case refresh,load}
                private var handle:UIScrollView!
                lazy var refreshSubject = PublishSubject<RefreshState>()
                var page:Int = 0
                var pageSize:Int = 20
                private var needRefreshData:Bool = true
                lazy var dataSource = BehaviorRelay<BaseResponseList<T>?>(value: nil)
                func configure(_ scrollView:UITableView,needMore:Bool = true){
                                scrollView.mj_header = CustomRefreshHeaer.refreshing(with: refreshData())
                                if needMore{
                                                scrollView.mj_footer = CustomRefreshFooter.refreshing(with: loadMoreData())
                                }
                                refreshSubject.bind(to: scrollView.rx.handlestatus()).disposed(by: disposeBag)
                                handle = scrollView
                }
                func configure(_ scrollView:UICollectionView,needMore:Bool = true,needRefreshData:Bool = true){
                                self.needRefreshData = needRefreshData
                                scrollView.mj_header = CustomRefreshHeaer.refreshing(with: refreshData())
                                if needMore{
                                                scrollView.mj_footer = CustomRefreshFooter.refreshing(with: loadMoreData())
                                }
                                refreshSubject.bind(to: scrollView.rx.handlestatus()).disposed(by: disposeBag)
                                handle = scrollView
                }
                func resetPage(){
                                page = 1
                }
                func beginRefresh(){
                                handle.mj_header?.beginRefreshing()
                }
                func refreshData() ->(()->Void) {
                                return {self.request(status: .refresh)}
                }
                func loadMoreData() ->(()->Void) {
                                return {self.request(status: .load)}
                }
                func request(status:RefreshType){
                                switch status {
                                                case .refresh:
                                                                self.page = 1
                                                                self.refreshSubject.onNext(.refreshing)
                                                case .load:
                                                                self.page += 1
                                                                self.refreshSubject.onNext(.moreLoading)
                                }
                                api()?.subscribe(onNext: { data in
                                                switch status{
                                                                case .refresh:
                                                                                self.dataSource.accept(data.data)
                                                                                self.refreshSubject.onNext(.completedRefresh)
                                                                case .load:
                                                                                var new = self.dataSource.value?.records ?? []
                                                                                new.append(contentsOf: data.data?.records ?? [])
                                                                                var model = self.dataSource.value
                                                                                model!.records = new
                                                                                self.dataSource.accept(model)
                                                                                if data.data?.records.count == 0{
                                                                                                self.refreshSubject.onNext(.completedLoadWithNoMoreData)
                                                                                }else{
                                                                                                self.refreshSubject.onNext(.completedLoad)
                                                                                }
                                                }
                                }, onError: { error in
                                                self.refreshSubject.onNext(.completedLoad)
                                }).disposed(by: disposeBag)
                }
}
class CustomRefreshHeaer:MJRefreshNormalHeader{
                static func refreshing(with refreshingBlock: @escaping MJRefreshComponentAction) -> MJRefreshNormalHeader? {
                                let refreshHeader = MJRefreshNormalHeader(refreshingBlock: refreshingBlock)
                                return refreshHeader
                }
}
class CustomRefreshFooter:MJRefreshAutoNormalFooter{
                static func refreshing(with refreshingBlock: @escaping MJRefreshComponentAction) -> MJRefreshAutoNormalFooter? {
                                let refrehFooter = MJRefreshAutoNormalFooter(refreshingBlock: refreshingBlock)
                                refrehFooter.isRefreshingTitleHidden = true
                                refrehFooter.stateLabel?.isHidden = true
                                return refrehFooter
                }
}
extension Reactive where Base : UITableView {
                func handlestatus() -> Binder<RefreshState> {
                                return Binder(self.base) { (tableView, status) in
                                                switch status {
                                                                case .moreLoading:
                                                                                self.base.mj_footer?.beginRefreshing()
                                                                case .refreshing:
                                                                                self.base.reloadData()
                                                                                self.base.mj_footer?.resetNoMoreData()
                                                                                self.base.mj_header?.beginRefreshing()
                                                                case .completedLoadWithNoMoreData:
                                                                                DispatchQueue.main.async {
                                                                                                self.base.reloadData()
                                                                                }
                                                                                (self.base.mj_footer as? MJRefreshAutoNormalFooter)?.stateLabel?.isHidden = false
                                                                                self.base.mj_footer?.endRefreshingWithNoMoreData()
                                                                                self.base.mj_header?.endRefreshing()
                                                                case .completedLoad:
                                                                                DispatchQueue.main.async {
                                                                                                self.base.reloadData()
                                                                                }
                                                                                self.base.mj_footer?.endRefreshing()
                                                                                self.base.mj_header?.endRefreshing()
                                                                default:
                                                                                DispatchQueue.main.async {
                                                                                                self.base.reloadData()
                                                                                }
                                                                                self.base.mj_header?.endRefreshing()
                                                                                self.base.mj_footer?.endRefreshing()
                                                }
                                }
                }
}
extension Reactive where Base : UICollectionView {
                func handlestatus() -> Binder<RefreshState> {
                                return Binder(self.base) { (tableView, status) in
                                                switch status {
                                                                case .moreLoading:
                                                                                self.base.mj_footer?.beginRefreshing()
                                                                case .refreshing:
                                                                                self.base.reloadData()
                                                                                self.base.mj_footer?.resetNoMoreData()
                                                                                self.base.mj_header?.endRefreshing()
                                                                case .completedLoadWithNoMoreData:
                                                                                DispatchQueue.main.async {
                                                                                                self.base.reloadData()
                                                                                }
                                                                                self.base.mj_footer?.endRefreshingWithNoMoreData()
                                                                case .completedLoad:
                                                                                DispatchQueue.main.async {
                                                                                                self.base.reloadData()
                                                                                }
                                                                                self.base.mj_footer?.endRefreshing()
                                                                default:
                                                                                DispatchQueue.main.async {
                                                                                                self.base.reloadData()
                                                                                }
                                                                                self.base.mj_header?.endRefreshing()
                                                                                self.base.mj_footer?.endRefreshing()
                                                }
                                }
                }
}
DolphinEnglishLearnStudent/ViewModel/UserViewModel.swift
New file
@@ -0,0 +1,55 @@
//
//  UserViewModel.swift
//  DolphinEnglishLearnStudent
//
//  Created by 无故事王国 on 2024/5/30.
//
import Foundation
import UserDefaultsStore
import HandyJSON
struct UserInfoModel:HandyJSON,Identifiable,Codable{
                static let idKey = \UserInfoModel.id
                var id: Int = 0
                var account: String = ""
                var birthday: String = ""
                var createBy: String = ""
                var createTime: String = ""
                var disabled: Bool = false
                var gender: Int = 0
                var headImg: String = ""
                var insertTime: String = ""
                var integral: Int = 0
                var isVip: Int = 0
                var name: String = ""
                var openId: String = ""
                var password: String = ""
                var phone: String = ""
                var state: Int = 0
                var updateBy: String = ""
                var updateTime: String = ""
                var vipEndTime: String = ""
                var vipPayTime: String = ""
}
class UserViewModel{
                private static let userInfo = UserDefaultsStore<UserInfoModel>(uniqueIdentifier: "UserInfoModel")!
                static func saveUserInfo(_ model:UserInfoModel){
                                do{
                                                try UserViewModel.userInfo.save(model)
                                }catch{
                                }
                }
                static func getUserInfo()->UserInfoModel?{
                                return UserViewModel.userInfo.allObjects().first
                }
                static func clearUserInfo(){
                                UserViewModel.userInfo.deleteAll()
                }
}
Podfile
@@ -13,5 +13,6 @@
#  pod 'Alamofire' # 网络请求框架
  pod 'SVProgressHUD' # 提示框组件
  pod 'CryptoSwift' # 常用加密算法
    pod 'Alamofire' # 网络请求框架
end