基础功能提交

This commit is contained in:
cc 2025-07-27 12:33:06 +08:00
commit c2df67d32f
103 changed files with 6433 additions and 0 deletions

152
.gitignore vendored Normal file
View File

@ -0,0 +1,152 @@
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Obj-C/Swift specific
*.hmap
## App packaging
*.ipa
*.dSYM.zip
*.dSYM
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
Packages/
Package.pins
Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
.swiftpm
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build/
# Accio dependency management
Dependencies/
.accio/
# fastlane
#
# It is recommended to not store the screenshots in the git repository.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
# Mac OS X
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# 自定义排除规则
# 排除LocationHelper.swift文件
IOS_study/Library/LocationHelper.swift
# 排除编译生成的文件
*.o
*.a
*.so
*.dylib
*.framework
*.app
*.ipa
*.dSYM
*.xcarchive
# 排除Xcode用户特定文件
*.xcuserstate
*.xcuserdata/
*.xcworkspace/xcuserdata/
# 排除临时文件
*.tmp
*.temp
*.swp
*.swo
*~
# 排除日志文件
*.log
# 排除配置文件中的敏感信息
*.plist
!IOS-study-Info.plist
# 排除图片文件(如果需要)
# IOS_study/Img/

13
IOS-study-Info.plist Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>maps</string>
<string>iosamap</string>
<string>baidumap</string>
<string>qqmap</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,750 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
940E13EB2E2E06F70026F8A5 /* MJRefresh in Frameworks */ = {isa = PBXBuildFile; productRef = 940E13EA2E2E06F70026F8A5 /* MJRefresh */; };
940E13EE2E2E07110026F8A5 /* LeanCloud in Frameworks */ = {isa = PBXBuildFile; productRef = 940E13ED2E2E07110026F8A5 /* LeanCloud */; };
940E13F12E2E07300026F8A5 /* DotLottie in Frameworks */ = {isa = PBXBuildFile; productRef = 940E13F02E2E07300026F8A5 /* DotLottie */; };
940E13F42E2E07470026F8A5 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 940E13F32E2E07470026F8A5 /* Kingfisher */; };
940E13F82E2F42000026F8A5 /* CompanyGalleryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940E13F72E2F42000026F8A5 /* CompanyGalleryView.swift */; };
940E13FA2E2FA2660026F8A5 /* CompanyGalleryORView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940E13F92E2FA2660026F8A5 /* CompanyGalleryORView.swift */; };
940E13FC2E2FA63B0026F8A5 /* PinchZoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940E13FB2E2FA63B0026F8A5 /* PinchZoomView.swift */; };
941830A12E252FA90004042F /* DBUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 941830A02E252FA90004042F /* DBUser.swift */; };
9418310C2E2533520004042F /* UserManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9418310B2E2533520004042F /* UserManager.swift */; };
941831EF2E25E6AC0004042F /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 941831EE2E25E6AC0004042F /* ProfileView.swift */; };
941831F12E25E6F10004042F /* AuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 941831F02E25E6F10004042F /* AuthViewModel.swift */; };
941831F32E2634BF0004042F /* ProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 941831F22E2634BF0004042F /* ProfileViewModel.swift */; };
941831F52E2638080004042F /* PostCompanyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 941831F42E2638070004042F /* PostCompanyView.swift */; };
941831F72E26626F0004042F /* StorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 941831F62E26626F0004042F /* StorageManager.swift */; };
943D8EB62E29E2870079191A /* ProfileView+ComUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943D8EB52E29E2870079191A /* ProfileView+ComUI.swift */; };
9441C9742E323A7B00805BF3 /* PLJobsView+JobUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9441C9732E323A7B00805BF3 /* PLJobsView+JobUI.swift */; };
9441C9762E325A7E00805BF3 /* Reportmanager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9441C9752E325A7E00805BF3 /* Reportmanager.swift */; };
9441C9782E325B4800805BF3 /* Report.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9441C9772E325B4800805BF3 /* Report.swift */; };
9441C9B82E33432500805BF3 /* PLJobView+RevUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9441C9B72E33432500805BF3 /* PLJobView+RevUI.swift */; };
9441C9BA2E3345D100805BF3 /* PostReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9441C9B92E3345D100805BF3 /* PostReviewView.swift */; };
9441C9BD2E33462000805BF3 /* ReviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9441C9BC2E33462000805BF3 /* ReviewViewModel.swift */; };
9441C9BF2E33465400805BF3 /* Review.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9441C9BE2E33465400805BF3 /* Review.swift */; };
9474AFCA2E14CAFC004AA9AB /* PLJobManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474AFC92E14CAFC004AA9AB /* PLJobManager.swift */; };
9474AFCC2E155E22004AA9AB /* LeanCloud+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9474AFCB2E155E22004AA9AB /* LeanCloud+.swift */; };
94790B4C2E1179A600BF0A09 /* CJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B342E1179A600BF0A09 /* CJob.swift */; };
94790B4D2E1179A600BF0A09 /* CUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B352E1179A600BF0A09 /* CUser.swift */; };
94790B4E2E1179A600BF0A09 /* GView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B362E1179A600BF0A09 /* GView.swift */; };
94790B4F2E1179A600BF0A09 /* View+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B382E1179A600BF0A09 /* View+.swift */; };
94790B502E1179A600BF0A09 /* View+Label.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B392E1179A600BF0A09 /* View+Label.swift */; };
94790B512E1179A600BF0A09 /* View+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B3A2E1179A600BF0A09 /* View+Style.swift */; };
94790B522E1179A600BF0A09 /* OverflowGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B3E2E1179A600BF0A09 /* OverflowGrid.swift */; };
94790B532E1179A600BF0A09 /* PLJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B402E1179A600BF0A09 /* PLJob.swift */; };
94790B542E1179A600BF0A09 /* IOS_studyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B452E1179A600BF0A09 /* IOS_studyApp.swift */; };
94790B552E1179A600BF0A09 /* PLJobCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B462E1179A600BF0A09 /* PLJobCellView.swift */; };
94790B562E1179A600BF0A09 /* PLJobsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B472E1179A600BF0A09 /* PLJobsView.swift */; };
94790B572E1179A600BF0A09 /* PLJobView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B482E1179A600BF0A09 /* PLJobView.swift */; };
94790B582E1179A600BF0A09 /* PostPLJobIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B492E1179A600BF0A09 /* PostPLJobIView.swift */; };
94790B592E1179A600BF0A09 /* TaBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B4A2E1179A600BF0A09 /* TaBarView.swift */; };
94790B5B2E1179A600BF0A09 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 94790B422E1179A600BF0A09 /* Preview Assets.xcassets */; };
94790B5C2E1179A600BF0A09 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 94790B442E1179A600BF0A09 /* Assets.xcassets */; };
94790B5E2E117AD800BF0A09 /* PLJobViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B5D2E117AD800BF0A09 /* PLJobViewModel.swift */; };
94790B602E11846F00BF0A09 /* Foundation+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B5F2E11846F00BF0A09 /* Foundation+.swift */; };
94790B622E118B3600BF0A09 /* PostPLJobIView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B612E118B3600BF0A09 /* PostPLJobIView+.swift */; };
94790B642E118CB400BF0A09 /* HUDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B632E118CB400BF0A09 /* HUDView.swift */; };
94790B662E129CC500BF0A09 /* GType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B652E129CC500BF0A09 /* GType.swift */; };
94790B682E137DB300BF0A09 /* PostJobView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B672E137DB300BF0A09 /* PostJobView.swift */; };
94790B6A2E13820500BF0A09 /* TaBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B692E13820500BF0A09 /* TaBarViewModel.swift */; };
94790B6C2E13879400BF0A09 /* PLJobView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94790B6B2E13879300BF0A09 /* PLJobView+.swift */; };
949F8B282E30C5210013AB3A /* ProfileViewModel+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 949F8B272E30C5210013AB3A /* ProfileViewModel+.swift */; };
949F8B2A2E30CCFB0013AB3A /* ProfileView+JobUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 949F8B292E30CCFB0013AB3A /* ProfileView+JobUI.swift */; };
94F008672E16345100989E46 /* GFunc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F008662E16345100989E46 /* GFunc.swift */; };
94F008692E16749B00989E46 /* RESTManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F008682E16749B00989E46 /* RESTManager.swift */; };
94F0E24E2E1CFA00003406F0 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F0E24D2E1CFA00003406F0 /* NetworkMonitor.swift */; };
94F0E2562E1F6530003406F0 /* LocationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F0E2552E1F6530003406F0 /* LocationHelper.swift */; };
94F0E2582E1F87D2003406F0 /* OF.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F0E2572E1F87D2003406F0 /* OF.swift */; };
94F0E25A2E1F996A003406F0 /* PLJobsView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F0E2592E1F996A003406F0 /* PLJobsView+.swift */; };
94F0E25D2E1FC171003406F0 /* SignInPhoneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F0E25C2E1FC170003406F0 /* SignInPhoneView.swift */; };
94F0E25F2E1FC186003406F0 /* SignInPhoneViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F0E25E2E1FC186003406F0 /* SignInPhoneViewModel.swift */; };
94F0E2622E1FC2D5003406F0 /* SettginsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F0E2612E1FC2D5003406F0 /* SettginsView.swift */; };
94F0E2642E1FDAB2003406F0 /* AuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F0E2632E1FDAB2003406F0 /* AuthManager.swift */; };
94F0E2662E1FFE58003406F0 /* SignInPhoneView+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F0E2652E1FFE58003406F0 /* SignInPhoneView+.swift */; };
94F0E2682E23FB7E003406F0 /* AgreeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F0E2672E23FB7E003406F0 /* AgreeView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
940E13F72E2F42000026F8A5 /* CompanyGalleryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompanyGalleryView.swift; sourceTree = "<group>"; };
940E13F92E2FA2660026F8A5 /* CompanyGalleryORView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompanyGalleryORView.swift; sourceTree = "<group>"; };
940E13FB2E2FA63B0026F8A5 /* PinchZoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinchZoomView.swift; sourceTree = "<group>"; };
941830A02E252FA90004042F /* DBUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBUser.swift; sourceTree = "<group>"; };
9418310B2E2533520004042F /* UserManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserManager.swift; sourceTree = "<group>"; };
941831EE2E25E6AC0004042F /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
941831F02E25E6F10004042F /* AuthViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewModel.swift; sourceTree = "<group>"; };
941831F22E2634BF0004042F /* ProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewModel.swift; sourceTree = "<group>"; };
941831F42E2638070004042F /* PostCompanyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCompanyView.swift; sourceTree = "<group>"; };
941831F62E26626F0004042F /* StorageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageManager.swift; sourceTree = "<group>"; };
943D8EB52E29E2870079191A /* ProfileView+ComUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+ComUI.swift"; sourceTree = "<group>"; };
9441C9732E323A7B00805BF3 /* PLJobsView+JobUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PLJobsView+JobUI.swift"; sourceTree = "<group>"; };
9441C9752E325A7E00805BF3 /* Reportmanager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reportmanager.swift; sourceTree = "<group>"; };
9441C9772E325B4800805BF3 /* Report.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Report.swift; sourceTree = "<group>"; };
9441C9B72E33432500805BF3 /* PLJobView+RevUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PLJobView+RevUI.swift"; sourceTree = "<group>"; };
9441C9B92E3345D100805BF3 /* PostReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostReviewView.swift; sourceTree = "<group>"; };
9441C9BC2E33462000805BF3 /* ReviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewViewModel.swift; sourceTree = "<group>"; };
9441C9BE2E33465400805BF3 /* Review.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Review.swift; sourceTree = "<group>"; };
9474AFC92E14CAFC004AA9AB /* PLJobManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PLJobManager.swift; sourceTree = "<group>"; };
9474AFCB2E155E22004AA9AB /* LeanCloud+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LeanCloud+.swift"; sourceTree = "<group>"; };
94790B342E1179A600BF0A09 /* CJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CJob.swift; sourceTree = "<group>"; };
94790B352E1179A600BF0A09 /* CUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CUser.swift; sourceTree = "<group>"; };
94790B362E1179A600BF0A09 /* GView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GView.swift; sourceTree = "<group>"; };
94790B382E1179A600BF0A09 /* View+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+.swift"; sourceTree = "<group>"; };
94790B392E1179A600BF0A09 /* View+Label.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Label.swift"; sourceTree = "<group>"; };
94790B3A2E1179A600BF0A09 /* View+Style.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Style.swift"; sourceTree = "<group>"; };
94790B3E2E1179A600BF0A09 /* OverflowGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverflowGrid.swift; sourceTree = "<group>"; };
94790B402E1179A600BF0A09 /* PLJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PLJob.swift; sourceTree = "<group>"; };
94790B422E1179A600BF0A09 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
94790B442E1179A600BF0A09 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
94790B452E1179A600BF0A09 /* IOS_studyApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOS_studyApp.swift; sourceTree = "<group>"; };
94790B462E1179A600BF0A09 /* PLJobCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PLJobCellView.swift; sourceTree = "<group>"; };
94790B472E1179A600BF0A09 /* PLJobsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PLJobsView.swift; sourceTree = "<group>"; };
94790B482E1179A600BF0A09 /* PLJobView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PLJobView.swift; sourceTree = "<group>"; };
94790B492E1179A600BF0A09 /* PostPLJobIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostPLJobIView.swift; sourceTree = "<group>"; };
94790B4A2E1179A600BF0A09 /* TaBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaBarView.swift; sourceTree = "<group>"; };
94790B5D2E117AD800BF0A09 /* PLJobViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PLJobViewModel.swift; sourceTree = "<group>"; };
94790B5F2E11846F00BF0A09 /* Foundation+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Foundation+.swift"; sourceTree = "<group>"; };
94790B612E118B3600BF0A09 /* PostPLJobIView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostPLJobIView+.swift"; sourceTree = "<group>"; };
94790B632E118CB400BF0A09 /* HUDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDView.swift; sourceTree = "<group>"; };
94790B652E129CC500BF0A09 /* GType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GType.swift; sourceTree = "<group>"; };
94790B672E137DB300BF0A09 /* PostJobView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostJobView.swift; sourceTree = "<group>"; };
94790B692E13820500BF0A09 /* TaBarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaBarViewModel.swift; sourceTree = "<group>"; };
94790B6B2E13879300BF0A09 /* PLJobView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PLJobView+.swift"; sourceTree = "<group>"; };
947A93B32DEEE882002E0937 /* IOS_study.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IOS_study.app; sourceTree = BUILT_PRODUCTS_DIR; };
949F8B272E30C5210013AB3A /* ProfileViewModel+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileViewModel+.swift"; sourceTree = "<group>"; };
949F8B292E30CCFB0013AB3A /* ProfileView+JobUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+JobUI.swift"; sourceTree = "<group>"; };
94F008662E16345100989E46 /* GFunc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GFunc.swift; sourceTree = "<group>"; };
94F008682E16749B00989E46 /* RESTManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTManager.swift; sourceTree = "<group>"; };
94F0E24D2E1CFA00003406F0 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = "<group>"; };
94F0E2542E1F5F15003406F0 /* IOS-study-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "IOS-study-Info.plist"; sourceTree = SOURCE_ROOT; };
94F0E2552E1F6530003406F0 /* LocationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHelper.swift; sourceTree = "<group>"; };
94F0E2572E1F87D2003406F0 /* OF.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OF.swift; sourceTree = "<group>"; };
94F0E2592E1F996A003406F0 /* PLJobsView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PLJobsView+.swift"; sourceTree = "<group>"; };
94F0E25C2E1FC170003406F0 /* SignInPhoneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInPhoneView.swift; sourceTree = "<group>"; };
94F0E25E2E1FC186003406F0 /* SignInPhoneViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInPhoneViewModel.swift; sourceTree = "<group>"; };
94F0E2612E1FC2D5003406F0 /* SettginsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettginsView.swift; sourceTree = "<group>"; };
94F0E2632E1FDAB2003406F0 /* AuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthManager.swift; sourceTree = "<group>"; };
94F0E2652E1FFE58003406F0 /* SignInPhoneView+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SignInPhoneView+.swift"; sourceTree = "<group>"; };
94F0E2672E23FB7E003406F0 /* AgreeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgreeView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
947A93B02DEEE882002E0937 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
940E13F42E2E07470026F8A5 /* Kingfisher in Frameworks */,
940E13EE2E2E07110026F8A5 /* LeanCloud in Frameworks */,
940E13EB2E2E06F70026F8A5 /* MJRefresh in Frameworks */,
940E13F12E2E07300026F8A5 /* DotLottie in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9441C9BB2E3345FE00805BF3 /* Review */ = {
isa = PBXGroup;
children = (
9441C9BC2E33462000805BF3 /* ReviewViewModel.swift */,
);
path = Review;
sourceTree = "<group>";
};
9474AFC52E14C6DC004AA9AB /* Job */ = {
isa = PBXGroup;
children = (
94790B5D2E117AD800BF0A09 /* PLJobViewModel.swift */,
94790B482E1179A600BF0A09 /* PLJobView.swift */,
9441C9732E323A7B00805BF3 /* PLJobsView+JobUI.swift */,
94790B6B2E13879300BF0A09 /* PLJobView+.swift */,
);
path = Job;
sourceTree = "<group>";
};
9474AFC62E14C6EB004AA9AB /* Post */ = {
isa = PBXGroup;
children = (
94790B612E118B3600BF0A09 /* PostPLJobIView+.swift */,
94790B492E1179A600BF0A09 /* PostPLJobIView.swift */,
94790B672E137DB300BF0A09 /* PostJobView.swift */,
941831F42E2638070004042F /* PostCompanyView.swift */,
940E13F72E2F42000026F8A5 /* CompanyGalleryView.swift */,
9441C9B92E3345D100805BF3 /* PostReviewView.swift */,
940E13F92E2FA2660026F8A5 /* CompanyGalleryORView.swift */,
);
path = Post;
sourceTree = "<group>";
};
9474AFC72E14C724004AA9AB /* Jobs */ = {
isa = PBXGroup;
children = (
94790B472E1179A600BF0A09 /* PLJobsView.swift */,
94F0E2592E1F996A003406F0 /* PLJobsView+.swift */,
94790B462E1179A600BF0A09 /* PLJobCellView.swift */,
);
path = Jobs;
sourceTree = "<group>";
};
9474AFC82E14CAC8004AA9AB /* Manager */ = {
isa = PBXGroup;
children = (
9418310B2E2533520004042F /* UserManager.swift */,
9441C9752E325A7E00805BF3 /* Reportmanager.swift */,
941831F62E26626F0004042F /* StorageManager.swift */,
94F0E2632E1FDAB2003406F0 /* AuthManager.swift */,
9474AFC92E14CAFC004AA9AB /* PLJobManager.swift */,
94F008682E16749B00989E46 /* RESTManager.swift */,
);
path = Manager;
sourceTree = "<group>";
};
94790B372E1179A600BF0A09 /* Constant */ = {
isa = PBXGroup;
children = (
94790B342E1179A600BF0A09 /* CJob.swift */,
94790B352E1179A600BF0A09 /* CUser.swift */,
94790B362E1179A600BF0A09 /* GView.swift */,
94F008662E16345100989E46 /* GFunc.swift */,
94790B652E129CC500BF0A09 /* GType.swift */,
);
path = Constant;
sourceTree = "<group>";
};
94790B3B2E1179A600BF0A09 /* Extension */ = {
isa = PBXGroup;
children = (
94790B5F2E11846F00BF0A09 /* Foundation+.swift */,
94790B382E1179A600BF0A09 /* View+.swift */,
9474AFCB2E155E22004AA9AB /* LeanCloud+.swift */,
94790B392E1179A600BF0A09 /* View+Label.swift */,
94790B3A2E1179A600BF0A09 /* View+Style.swift */,
);
path = Extension;
sourceTree = "<group>";
};
94790B3F2E1179A600BF0A09 /* Library */ = {
isa = PBXGroup;
children = (
94790B632E118CB400BF0A09 /* HUDView.swift */,
94790B3E2E1179A600BF0A09 /* OverflowGrid.swift */,
94F0E24D2E1CFA00003406F0 /* NetworkMonitor.swift */,
94F0E2552E1F6530003406F0 /* LocationHelper.swift */,
940E13FB2E2FA63B0026F8A5 /* PinchZoomView.swift */,
);
path = Library;
sourceTree = "<group>";
};
94790B412E1179A600BF0A09 /* Model */ = {
isa = PBXGroup;
children = (
94790B402E1179A600BF0A09 /* PLJob.swift */,
9441C9BE2E33465400805BF3 /* Review.swift */,
9441C9772E325B4800805BF3 /* Report.swift */,
94F0E2572E1F87D2003406F0 /* OF.swift */,
941830A02E252FA90004042F /* DBUser.swift */,
);
path = Model;
sourceTree = "<group>";
};
94790B432E1179A600BF0A09 /* Preview Content */ = {
isa = PBXGroup;
children = (
94790B422E1179A600BF0A09 /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
94790B4B2E1179A600BF0A09 /* IOS_study */ = {
isa = PBXGroup;
children = (
94F0E2542E1F5F15003406F0 /* IOS-study-Info.plist */,
94790B452E1179A600BF0A09 /* IOS_studyApp.swift */,
94790B4A2E1179A600BF0A09 /* TaBarView.swift */,
94790B692E13820500BF0A09 /* TaBarViewModel.swift */,
9441C9BB2E3345FE00805BF3 /* Review */,
94790B412E1179A600BF0A09 /* Model */,
94F0E2602E1FC2AD003406F0 /* Profile */,
94F0E25B2E1FC14A003406F0 /* Auth */,
9474AFC82E14CAC8004AA9AB /* Manager */,
9474AFC72E14C724004AA9AB /* Jobs */,
9474AFC52E14C6DC004AA9AB /* Job */,
9474AFC62E14C6EB004AA9AB /* Post */,
94790B3B2E1179A600BF0A09 /* Extension */,
94790B372E1179A600BF0A09 /* Constant */,
94790B442E1179A600BF0A09 /* Assets.xcassets */,
94790B3F2E1179A600BF0A09 /* Library */,
94790B432E1179A600BF0A09 /* Preview Content */,
);
path = IOS_study;
sourceTree = "<group>";
};
947A93AA2DEEE882002E0937 = {
isa = PBXGroup;
children = (
94790B4B2E1179A600BF0A09 /* IOS_study */,
947A93B42DEEE882002E0937 /* Products */,
);
sourceTree = "<group>";
};
947A93B42DEEE882002E0937 /* Products */ = {
isa = PBXGroup;
children = (
947A93B32DEEE882002E0937 /* IOS_study.app */,
);
name = Products;
sourceTree = "<group>";
};
94F0E25B2E1FC14A003406F0 /* Auth */ = {
isa = PBXGroup;
children = (
94F0E25E2E1FC186003406F0 /* SignInPhoneViewModel.swift */,
94F0E25C2E1FC170003406F0 /* SignInPhoneView.swift */,
94F0E2652E1FFE58003406F0 /* SignInPhoneView+.swift */,
94F0E2672E23FB7E003406F0 /* AgreeView.swift */,
941831F02E25E6F10004042F /* AuthViewModel.swift */,
);
path = Auth;
sourceTree = "<group>";
};
94F0E2602E1FC2AD003406F0 /* Profile */ = {
isa = PBXGroup;
children = (
941831F22E2634BF0004042F /* ProfileViewModel.swift */,
949F8B272E30C5210013AB3A /* ProfileViewModel+.swift */,
941831EE2E25E6AC0004042F /* ProfileView.swift */,
94F0E2612E1FC2D5003406F0 /* SettginsView.swift */,
949F8B292E30CCFB0013AB3A /* ProfileView+JobUI.swift */,
9441C9B72E33432500805BF3 /* PLJobView+RevUI.swift */,
943D8EB52E29E2870079191A /* ProfileView+ComUI.swift */,
);
path = Profile;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
947A93B22DEEE882002E0937 /* IOS_study */ = {
isa = PBXNativeTarget;
buildConfigurationList = 947A93C12DEEE885002E0937 /* Build configuration list for PBXNativeTarget "IOS_study" */;
buildPhases = (
947A93AF2DEEE882002E0937 /* Sources */,
947A93B02DEEE882002E0937 /* Frameworks */,
947A93B12DEEE882002E0937 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = IOS_study;
packageProductDependencies = (
940E13EA2E2E06F70026F8A5 /* MJRefresh */,
940E13ED2E2E07110026F8A5 /* LeanCloud */,
940E13F02E2E07300026F8A5 /* DotLottie */,
940E13F32E2E07470026F8A5 /* Kingfisher */,
);
productName = IOS_study;
productReference = 947A93B32DEEE882002E0937 /* IOS_study.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
947A93AB2DEEE882002E0937 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1620;
LastUpgradeCheck = 1620;
TargetAttributes = {
947A93B22DEEE882002E0937 = {
CreatedOnToolsVersion = 16.2;
};
};
};
buildConfigurationList = 947A93AE2DEEE882002E0937 /* Build configuration list for PBXProject "IOS_study" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 947A93AA2DEEE882002E0937;
minimizedProjectReferenceProxies = 1;
packageReferences = (
940E13E92E2E06F70026F8A5 /* XCRemoteSwiftPackageReference "MJRefresh" */,
940E13EC2E2E07110026F8A5 /* XCRemoteSwiftPackageReference "swift-sdk" */,
940E13EF2E2E07300026F8A5 /* XCRemoteSwiftPackageReference "dotlottie-ios" */,
940E13F22E2E07470026F8A5 /* XCRemoteSwiftPackageReference "Kingfisher" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = 947A93B42DEEE882002E0937 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
947A93B22DEEE882002E0937 /* IOS_study */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
947A93B12DEEE882002E0937 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
94790B5B2E1179A600BF0A09 /* Preview Assets.xcassets in Resources */,
94790B5C2E1179A600BF0A09 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
947A93AF2DEEE882002E0937 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
94F0E2582E1F87D2003406F0 /* OF.swift in Sources */,
941831EF2E25E6AC0004042F /* ProfileView.swift in Sources */,
94F0E2562E1F6530003406F0 /* LocationHelper.swift in Sources */,
9418310C2E2533520004042F /* UserManager.swift in Sources */,
94790B642E118CB400BF0A09 /* HUDView.swift in Sources */,
94790B4C2E1179A600BF0A09 /* CJob.swift in Sources */,
94790B662E129CC500BF0A09 /* GType.swift in Sources */,
94790B4D2E1179A600BF0A09 /* CUser.swift in Sources */,
94790B682E137DB300BF0A09 /* PostJobView.swift in Sources */,
9441C9BF2E33465400805BF3 /* Review.swift in Sources */,
94F0E2642E1FDAB2003406F0 /* AuthManager.swift in Sources */,
9474AFCC2E155E22004AA9AB /* LeanCloud+.swift in Sources */,
94790B4E2E1179A600BF0A09 /* GView.swift in Sources */,
94F0E24E2E1CFA00003406F0 /* NetworkMonitor.swift in Sources */,
94790B4F2E1179A600BF0A09 /* View+.swift in Sources */,
94790B502E1179A600BF0A09 /* View+Label.swift in Sources */,
9441C9762E325A7E00805BF3 /* Reportmanager.swift in Sources */,
94F0E2622E1FC2D5003406F0 /* SettginsView.swift in Sources */,
9441C9B82E33432500805BF3 /* PLJobView+RevUI.swift in Sources */,
941831F52E2638080004042F /* PostCompanyView.swift in Sources */,
9441C9BD2E33462000805BF3 /* ReviewViewModel.swift in Sources */,
94F0E25D2E1FC171003406F0 /* SignInPhoneView.swift in Sources */,
94790B512E1179A600BF0A09 /* View+Style.swift in Sources */,
94790B522E1179A600BF0A09 /* OverflowGrid.swift in Sources */,
94790B532E1179A600BF0A09 /* PLJob.swift in Sources */,
94790B542E1179A600BF0A09 /* IOS_studyApp.swift in Sources */,
9441C9BA2E3345D100805BF3 /* PostReviewView.swift in Sources */,
940E13FC2E2FA63B0026F8A5 /* PinchZoomView.swift in Sources */,
94F008672E16345100989E46 /* GFunc.swift in Sources */,
9474AFCA2E14CAFC004AA9AB /* PLJobManager.swift in Sources */,
941831F32E2634BF0004042F /* ProfileViewModel.swift in Sources */,
949F8B282E30C5210013AB3A /* ProfileViewModel+.swift in Sources */,
94F0E2682E23FB7E003406F0 /* AgreeView.swift in Sources */,
94F0E2662E1FFE58003406F0 /* SignInPhoneView+.swift in Sources */,
94790B552E1179A600BF0A09 /* PLJobCellView.swift in Sources */,
9441C9782E325B4800805BF3 /* Report.swift in Sources */,
9441C9742E323A7B00805BF3 /* PLJobsView+JobUI.swift in Sources */,
941830A12E252FA90004042F /* DBUser.swift in Sources */,
94F0E25A2E1F996A003406F0 /* PLJobsView+.swift in Sources */,
94790B6A2E13820500BF0A09 /* TaBarViewModel.swift in Sources */,
94790B6C2E13879400BF0A09 /* PLJobView+.swift in Sources */,
940E13F82E2F42000026F8A5 /* CompanyGalleryView.swift in Sources */,
941831F72E26626F0004042F /* StorageManager.swift in Sources */,
94790B562E1179A600BF0A09 /* PLJobsView.swift in Sources */,
943D8EB62E29E2870079191A /* ProfileView+ComUI.swift in Sources */,
941831F12E25E6F10004042F /* AuthViewModel.swift in Sources */,
94F008692E16749B00989E46 /* RESTManager.swift in Sources */,
94790B622E118B3600BF0A09 /* PostPLJobIView+.swift in Sources */,
949F8B2A2E30CCFB0013AB3A /* ProfileView+JobUI.swift in Sources */,
94790B572E1179A600BF0A09 /* PLJobView.swift in Sources */,
940E13FA2E2FA2660026F8A5 /* CompanyGalleryORView.swift in Sources */,
94790B582E1179A600BF0A09 /* PostPLJobIView.swift in Sources */,
94790B592E1179A600BF0A09 /* TaBarView.swift in Sources */,
94790B602E11846F00BF0A09 /* Foundation+.swift in Sources */,
94F0E25F2E1FC186003406F0 /* SignInPhoneViewModel.swift in Sources */,
94790B5E2E117AD800BF0A09 /* PLJobViewModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
947A93BF2DEEE885002E0937 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
947A93C02DEEE885002E0937 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
947A93C22DEEE885002E0937 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"IOS_study/Preview Content\"";
DEVELOPMENT_TEAM = 6M7MAFWB7K;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "IOS-study-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = Boss_tdcat;
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "仅添加照片,不会读取任何照片";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "cn.tdcat.IOS-study";
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
947A93C32DEEE885002E0937 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"IOS_study/Preview Content\"";
DEVELOPMENT_TEAM = 6M7MAFWB7K;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "IOS-study-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = Boss_tdcat;
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "仅添加照片,不会读取任何照片";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "cn.tdcat.IOS-study";
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
947A93AE2DEEE882002E0937 /* Build configuration list for PBXProject "IOS_study" */ = {
isa = XCConfigurationList;
buildConfigurations = (
947A93BF2DEEE885002E0937 /* Debug */,
947A93C02DEEE885002E0937 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
947A93C12DEEE885002E0937 /* Build configuration list for PBXNativeTarget "IOS_study" */ = {
isa = XCConfigurationList;
buildConfigurations = (
947A93C22DEEE885002E0937 /* Debug */,
947A93C32DEEE885002E0937 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
940E13E92E2E06F70026F8A5 /* XCRemoteSwiftPackageReference "MJRefresh" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/CoderMJLee/MJRefresh.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 3.7.9;
};
};
940E13EC2E2E07110026F8A5 /* XCRemoteSwiftPackageReference "swift-sdk" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/leancloud/swift-sdk.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 17.11.0;
};
};
940E13EF2E2E07300026F8A5 /* XCRemoteSwiftPackageReference "dotlottie-ios" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/LottieFiles/dotlottie-ios";
requirement = {
branch = main;
kind = branch;
};
};
940E13F22E2E07470026F8A5 /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/onevcat/Kingfisher.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 8.5.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
940E13EA2E2E06F70026F8A5 /* MJRefresh */ = {
isa = XCSwiftPackageProductDependency;
package = 940E13E92E2E06F70026F8A5 /* XCRemoteSwiftPackageReference "MJRefresh" */;
productName = MJRefresh;
};
940E13ED2E2E07110026F8A5 /* LeanCloud */ = {
isa = XCSwiftPackageProductDependency;
package = 940E13EC2E2E07110026F8A5 /* XCRemoteSwiftPackageReference "swift-sdk" */;
productName = LeanCloud;
};
940E13F02E2E07300026F8A5 /* DotLottie */ = {
isa = XCSwiftPackageProductDependency;
package = 940E13EF2E2E07300026F8A5 /* XCRemoteSwiftPackageReference "dotlottie-ios" */;
productName = DotLottie;
};
940E13F32E2E07470026F8A5 /* Kingfisher */ = {
isa = XCSwiftPackageProductDependency;
package = 940E13F22E2E07470026F8A5 /* XCRemoteSwiftPackageReference "Kingfisher" */;
productName = Kingfisher;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 947A93AB2DEEE882002E0937 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1640"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "947A93B22DEEE882002E0937"
BuildableName = "IOS_study.app"
BlueprintName = "IOS_study"
ReferencedContainer = "container:IOS_study.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "947A93B22DEEE882002E0937"
BuildableName = "IOS_study.app"
BlueprintName = "IOS_study"
ReferencedContainer = "container:IOS_study.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "947A93B22DEEE882002E0937"
BuildableName = "IOS_study.app"
BlueprintName = "IOS_study"
ReferencedContainer = "container:IOS_study.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,18 @@
{
"colors" : [
{
"color" : {
"platform" : "universal",
"reference" : "systemIndigoColor"
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"localizable" : true
}
}

View File

@ -0,0 +1,36 @@
{
"images" : [
{
"filename" : "TDCAT..png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "0.996"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.122",
"green" : "0.114",
"red" : "0.114"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.965",
"green" : "0.961",
"red" : "0.961"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.078",
"green" : "0.075",
"red" : "0.075"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.122",
"green" : "0.114",
"red" : "0.114"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "0.996"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.200",
"green" : "0.200",
"red" : "0.200"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.753",
"green" : "0.753",
"red" : "0.753"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.200",
"green" : "0.200",
"red" : "0.200"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.878",
"green" : "0.878",
"red" : "0.878"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.878",
"green" : "0.878",
"red" : "0.878"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.200",
"green" : "0.200",
"red" : "0.200"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "benefit.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "company.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 955 B

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "content.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "employ.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "leader.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "need.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "station.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "time.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "want.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "冰岛极光风景4k壁纸3840x2160_彼岸图网.jpg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

View File

@ -0,0 +1,137 @@
//
// AgreeView.swift
// IOS_study
//
// Created by CC-star on 2025/7/13.
//
import SwiftUI
struct AgreeView: View {
@Environment(\.dismiss) var dismiss
@Binding var agreeType: Int
var body: some View {
ScrollView {
VStack(spacing: 18) {
VStack {
Button { dismiss() } label: { Image(systemName: "xmark") }.push(to: .trailing).font(.title3).secondary()
Text(agreeType == 0 ? "用户协议" : "隐私政策").size18b()
}
Text(agreeType == 0 ? userAgreement : privacyPolicy).size15().tc2().lineSpacing(6)
}.padding()
}.bg()
}
let userAgreement = """
1 Tian Xiao "运营方" Boss_tdcatAPP等产品提供的服务"产品和服务""用户"
2
3
4使
使
1
2便
3
4
5使
6使
1
2使
3
4使
1使
2 使
1
2
3
4
1使
2使
1
2
3
4
5
6
7
8
3使
4
5
6
1
1使使
2
3
1
2便
1线13123456789
2mail@tdcat.cn
3797687Tian Xiao
"""
let privacyPolicy = """
访 Boss_tdcat APP等产品提供的服务 Tian Xiao 访使
使 使使 mail@tdcat.cn 使
使
Tian Xiao
使
18
14
使
GB/T 35273-2017 使/使
使
使
使 访使 使使
使使
1
2
3
4
5
6
7
8
1
2
3
4使
5
6
1.
1线13123456789
2mail@tdcat.cn
3797687Tian Xiao
30
2.Tian Xiao所在地人民法院提起诉讼
"""
}

View File

@ -0,0 +1,29 @@
//
// AuthViewModel.swift
// IOS_study
//
// Created by CC-star on 2025/7/15.
//
import Foundation
import LeanCloud
@Observable final class AuthViewModel {
var isLogin = LCApplication.default.currentUser != nil
init() {
NotificationCenter.default.addObserver(self, selector: #selector(handleLogin), name: .loginSuccess, object: nil)//,nil
NotificationCenter.default.addObserver(self, selector: #selector(handlelogout), name: .logoutSuccess, object: nil)
}
@objc func handleLogin() {
isLogin = true
}
@objc func handlelogout() {
isLogin = false
}
deinit {//
NotificationCenter.default.removeObserver(self)
}
}

View File

@ -0,0 +1,45 @@
//
// SignInPhoneView+.swift
// IOS_study
//
// Created by CC-star on 2025/7/10.
//
import Foundation
extension SignInPhoneView {
func stopTimer() { timer?.upstream.connect().cancel(); timer = nil }//
func getVerCode() async {//async ->
guard vm.phoneNum.isPhoneNum else { return }
do {
//todo
try await vm.getVerCode(phoneNum: "+86\(vm.phoneNum)", tName: "login", sName: "名称")
} catch {
print("获取验证码失败:\(error)")
hud.show("获取验证码失败:\(error)")
}
}
func preLogin() {
focusedField = nil
if isAgree {
Task { await login() }
} else {
showAgreeSheet = true
}
}
//+8613123456789 797687
func login() async {
guard vm.phoneNum.isPhoneNum, vm.verCode.isVerCode else { hud.show("手机号或验证码错误"); return }
vm.isLogining = true
defer { vm.isLogining = false }
do {
try await vm.login()
stopTimer()
hud.show("登录成功")
} catch {
print("登录失败:\(error)")
hud.show("登录失败:\(error)")
}
}
}

View File

@ -0,0 +1,149 @@
//
// SignInPhoneView.swift
// IOS_study
//
// Created by CC-star on 2025/7/10.
//
import SwiftUI
import Combine
private let totalTime = 60
struct SignInPhoneView: View {
enum FocusedField { case phoneNum, verCode }//case
@Environment(SignInPhoneViewModel.self) var vm
@Environment(HUD.self) var hud
@State var showVerBtn = true
@State var timer: Publishers.Autoconnect<Timer.TimerPublisher>?// ?
@State var timeRemain = totalTime//
@State var isAgree = false //
@State var showAgreeSheet = false//
@State var showAgreeView = false//
@State var agreeType = 0//01
@FocusState var focusedField: FocusedField?//便nil
var enableLoginBtn: Bool { vm.phoneNum.isPhoneNum && vm.verCode.isVerCode }
var totalNumCount: Int { vm.phoneNum.count + vm.verCode.count }
var body: some View {
@Bindable var vm = vm
NavigationStack {
ScrollView {
VStack(spacing: 33) {
HStack {
NavigationLink {
SettginsView(naviPath: .constant(NavigationPath()), isLogin: false)
} label: {
Image(systemName: "gearshape")
}
Button {
focusedField = nil// ->
} label: {
Image(systemName: "xmark")
}
}.push(to: .trailing).font(.title3).secondary()
Text("手机号登陆").tc().font(.title).bold()
.kerning(1.2)//,1
VStack(spacing: 14) {
HStack {
TextField("请输入手机号", text: $vm.phoneNum).tc().kerning(1.2)
.keyboardType(.numberPad)//
.focused($focusedField, equals: .phoneNum)
if !vm.phoneNum.isEmpty {
Image(systemName: "xmark.circle.fill").font(.title2).secondary().onTapGesture { vm.phoneNum = "" }
}
}
exDivider()
HStack {
//797687
TextField("请输入验证码", text: $vm.verCode).tc().tc().kerning(1.2).keyboardType(.numberPad)
.focused($focusedField, equals: .verCode)
if vm.phoneNum.isPhoneNum || !showVerBtn {//
if showVerBtn {
Button("获取验证码") {
timeRemain = totalTime
focusedField = .verCode//caseverCode
timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()//,
showVerBtn = false
Task {
await getVerCode()
}
}.headline()
} else {
if let timer {
Text("重新发送(\(timeRemain)s").headline().secondary()
//
.onReceive(timer) { _ in
timeRemain -= 1
if timeRemain <= 0 {
stopTimer()
showVerBtn = true
}
}
}
}
}
}
exDivider()
Button {
preLogin()
} label: {
if vm.isLogining {
ProgressView().appleStyleP()
} else {
Text("登录").appleStyle(disabled: !enableLoginBtn)
}
}.disabled(!enableLoginBtn || vm.isLogining)
HStack(spacing: 3) {
Button { isAgree.toggle()} label: {
Image(systemName: isAgree ? "checkmark.circle.fill" : "circle")
}.tint(isAgree ? .accent : .secondary).size16()
agreeTextView.size14()
}
}.font(.title3)
}.padding(33)
}
.scrollDismissesKeyboard(.immediately).bg()//
.fullScreenCover(isPresented: $showAgreeView, content: { AgreeView(agreeType: $agreeType) })
.sheet(isPresented: $showAgreeSheet) {//
VStack(spacing: 18) {
agreeTextView.size15()
Button {
isAgree = true//
showAgreeSheet = false//
Task { await login() }//
} label: {
Text("同意并登录").font(.body).push(to: .center).padding(.vertical, 6)
}.buttonStyle(.borderedProminent)
}.padding(33)
.presentationDetents([.height(200)])//
// .presentationDetents([.medium, .large])//
}
}
.onChange(of: totalNumCount) {
if totalNumCount == 17 {
preLogin()
}
}
}
var agreeTextView: some View {
HStack(spacing: 3) {
Text("我已阅读并同意").secondary()
Button("用户协议") {
agreeType = 0
showAgreeView = true
}
Text("").secondary()
Button("隐私政策") {
agreeType = 1
showAgreeView = true
}
}
}
}

View File

@ -0,0 +1,37 @@
//
// SignInPhoneViewModel.swift
// IOS_study
//
// Created by CC-star on 2025/7/10.
//
import Foundation
@Observable final class SignInPhoneViewModel {
// @Environment(AuthViewModel.self) var vm
var phoneNum = ""
var verCode = ""
var isLogining = false
@MainActor func getVerCode(phoneNum: String, tName: String, sName: String) async throws {
try await AuthManager.shared.getVerCode(phoneNum: phoneNum, tName: tName, sName: sName)
}
@MainActor func login() async throws {
let user = try await AuthManager.shared.login(phoneNum: phoneNum, verCode: verCode)
// printf(user.mobilePhoneNumber?.value)
if let isNewUser = user.get("isNewUser")?.boolValue, !isNewUser {//
} else {//
try user.set("isNewUser", value: false)
try await user.save()//
try await UserManager.shared.create(user: user)
}
NotificationCenter.default.post(name: .loginSuccess, object: nil)//
verCode = ""//
}
}

View File

@ -0,0 +1,43 @@
//
// CJob.swift
//
//
// Created by CC-star on 2025/6/28.
//
import Foundation
let kJobLimitS = 2
let kJobLimit = 10
let kTFAndPHeight: CGFloat = 36
let kBusinessArr = [ "互联网", "便利店", "饭馆", "制造业", "金融", "教育", "医疗健康", "零售", "房地产", "物流", "文化传媒", "咨询服务", "政府机关", "公共事业", "新能源", "旅游", "电子商务", "汽车", "餐饮", "IT服务" ]
let kCityArr = [
"北京", "上海", "广州", "深圳", "杭州",
"南京", "武汉", "成都", "西安", "重庆",
"天津", "苏州", "郑州", "长沙", "青岛",
"大连", "宁波", "厦门", "无锡", "福州"
]
let kTaxArr = ["详谈","报税","不报税"]
let kWantNumArr = [1,2,3,4,5,6,7,8,9,10,13,15,16,63,75,856,345]
let kNeedExpArr = ["经验不限","1年以内","1-3年","3-5年","5-10年"]
let kNeedEduArr = ["中专","高中","大专","本科","硕士研究生","博士研究生","博士后"]
let kNeedLanArr = ["Swift","Objective-c","Java","C","Vue","Golong","Kotlin"]
let kNeedFrameArr = ["SwiftUI","UIKit","Spring","Jetpack Compose"]
let kProvinceArr = [
"北京", "天津", "上海", "重庆", //
"河北", "山西", "辽宁", "吉林", "黑龙江", //
"江苏", "浙江", "安徽", "福建", "江西", "山东", //
"河南", "湖北", "湖南", //
"广东", "海南", //
"四川", "贵州", "云南", // 西
"陕西", "甘肃", "青海", // 西
"台湾", //
"内蒙古", "广西", "西藏", "宁夏", "新疆", //
"香港", "澳门" //
]
let kContactTypeArr = ["微信","QQ","电话","邮件","其他"]
let jobReportsKey = "jobReports"//key

View File

@ -0,0 +1,21 @@
//
// CUser.swift
//
//
// Created by CC-star on 2025/6/14.
//
import Foundation
let kAppName = "数据猫"
let kIconSpacing: CGFloat = 3
let kStackSpacing: CGFloat = 14
let kStackSpacingS: CGFloat = 9
let KdevContact = "IC_lucky"//
//LeanCloud
let kAppID = "jHdEBD3pF6YLyDlJQXEn8bK1-gzGzoHsz"
let kAppKey = "48dkfFMv5oa0pvtORJtkEZyM"
let kServerURLStr = "https://boss.tdcat.cn"

View File

@ -0,0 +1,11 @@
//
// GFunc.swift
// IOS_study
//
// Created by CC-star on 2025/7/3.
//
import Foundation
func sleep5() async throws { try await Task.sleep(nanoseconds: 5_000_000_000) }//5s
func sleep3() async throws { try await Task.sleep(nanoseconds: 3_000_000_000) }

View File

@ -0,0 +1,19 @@
//
// GType.swift
//
//
// Created by CC-star on 2025/6/30.
//
import Foundation
enum Navi1 {
case PLJobsToPLJob
}
enum Navi5 {
case AddCompany,EditCompany
case PLJobsToPLJob
case PLJobToEdit
case Tosetting
}

View File

@ -0,0 +1,35 @@
//
// GView.swift
// IOS_study
//
// Created by CC-star on 2025/6/26.
//
import SwiftUI
func exDivider() -> some View{
Rectangle().fill(Color.labeBG).frame(height: 1)//线[Divider]
}
//
func arrowIcon(_ showView: Bool) -> some View{
showView ? Image(systemName: "arrowtriangle.up.fill").size9() : Image(systemName: "arrowtriangle.down.fill").size9()
}
func listBottomEView() -> some View{
Color.clear.frame(height: 120).listRowStyle()
}
func postBottomEView() -> some View{
Color.clear.frame(height: 120)
}
func BprogressView(text: String = "加载中") -> some View {
VStack {
ProgressView()
Text(text).font(.subheadline).secondary()
}.padding(25)
.background(.thinMaterial)//
.radius(6)
}

View File

@ -0,0 +1,94 @@
//
// Foundation+.swift
//
//
// Created by CC-star on 2025/6/29.
//
import Foundation
//1,10\d(\\d -> \ -> \d)
let kPhoneRegEx = "^1\\d{10}$"//
let kVerCodeRegEx = "^\\d{6}$"//
extension NSRegularExpression {
convenience init(_ pattern: String) {
do {
try self.init(pattern: pattern)
} catch {
fatalError("非法的正则表达式")//init
}
}
func matches(_ string: String) -> Bool {
let range = NSRange(location: 0, length: string.utf16.count)
return firstMatch(in: string, options: [], range: range) != nil
}
}
extension String {
var isBlank: Bool { self.isEmpty || self.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }//
var trimmed: String { self.trimmingCharacters(in: .whitespacesAndNewlines) }
var isPhoneNum: Bool {
Int(self) != nil && NSRegularExpression(kPhoneRegEx).matches(self)
//int nor ->
}
var isVerCode: Bool { Int(self) != nil && NSRegularExpression(kVerCodeRegEx).matches(self) }
//
static func randomString(_ length: Int) -> String{
let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return String((0..<length).map{ _ in letters.randomElement()! })
}
}
extension Date {
var daysFromNow: Int {
Calendar.current.dateComponents([.day], from: self, to: .now).day ?? 0//??
}
// let date = Date()
// print(date.string(withFormat: "yyyy/MM/dd HH:mm")) // 2023/08/24 16:21
// print(date.string(withFormat: "yyyy/MM/dd hh:mm")) // 2023/08/24 04:21
var toStr: String {
let year = Calendar.current.component(.year, from: self)
let currentYear = Calendar.current.component(.year, from: Date())
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = (year == currentYear) ? "M-d" : "yyyy-M-d"
return dateFormatter.string(from: self)
}
}
enum UserError: String, Error {
case lcObjToDicError = "lc对象转换字典失败"
case saveFileSuccessButNoURL = "保存文件成功,但未能获取文件的地址"
case imageToheicDataError = "图片转换成heicData失败"
case imageTooBig = "图片太大"
case objectIdNotFound = "没找到objectId"
}
enum NetworkError: Error {
case invalidURL
case notHTTPResponse
case requestFailed(String)//
}
extension NetworkError: LocalizedError {//NetworkError
var errorDescription: String? {
switch self {
case .invalidURL:
return "请求的网址错误"
case .notHTTPResponse:
return "响应不能转化为HTTPURLResponse"
case .requestFailed(let errorMsg):
return "请求失败:\(errorMsg)"
}
}
}
extension Notification.Name {
static let loginSuccess = Notification.Name("loginSuccess")
static let logoutSuccess = Notification.Name("logoutSuccess")
}

View File

@ -0,0 +1,182 @@
//
// LeanCloud+.swift
// leancloud
//
// Created by CC-star on 2025/7/2.
//
import Foundation
import LeanCloud
extension LCQuery {
func getFirst() async throws -> LCObject {
try await withCheckedThrowingContinuation { continuation in
_ = self.getFirst { result in
switch result {
case .success(object: let lcObject):
continuation.resume(returning: lcObject)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
///
func count() async throws -> Int {
try await withCheckedThrowingContinuation { continuation in
self.count { result in
switch result {
case .success(let count):
continuation.resume(returning: count)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
//lcobject
func find() async throws -> [LCObject] {
try await withCheckedThrowingContinuation { continuation in
self.find { result in
switch result {
case .success(let objects):
continuation.resume(returning: objects)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
//
func find<T: Decodable>(as type: T.Type) async throws -> [T] {
let lcObjects = try await withCheckedThrowingContinuation { continuation in
self.find { result in
switch result {
case .success(objects: let lcObjects):
continuation.resume(returning: lcObjects)
case .failure(error: let error):
continuation.resume(throwing: error)
}
}
}
var res: [T] = []
for lcObject in lcObjects {
//dictionaryValue
guard let lcDic = lcObject.dictionaryValue else { throw UserError.lcObjToDicError }
let data = try JSONSerialization.data(withJSONObject: lcDic)
let object = try JSONDecoder().decode(T.self, from: data)
res.append(object)
}
return res
}
}
extension LCObject {
// LeanCloud async
func save() async throws {
try await withCheckedThrowingContinuation { continuation in
_ = self.save() { result in
switch result {
case .success:
//
continuation.resume()
case .failure(let error):
//
continuation.resume(throwing: error)
}
}
}
}
///
func delete() async throws {
try await withCheckedThrowingContinuation { continuation in
_ = self.delete { result in
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
///
static func delete(_ objects: [LCObject]) async throws {
try await withCheckedThrowingContinuation { continuation in
self.delete(objects) { result in
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
}
extension LCSMSClient {
//requestShortMessage()async
static func requestShortMessage(
mobilePhoneNumber: String,
templateName: String,
signatureName: String
) async throws {
try await withCheckedThrowingContinuation { continuation in
LCSMSClient.requestShortMessage(
mobilePhoneNumber: mobilePhoneNumber,
templateName: templateName,
signatureName: signatureName
// ,variables: ["name": LCString("BOSS"), "ttl": LCNumber("5")]
) { result in
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
}
extension LCUser {
//signUpOrLogIn()async
static func signUpOrLogIn(mobilePhoneNumber: String, verificationCode: String) async throws -> LCUser {
try await withCheckedThrowingContinuation { continuation in
_ = LCUser.signUpOrLogIn(mobilePhoneNumber: mobilePhoneNumber, verificationCode: verificationCode) { result in
switch result {
case .success(object: let user):
continuation.resume(returning: user)
case .failure(error: let error):
continuation.resume(throwing: error)
}
}
}
}
}
extension LCFile {
func save() async throws -> String {
try await withCheckedThrowingContinuation { continuation in
_ = self.save { result in
switch result {
case .success:
if let url = self.url?.value {
continuation.resume(returning: url) // URL
} else {
let error = UserError.saveFileSuccessButNoURL //
continuation.resume(throwing: error)
}
case .failure(let error):
continuation.resume(throwing: error) // LeanCloud
}
}
}
}
}

View File

@ -0,0 +1,99 @@
//
// View+.swift
//
//
// Created by CC-star on 2025/6/12.
//
import SwiftUI
extension View{
//
func radius(_ radius: CGFloat = 4) -> some View{ clipShape(RoundedRectangle(cornerRadius: radius)) }
// func radius2(_ radius: CGFloat = 4) -> some View{ clipShape(RoundedRectangle(cornerRadius: radius)) }
func border(radius: CGFloat = 4, borderColor: Color) -> some View{
overlay { RoundedRectangle(cornerRadius: radius).strokeBorder(borderColor, lineWidth: 1) }
}
//
func tc() -> some View{ foregroundStyle(.aTC) }
func tc2() -> some View{ foregroundStyle(.aTC2) }
func secondary() -> some View{ foregroundStyle(.secondary) }
func accent() -> some View{ foregroundStyle(.accent) }
func white() -> some View{ foregroundStyle(.white) }
func red() -> some View{ foregroundStyle(.red) }
func gray() -> some View{ foregroundStyle(.gray) }
func purple() -> some View{ foregroundStyle(.purple) }
func bg() -> some View{ background(Color.aBG) }
func bgA() -> some View{ background(Color.accent) }
func bg2() -> some View{ background(Color.aBG2) }
//
func size9() -> some View{ font(.system(size: 9)) }
func size11() -> some View{ font(.system(size: 11)) }
func size12() -> some View{ font(.system(size: 12)) }
func size13() -> some View{ font(.system(size: 13)) }
func size13s() -> some View{ size13().secondary() }
func size13sb() -> some View{ size13().secondary().bold() }
func size14() -> some View{ font(.system(size: 14)) }
func size15semib() -> some View{ font(.system(size: 15)).semib() }
func size15() -> some View{ font(.system(size: 15)) }
func size15sb() -> some View{ font(.system(size: 15)).secondary().bold() }
func size15pb() -> some View{ font(.system(size: 15)).foregroundStyle(.pink).bold() }
func size16() -> some View{ font(.system(size: 16)) }
func size16semib() -> some View{ font(.system(size: 16)).semib() }
func size17() -> some View{ font(.system(size: 17)) }
func headline() -> some View{ font(.headline) }
func size18b() -> some View{ font(.system(size: 18)).bold() }
func size18ab() -> some View{ font(.system(size: 18)).bold().accent() }
func size20b() -> some View{ font(.title3).bold() }
//
func med() -> some View{ fontWeight(.medium) }
func semib() -> some View{ fontWeight(.semibold) }
func push(to alignment: Alignment) -> some View {
frame(maxWidth: .infinity, alignment: alignment)
}
//List
func allowMulti() -> some View{
fixedSize(horizontal: false, vertical: true).textSelection(.enabled).multilineTextAlignment(.leading)
}
//
func thumbFrame() -> some View {
frame(width: 100, height: 100)
}
func thumbFrameS(aspectRatio: CGFloat = 1) -> some View {
frame(width: 40, height: 40 / aspectRatio)
}
}
extension Color{
static let labeBG = Color(.systemFill)
static let labelBG2 = Color(.systemGray5)
static let bg = Color.aBG
static let bg2 = Color.aBG2
}
// MARK: -
extension UINavigationController: @retroactive UIGestureRecognizerDelegate {
override open func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return viewControllers.count > 1 }
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { true }
}
//
extension UIApplication {
func hideKeyboard() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}

View File

@ -0,0 +1,36 @@
//
// View+Label.swift
// IOS_study
//
// Created by CC-star on 2025/6/12.
//
import SwiftUI
extension View {
func padding1() -> some View { padding(.vertical,2).padding(.horizontal,3) }
func padding2() -> some View { padding(.vertical,3).padding(.horizontal,7) }
func padding3() -> some View { padding(.vertical,4).padding(.horizontal,7) }
func redBorder() -> some View { size12().red().semib().padding1().border(borderColor: Color.red) }
func grayBorder() -> some View {size12().gray().semib().padding1().border(borderColor: Color.gray) }
func accentBorder() -> some View{ size12().accent().semib().padding1().border(borderColor: Color.accent) }
func labelBG() -> some View{ size13().tc().semib().padding3()
.background(Color.labeBG)//paddingbackground
.radius()
}
func labelBG2() -> some View{ size13().tc().med().padding3().background(Color.labelBG2).radius()}
func labelPP() -> some View{ size13().purple().med().padding3().background(Color.purple.opacity(0.18)).radius() }
func labelWA() -> some View{ size11().white().padding1().background(Color.accent).radius() }
func labelWAL() -> some View{ size14().white().med().padding3().background(Color.accent).radius() }
//Label
func labelPL() -> some View{ font(.caption).foregroundStyle(.blue).padding2().background(.blue.opacity(0.18)).radius() }
// - 线
func labelTA() -> some View{ size13().tc2().med().padding3().background(.accent.opacity(0.18)).radius() }
}

View File

@ -0,0 +1,136 @@
//
// View+Style.swift
// style
//
// Created by CC-star on 2025/6/12.
//
import SwiftUI
extension View{
func btnStyle() -> some View{ size15().buttonStyle(.borderless).accent() }
func textStyle() -> some View{ size15().tc().lineSpacing(6) }
func textStyle2() -> some View{ size15().tc2().lineSpacing(6) }
func oneLineStyleMiddle() -> some View{ lineLimit(1).truncationMode(.middle) }
func explainStyle() -> some View { size15sb().frame(maxWidth: .infinity, maxHeight: .infinity).padding() }//
//navi
func naviBarStyle() -> some View {
navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.toolbarBackground(.aBG, for: .navigationBar) }
//
func labelStyleOF() -> some View { tc2().size15semib() }
func badgeStyle() -> some View { size13().semib().white().padding(6).bgA()//
.clipShape(Circle())//clipShape
}
@ViewBuilder func selectBtnStyle(_ selected: Bool) -> some View {//ViewBuilder
if selected {
buttonStyle(.borderedProminent)
} else {
tint(Color.aTC).buttonStyle(.bordered)
}
}
//List西
func subTStyle() -> some View{
size16semib().tc()
}
func subTStyleL() -> some View{
headline().tc()
}
//cell
func cellPaddingRadius() -> some View{//radius
padding(.vertical)
.padding(.horizontal,12)
.bg()
.radius(8)
}
func cellOutPadding() -> some View{
padding([.top,.horizontal],9)
//.padding(.bottom,10)
}
//list
func listRowStyle() -> some View{
listRowInsets(EdgeInsets())
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
}
func listStyle() -> some View{
listStyle(.inset)
.scrollContentBackground(.hidden)
.bg2()
.environment(\.defaultMinListRowHeight,0) }
func listRowStyleLoading2() -> some View {//
padding(.top).padding(.bottom,100).size13sb().push(to: .center).bg2().listRowStyle()
}
//
func cellStyle() -> some View{
cellPaddingRadius().cellOutPadding().listRowStyle()
}
//Form
func littleTStyle() -> some View{
size13().tc2().fontWeight(.light)
}
func pStyle() -> some View{
frame(height: kTFAndPHeight).border(borderColor: Color(.systemGray4))
}
func tfStyle() -> some View{//
frame(height: kTFAndPHeight).padding(.horizontal,9).border(borderColor: Color(.systemGray4)).submitLabel(.done)
}
func tfStyleMultiS() -> some View{
lineLimit(1...4).frame(minHeight: kTFAndPHeight).padding(.horizontal,9).border(borderColor: Color(.systemGray4)).lineSpacing(4)
.fixedSize(horizontal: false, vertical: true)
}
func tfStyleMultiM() -> some View{
lineLimit(3...8).frame(minHeight: kTFAndPHeight).padding(.horizontal,9).border(borderColor: Color(.systemGray4)).lineSpacing(4)
.fixedSize(horizontal: false, vertical: true)
}
func tfStyleMultiL() -> some View{
lineLimit(4...10).frame(minHeight: kTFAndPHeight).padding(.horizontal,9).border(borderColor: Color(.systemGray4)).lineSpacing(4)
.fixedSize(horizontal: false, vertical: true)
}
func conlonStyle() -> some View{
bold().padding(.horizontal,2).offset(y: -1)
}
func toggleStyle(isTap: Bool) -> some View{
size15().foregroundStyle(isTap ? .white : .aTC).padding(.horizontal,10).frame(height: kTFAndPHeight)
.background(isTap ? Color.accent.gradient : Color.bg.gradient).radius()
.border(borderColor: isTap ? .clear : .accent)
}
func appleStyleS() -> some View{ font(.body).frame(maxWidth: .infinity).padding(.vertical, 6) }
func appleStyle(disabled: Bool = false) -> some View{
headline().white().frame(height: 55).frame(maxWidth: .infinity)
.background(disabled ? Color.secondary.gradient : Color.accent.gradient).radius() }
func appleStyleP() -> some View{ tint(.white).frame(height: 55).frame(maxWidth: .infinity).background(.accent.gradient).radius() }
//
//
func loadingJobStyle() -> some View { size13().white().frame(width: 150, height: 25).background(Color.accent.gradient).radius(6) }
//progress
func loadingJobStyleP() -> some View { tint(.white).frame(width: 150, height: 25).background(Color.accent.gradient).radius(6) }
func settingsStyle() -> some View { tc().size16() }
}
extension Image{
func iconStyle() -> some View {
resizable().frame(width: 16, height: 16)
}
}

View File

@ -0,0 +1,59 @@
//
// IOS_studyApp.swift
// IOS_study
//
// Created by CC-star on 2025/6/3.
//
import SwiftUI
import LeanCloud
@main
struct IOS_studyApp: App {
// init(){
//
// }
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
@State var hud = HUD()
@State var tabbarVM = TabBarViewModel()
@State var signInPhoneVM = SignInPhoneViewModel()
@State var reviewVM = ReviewViewModel()
@State var ProfileVM = ProfileViewModel()
@State var nwMonitor = NetworkMonitor()
var body: some Scene {
WindowGroup {
TaBarView()
// PostCompanyView(naviPath: .constant(NavigationPath()))
.hud(isPresented: $hud.isPresented){
Text(hud.title)
}
//
.environment(reviewVM)
.environment(ProfileVM)
.environment(signInPhoneVM)
.environment(hud)
.environment(tabbarVM)
.environment(nwMonitor)
}
}
}
@MainActor class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
UISegmentedControl.appearance().selectedSegmentTintColor = .accent
UISegmentedControl.appearance().setTitleTextAttributes([.font: UIFont.systemFont(ofSize: 17,weight: .medium),.foregroundColor: UIColor(Color.aTC)], for: .normal)
UISegmentedControl.appearance().setTitleTextAttributes([.font: UIFont.systemFont(ofSize: 17,weight: .semibold),.foregroundColor: UIColor(Color.white)], for: .selected)
do {
try LCApplication.default.set(
id: kAppID,
key: kAppKey,
serverURL: kServerURLStr)
} catch {
print(error)
}
return true
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,34 @@
//
// PLJobView+.swift
// IOS_study
//
// Created by CC-star on 2025/7/1.
//
import SwiftUI
extension PLJobView {
func toggleJobStatus() async {
do {
try await vm.toggleJobStatus(id: job.id)
profileVM.shouldRefreshMyPLJobs = true
hud.show(isActive ? "上线成功" : "下线成功")//
} catch {
print("修改pljob的状态失败\(error)")
hud.show("修改失败,请检查网络并重试")
vm.job.isActive.toggle()
}
}
func removeJob() async {
do {
try await vm.removeJob(id: job.id)
profileVM.shouldRefreshMyPLJobs = true
hud.show("删除成功")
if !naviPath.isEmpty { naviPath.removeLast() }
} catch {
print("删除plJob失败\(error)")
hud.show("删除失败,请检查网络并重试")
}
}
}

View File

@ -0,0 +1,145 @@
//
// PLJobView.swift
// IOS_study
//
// Created by CC-star on 2025/6/13.
//
import SwiftUI
import Photos
struct PLJobView: View {
var canEdit = false
var showBoss = true
@Environment(HUD.self) var hud
@Environment(PLJobViewModel.self) var vm
@Environment(ProfileViewModel.self) var profileVM
@Environment(AuthViewModel.self) var authVM
@Environment(ReviewViewModel.self) var reviewVM
@Environment(\.displayScale) var displayScale//
@State var showActiveDialog = false///线
@State var showRemoveDialog = false//
@State var showRemoveAlert = false//
@State var saveImage: UIImage?
@State var openSetting = false
@State var viewDidLoad = false
@State var showReportDialog = false
@State var showPostReview = false
@Binding var naviPath : NavigationPath
var job: PLJob { vm.job }
var isActive:Bool{ vm.job.isActive }
var titleAD: String { isActive ? "" : "[已下线]" }
var isIng: Bool { vm.isRemoving || vm.isToggling }
var shareImage: Image? { if let saveImage { return Image(uiImage: saveImage) } else { return nil } }
var body: some View {
List {
tilteView//
if showBoss { companyView }//
detailView//
contactView//
goRevireBtn
listBottomEView()
}.listStyle().navigationTitle("长期兼职").navigationBarTitleDisplayMode(.inline).navigationBarBackButtonHidden()
//
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button {
if !naviPath.isEmpty {//
naviPath.removeLast()//
}
} label: {
Image(systemName: "chevron.left").secondary().padding(.horizontal,6)
}
}
ToolbarItem(placement: .topBarTrailing) {
HStack(spacing: 3) {
if !canEdit {
Button("举报") { showReportDialog = true }
.confirmationDialog("确定举报吗?", isPresented: $showReportDialog, titleVisibility: .visible) {
Button("确定") {
Task {
await report()
}
}
Button("取消", role: .cancel) { }
}
}
Button { Task { await saveImageToAlbum() } } label: { Text("保存") }
if let shareImage {
ShareLink(item: shareImage, preview: SharePreview("以长图形式分享", image: shareImage)) { Text("分享") }
}
}.tc2().size15()
}
}
.alert("请允许保存图片权限", isPresented: $openSetting) {
Button("再想想") { openSetting = false }
Button("去设置") {
if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url) }
}
} message: { Text("请重新设置照片权限") }
.sheet(isPresented: $showPostReview, content: {
PostReviewView()
})
.onAppear {
if !viewDidLoad {
Task { generateSnapshot() }
viewDidLoad = true
}
}
}
var shareView: some View {
VStack(spacing: 0) {
Text("招兼职").size18b()
tilteView//
companyView //
detailView//
contactView//
listBottomEView()
Text("\(kAppName)App").accent().bold().padding()
}.padding(.top, 60).padding(.bottom, 30)
.frame(width: UIScreen.main.bounds.size.width)//
.bg2()
}
@MainActor func generateSnapshot() {
let renderer = ImageRenderer(content: shareView)
renderer.scale = displayScale
saveImage = renderer.uiImage
}
@MainActor func saveImageToAlbum() async {
if let saveImage {
let currenStatus = PHPhotoLibrary.authorizationStatus(for: .addOnly)
if currenStatus == .notDetermined {//
let status = await PHPhotoLibrary.requestAuthorization(for: .addOnly)//
if status == .authorized {//
writeToAlbum(saveImage)
} else if currenStatus == .authorized {//
writeToAlbum(saveImage)
} else { openSetting = true }
} else { hud.show("相片保存失败,请退出本页后重试") }
}
}
func writeToAlbum(_ saveImage: UIImage) { UIImageWriteToSavedPhotosAlbum(saveImage, nil, nil, nil); hud.show("相片保存成功,请返回相册查看") }
// MARK: -
func report() async {
var reports = (UserDefaults.standard.array(forKey: jobReportsKey) as? [String]) ?? []
if reports.contains(job.id) {
hud.show("已举报,会尽快处理")
} else {
let userID = AuthManager.shared.getUserID()
if !userID.isEmpty {
if reports.count > 50 { reports.removeAll() }
do {
try await JobReportManger.shared.save(report: JobReport(jobID: job.id, jobType: "兼职", jobTitle: job.title, bossID: job.userID, userID: userID))
reports.append(job.id)
UserDefaults.standard.set(reports, forKey: jobReportsKey)
hud.show("举报成功,请等候处理")
} catch {
print("举报失败:\(error)")
hud.show("举报失败,请检查网络")
}
} else { hud.show("请先登录") }
}
}
}

View File

@ -0,0 +1,103 @@
//
// PLJobViewModel.swift
// IOS_study
//
// Created by CC-star on 2025/6/29.
//
import Foundation
@Observable final class PLJobViewModel {
var jobs: [PLJob] = []
var currentPage = 0
var isFcJobs = false
var isJobsLoaded = false
//
var fcJobsErrMsg = ""
var isFinshed = false
var filter = PLFilter()//PLFilter
var order = PLOrder.updatedAt//
var isOF = false //
var job = PLJob()
var draftJob = PLJob()//稿
var isPosting = false//
var isRemoving = false
var isToggling = false///线
@MainActor func getJobs(isResFresh: Bool = false) async {
if isFcJobs { return }//
isFcJobs = true//
defer{ isFcJobs = false }//
isJobsLoaded = false
fcJobsErrMsg = ""
if isResFresh {
currentPage = 0
isFinshed = false
}
do {
let skip = kJobLimit * currentPage
let newJobs = try await PLJobManager.shared.findJobsByREST(limit: kJobLimit, skip: skip, filter: filter, order: order)
if isResFresh {
jobs = newJobs
} else {
jobs.append(contentsOf: newJobs)//
}
currentPage += 1//
isJobsLoaded = true//onappear
if newJobs.count < kJobLimit || newJobs.isEmpty {
isFinshed = true
}
} catch {
if isResFresh {
jobs.removeAll()//
}
fcJobsErrMsg = "\(error)"
print("获取plJobs失败\(error)")
}
}
@MainActor func postJob(isEditing: Bool = false) async throws {
job = draftJob
// job.updatedAT = Date()
//)
job.title = job.title.trimmed
job.placeName = job.placeName.trimmed
job.province = job.province.trimmed
job.city = job.city.trimmed
//)
if job.otherTime.isBlank { job.otherTime = "" } else { job.otherTime = job.otherTime.trimmed }
if job.otherNeed.isBlank { job.otherNeed = "" } else { job.otherNeed = job.otherNeed.trimmed }
if job.workContent.isBlank { job.workContent = "" } else { job.workContent = job.workContent.trimmed }
if job.otherBenefit.isBlank { job.otherBenefit = "" } else { job.otherBenefit = job.otherBenefit.trimmed }
job.userID = AuthManager.shared.getUserID()
//
try await PLJobManager.shared.save(job: job, isEditing: isEditing)
// do {
// try await PLJobManager.shared.saveByREST(job: job)//使
// } catch {
// print(error.localizedDescription)//
// }
}
@MainActor func toggleJobStatus(id: String) async throws {
isToggling = true
defer {
isToggling = false
}
job.isActive.toggle()
try await PLJobManager.shared.toggleJobStatus(id: id, isAictive: job.isActive)
}
@MainActor func removeJob(id: String) async throws {
isRemoving = true
defer { isRemoving = false }
try await PLJobManager.shared.removeJob(id: id)
}
}

View File

@ -0,0 +1,235 @@
//
// PLJobsView+JobUI.swift
// IOS_study
//
// Created by CC-star on 2025/7/24.
//
import SwiftUI
extension PLJobView {
// MARK: -
var tilteView: some View {
VStack(alignment: .leading,spacing: kStackSpacing) {
//
HStack {
VStack(alignment: .leading) {
HStack {
Text(job.business)
.labelBG2()
if job.updatedAt.daysFromNow <= 7 { Text("新到").labelWA() }
if job.wantWorkNum > 0 {
Text("\(job.wantWorkNum)").size13s()
}
}
Text(job.title + titleAD).tc().size18b()
}
Spacer()
Text("\(job.hourlyWage)").accent().headline()
}.padding(.bottom,-2)
//
HStack{
Text(job.placeName)
if job.tax == "不报税" {
Text(job.tax).accentBorder()
} else if job.tax == "报税" {
Text(job.tax).redBorder()
} else {
Text(job.tax).grayBorder()
}
Text("\(job.updatedAt.toStr)发布").secondary()
}
//
HStack(spacing: kStackSpacing){
HStack {
HStack(spacing: kIconSpacing) {
//icon
Image("station").iconStyle()
Text("\(job.province)-\(job.city)").semib()
//
Button {
let destination = LocationHelper.shared.buildSmartAddress(job: job)
LocationHelper.shared.navigateToDestination(destination: destination) { errorMessage in
if let message = errorMessage {
hud.show(message)
}
}
} label: {
Text("导航")
}.btnStyle()
}
HStack(spacing: kIconSpacing) {
Image("want").iconStyle()
Text("\(job.wantNum)")
}
if canEdit && job.userID == AuthManager.shared.getUserID(){
HStack {
Button("编辑") {
vm.draftJob = job
naviPath.append(Navi5.PLJobToEdit)
}.buttonStyle(.borderless).accent().disabled(isIng)
Button { showActiveDialog = true } label: {
if vm.isToggling { ProgressView().controlSize(.small) } else { Text(isActive ? "下线" : "上线") }
}.buttonStyle(.borderless).accent().disabled(isIng)
.confirmationDialog(isActive ? "确定下线吗" : "确定上线吗", isPresented: $showActiveDialog, titleVisibility: .visible) {
Button("确定") {
Task { await toggleJobStatus() }
}
Button("再想想", role: .cancel) { }
}
Button { showRemoveDialog = true } label: {
if vm.isRemoving { ProgressView().controlSize(.small) } else { Text("删除") }
}.buttonStyle(.borderless).accent().disabled(isIng)
.confirmationDialog("确定删除吗", isPresented: $showRemoveDialog, titleVisibility: .visible) {
Button("确定", role: .destructive) {
showRemoveAlert = true
}
Button("再想想", role: .cancel) { }
}.alert("删除之后不可恢复", isPresented: $showRemoveAlert) {
Button("确定", role: .destructive) { Task { await removeJob() } }
Button("取消", role: .cancel) { }
}
}
}
}
}
}.textStyle2().cellStyle()
}
// MARK: -
var companyView: some View {
Button {
//todo
} label: {
HStack(spacing: kIconSpacing){
Image("company").iconStyle()
Text(job.companyName).size16semib().oneLineStyleMiddle()
Spacer()
Group {
Text("更多工作")
Image(systemName: "chevron.right")
}.secondary()
}
}.textStyle().cellStyle().buttonStyle(.borderless)
}
// MARK: -
var detailView: some View {
VStack(alignment: .leading,spacing:kStackSpacingS) {
//
VStack(alignment: .leading,spacing: kStackSpacingS) {
HStack(spacing: kIconSpacing) {
Image("time").iconStyle()
Text("工作时间").subTStyle()
}
HStack(spacing: 2) {
Text("\(job.startHour)")
Text(":").conlonStyle()
Text(job.startMin == 0 ? "00" : "\(job.startMin)")
Text("-").conlonStyle()
Text("\(job.endHour)")
Text(":").conlonStyle()
Text(job.endMin == 0 ? "00" : "\(job.endMin)")
}
if job.has2 {
HStack(spacing: 2) {
Text("\(job.startHour2)")
Text(":").conlonStyle()
Text(job.startMin2 == 0 ? "00" : "\(job.startMin2)")
Text("-").conlonStyle()
Text("\(job.endHour2)")
Text(":").conlonStyle()
Text(job.endMin2 == 0 ? "00" : "\(job.endMin2)")
}
}
// Text("\(job.startHour)-\(job.endHour)").labelBG()
// if job.has2 {
// Text("\(job.startHour2)-\(job.endHour2)").labelBG()
// }
}
// Divider()
exDivider()
if !job.workContent.isEmpty {
//
VStack(alignment: .leading,spacing: kStackSpacingS) {
HStack(spacing: kIconSpacing) {
Image("content").iconStyle()
Text("工作内容").subTStyle()
}
Text(job.workContent).allowMulti()
}
exDivider()
}
//
VStack(alignment: .leading,spacing: kStackSpacingS) {
HStack(spacing: kIconSpacing) {
Image("need").iconStyle()
Text("工作要求").subTStyle()
}
HStack {
Text(job.needExp).labelWAL()
Text(job.needEdu).labelWAL()
}
HStack {
Text(job.needLan).labelWAL()
Text(job.needFrame).labelWAL()
}
if !job.otherNeed.isEmpty {
Text(job.otherNeed).allowMulti()
}
}
//
exDivider()
VStack(alignment: .leading,spacing: kStackSpacingS) {
HStack(spacing: kIconSpacing) {
Image("benefit").iconStyle()
Text("公式福利").subTStyle()
}
HStack {
if job.noOverTime {
Text("不加班").labelWAL()
}
if job.moveFee {
Text("交通费").labelWAL()
}
}
//
if !job.otherBenefit.isEmpty {
Text(job.otherBenefit).allowMulti()
}
}
}.textStyle2().cellStyle()
}
// MARK: -
var contactView: some View {
VStack(alignment: .leading,spacing:kStackSpacingS){
Text("应聘方式(\(job.contactType)").subTStyleL()
HStack {
Text("\(job.contact)").size20b().tc().allowMulti()
Spacer()
Button("复制"){
UIPasteboard.general.string = job.contact
hud.show("复制成功")
//todo
}.btnStyle().padding(6)
// Button {
// UIPasteboard.general.string = "0423-1230-1241"
// //todo
// }
// } label: {
// Text("")
// }.btnStyle().padding(6)
}
Text("若问起,请说是在测试软件上看到的").size14().purple()
}.cellStyle()
}
}

View File

@ -0,0 +1,31 @@
//
// FilterView.swift
// IOS_study
//
// Created by CC-star on 2025/7/8.
//
import SwiftUI
struct FilterView: View {
var body: some View {
VStack(spacing: 14) {
VStack(alignment: .leading) {
Text("行业").labelStyleOF()
LazyVGrid(columns: [GridItem(.adaptive(minimum: 70))]) {
ForEach(kBusinessArr,id: \.self) { business in
Button {
} label: {
Text(business).frame(maxWidth: .infinity)
}.buttonStyle(.borderless)
}
}
}
}.padding()
}
}
#Preview {
FilterView()
}

View File

@ -0,0 +1,68 @@
//
// PLJobCellView.swift
// IOS_study
//
// Created by CC-star on 2025/6/12.
//
import SwiftUI
struct PLJobCellView: View {
let job: PLJob
var body: some View {
VStack(spacing: 8) {
//
HStack {
VStack(alignment: .leading) {
HStack {
Text(job.business)
.labelBG2()
if job.updatedAt.daysFromNow <= 7 { Text("新到").labelWA() }
if job.wantWorkNum > 0 {
Text("\(job.wantWorkNum)").size13s()
}
}
HStack {
Text(job.title).tc().headline().oneLineStyleMiddle()
if job.tax == "不报税" {
Text(job.tax).accentBorder()
} else if job.tax == "报税" {
Text(job.tax).redBorder()
} else {
Text(job.tax).grayBorder()
}
}
}
Spacer()
Text("\(job.hourlyWage)").accent().headline()
}
//
OverflowGrid(horizontalSpacing: 5) {
Text(job.needExp).labelBG()
Text(job.needEdu).labelBG()
Text(job.needLan).labelBG()
Text(job.needFrame).labelBG()
Text("\(job.startHour)-\(job.endHour)").labelBG()
if job.has2 {
Text("\(job.startHour2)-\(job.endHour2)").labelBG()
}
if job.moveFee { Text("交通费").labelPP() }
if job.noOverTime { Text("不加班").labelPP() }
}.push(to: .leading)
//
HStack {
Text(job.placeName).tc().lineLimit(1)
Spacer()
Text("\(job.province)-\(job.city)").accent().semib().oneLineStyleMiddle()
}.size14()
}
.cellPaddingRadius().cellOutPadding()
}
}
#Preview {
PLJobCellView(job: PLJob.job)
}

View File

@ -0,0 +1,118 @@
//
// PLJobsView+.swift
// IOS_study
//
// Created by CC-star on 2025/7/10.
//
import SwiftUI
extension PLJobsView {
//
var filterBtn: some View {
Button {
showOrderView = false
showFilterView.toggle()//
} label: {
HStack(spacing: 3) {
Text("筛选")
if vm.filter.totalCount > 0 {
Text("\(vm.filter.totalCount)").badgeStyle()
}
arrowIcon(showFilterView)
}.tc2().size15()
}.disabled(vm.isFcJobs)
}
var plFilterView: some View {
VStack(spacing: 14) {
VStack(alignment: .leading) {
Text("行业").labelStyleOF()
LazyVGrid(columns: [GridItem(.adaptive(minimum: 80))]) {
ForEach(kBusinessArr,id: \.self) { business in
Button {
if vm.filter.business == business {
vm.filter.business = nil
} else {
vm.filter.business = business
}
} label: {
Text(business).frame(maxWidth: .infinity)
}.selectBtnStyle(vm.filter.business == business)
}
}
}
VStack(alignment: .leading) {
Text("城市").labelStyleOF()
LazyVGrid(columns: [GridItem(.adaptive(minimum: 70))]) {
ForEach(kCityArr,id: \.self) { city in
Button {
if vm.filter.city == city {
vm.filter.city = nil
} else {
vm.filter.city = city
}
} label: {
Text(city).frame(maxWidth: .infinity)
}.selectBtnStyle(vm.filter.city == city)
}
}
}
HStack {
Button {
vm.filter = PLFilter()
} label: {
Text("清空").appleStyleS()
}.buttonStyle(.bordered)
Button {
showFilterView = false
getJobsByOF()
} label: {
Text("筛选").appleStyleS()
}.tint(.aTC).buttonStyle(.borderedProminent)
}.padding(.top, 9)
}.size14().padding().bg()
}
//
var orderBtn: some View {
Button {
showFilterView = false
showOrderView.toggle()//
} label: {
HStack(spacing: 3) {
Text(vm.order.rawValue)
arrowIcon(showOrderView)
}.tc2().size15()
}.disabled(vm.isFcJobs)
}
//
var plOrderView: some View {
VStack(spacing: 14) {
ForEach(PLOrder.allCases.indices, id: \.self) { index in
let order = PLOrder.allCases[index]
HStack {
Text(order.rawValue).foregroundStyle(vm.order == order ? .accent : .aTC)
Spacer()
if vm.order == order { Image(systemName: "checkmark.circle.fill").accent() }
}.contentShape(Rectangle())//
.onTapGesture {
vm.order = order
showOrderView = false
getJobsByOF()
}
if order != PLOrder.allCases.last { exDivider() }
}
}.size15().padding().bg()
}
func getJobsByOF() {//
vm.isOF.toggle()
if vm.filter == lastPLFilter && vm.order == lastPLOrder { return }//\
lastPLFilter = vm.filter
lastPLOrder = vm.order
Task {
await vm.getJobs(isResFresh: true)//
}
}
}

View File

@ -0,0 +1,158 @@
//
// PLJobsView.swift
// IOS_study
//
// Created by CC-star on 2025/6/12.
//
import SwiftUI
import LeanCloud
struct PLJobsView: View {
@State var lastPLFilter = PLFilter()
@State var showFilterView = false
@State var showOrderView = false
@State var lastPLOrder: PLOrder = .updatedAt
@Environment(HUD.self) var hud
@Environment(PLJobViewModel.self) var vm
@Environment(TabBarViewModel.self) var TabBarVM
@Environment(NetworkMonitor.self) var nt
@State var naviPath = NavigationPath()
var body: some View {
NavigationStack(path: $naviPath) {
ZStack {
if !vm.jobs.isEmpty {
ScrollViewReader { scrollView in
List{
Color.clear.frame(height: 1).listRowStyle().id("top")//
ForEach(vm.jobs) {job in
Button {
vm.job = job
naviPath.append(Navi1.PLJobsToPLJob)
} label: {
PLJobCellView(job: job)
}.buttonStyle(.borderless)
}.listRowStyle()
// Button("", action: {
// loadMore()
// }).buttonStyle(.borderless)
Group {
if vm.fcJobsErrMsg.isEmpty {
if !vm.isFinshed {
if vm.isFcJobs { Text("正在加载中...") } else { Text("正在加载中...").onAppear { loadMore() } }
} else {
if nt.isConnected {
Text("已全部加载")
} else {
Text("当前无网络,无法加载更多数据,请联网后下拉刷新")
}
}
} else {
HStack {
Text("自动加载数据失败,请手动加载")
Button("点击加载", action: loadMore).buttonStyle(.borderless).accent()
}
}
}.listRowStyleLoading2()
}.listStyle().refreshable { await refreashable() }
.onChange(of: vm.isOF) {//
withAnimation { scrollView.scrollTo("top", anchor: .top) }
}
}
} else {
//,
if vm.fcJobsErrMsg.isEmpty {//
if !vm.isFcJobs {
if nt.isConnected && vm.filter.totalCount > 0 {
//todo
Text("没有找到符合筛选条件的在招职位,请更改筛选条件").explainStyle()
} else {//1. 2.
VStack(spacing: 9) {//SDK
Button {
refreash()
} label: {
Text("点击刷新").appleStyle()
}
Text("网络问题若刷新失败请重启App").explainStyle()
}
}
} else {
Color.bg2//
}
} else {//
VStack(alignment: .leading, spacing: 9) {
Button {
refreash()
} label: {
Text("点击刷新").appleStyle()
}
Text("加载数据失败,请点击刷新")
HStack {
Text("若刷新失败,请截图并联系客服微信:\(KdevContact)")
Button {
UIPasteboard.general.string = KdevContact
hud.show("复制成功")
} label: {
Text("复制").accent()
}
}
exDivider()
// HStack {
Text("错误信息").push(to: .center)
Text(vm.fcJobsErrMsg).red()
// }
}.explainStyle()
}
}
}.naviBarStyle()
.navigationDestination(for: Navi1.self) { navi in
switch navi {
case .PLJobsToPLJob : PLJobView(naviPath: $naviPath)
}
}
.toolbar {
ToolbarItem(placement: .topBarLeading) { orderBtn }
ToolbarItem(placement: .principal) { Text("好工作").tc().bold() }
ToolbarItem(placement: .topBarTrailing) { filterBtn }
}
.overlay {
if showFilterView || showOrderView {//\
Color.black.opacity(0.3)//
.onTapGesture {//
showFilterView = false
showOrderView = false
}
}
}
.overlay(alignment: .top) {
if showFilterView { plFilterView } else if showOrderView { plOrderView }
}
.overlay { if vm.isFcJobs { BprogressView() } }
}.animation(.default, value: showFilterView)//showFilterView使default
.onAppear {
getJobsOnce()
refreshIFNeed()
}
}
func getJobsOnce() {
if vm.isJobsLoaded { return }
Task { await vm.getJobs() }
}
func refreshIFNeed() {
if vm.isJobsLoaded && TabBarVM.refreshJobs && TabBarVM.jobType == "兼职" {
vm.filter = PLFilter()
vm.order = .updatedAt
refreash()
TabBarVM.refreshJobs = false
}
}
func refreashable() async { await vm.getJobs(isResFresh: true) }
func refreash() { Task { await refreashable() } }
func loadMore() { Task { await vm.getJobs() } }
}
#Preview {
PLJobsView()
}

View File

@ -0,0 +1,45 @@
//
// HUDView.swift
//
//
// Created by CC-star on 2025/7/15.
//
import SwiftUI
import Combine
//
extension View {
func hud<Content: View> (isPresented: Binding<Bool>, @ViewBuilder content: () -> Content) -> some View {
ZStack {
self
if isPresented.wrappedValue {
HUDView(content: content).zIndex(1)
}
}.animation(.default, value: isPresented.wrappedValue)
}
}
struct HUDView<Content: View>: View {
@ViewBuilder let content: Content
var body: some View {
content.font(.subheadline).foregroundStyle(Color.aTCR)
.padding(.horizontal, 4).padding(12)
.background(RoundedRectangle(cornerRadius: 8).fill(Color.aBGR))
}
}
@Observable final class HUD {
var isPresented = false
private(set) var title: String = ""
private var dismissTimer: AnyCancellable? // --
func show(_ title: String) {
self.title = title
isPresented = true
dismissTimer?.cancel()// -
// 3-
dismissTimer = Timer.publish(every: 3, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
self?.isPresented = false
self?.dismissTimer?.cancel() //
}
}
}

View File

@ -0,0 +1,22 @@
//
// NetworkMonitor.swift
//
//
// Created by CC-star on 2025/7/8.
//
import Foundation
import Network
@Observable final class NetworkMonitor {
private let networkMonitor = NWPathMonitor()
private let workerQueue = DispatchQueue(label: "Monitor")
var isConnected = false
init() {
networkMonitor.pathUpdateHandler = { path in
self.isConnected = path.status == .satisfied
}
networkMonitor.start(queue: workerQueue)
}
}

View File

@ -0,0 +1,50 @@
import SwiftUI
//
//https://stackoverflow.com/questions/58842453/hstack-with-wrap RelativeJoe
public struct OverflowGrid: Layout {
private var horizontalSpacing: CGFloat
private var vericalSpacing: CGFloat
public init(horizontalSpacing: CGFloat, vericalSpacing: CGFloat? = nil) {
self.horizontalSpacing = horizontalSpacing
self.vericalSpacing = vericalSpacing ?? horizontalSpacing
}
public func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let height = subviews.max(by: {$0.dimensions(in: proposal).height > $1.dimensions(in: proposal).height})?.dimensions(in: proposal).height ?? 0
var rows = [CGFloat]()
subviews.indices.forEach { index in
let rowIndex = rows.count - 1
let subViewWidth = subviews[index].dimensions(in: proposal).width
guard !rows.isEmpty else {
rows.append(subViewWidth)
return
}
let newWidth = rows[rowIndex] + subViewWidth + horizontalSpacing
if newWidth < proposal.width ?? 0 {
rows[rowIndex] += (rows[rowIndex] > 0 ? horizontalSpacing: 0) + subViewWidth
}else {
rows.append(subViewWidth)
}
}
let count = CGFloat(rows.count)
return CGSize(width: rows.max() ?? 0, height: count * height + (count - 1) * vericalSpacing)
}
public func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
let height = subviews.max(by: {$0.dimensions(in: proposal).height > $1.dimensions(in: proposal).height})?.dimensions(in: proposal).height ?? 0
guard !subviews.isEmpty else {return}
var x = bounds.minX
var y = height/2 + bounds.minY
subviews.indices.forEach { index in
let subView = subviews[index]
x += subView.dimensions(in: proposal).width/2
subviews[index].place(at: CGPoint(x: x, y: y), anchor: .center, proposal: ProposedViewSize(width: subView.dimensions(in: proposal).width, height: subView.dimensions(in: proposal).height))
x += horizontalSpacing + subView.dimensions(in: proposal).width/2
if x > bounds.width {
x = bounds.minX
y += height + vericalSpacing
}
}
}
}

View File

@ -0,0 +1,139 @@
//
// PinchZoomView.swift
//
//
// Created by CC-star on 2025/7/22.
//
import SwiftUI
//-
//Image("Zoom").pinchToZoom()
class PinchZoomView: UIView {
weak var delegate: PinchZoomViewDelgate?
private(set) var scale: CGFloat = 0 {
didSet { delegate?.pinchZoomView(self, didChangeScale: scale) }
}
private(set) var anchor: UnitPoint = .center {
didSet { delegate?.pinchZoomView(self, didChangeAnchor: anchor) }
}
private(set) var offset: CGSize = .zero {
didSet { delegate?.pinchZoomView(self, didChangeOffset: offset) }
}
private(set) var isPinching: Bool = false {
didSet { delegate?.pinchZoomView(self, didChangePinching: isPinching) }
}
private var startLocation: CGPoint = .zero
private var location: CGPoint = .zero
private var numberOfTouches: Int = 0
init() {
super.init(frame: .zero)
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinch(gesture:)))
pinchGesture.cancelsTouchesInView = false
addGestureRecognizer(pinchGesture)
}
required init?(coder: NSCoder) { fatalError() }
@objc private func pinch(gesture: UIPinchGestureRecognizer) {
switch gesture.state {
case .began:
isPinching = true
startLocation = gesture.location(in: self)
anchor = UnitPoint(x: startLocation.x / bounds.width, y: startLocation.y / bounds.height)
numberOfTouches = gesture.numberOfTouches
case .changed:
if gesture.numberOfTouches != numberOfTouches {
// If the number of fingers being used changes, the start location needs to be adjusted to avoid jumping.
let newLocation = gesture.location(in: self)
let jumpDifference = CGSize(width: newLocation.x - location.x, height: newLocation.y - location.y)
startLocation = CGPoint(x: startLocation.x + jumpDifference.width, y: startLocation.y + jumpDifference.height)
numberOfTouches = gesture.numberOfTouches
}
scale = gesture.scale
location = gesture.location(in: self)
offset = CGSize(width: location.x - startLocation.x, height: location.y - startLocation.y)
case .ended, .cancelled, .failed:
isPinching = false
//--
//withAnimation(.interactiveSpring(response: 0.2, dampingFraction: 0.75, blendDuration: 0.1))
withAnimation(.interactiveSpring(response: 0.2, dampingFraction: 0.65, blendDuration: 0.1)) {
scale = 1.0
anchor = .center
offset = .zero
}
default: break
}
}
}
protocol PinchZoomViewDelgate: AnyObject {
func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangePinching isPinching: Bool)
func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeScale scale: CGFloat)
func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeAnchor anchor: UnitPoint)
func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeOffset offset: CGSize)
}
struct PinchZoom: UIViewRepresentable {
@Binding var scale: CGFloat
@Binding var anchor: UnitPoint
@Binding var offset: CGSize
@Binding var isPinching: Bool
func makeCoordinator() -> Coordinator { Coordinator(self) }
func makeUIView(context: Context) -> PinchZoomView {
let pinchZoomView = PinchZoomView()
pinchZoomView.delegate = context.coordinator
return pinchZoomView
}
func updateUIView(_ pageControl: PinchZoomView, context: Context) { }
class Coordinator: NSObject, PinchZoomViewDelgate {
var pinchZoom: PinchZoom
init(_ pinchZoom: PinchZoom) { self.pinchZoom = pinchZoom }
func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangePinching isPinching: Bool) {
pinchZoom.isPinching = isPinching
}
func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeScale scale: CGFloat) {
pinchZoom.scale = scale
}
func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeAnchor anchor: UnitPoint) {
pinchZoom.anchor = anchor
}
func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeOffset offset: CGSize) {
pinchZoom.offset = offset
}
}
}
struct PinchToZoom: ViewModifier {
@State var scale: CGFloat = 1.0
@State var anchor: UnitPoint = .center
@State var offset: CGSize = .zero
@State var isPinching: Bool = false
func body(content: Content) -> some View {
content.scaleEffect(scale, anchor: anchor).offset(offset)
//pinchwithanimation
// .animation(isPinching ? .none : .spring(), value: scale)
// .animation(.spring(duration: 1), value: scale)
.overlay(PinchZoom(scale: $scale, anchor: $anchor, offset: $offset, isPinching: $isPinching))
// .onChange(of: isPinching) {
// if !isPinching {
// //
// withAnimation(.spring()) {
// scale = 1.0
// offset = .zero
// anchor = .center
// }
// }
// }
}
}
extension View {
func pinchToZoom() -> some View { self.modifier(PinchToZoom()) }
}

View File

@ -0,0 +1,29 @@
//
// AuthManager.swift
// IOS_study
//
// Created by CC-star on 2025/7/10.
//
import Foundation
import LeanCloud
final class AuthManager {
static let shared = AuthManager()
private init() {}
func getVerCode(phoneNum: String, tName: String, sName: String) async throws {
try await LCSMSClient.requestShortMessage(mobilePhoneNumber: phoneNum, templateName: tName, signatureName: sName)
}
func login(phoneNum: String, verCode: String) async throws -> LCUser {
try await LCUser.signUpOrLogIn(mobilePhoneNumber: phoneNum, verificationCode: verCode)
}
func getUserID() -> String {
LCApplication.default.currentUser?.objectId?.value ?? ""//dbuserid
}
func getUser() -> LCUser? { LCApplication.default.currentUser }
func logOut() {
LCUser.logOut()
}
}

View File

@ -0,0 +1,194 @@
//
// PLJobManager.swift
// IOS_study
//
// Created by CC-star on 2025/7/2.
//
import Foundation
import LeanCloud
final class PLJobManager {
//
static var shared = PLJobManager()
private init() {}
private let className = "PLJob"
// //
// func findJobs() async throws -> [PLJob] {
// let query = LCQuery(className: className)
// query.whereKey(PLJob.CodingKeys.isActive.rawValue,.equalTo(true))//equalTo -> =
// query.whereKey("updatedAt", .descending)//
//
// return try await query.find(as: PLJob.self)
// }
// MARK: -
func findJobsByREST(limit: Int, skip: Int, filter: PLFilter, order: PLOrder) async throws -> [PLJob] {
// let urlStr = """
// \(kServerURLStr)/1.1/classes/\(className)?where={\(PLJob.CodingKeys.isActive.rawValue): true }&order=-updatedAt
// """
var components = URLComponents(string: "\(kServerURLStr)/1.1/classes/\(className)")!
//
var whereDic: [String: Any] = [PLJob.CodingKeys.isActive.rawValue: true]
if let business = filter.business {//
whereDic[PLJob.CodingKeys.business.rawValue] = business
}
if let city = filter.city {//
whereDic[PLJob.CodingKeys.city.rawValue] = city
}
//
let whereData = try JSONSerialization.data(withJSONObject: whereDic)
guard let whereStr = String(data: whereData, encoding: .utf8) else { throw URLError(.badURL) }
//
var orderStr = ""
switch order {
case .updatedAt:
orderStr = "-updatedAt"
case .hourlywage:
orderStr = "-hourlywage"
}
components.queryItems = [
URLQueryItem(name: "where", value: whereStr),
URLQueryItem(name: "order", value: orderStr),
URLQueryItem(name: "limit", value: "\(limit)"),
URLQueryItem(name: "skip", value: "\(skip)")
]
guard let urlstr = components.url?.absoluteString else { throw URLError(.badURL) }
return try await RESTManager.shared.find(form: urlstr)
}
// MARK: -
func findJobsByREST(by userID: String, limit: Int, skip: Int) async throws -> [PLJob] {
var components = URLComponents(string: "\(kServerURLStr)/1.1/classes/\(className)")!
//
let whereDic: [String: Any] = [PLJob.CodingKeys.userID.rawValue: userID]
//
let whereData = try JSONSerialization.data(withJSONObject: whereDic)
guard let whereStr = String(data: whereData, encoding: .utf8) else { throw URLError(.badURL) }
//
components.queryItems = [
URLQueryItem(name: "where", value: whereStr),
URLQueryItem(name: "order", value: "-updatedAt"),
URLQueryItem(name: "limit", value: "\(limit)"),
URLQueryItem(name: "skip", value: "\(skip)")
]
guard let urlstr = components.url?.absoluteString else { throw URLError(.badURL) }
return try await RESTManager.shared.find(form: urlstr)
}
// MARK: - job线/
func findJobsCount(by userID: String, onlyActive: Bool = false) async -> Int {
let query = LCQuery(className: className)
query.whereKey(PLJob.CodingKeys.userID.rawValue, .equalTo(userID))
if onlyActive { query.whereKey(PLJob.CodingKeys.isActive.rawValue, .equalTo(true)) }
return (try? await query.count()) ?? 0
}
func save(job: PLJob, isEditing: Bool = false) async throws {
//
var lcObject = LCObject(className: "PLJob")
if isEditing {
let query = LCQuery(className: className)
query.whereKey("id", .equalTo(job.id))
lcObject = try await query.getFirst()
}
//
try lcObject.set(PLJob.CodingKeys.id.rawValue, value: job.id)
try lcObject.set(PLJob.CodingKeys.isActive.rawValue, value: job.isActive)
try lcObject.set(PLJob.CodingKeys.title.rawValue, value: job.title)
try lcObject.set(PLJob.CodingKeys.business.rawValue, value: job.business)
try lcObject.set(PLJob.CodingKeys.tax.rawValue, value: job.tax)
try lcObject.set(PLJob.CodingKeys.hourlyWage.rawValue, value: job.hourlyWage)
try lcObject.set(PLJob.CodingKeys.wantNum.rawValue, value: job.wantNum)
try lcObject.set(PLJob.CodingKeys.contactType.rawValue, value: job.contactType)
try lcObject.set(PLJob.CodingKeys.contact.rawValue, value: job.contact)
//
try lcObject.set(PLJob.CodingKeys.startHour.rawValue, value: job.startHour)
try lcObject.set(PLJob.CodingKeys.startMin.rawValue, value: job.startMin)
try lcObject.set(PLJob.CodingKeys.endHour.rawValue, value: job.endHour)
try lcObject.set(PLJob.CodingKeys.endMin.rawValue, value: job.endMin)
try lcObject.set(PLJob.CodingKeys.has2.rawValue, value: job.has2)
try lcObject.set(PLJob.CodingKeys.startHour2.rawValue, value: job.startHour2)
try lcObject.set(PLJob.CodingKeys.startMin2.rawValue, value: job.startMin2)
try lcObject.set(PLJob.CodingKeys.endHour2.rawValue, value: job.endHour2)
try lcObject.set(PLJob.CodingKeys.endMin2.rawValue, value: job.endMin2)
try lcObject.set(PLJob.CodingKeys.otherTime.rawValue, value: job.otherTime)
//
try lcObject.set(PLJob.CodingKeys.placeName.rawValue, value: job.placeName)
try lcObject.set(PLJob.CodingKeys.province.rawValue, value: job.province)
try lcObject.set(PLJob.CodingKeys.city.rawValue, value: job.city)
try lcObject.set(PLJob.CodingKeys.workContent.rawValue, value: job.workContent)
//
try lcObject.set(PLJob.CodingKeys.needExp.rawValue, value: job.needExp)
try lcObject.set(PLJob.CodingKeys.needEdu.rawValue, value: job.needEdu)
try lcObject.set(PLJob.CodingKeys.needLan.rawValue, value: job.needLan)
try lcObject.set(PLJob.CodingKeys.needFrame.rawValue, value: job.needFrame)
try lcObject.set(PLJob.CodingKeys.otherNeed.rawValue, value: job.otherNeed)
//
try lcObject.set(PLJob.CodingKeys.moveFee.rawValue, value: job.moveFee)
try lcObject.set(PLJob.CodingKeys.noOverTime.rawValue, value: job.noOverTime)
try lcObject.set(PLJob.CodingKeys.otherBenefit.rawValue, value: job.otherBenefit)
//
try lcObject.set(PLJob.CodingKeys.companyName.rawValue, value: job.companyName)
try lcObject.set(PLJob.CodingKeys.userID.rawValue, value: job.userID)
// try lcObject.set("updatedAT", value: job.updatedAT)
//
// _ = lcObject.save { result in
// switch result {
// case .success:
// //
// break
// case .failure(error: let error):
// //
// print(error)
// }
// }
try await lcObject.save()
}
func saveByREST(job: PLJob) async throws {
//
// guard let url = URL(string: "\(kServerURLStr)/1.1/classes/PLJob") else {
// throw NetworkError.invalidURL// ->
// }//urlelse
//
try await RESTManager.shared.save(to: "\(kServerURLStr)/1.1/classes/\(className)", object: job)
}
// MARK: - /线
func toggleJobStatus(id: String, isAictive: Bool) async throws {
let query = LCQuery(className: className)
query.whereKey("id", .equalTo(id))
let lcObject = try await query.getFirst()
try lcObject.set(PLJob.CodingKeys.isActive.rawValue, value: isAictive)
try await lcObject.save()
}
// MARK: -
func removeJob(id: String) async throws {
let query = LCQuery(className: className)
query.whereKey("id", .equalTo(id))
let lcObject = try await query.getFirst()
try await lcObject.delete()
}
// MARK: -job
func removeJobs(by userID: String) async {
let query = LCQuery(className: className)
query.whereKey(PLJob.CodingKeys.userID.rawValue, .equalTo(userID))
guard let jobObjects = try? await query.find() else { return }
try? await LCObject.delete(jobObjects)//
}
}

View File

@ -0,0 +1,106 @@
//
// RESTManager.swift
// IOS_study
//
// Created by CC-star on 2025/7/3.
//
import Foundation
final class RESTManager {
static let shared = RESTManager()
private init() {}//RESTManagerclass
//REST API
func findOne<T: Decodable>(form urlStr: String) async throws -> T {
guard let url = URL(string: urlStr) else { throw URLError(.badURL) }
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(kAppID, forHTTPHeaderField: "X-LC-Id")
request.setValue(kAppKey, forHTTPHeaderField: "X-LC-Key")
let (data, response) = try await URLSession.shared.data(for: request)
//print(String(data: data, encoding: .utf8) ?? "Invalid data")
guard let httpRespsonse = response as? HTTPURLResponse, 200..<300 ~= httpRespsonse.statusCode else { throw URLError(.badServerResponse) }
let decoder = JSONDecoder()
//decoder.keyDecodingStrategy = .convertFromSnakeCase//线
//Date
decoder.dateDecodingStrategy = .custom { decoder -> Date in
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] //
if let date = isoFormatter.date(from: dateString) { return date }
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date format: \(dateString)")
}
return try decoder.decode( T.self, from: data)
}
//REST API
func find<T: Decodable>(form urlStr: String) async throws ->[T] {
guard let url = URL(string: urlStr) else { throw URLError(.badURL) }
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(kAppID, forHTTPHeaderField: "X-LC-Id")
request.setValue(kAppKey, forHTTPHeaderField: "X-LC-Key")
let (data,_) = try await URLSession.shared.data(for: request)
//print(String(data: data, encoding: .utf8) ?? "Invalid data")
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let results = json["results"] as? [[String: Any]] else {
throw URLError(.cannotParseResponse)
}
let decoder = JSONDecoder()
//decoder.keyDecodingStrategy = .convertFromSnakeCase//线
//Date
decoder.dateDecodingStrategy = .custom { decoder -> Date in
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] //
if let date = isoFormatter.date(from: dateString) { return date }
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date format: \(dateString)")
}
let tData = try JSONSerialization.data(withJSONObject: results)
return try decoder.decode([T].self, from: tData)
}
//REST API
func save<T: Encodable>(to urlStr: String, object: T) async throws {//TEncodable
guard let url = URL(string: urlStr) else { throw NetworkError.invalidURL// ->
}//urlelse
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(kAppID, forHTTPHeaderField: "X-LC-Id")
request.setValue(kAppKey, forHTTPHeaderField: "X-LC-Key")
let encoder = JSONEncoder()
// encoder.keyEncodingStrategy = .convertToSnakeCase//线
request.httpBody = try encoder.encode(object)//使LeanCloud RESTDate
let (data, response) = try await URLSession.shared.data(for: request)//
guard let httpRespsonse = response as? HTTPURLResponse else { throw NetworkError.notHTTPResponse }
if !(200..<300).contains(httpRespsonse.statusCode) {// 2 -> [200,300)
let errRespsonse = try JSONSerialization.jsonObject(with: data) as? [String: Any]//Any
let errMsg = errRespsonse?["error"] as? String ?? "响应数据类型转换失败"
//errRespsonseerror
//errRespsonse?["error"] as? String??
throw NetworkError.requestFailed("错误码:\(httpRespsonse.statusCode),错误信息:\(errMsg)")
}
}
}

View File

@ -0,0 +1,25 @@
//
// Reportmanager.swift
// IOS_study
//
// Created by CC-star on 2025/7/24.
//
import Foundation
import LeanCloud
class JobReportManger {
static let shared = JobReportManger()
private init() {}
private let className = "JobReport"
func save(report: JobReport) async throws {
let reportObject = LCObject(className: className)
try reportObject.set(JobReport.CodingKeys.jobID.rawValue, value: report.jobID)
try reportObject.set(JobReport.CodingKeys.jobType.rawValue, value: report.jobType)
try reportObject.set(JobReport.CodingKeys.jobTitle.rawValue, value: report.jobTitle)
try reportObject.set(JobReport.CodingKeys.bossID.rawValue, value: report.bossID)
try reportObject.set(JobReport.CodingKeys.userID.rawValue, value: report.userID)
try await reportObject.save()
}
}

View File

@ -0,0 +1,29 @@
//
// StorageManager.swift
// IOS_study
//
// Created by CC-star on 2025/7/15.
//
import UIKit
import LeanCloud
final class StorageManager {
static let shared = StorageManager()
private init() { }
func saveImage(_ image: UIImage) async throws -> String {
if let data = image.heicData() {
if Double(data.count) / (1024 * 1024) >= 4 {//4MB
throw UserError.imageTooBig
}
let file = LCFile(payload: .data(data: data))
file.mimeType = "image/heic"
return try await file.save()
} else {
throw UserError.imageToheicDataError
}
}
}

View File

@ -0,0 +1,66 @@
//
// UserManager.swift
// IOS_study
//
// Created by CC-star on 2025/7/14.
//
import Foundation
import LeanCloud
final class UserManager {
static var shared = UserManager()
private init() {}
private let className = "DBUser"
func findUserByREST() async throws -> DBUser {
let query = LCQuery(className: className)
query.whereKey("id", .equalTo(AuthManager.shared.getUserID()))
guard let objectId = (try await query.getFirst()).objectId?.value else { throw UserError.objectIdNotFound }
let urlStr = "\(kServerURLStr)/1.1/classes/\(className)/\(objectId)"
return try await RESTManager.shared.findOne(form: urlStr)
}
func create(user: LCUser) async throws {
let dbUser = LCObject(className: className)
try dbUser.set(DBUser.CodingKeys.id.rawValue, value: user.objectId)
var nickName = ""
if let objectId = user.objectId?.value {
nickName = objectId.suffix(4).uppercased()//
} else {
nickName = String.randomString(4)
}
try dbUser.set(DBUser.CodingKeys.nickName.rawValue, value: "数据猫用户\(nickName)")
try await dbUser.save()
}
func update(user: DBUser) async throws {
let query = LCQuery(className: className)
query.whereKey("id", .equalTo(AuthManager.shared.getUserID()))
let userObject = try await query.getFirst()
try userObject.set(DBUser.CodingKeys.cName.rawValue, value: user.cName)
try userObject.set(DBUser.CodingKeys.cLogoURLStr.rawValue, value: user.cLogoURLStr)
try userObject.set(DBUser.CodingKeys.cImageURLStrs.rawValue, value: user.cImageURLStrs)
try userObject.set(DBUser.CodingKeys.cAboutUS.rawValue, value: user.cAboutUS)
try userObject.set(DBUser.CodingKeys.cProvince.rawValue, value: user.cProvince)
try userObject.set(DBUser.CodingKeys.cCity.rawValue, value: user.cCity)
try await userObject.save()
}
func setTo(filed: String, _ value: Bool) async throws {
let query = LCQuery(className: className)
query.whereKey("id", .equalTo(AuthManager.shared.getUserID()))
let userObject = try await query.getFirst()
try userObject.set(filed, value: value)
try await userObject.save()
}
func removeUser(id: String) async {
let query = LCQuery(className: className)
query.whereKey("id", .equalTo(id))
guard let userObject = try? await query.getFirst() else { return }
try? await userObject.delete()
}
}

View File

@ -0,0 +1,90 @@
//
// DBUser.swift
// IOS_study
//
// Created by CC-star on 2025/7/14.
//
import Foundation
struct DBUser: Codable, Identifiable {//
var id = UUID().uuidString//id
var nickName = ""//
var cName = ""//
var cLogoURLStr = ""//logo
var cImageURLStrs: [String] = []//
var cAboutUS = ""//
var cProvince = "重庆"//
var cCity = ""//
var cIsUploaded: Bool { !cName.isEmpty }//
var hasPLJobs = false//
var hasReviews = false//
enum CodingKeys: String,CodingKey {
case id
case nickName
case cName
case cLogoURLStr
case cImageURLStrs
case cAboutUS
case cProvince
case cCity
case hasPLJobs
case hasReviews
}
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: .id)
try container.encode(self.nickName, forKey: .nickName)
try container.encode(self.cName, forKey: .cName)
try container.encode(self.cLogoURLStr, forKey: .cLogoURLStr)
try container.encode(self.cImageURLStrs, forKey: .cImageURLStrs)
try container.encode(self.cAboutUS, forKey: .cAboutUS)
try container.encode(self.cProvince, forKey: .cProvince)
try container.encode(self.cCity, forKey: .cCity)
try container.encode(self.hasPLJobs, forKey: .hasPLJobs)
try container.encode(self.hasReviews, forKey: .hasReviews)
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.nickName = try container.decodeIfPresent(String.self, forKey: .nickName) ?? ""
self.cName = try container.decodeIfPresent(String.self, forKey: .cName) ?? ""
self.cLogoURLStr = try container.decodeIfPresent(String.self, forKey: .cLogoURLStr) ?? ""
self.cImageURLStrs = try container.decodeIfPresent([String].self, forKey: .cImageURLStrs) ?? []
self.cAboutUS = try container.decodeIfPresent(String.self, forKey: .cAboutUS) ?? ""
self.cProvince = try container.decodeIfPresent(String.self, forKey: .cProvince) ?? "重庆"
self.cCity = try container.decodeIfPresent(String.self, forKey: .cCity) ?? ""
self.hasPLJobs = try container.decodeIfPresent(Bool.self, forKey: .hasPLJobs) ?? false
self.hasReviews = try container.decodeIfPresent(Bool.self, forKey: .hasReviews) ?? false
}
//initinit
init(id: String = UUID().uuidString, nickName: String = "", cName: String = "", cLogoURLStr: String = "", cImageURLStrs: [String] = [], cAboutUS: String = "", cProvince: String = "重庆", cCity: String = "", hasPLJobs: Bool = false, hasReviews: Bool = false) {
self.id = id
self.nickName = nickName
self.cName = cName
self.cLogoURLStr = cLogoURLStr
self.cImageURLStrs = cImageURLStrs
self.cAboutUS = cAboutUS
self.cProvince = cProvince
self.cCity = cCity
self.hasPLJobs = hasPLJobs
self.hasReviews = hasReviews
}
}
//struct Company: Codable {//
// var name = ""//
// var logoURLStr = ""//logo
// var imageURLStrs: [String] = []//
// var aboutUS = ""//
// var province = ""//
// var city = ""//
// var isUploaded: Bool { !name.isEmpty }//
//}

25
IOS_study/Model/OF.swift Normal file
View File

@ -0,0 +1,25 @@
//
// OF.swift
// IOS_study
//
// Created by CC-star on 2025/7/10.
//
import Foundation
struct PLFilter: Equatable {//Equatable
var business: String?
var city: String?
var totalCount:Int {
var count = 0
if business != nil { count += 1 }
if city != nil { count += 1 }
return count
}
}
enum PLOrder: String, CaseIterable, Identifiable {
var id: String { self.rawValue }
case updatedAt = "按更新时间"
case hourlywage = "按时给"
}

217
IOS_study/Model/PLJob.swift Normal file
View File

@ -0,0 +1,217 @@
//
// PLJob.swift
// IOS_study
//
// Created by CC-star on 2025/6/28.
//
import Foundation
//-->codingkeys(String)decodedecodeinitdecode
struct PLJob: Identifiable, Codable {//,Codable:/
var id = UUID().uuidString//id
var isActive = true//线
var title = ""//
var business = "互联网"//
var tax = "详谈"//
var hourlyWage = 100//
var wantNum = 3//
var contactType = "微信"//
var contact = ""//
//
var startHour = 10//
var startMin = 0//
var endHour = 20//
var endMin = 0//
//
var has2 = false//
//
var startHour2 = 10//
var startMin2 = 0//
var endHour2 = 20//
var endMin2 = 0//
var otherTime = ""//
var placeName = ""//
var province = "北京"//
var city = ""//
var workContent = ""//
//
var needExp = "1-3年"//
var needEdu = "本科"//
var needLan = "Swift"//
var needFrame = "SwiftUI"//
var otherNeed = ""//
//
var moveFee = true//
var noOverTime = true//
var otherBenefit = ""//
//
var companyName = ""//
var userID = ""//id
//var creatTime = ""
var updatedAt = Date()//
var wantWorkNum = 0//
enum CodingKeys: String,CodingKey {
case id
case isActive
case title
case business
case tax
case hourlyWage
case wantNum
case contactType
case contact
case startHour
case startMin
case endHour
case endMin
case has2
case startHour2
case startMin2
case endHour2
case endMin2
case otherTime
case placeName
case province
case city
case workContent
case needExp
case needEdu
case needLan
case needFrame
case otherNeed
case moveFee
case noOverTime
case otherBenefit
case companyName
case userID
case updatedAt
case wantWorkNum
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.isActive = try container.decodeIfPresent(Bool.self, forKey: .isActive) ?? true
self.title = try container.decodeIfPresent(String.self, forKey: .title) ?? ""
self.business = try container.decodeIfPresent(String.self, forKey: .business) ?? "互联网"
self.tax = try container.decodeIfPresent(String.self, forKey: .tax) ?? "详谈"
self.hourlyWage = try container.decodeIfPresent(Int.self, forKey: .hourlyWage) ?? 100
self.wantNum = try container.decodeIfPresent(Int.self, forKey: .wantNum) ?? 3
self.contactType = try container.decodeIfPresent(String.self, forKey: .contactType) ?? "微信"
self.contact = try container.decodeIfPresent(String.self, forKey: .contact) ?? ""
self.startHour = try container.decodeIfPresent(Int.self, forKey: .startHour) ?? 10
self.startMin = try container.decodeIfPresent(Int.self, forKey: .startMin) ?? 0
self.endHour = try container.decodeIfPresent(Int.self, forKey: .endHour) ?? 20
self.endMin = try container.decodeIfPresent(Int.self, forKey: .endMin) ?? 0
self.has2 = try container.decodeIfPresent(Bool.self, forKey: .has2) ?? false
self.startHour2 = try container.decodeIfPresent(Int.self, forKey: .startHour2) ?? 10
self.startMin2 = try container.decodeIfPresent(Int.self, forKey: .startMin2) ?? 0
self.endHour2 = try container.decodeIfPresent(Int.self, forKey: .endHour2) ?? 20
self.endMin2 = try container.decodeIfPresent(Int.self, forKey: .endMin2) ?? 0
self.otherTime = try container.decodeIfPresent(String.self, forKey: .otherTime) ?? ""
self.placeName = try container.decodeIfPresent(String.self, forKey: .placeName) ?? ""
self.province = try container.decodeIfPresent(String.self, forKey: .province) ?? "浙江"
self.city = try container.decodeIfPresent(String.self, forKey: .city) ?? ""
self.workContent = try container.decodeIfPresent(String.self, forKey: .workContent) ?? ""
self.needExp = try container.decodeIfPresent(String.self, forKey: .needExp) ?? "1-3年"
self.needEdu = try container.decodeIfPresent(String.self, forKey: .needEdu) ?? "本科"
self.needLan = try container.decodeIfPresent(String.self, forKey: .needLan) ?? "Swift"
self.needFrame = try container.decodeIfPresent(String.self, forKey: .needFrame) ?? "SwiftUI"
self.otherNeed = try container.decodeIfPresent(String.self, forKey: .otherNeed) ?? ""
self.moveFee = try container.decodeIfPresent(Bool.self, forKey: .moveFee) ?? true
self.noOverTime = try container.decodeIfPresent(Bool.self, forKey: .noOverTime) ?? true
self.otherBenefit = try container.decodeIfPresent(String.self, forKey: .otherBenefit) ?? ""
self.companyName = try container.decodeIfPresent(String.self, forKey: .companyName) ?? ""
self.userID = try container.decodeIfPresent(String.self, forKey: .userID) ?? ""
self.updatedAt = try container.decodeIfPresent(Date.self, forKey: .updatedAt) ?? Date()
self.wantWorkNum = try container.decodeIfPresent(Int.self, forKey: .wantWorkNum) ?? 0
}
// func encode(to encoder: any Encoder) throws {
// var container = encoder.container(keyedBy: CodingKeys.self)
// try container.encode(self.id, forKey: .id)
// try container.encode(self.isActive, forKey: .isActive)
// try container.encode(self.title, forKey: .title)
// try container.encode(self.business, forKey: .business)
// try container.encode(self.tax, forKey: .tax)
// try container.encode(self.hourlyWage, forKey: .hourlyWage)
// try container.encode(self.wantNum, forKey: .wantNum)
// try container.encode(self.contactType, forKey: .contactType)
// try container.encode(self.contact, forKey: .contact)
// try container.encode(self.startHour, forKey: .startHour)
// try container.encode(self.startMin, forKey: .startMin)
// try container.encode(self.endHour, forKey: .endHour)
// try container.encode(self.endMin, forKey: .endMin)
// try container.encode(self.has2, forKey: .has2)
// try container.encode(self.startHour2, forKey: .startHour2)
// try container.encode(self.startMin2, forKey: .startMin2)
// try container.encode(self.endHour2, forKey: .endHour2)
// try container.encode(self.endMin2, forKey: .endMin2)
// try container.encode(self.otherTime, forKey: .otherTime)
// try container.encode(self.placeName, forKey: .placeName)
// try container.encode(self.province, forKey: .province)
// try container.encode(self.city, forKey: .city)
// try container.encode(self.workContent, forKey: .workContent)
// try container.encode(self.needExp, forKey: .needExp)
// try container.encode(self.needEdu, forKey: .needEdu)
// try container.encode(self.needLan, forKey: .needLan)
// try container.encode(self.needFrame, forKey: .needFrame)
// try container.encode(self.otherNeed, forKey: .otherNeed)
// try container.encode(self.moveFee, forKey: .moveFee)
// try container.encode(self.noOverTime, forKey: .noOverTime)
// try container.encode(self.otherBenefit, forKey: .otherBenefit)
// try container.encode(self.companyName, forKey: .companyName)
// try container.encode(self.userID, forKey: .userID)
// try container.encode(self.updatedAt, forKey: .updatedAt)
// try container.encode(self.wantWorkNum, forKey: .wantWorkNum)
// }
init(id: String = UUID().uuidString, isActive: Bool = true, title: String = "", business: String = "互联网", tax: String = "详谈", hourlyWage: Int = 100, wantNum: Int = 3, contactType: String = "微信", contact: String = "", startHour: Int = 10, startMin: Int = 0, endHour: Int = 20, endMin: Int = 0, has2: Bool = false, startHour2: Int = 10, startMin2: Int = 0, endHour2: Int = 20, endMin2: Int = 0, otherTime: String = "", placeName: String = "", province: String = "浙江", city: String = "", workContent: String = "", needExp: String = "1-3年", needEdu: String = "本科", needLan: String = "Swift", needFrame: String = "SwiftUI", otherNeed: String = "", moveFee: Bool = true, noOverTime: Bool = true, otherBenefit: String = "", companyName: String = "", userID: String = "", updatedAt: Date = Date(), wantWorkNum: Int = 0) {
self.id = id
self.isActive = isActive
self.title = title
self.business = business
self.tax = tax
self.hourlyWage = hourlyWage
self.wantNum = wantNum
self.contactType = contactType
self.contact = contact
self.startHour = startHour
self.startMin = startMin
self.endHour = endHour
self.endMin = endMin
self.has2 = has2
self.startHour2 = startHour2
self.startMin2 = startMin2
self.endHour2 = endHour2
self.endMin2 = endMin2
self.otherTime = otherTime
self.placeName = placeName
self.province = province
self.city = city
self.workContent = workContent
self.needExp = needExp
self.needEdu = needEdu
self.needLan = needLan
self.needFrame = needFrame
self.otherNeed = otherNeed
self.moveFee = moveFee
self.noOverTime = noOverTime
self.otherBenefit = otherBenefit
self.companyName = companyName
self.userID = userID
self.updatedAt = updatedAt
self.wantWorkNum = wantWorkNum
}
static let job = PLJob(title: "iOS程序员", tax: "报税", contact: "12345", has2: true, startHour2: 14, endHour2: 22, otherTime: "可以根据实际情况调整", placeName: "阿里巴巴西溪园区", province: "浙江", city: "杭州", workContent: "iOS开发啊啊iOS开发啊啊iOS开发啊啊iOS开发啊啊iOS开发啊啊iOS开发啊啊", otherNeed: "什么都不需要啊啊什么都不需要啊啊什么都不需要啊啊什么都不需要啊啊", otherBenefit: "什么都有啊啊什么都有啊啊什么都有啊啊什么都有啊啊什么都有啊啊",companyName:"阿里巴巴")
}

View File

@ -0,0 +1,31 @@
//
// Report.swift
// IOS_study
//
// Created by CC-star on 2025/7/24.
//
import Foundation
struct JobReport: Codable, Identifiable {
var id = UUID().uuidString
//
var jobID = ""
var jobType = ""
var jobTitle = ""
var bossID = ""
//
var userID = ""
// var userCompanyName = ""
enum CodingKeys: String, CodingKey {
case id
case jobType
case jobID
case jobTitle
case bossID
case userID
}
}

View File

@ -0,0 +1,18 @@
//
// Review.swift
// IOS_study
//
// Created by CC-star on 2025/7/25.
//
import Foundation
struct Review: Identifiable, Codable {
var id = UUID().uuidString
var userAvatar = "0"//
var userNickName = ""//
var reviewType = "未面试"//
var goods: [String] = []//
var bads: [String] = []//
var content = ""
}

View File

@ -0,0 +1,50 @@
//
// CompanyGalleryORView.swift
// IOS_study
//
// Created by CC-star on 2025/7/22.
//
import SwiftUI
import Kingfisher
struct CompanyGalleryORView: View {
let imageURLStrs: [String]
@Environment(\.dismiss) var dismiss
@State var currentPage: Int
@State var dragOffset: CGSize = .zero
var body: some View {
TabView(selection: $currentPage) {
ForEach(imageURLStrs.indices, id: \.self) { i in
VStack {
KFImage(URL(string: imageURLStrs[i]))
.placeholder { ProgressView() }.loadDiskFileSynchronously().fade(duration: 0.15)
.resizable().scaledToFit().tag(i)
.offset(y: dragOffset.height)
}.frame(maxWidth: .infinity, maxHeight: .infinity).pinchToZoom()
}
}.bg().tabViewStyle(.page)
.toolbarVisibility(.hidden, for: .tabBar)
.toolbar {
ToolbarItem(placement: .principal) {
Text("\(currentPage + 1)/\(imageURLStrs.count)").tc().bold()
}
}
.gesture(//
DragGesture()
.onChanged({ gesture in
if gesture.translation.height > 0 {
dragOffset = gesture.translation
}
})
.onEnded { gesture in
if gesture.translation.height > 80 {//80 ->
dismiss()
} else {
withAnimation { dragOffset = .zero }
}
}
)
}
}

View File

@ -0,0 +1,46 @@
//
// CompanyGalleryView.swift
// IOS_study
//
// Created by CC-star on 2025/7/22.
//
import SwiftUI
struct CompanyGalleryView: View {
@Environment(ProfileViewModel.self) var vm
@Environment(\.dismiss) var dismiss
@State var currentPage: Int
@State var showConfirm = false
var body: some View {
TabView(selection: $currentPage) {
ForEach(vm.companyUIImages.indices, id: \.self) { i in
Image(uiImage: vm.companyUIImages[i]).resizable().scaledToFit().tag(i)
}
}.bg().tabViewStyle(.page)
.toolbar {
ToolbarItem(placement: .principal) {
Text("\(currentPage + 1)/\(vm.companyUIImages.count)").tc().bold()
}
ToolbarItem(placement: .topBarTrailing) {
Image(systemName: "trash").onTapGesture {
showConfirm = true
}
}
}
.confirmationDialog("删除一张图片", isPresented: $showConfirm, actions: {
Button {
guard currentPage < vm.companyUIImages.count else { return }
vm.companyUIImages.remove(at: currentPage)
if vm.companyUIImages.isEmpty {
dismiss()
} else if currentPage >= vm.companyUIImages.count {
currentPage = vm.companyUIImages.count - 1
}
} label: {
Text("删除")
}
}, message: { Text("要删除这张图片吗?") })
}
}

View File

@ -0,0 +1,152 @@
//
// PostCompanyView.swift
// IOS_study
//
// Created by CC-star on 2025/7/15.
//
import SwiftUI
import PhotosUI
struct PostCompanyView: View {
@State var isEditing = false//
@Environment(HUD.self) var hud//
@Environment(ProfileViewModel.self) var ProfileVM
@Binding var naviPath: NavigationPath
var draftUser: DBUser { ProfileVM.draftUser }
var invalidMsg: String? {
if draftUser.cName.isBlank { return "公司名字没有写" }
if draftUser.cProvince.isBlank { return "公司所在省份没有写" }
if draftUser.cCity.isBlank { return "公司所在城市没有写" }
if draftUser.cName.count > 50 { return "公司名字不能超过50字" }
if draftUser.cCity.count > 50 { return "公司所在城市名不能超过50字" }
if draftUser.cAboutUS.count > 20 { return "公司简介不能超过20字" }
return nil
}
var body: some View {
@Bindable var vm = ProfileVM
ScrollView {
VStack(alignment: .leading, spacing: 14) {
HStack {
Text("公司名称:").littleTStyle()
TextField("请输入完整公司名称", text: $vm.draftUser.cName, axis: .vertical)
.tfStyle()//
}
HStack {
Text("公司地址:").littleTStyle()
Picker("省份", selection: $vm.draftUser.cProvince) {
ForEach(kProvinceArr, id: \.self) { Text($0) }
}.pStyle()
TextField("城市",text: $vm.draftUser.cCity).tfStyle()
}
VStack(alignment: .leading, spacing: 9) {
Text("公司简介(选填)").littleTStyle()
TextField("", text: $vm.draftUser.cAboutUS).tfStyleMultiL()
}
VStack(alignment: .leading) {
HStack {
Text("公司Logo选填").littleTStyle()
Text(vm.logoImageErrorMsg).size15pb()
}
PhotosPicker(selection: $vm.logoItem, matching: .images, photoLibrary: .shared()) {
if let uiImage = vm.logoUIImage {
Image(uiImage: uiImage)
.resizable()//
// .scaledToFit()//
.scaledToFill()//
.thumbFrame().radius()
} else {
addImageBtn
}
}
}
VStack(alignment: .leading) {
HStack {
Text("公司图片(选填)").littleTStyle()
Text(vm.companyUIImagesErrorMsg).size15pb()
}
ScrollView(.horizontal) {
HStack {
if vm.isLoadingCompanyImages {
ProgressView().thumbFrame()
} else {
ForEach(vm.companyUIImages.indices, id: \.self) { i in
NavigationLink {
CompanyGalleryView(currentPage: i)
} label: {
Image(uiImage: vm.companyUIImages[i]).resizable().scaledToFill().thumbFrame().radius()
}.disabled(!vm.isCompanyImagesLoaded)
}
}
if vm.companyUIImages.count < 6 {
PhotosPicker(selection: $vm.companyItems, maxSelectionCount: 6 - vm.companyUIImages.count, matching: .images, photoLibrary: .shared()) { addImageBtn }
}
}
}.scrollIndicators(.hidden)
}
Button {
// hud.show("")
Task {//
await postCompany()
}
} label: {
if vm.isPostingCompany {
ProgressView().appleStyleP()//
}else {
Text("提交").appleStyle()
}
}.disabled(vm.isPostingCompany)
}.padding()
postBottomEView()
}.scrollIndicators(.hidden).tc().size17().bg()
.navigationBarBackButtonHidden().navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) { Text("公司信息").tc().bold() }
ToolbarItem(placement: .topBarLeading) {
Button {
if !naviPath.isEmpty {//
naviPath.removeLast()//
}
} label: {
Image(systemName: "chevron.left").secondary().padding(.horizontal,6)
}
}
}
.toolbarVisibility(.hidden, for: .tabBar)
.onAppear {
if ProfileVM.isCompanyImagesLoaded { return }
Task {
await vm.loadCompanyLogoAndIamges()
}
}
}
var addImageBtn: some View {
Image(systemName: "plus").font(.title).secondary().thumbFrame()
.border(borderColor: Color(.systemGray4))
}
func postCompany() async {
if let invalidMsg = invalidMsg {//if let invalidMsg
hud.show(invalidMsg)
return
}
ProfileVM.isPostingCompany = true
defer { ProfileVM.isPostingCompany = false }
UIApplication.shared.hideKeyboard()//
do {
try await ProfileVM.postCompany()
hud.show("发布成功")
if !naviPath.isEmpty { naviPath.removeLast() }
} catch {
print("上传或修改公司信息失败:\(error)")
hud.show("上传或修改公司信息失败:\(error)")
}
}
}

View File

@ -0,0 +1,27 @@
//
// PostJobView.swift
// IOS_study
//
// Created by CC-star on 2025/6/28.
//
import SwiftUI
struct PostJobView: View {
@Environment(AuthViewModel.self) var authVM
@Environment(TabBarViewModel.self) var TabBarVM
@State var naviPath = NavigationPath()
var body: some View {
if authVM.isLogin {
NavigationStack(path: $naviPath) {
PostPLJobIView(naviPath: $naviPath)
}
} else {
Button {
TabBarVM.selectedTab = "profile"
} label: {
Text("去登录").appleStyle().padding()
}
}
}
}

View File

@ -0,0 +1,69 @@
//
// PostPLJobIView+.swift
// IOS_study
//
// Created by CC-star on 2025/6/29.
//
import SwiftUI
extension PostPLJobIView {
var postBtn: some View {
Button {
// hud.show("")
Task {//
await postJob()
}
} label: {
if vm.isPosting {
ProgressView().appleStyleP()//
}else {
Text("提交").appleStyle()
}
}.disabled(vm.isPosting).padding().bg()
}
func postJob() async {
if let invalidMsg = invalidMsg {//if let invalidMsg
hud.show(invalidMsg)
return
}
// print("ok")
vm.isPosting = true
defer {
vm.isPosting = false
}
UIApplication.shared.hideKeyboard()//
workPlaceName = draftJob.placeName
workProvince = draftJob.province
workCity = draftJob.city
workContact = draftJob.contact
vm.draftJob.companyName = profileVM.user.cName
do {
try await vm.postJob(isEditing: isEditing)
if !profileVM.user.hasPLJobs {
profileVM.user.hasPLJobs = true
try await UserManager.shared.setTo(filed: DBUser.CodingKeys.hasPLJobs.rawValue, true)
}
profileVM.shouldRefreshMyPLJobs = true
if isEditing {
hud.show("修改成功")
if !naviPath.isEmpty { naviPath.removeLast() }
} else {
hud.show("发布成功")
tabbarVM.goJobsAndRefresh("兼职")
}
scrollView?.scrollTo("title",anchor: .top)
vm.draftJob = PLJob()
} catch {
hud.show("\(error)")
print("用户上传或修改兼职失败:\(error)")
}
// vm.isPosting = false
}
}

View File

@ -0,0 +1,320 @@
//
// PostPLJobIView.swift
// IOS_study
//
// Created by CC-star on 2025/6/28.
//
import SwiftUI
struct PostPLJobIView: View {
var isEditing = false //
@AppStorage("workPlaceName") var workPlaceName = ""
@AppStorage("workProvince") var workProvince = ""
@AppStorage("workCity") var workCity = ""
@AppStorage("workContact") var workContact = ""
@Environment(PLJobViewModel.self) var vm
@Environment(ProfileViewModel.self) var profileVM
@Environment(HUD.self) var hud
@Environment(TabBarViewModel.self) var tabbarVM
@State var scrollView: ScrollViewProxy?// = nil
@Binding var naviPath: NavigationPath
@State var WordNum20 = 20
@State var WordNum50 = 50
@State var WordNum100 = 100
@State var WordNum200 = 200
@State var WordNum300 = 300
@State var WordNum500 = 500
var draftJob: PLJob{ vm.draftJob }
var invalidMsg: String? {
if !profileVM.user.cIsUploaded { return "需要先上传公司信息" }
if draftJob.title.isBlank { return "工作名称未写" }
if draftJob.hourlyWage < 20 || draftJob.hourlyWage > 1000 { return "基本时给太低或太高" }
if draftJob.placeName.isBlank { return "工作地点未填" }
if draftJob.city.isBlank { return "城市未填" }
if draftJob.contact.isBlank { return "联系方式未填"}
if draftJob.title.count > WordNum50 { return "工作名称不能超过\($WordNum50)" }
if draftJob.otherTime.count > WordNum100 { return "时间说明不能超过\($WordNum100)" }
if draftJob.otherNeed.count > WordNum300 { return "工作要求不能超过\($WordNum300)" }
if draftJob.workContent.count > WordNum500 { return "工作内容不能超过\($WordNum500)" }
if draftJob.otherBenefit.count > WordNum300 { return "工作福利不能超过\($WordNum300)" }
if draftJob.placeName.count > WordNum50 { return "工作地点名称不能超过\($WordNum50)" }
if draftJob.city.count > WordNum20 { return "工作地城市不能超过\($WordNum20)" }
if draftJob.contact.count > WordNum200 { return "联系方式不能超过\($WordNum200)" }
return nil
}
var body: some View {
@Bindable var vm = vm
ScrollViewReader { scrollView in
ScrollView(showsIndicators: false) {
VStack(spacing: kStackSpacingS) {
VStack {
if profileVM.user.cIsUploaded {
Text(profileVM.user.cName).subTStyleL()
} else {
Text("请先至个人页添加公司").size15().red()
}
}.push(to: .center).padding().bg()
// MARK: -
VStack(alignment: .leading,spacing: kStackSpacing) {
HStack {
VStack(alignment: .leading) {
Text("工作业种").littleTStyle()
Picker("工作业种", selection: $vm.draftJob.business) {
ForEach(kBusinessArr, id: \.self){
Text($0)
}
}.pStyle()
}
VStack(alignment: .leading) {
Text("工作名称").littleTStyle()
TextField("IOS程序员", text: $vm.draftJob.title).tfStyle()
}
}.id("title")
VStack(alignment: .leading) {
Text("是否报税").littleTStyle()
Picker("是否报税", selection: $vm.draftJob.tax){
ForEach(kTaxArr, id: \.self){
Text($0)
}
}.pickerStyle(.segmented)
}.padding(.bottom,5)
HStack {
Text("基本时给:").littleTStyle()
TextField("", value: $vm.draftJob.hourlyWage, formatter: NumberFormatter())
.size18ab().multilineTextAlignment(.center).kerning(2)
.keyboardType(.numberPad).tfStyle().frame(width: 88)
Text("请填写最低时给").size13s()
}
HStack {
Text("招聘人数:").littleTStyle()
Picker("招聘人数", selection: $vm.draftJob.wantNum) { ForEach(kWantNumArr, id: \.self){
Text("\($0)")//
}
}.pStyle()
Text("请填写最多招聘人数").size13s()
}
}.padding().bg()
// MARK: -
VStack(alignment: .leading,spacing: kStackSpacingS) {
Text("工作时间").littleTStyle()
HStack {
HStack(spacing: 3) {
Picker("起始小时", selection: $vm.draftJob.startHour) { ForEach(0..<25) {Text("\($0)")}
}.pStyle()
Text(":").conlonStyle()
Picker("起始分钟", selection: $vm.draftJob.startMin) { ForEach([0,30], id: \.self) {Text($0 == 0 ? "00":"\($0)")}
}.pStyle()
Image(systemName: "minus")
Picker("结束小时", selection: $vm.draftJob.endHour) { ForEach(0..<25) {Text("\($0)")}
}.pStyle()
Text(":").conlonStyle()
Picker("结束分钟", selection: $vm.draftJob.endMin) { ForEach([0,30], id: \.self) {Text($0 == 0 ? "00":"\($0)")}
}.pStyle()
}
Button {
withAnimation {
vm.draftJob.has2.toggle()
}
} label: {
Image(systemName: vm.draftJob.has2 ? "minus.circle.fill" : "plus.circle.fill")
.foregroundStyle(vm.draftJob.has2 ? .red : .accent)
}.font(.title2).padding(.horizontal,3)
}
if(vm.draftJob.has2){
HStack(spacing: 3) {
Picker("第二个起始小时", selection: $vm.draftJob.startHour2) { ForEach(0..<25) {Text("\($0)")}
}.pStyle()
Text(":").conlonStyle()
Picker("第二个起始分钟2", selection: $vm.draftJob.startMin2) { ForEach([0,30], id: \.self) {Text($0 == 0 ? "00":"\($0)")}
}.pStyle()
Image(systemName: "minus")
Picker("第二个结束小时2", selection: $vm.draftJob.endHour2) { ForEach(0..<25) {Text("\($0)")}
}.pStyle()
Text(":").conlonStyle()
Picker("第二个结束分钟2", selection: $vm.draftJob.endMin2) { ForEach([0,30], id: \.self) {Text($0 == 0 ? "00":"\($0)")}
}.pStyle()
}
}
Text("追加说明").littleTStyle()
TextField("具体时间段可以详谈", text: $vm.draftJob.otherTime, axis: .vertical).tfStyleMultiS()
}.push(to: .leading).padding().bg()
// MARK: -
VStack(alignment: .leading,spacing: kStackSpacing) {
HStack {
VStack(alignment: .leading) {
Text("经验要求").littleTStyle()
Picker("经验要求", selection: $vm.draftJob.needExp) { ForEach(kNeedExpArr, id: \.self) {Text($0)}
}.pStyle()
}
VStack(alignment: .leading) {
Text("学历要求").littleTStyle()
Picker("学历要求", selection: $vm.draftJob.needEdu) { ForEach(kNeedEduArr, id: \.self) {Text($0)}
}.pStyle()
}
}
HStack {
VStack(alignment: .leading) {
Text("语言要求").littleTStyle()
Picker("学历要求", selection: $vm.draftJob.needLan) { ForEach(kNeedLanArr, id: \.self) {Text($0)}
}.pStyle()
}
VStack(alignment: .leading) {
Text("框架要求").littleTStyle()
Picker("学历要求", selection: $vm.draftJob.needFrame) { ForEach(kNeedFrameArr, id: \.self) {Text($0)}
}.pStyle()
}
}
VStack(alignment: .leading) {
Text("更多要求/加分项(选填)").littleTStyle()
TextField("", text: $vm.draftJob.otherNeed, axis: .vertical).tfStyleMultiM()
}
}.padding().bg()
// MARK: -
VStack(alignment: .leading) {
Text("工作内容(选填)").littleTStyle()
TextField("", text: $vm.draftJob.workContent, axis: .vertical).tfStyleMultiM()
}.padding().bg()
// MARK: -
VStack(alignment: .leading,spacing: kStackSpacing) {
Text("公司福利").littleTStyle()
HStack {
Text("交通费报销").onTapGesture {
vm.draftJob.moveFee.toggle()
}.toggleStyle(isTap: vm.draftJob.moveFee)
Text("不加班").onTapGesture {
vm.draftJob.noOverTime.toggle()
}.toggleStyle(isTap: vm.draftJob.noOverTime)
}
VStack(alignment: .leading) {
Text("更多福利(选填)").littleTStyle()
TextField("", text: $vm.draftJob.otherBenefit, axis: .vertical).tfStyleMultiL()
}
}.padding().bg()
// MARK: -
VStack(alignment: .leading,spacing: kStackSpacing) {
HStack {
Text("省份").littleTStyle()
Picker("省份", selection: $vm.draftJob.province) { ForEach(kProvinceArr, id: \.self) {Text($0)}
}.pStyle()
if !workProvince.isEmpty {
HStack(spacing: 3) {
Text("上次填的:").size13s()
Button {
vm.draftJob.province = workProvince
} label: {
Text(workProvince).labelBG()
}
}
}
}
HStack {
Text("城市").littleTStyle()
TextField("", text: $vm.draftJob.city, axis: .vertical).tfStyle().frame(width: 115)
if !workCity.isEmpty {
HStack(spacing: 3) {
Text("上次填的:").size13s()
Button {
vm.draftJob.city = workCity
} label: {
Text(workCity).labelBG()
}
}
}
}
VStack(alignment: .leading) {
Text("工作地点").littleTStyle()
TextField("请填写完整工作地址", text: $vm.draftJob.placeName, axis: .vertical).tfStyle()
if !workPlaceName.isEmpty {
HStack(spacing: 3) {
Text("上次填的:").size13s()
Button {
vm.draftJob.placeName = workPlaceName
} label: {
Text(workPlaceName).labelBG()
}
}
}
}
// Button {
// workProvince = province
// workCity = city
// workProvince = province
// } label: {
// Text("")
// }.btnStyle()
//
}.padding().bg()
// MARK: -
VStack(alignment: .leading,spacing: kStackSpacing){
Text("联系方式/应聘方式").littleTStyle()
HStack {
Picker("联系方式种类", selection: $vm.draftJob.contactType) {
ForEach(kContactTypeArr, id: \.self) {Text($0)}
}.pStyle()
TextField("", text: $vm.draftJob.contact, axis: .vertical).tfStyle()
}
if !workContact.isEmpty {
HStack(spacing: 3) {
Text("上次填的:").size13s()
Button {
vm.draftJob.contact = workContact
} label: {
Text(workContact).labelBG()
}
}
}
Text("据调查99%的人才都喜欢使用微信联系").size13().red()
}.padding().bg()
// MARK: -
postBtn
//
// withAnimation {//withAnimation ->
// Button("") {
// scrollView.scrollTo("title",anchor: .top)//idtitleviewtopidtitleview
// }
// }
}.tc().bg2()
postBottomEView().bg()
}.navigationBarBackButtonHidden(isEditing).navigationBarTitleDisplayMode(.inline)
.toolbar {
if isEditing {
ToolbarItem(placement: .principal) { Text("编辑兼职").tc().bold() }
ToolbarItem(placement: .topBarLeading) {
Button {
if !naviPath.isEmpty {//
naviPath.removeLast()//
}
} label: {
Image(systemName: "chevron.left").secondary().padding(.horizontal,6)
}
}
}
}
.onAppear() {
self.scrollView = scrollView
}
}
}
}
#Preview {
PostPLJobIView(naviPath: .constant(NavigationPath()))
}

View File

@ -0,0 +1,18 @@
//
// PostReviewView.swift
// IOS_study
//
// Created by CC-star on 2025/7/25.
//
import SwiftUI
struct PostReviewView: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
}
}
#Preview {
PostReviewView()
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,23 @@
//
// PLJobView+RevUI.swift
// IOS_study
//
// Created by CC-star on 2025/7/25.
//
import SwiftUI
extension PLJobView {
var goRevireBtn: some View {
Button {
if authVM.isLogin {
reviewVM.drafReview = Review()
showPostReview = true
} else {
hud.show("请先登录")
}
} label: {
Text("对这个工作有看法?去点评").appleStyle()
}.buttonStyle(.borderless).cellStyle()
}
}

View File

@ -0,0 +1,98 @@
//
// ProfileView+ComUI.swift
// IOS_study
//
// Created by CC-star on 2025/7/18.
//
import SwiftUI
import Kingfisher
extension ProfileView {
var myCompanyView: some View {
VStack(alignment: .leading, spacing: 14) {
HStack {
// MARK: -
Text("我的公司").headline().tc()
if user.cIsUploaded {
Button {
ProfileVM.draftUser = user
naviPath.append(Navi5.EditCompany)
} label: { Text("编辑") }.btnStyle()
} else {
Button {
ProfileVM.draftUser = DBUser()
naviPath.append(Navi5.AddCompany)
} label: { Text("去添加") }.btnStyle()
}
}
if user.cIsUploaded {//
VStack(alignment: .leading, spacing: 9) {
HStack {
if user.cLogoURLStr.isEmpty {
Text("🏬").font(.system(size: 40))
} else {
Button {
showLogo = true
} label: {
KFImage(URL(string: user.cLogoURLStr))
.placeholder { ProgressView() }.loadDiskFileSynchronously().fade(duration: 0.15)
.onSuccess { result in
let image = result.image
logoAspectRatio = image.size.width / image.size.height
}
.onFailure { error in
print("图片加载失败:\(error)")
}
.resizable().scaledToFit().thumbFrameS(aspectRatio: logoAspectRatio).radius(6)
}.buttonStyle(.borderless)
}
VStack(alignment: .leading, spacing: 5) {
Text(user.cName).subTStyle()//
Text(user.cProvince + "-" + user.cCity)
}
}
}.textStyle()
// MARK: -
if !user.cAboutUS.isEmpty {
VStack(alignment: .leading, spacing: 9) {
Text("公司简介").subTStyle()
if sizeClass == .compact {
ZStack(alignment: .bottomTrailing) {
Text(user.cAboutUS).allowMulti().lineLimit((user.cAboutUS.count < 88 || isExpand) ? nil : 4)
if user.cAboutUS.count > 88 {
Text(isExpand ? "收起" : "展开").size14().accent().padding([.leading, .top], 3).bg()
.onTapGesture { isExpand.toggle() }
}
}
} else {
Text(user.cAboutUS).allowMulti()
}
}.size15().tc2().lineSpacing(6)
}
// MARK: -
if !user.cImageURLStrs.isEmpty {
ScrollView(.horizontal) {
HStack {
ForEach(user.cImageURLStrs.indices, id: \.self) { i in
if i < user.cImageURLStrs.count {
NavigationLink {
CompanyGalleryORView(imageURLStrs: user.cImageURLStrs, currentPage: i)
} label: {
KFImage(URL(string: user.cImageURLStrs[i]))
.placeholder { ProgressView() }.loadDiskFileSynchronously().fade(duration: 0.15)
.resizable().scaledToFill().thumbFrame().radius(6)
}
}
}
}
}.scrollIndicators(.hidden)
}
}
}.push(to: .leading).cellStyle()
}
}

View File

@ -0,0 +1,56 @@
//
// ProfileView+JobUI.swift
// IOS_study
//
// Created by CC-star on 2025/7/23.
//
import SwiftUI
extension ProfileView {
var myJobsView: some View {
VStack(alignment: .leading, spacing: kStackSpacing) {
let jobsCount = ProfileVM.pLJobsCount
let jobsCountText = jobsCount > 0 ? "\(ProfileVM.aPLJobsCount)/\(ProfileVM.pLJobsCount)" : ""
Text("我发布的工作\(jobsCountText)").headline().tc()
if !ProfileVM.plJobs.isEmpty {
ForEach(ProfileVM.plJobs) { job in
Button {
plJobVM.job = job
naviPath.append(Navi5.PLJobsToPLJob)
} label: {
HStack {
VStack(alignment: .leading, spacing: kStackSpacingS) {
HStack {
Text("兼职").labelPL()
Text(job.title).oneLineStyleMiddle()
}
HStack {
Text(job.needExp).labelBG()
Text(job.province + "-" + job.city).labelBG()
if !job.isActive {
Text("已下线").labelTA()
}
}
}.push(to: .leading)
Text("\(job.hourlyWage)").bold().accent()
}
}.buttonStyle(.borderless)
exDivider()
}
if !ProfileVM.isPLFinshed {
Button {
Task {
await ProfileVM.getPLJobs()
}
} label: {
if ProfileVM.isFcPLJobs { ProgressView().loadingJobStyleP() } else { Text("加载更多兼职职位").loadingJobStyle() }
}.buttonStyle(.borderless).padding(.top, -6).push(to: .center)
}
} else {
Text("暂无发布的工作").secondary()
}
}.push(to: .leading).textStyle().cellStyle()
}
}

View File

@ -0,0 +1,88 @@
//
// ProfileView.swift
// IOS_study
//
// Created by CC-star on 2025/7/15.
//
import SwiftUI
import Kingfisher
struct ProfileView: View {
@Environment(HUD.self) var hud
@Environment(AuthViewModel.self) var authVM
@Environment(SignInPhoneViewModel.self) var SignInPhoneVM
@Environment(ProfileViewModel.self) var ProfileVM
@Environment(PLJobViewModel.self) var plJobVM
@Environment(\.horizontalSizeClass) var sizeClass
@State var naviPath = NavigationPath()
@State var logoAspectRatio: CGFloat = 1
@State var isExpand = false
@State var showLogo = false
var user: DBUser { ProfileVM.user }
var body: some View {
if authVM.isLogin {
NavigationStack(path: $naviPath) {
List {
myCompanyView//
myJobsView//
}.listStyle().naviBarStyle().refreshable { await getUsersData() }
.toolbar {
ToolbarItem(placement: .principal) { Text("我的").tc().bold() }
ToolbarItem(placement: .topBarTrailing) {
Button {
naviPath.append(Navi5.Tosetting)
} label: {
Text("设置")
}.tc2().size15()
}
}
.navigationDestination(isPresented: $showLogo, destination: {
KFImage(URL(string: user.cLogoURLStr)).resizable().scaledToFit()
.toolbarVisibility(.hidden, for: .tabBar)
})
.navigationDestination(for: Navi5.self) { navi in
switch navi {
case .AddCompany: PostCompanyView(naviPath: $naviPath)
case .EditCompany: PostCompanyView(isEditing: true, naviPath: $naviPath)
case .PLJobsToPLJob: PLJobView(canEdit: true, showBoss: false, naviPath: $naviPath)
case .PLJobToEdit: PostPLJobIView(isEditing: true,naviPath: $naviPath)
case .Tosetting: SettginsView(naviPath: $naviPath)
}
}
.overlay {
if ProfileVM.isFcSome {
BprogressView()
}
}
.onAppear {
Task {
if !ProfileVM.viewDidLoad {
await getUsersData()
ProfileVM.viewDidLoad = true
} else { await refreshIFNeed() }
}
}
}
} else {
SignInPhoneView()
}
}
func getUsersData() async {
do { try await ProfileVM.getUsersData() } catch {
hud.show("数据加载失败,请刷新\n错误信息:\n\(error.localizedDescription)")
print("个人页有数据加载失败:\(error)")
}
}
func refreshIFNeed() async {
do { try await ProfileVM.refreshIFNeed() } catch {
hud.show("数据加载失败,请刷新\n错误信息:\n\(error.localizedDescription)")
print("个人页有数据加载失败:\(error)")
}
}
}

View File

@ -0,0 +1,91 @@
//
// ProfileViewModel+.swift
// IOS_study
//
// Created by CC-star on 2025/7/23.
//
import Foundation
extension ProfileViewModel {
@MainActor func getUser() async throws {
if isFcUser { return }
isFcUser = true
defer { isFcUser = false }
user = try await UserManager.shared.findUserByREST()
}
@MainActor func getPLJobs(isRefresh: Bool = false) async {
if isFcPLJobs { return }
isFcPLJobs = true
defer { isFcPLJobs = false }
if isRefresh {
currentPLPage = 0
isPLFinshed = false
}
do {
if user.hasPLJobs {
let skip = kJobLimitS * currentPLPage
let newJobs = try await PLJobManager.shared.findJobsByREST(by: userID, limit: kJobLimitS, skip: skip,)
// print(newJobs[0].title)
// print(newJobs[1].title)
if isRefresh { plJobs = newJobs } else {
plJobs.append(contentsOf: newJobs)//
}
if plJobs.isEmpty {//hasPLJobs
user.hasPLJobs = false
try await UserManager.shared.setTo(filed: DBUser.CodingKeys.hasPLJobs.rawValue, false)
}
currentPLPage += 1//
if newJobs.count < kJobLimitS || newJobs.isEmpty {
isPLFinshed = true
}
} else {
plJobs.removeAll()
}
} catch {
print("获取我发布的plJobs失败\(error)")
}
}
@MainActor func getPLJobsCount() async {
if user.hasPLJobs {
async let aCount = PLJobManager.shared.findJobsCount(by: userID, onlyActive: true)
async let Count = PLJobManager.shared.findJobsCount(by: userID)
(aPLJobsCount, pLJobsCount) = await (aCount, Count)
}
}
@MainActor func logOut() {
AuthManager.shared.logOut()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {//,reset,
self.reset()
}
}
@MainActor func deletUserAll() async {
let userID = AuthManager.shared.getUserID()
await PLJobManager.shared.removeJobs(by: userID)
await UserManager.shared.removeUser(id: userID)
if let lcuser = AuthManager.shared.getUser() {
try? lcuser.set("isNewUser", value: true)
try? await lcuser.save()//
}
AuthManager.shared.logOut()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { self.reset() }
}
@MainActor func reset() {
viewDidLoad = false
user = DBUser()
plJobs.removeAll()
currentPLPage = 0
isPLFinshed = false
aPLJobsCount = 0
pLJobsCount = 0
}
}

View File

@ -0,0 +1,159 @@
//
// ProfileViewModel.swift
// IOS_study
//
// Created by CC-star on 2025/7/15.
//
import PhotosUI
import SwiftUI
@Observable final class ProfileViewModel {
var viewDidLoad = false
//user
var user = DBUser()
var userID: String { user.id }
var draftUser = DBUser()
var isFcUser = false
var isCompanyImagesLoaded = false
var isPostingCompany = false
//
var plJobs: [PLJob] = []
var aPLJobsCount = 0
var pLJobsCount = 0
var isFcPLJobs = false
var shouldRefreshMyPLJobs = false
//
var currentPLPage = 0
var isPLFinshed = false
//
var isFcSome: Bool { isFcUser || isFcPLJobs }
@MainActor func getUsersData() async throws {
try await getUser()
async let getPLJobs: () = getPLJobs(isRefresh: true)
async let getPLJobsCount: () = getPLJobsCount()
let _ = await (getPLJobs, getPLJobsCount)
}
@MainActor func refreshIFNeed() async throws {
if shouldRefreshMyPLJobs {
async let getPLJobs: () = getPLJobs(isRefresh: true)
async let getPLJobsCount: () = getPLJobsCount()
let _ = await (getPLJobs, getPLJobsCount)
shouldRefreshMyPLJobs = false
}
}
var logoUIImage: UIImage?// -> nil
var logoImageErrorMsg = ""
var logoItem: PhotosPickerItem? {
didSet {
//guard let logoItem -> guard let logoItem = logoItem
guard let logoItem else { return }
Task {
//item
if let data = try? await logoItem.loadTransferable(type: Data.self), let uiImage = UIImage(data: data) {
logoImageErrorMsg = ""
logoUIImage = uiImage
} else {
print("PhotosPickerItem转换成图片失败")
logoImageErrorMsg = "上传图片失败,请重新上传"
}
self.logoItem = nil
}
}
}
var isLoadingCompanyImages = false
var companyUIImages: [UIImage] = []
var companyUIImagesErrorMsg = ""
var companyItems: [PhotosPickerItem] = [] {
didSet {
if companyItems.isEmpty { return }
Task { @MainActor in
isLoadingCompanyImages = true
defer {
isLoadingCompanyImages = false
}
var newImages: [UIImage] = []
for item in companyItems {
if let data = try? await item.loadTransferable(type: Data.self), let uiImage = UIImage(data: data) {
newImages.append(uiImage)
}
}
companyUIImages.append(contentsOf: newImages)
if newImages.count == companyItems.count {
companyUIImagesErrorMsg = ""
} else {
companyUIImagesErrorMsg = "有图片上传失败,请重新上传"
}
companyItems.removeAll()
}
}
}
@MainActor func loadCompanyLogoAndIamges() async {
isCompanyImagesLoaded = false
if !user.cLogoURLStr.isEmpty {
if let url = URL(string: user.cLogoURLStr),
let (data, _) = try? await URLSession.shared.data(from: url),
let uiImage = UIImage(data: data) {
logoUIImage = uiImage
}
}
if !user.cImageURLStrs.isEmpty {
var newImages: [UIImage] = []
for cImageURLStr in user.cImageURLStrs {
if let url = URL(string: cImageURLStr),
let (data, _) = try? await URLSession.shared.data(from: url),
let uiImage = UIImage(data: data) {
newImages.append(uiImage)
}
}
companyUIImages = newImages
}
isCompanyImagesLoaded = true
}
@MainActor func postCompany() async throws {
user = draftUser
//()
user.cName = user.cName.trimmed
user.cCity = user.cCity.trimmed
//
if user.cAboutUS.isBlank {
user.cAboutUS = ""
} else {
user.cAboutUS = user.cAboutUS.trimmed
}
//async let withThrowingTaskGroup
async let logoURLStr: String? = {//
guard let logoUIImage else { return nil }
return try await StorageManager.shared.saveImage(logoUIImage)
}()
if !companyUIImages.isEmpty {
let companyImageResults = try await withThrowingTaskGroup(of:((Int, String).self)) { group in
for (index, uiImage) in companyUIImages.enumerated() {
group.addTask {
let urlStr = try await StorageManager.shared.saveImage(uiImage)
return (index, urlStr)
}
}
var results: [(Int, String)] = []
for try await result in group {
results.append(result)
}
return results
}
user.cImageURLStrs = companyImageResults.sorted(by: { $0.0 < $1.0 }).map { $0.1 }
}
if let clogoURLStr = try await logoURLStr {
user.cLogoURLStr = clogoURLStr
}
try await UserManager.shared.update(user: user)
}
}

View File

@ -0,0 +1,84 @@
//
// SettginsView.swift
// IOS_study
//
// Created by CC-star on 2025/7/10.
//
import SwiftUI
struct SettginsView: View {
@Environment(ProfileViewModel.self) var ProfileVM
@Environment(HUD.self) var hud
@Binding var naviPath: NavigationPath
@State var showSignOutDialog = false
@State var showRemoveUserDialog = false
@State var showRemoveUserAlert = false
var isLogin = true
var body: some View {
List {
Section("客服/商业合作") {
Button {
UIPasteboard.general.string = KdevContact
hud.show("复制成功")
} label: {
HStack {
Text("微信号")
Spacer()
Text(KdevContact).secondary()
}
}.settingsStyle()
}
if isLogin {
Section("账号") {
HStack {
Button {
showSignOutDialog = true
} label: {
Text("退出登录").push(to: .leading).settingsStyle()
}.settingsStyle()
.confirmationDialog("确定退出吗?", isPresented: $showSignOutDialog, titleVisibility: .visible) {
Button("确定", role: .destructive) {
if !naviPath.isEmpty { naviPath.removeLast() }
ProfileVM.logOut()
NotificationCenter.default.post(name: .logoutSuccess, object: nil)
}
Button("取消", role: .cancel) { }
}
}
HStack {
Button {
showRemoveUserDialog = true
} label: {
Text("注销账号").push(to: .leading).settingsStyle()
}.settingsStyle()
.confirmationDialog("确定注销吗?", isPresented: $showRemoveUserDialog, titleVisibility: .visible) {
Button("确定", role: .destructive) { showRemoveUserAlert = true }
Button("取消", role: .cancel) { }
}
.alert("注销之后不可恢复,请再次确认", isPresented: $showRemoveUserAlert) {
Button("确定", role: .destructive) { Task { await ProfileVM.deletUserAll() } }
Button("取消", role: .cancel) { }
}
}
}
}
}.listStyle(.grouped)
.toolbarBackgroundVisibility(.visible, for: .navigationBar)
.navigationBarBackButtonHidden(isLogin).navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) { Text("设置") }
if isLogin {
ToolbarItem(placement: .topBarLeading) {
Button {
if !naviPath.isEmpty { naviPath.removeLast() }
} label: {
Image(systemName: "chevron.left").secondary().padding(.horizontal,6)
}
}
}
}
}
}

View File

@ -0,0 +1,12 @@
//
// ReviewViewModel.swift
// IOS_study
//
// Created by CC-star on 2025/7/25.
//
import Foundation
@Observable final class ReviewViewModel {
var drafReview = Review()
}

37
IOS_study/TaBarView.swift Normal file
View File

@ -0,0 +1,37 @@
//
// TaBarView.swift
// IOS_study
//
// Created by CC-star on 2025/6/29.
//
import SwiftUI
struct TaBarView: View {
@Environment(TabBarViewModel.self) var vm
@State var authVM = AuthViewModel()
@State var plJobVM1 = PLJobViewModel()
@State var plJobVM4 = PLJobViewModel()
@State var plJobVM5 = PLJobViewModel()
var body: some View {
@Bindable var vm = vm
TabView(selection: $vm.selectedTab) {
Tab("工作", systemImage: "yensign.circle", value: "job") {
PLJobsView().toolbarBackground(.visible, for: .tabBar)
.environment(plJobVM1)
}
Tab("雇佣", systemImage: "plus.circle", value: "postJob") {
PostJobView().toolbarBackground(.visible, for: .tabBar)
.environment(plJobVM4)
}
Tab("我的", systemImage: "person.circle", value: "profile") {
ProfileView().toolbarBackground(.visible, for: .tabBar)
.environment(plJobVM5)
}
}.environment(authVM)
}
}
#Preview {
TaBarView()
}

Some files were not shown because too many files have changed in this diff Show More