feat: 上传组件
This commit is contained in:
parent
093f9048f9
commit
1a2c1bf959
@ -4,11 +4,11 @@ import AVFoundation
|
||||
import os.log
|
||||
|
||||
/// 媒体类型
|
||||
enum MediaType: Equatable {
|
||||
public enum MediaType: Equatable {
|
||||
case image(UIImage)
|
||||
case video(URL, UIImage?) // URL 是视频地址,UIImage 是视频缩略图
|
||||
|
||||
var thumbnail: UIImage? {
|
||||
public var thumbnail: UIImage? {
|
||||
switch self {
|
||||
case .image(let image):
|
||||
return image
|
||||
@ -17,14 +17,14 @@ enum MediaType: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
var isVideo: Bool {
|
||||
public var isVideo: Bool {
|
||||
if case .video = self {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
static func == (lhs: MediaType, rhs: MediaType) -> Bool {
|
||||
public static func == (lhs: MediaType, rhs: MediaType) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.image(let lhsImage), .image(let rhsImage)):
|
||||
return lhsImage.pngData() == rhsImage.pngData()
|
||||
|
||||
184
wake/View/Examples/MediaDemo.swift
Normal file
184
wake/View/Examples/MediaDemo.swift
Normal file
@ -0,0 +1,184 @@
|
||||
import SwiftUI
|
||||
|
||||
struct MediaUploadDemo: View {
|
||||
@StateObject private var uploadManager = MediaUploadManager()
|
||||
@State private var showMediaPicker = false
|
||||
@State private var showUploadAlert = false
|
||||
@State private var isUploading = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack(spacing: 20) {
|
||||
// 上传按钮
|
||||
Button(action: {
|
||||
showMediaPicker = true
|
||||
}) {
|
||||
Label("添加图片或视频", systemImage: "plus.circle.fill")
|
||||
.font(.headline)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(Color.blue)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.sheet(isPresented: $showMediaPicker) {
|
||||
MediaPickerWithLogging(
|
||||
selectedMedia: $uploadManager.selectedMedia,
|
||||
selectionLimit: 10,
|
||||
onDismiss: {
|
||||
showMediaPicker = false
|
||||
// 当媒体选择器关闭时,如果有选中的媒体,开始上传
|
||||
if !uploadManager.selectedMedia.isEmpty {
|
||||
isUploading = true
|
||||
uploadManager.startUpload()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 预览区域
|
||||
if uploadManager.selectedMedia.isEmpty {
|
||||
VStack(spacing: 16) {
|
||||
Image(systemName: "photo.on.rectangle.angled")
|
||||
.font(.system(size: 60))
|
||||
.foregroundColor(.gray)
|
||||
Text("暂无媒体文件")
|
||||
.font(.headline)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
ScrollView {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 120), spacing: 10)], spacing: 10) {
|
||||
ForEach(0..<uploadManager.selectedMedia.count, id: \.self) { index in
|
||||
MediaItemView(
|
||||
media: uploadManager.selectedMedia[index],
|
||||
status: uploadManager.uploadStatus["\(index)"] ?? .pending
|
||||
)
|
||||
.onTapGesture {
|
||||
// 点击查看大图或播放视频
|
||||
if case .video = uploadManager.selectedMedia[index] {
|
||||
// 处理视频播放
|
||||
print("Play video at index \(index)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
// 显示上传状态
|
||||
if isUploading {
|
||||
VStack {
|
||||
// 显示上传进度
|
||||
if let progress = uploadManager.uploadStatus.values.compactMap({ status -> Double? in
|
||||
if case .uploading(let progress) = status { return progress }
|
||||
return nil
|
||||
}).first {
|
||||
ProgressView(value: progress, total: 1.0)
|
||||
.padding(.horizontal)
|
||||
Text("上传中 \(Int(progress * 100))%")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
} else {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle())
|
||||
.scaleEffect(1.5)
|
||||
Text("正在准备上传...")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
.padding(.top, 8)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $showUploadAlert) {
|
||||
Alert(
|
||||
title: Text(uploadManager.isAllUploaded ? "上传完成" : "上传状态"),
|
||||
message: Text(uploadManager.isAllUploaded ?
|
||||
"所有文件上传完成!" :
|
||||
"正在处理上传..."),
|
||||
dismissButton: .default(Text("确定"))
|
||||
)
|
||||
}
|
||||
.onChange(of: uploadManager.uploadStatus) { _ in
|
||||
// 检查是否所有上传都已完成或失败
|
||||
let allFinished = uploadManager.uploadStatus.values.allSatisfy { status in
|
||||
if case .completed = status { return true }
|
||||
if case .failed = status { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
if allFinished && !uploadManager.uploadStatus.isEmpty {
|
||||
isUploading = false
|
||||
showUploadAlert = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 媒体项视图
|
||||
struct MediaItemView: View {
|
||||
let media: MediaType
|
||||
let status: MediaUploadStatus
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .bottom) {
|
||||
// 缩略图
|
||||
if let thumbnail = media.thumbnail {
|
||||
Image(uiImage: thumbnail)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 120, height: 120)
|
||||
.cornerRadius(8)
|
||||
.clipped()
|
||||
|
||||
// 视频标识
|
||||
if media.isVideo {
|
||||
Image(systemName: "play.circle.fill")
|
||||
.font(.system(size: 30))
|
||||
.foregroundColor(.white)
|
||||
.shadow(radius: 5)
|
||||
}
|
||||
|
||||
// 上传状态
|
||||
VStack {
|
||||
Spacer()
|
||||
if case .uploading(let progress) = status {
|
||||
ProgressView(value: progress, total: 1.0)
|
||||
.progressViewStyle(LinearProgressViewStyle())
|
||||
.frame(height: 4)
|
||||
.padding(.horizontal, 4)
|
||||
} else if case .completed = status {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(.green)
|
||||
.padding(4)
|
||||
.background(Circle().fill(Color.white))
|
||||
} else if case .failed = status {
|
||||
Image(systemName: "exclamationmark.circle.fill")
|
||||
.foregroundColor(.red)
|
||||
.padding(4)
|
||||
.background(Circle().fill(Color.white))
|
||||
}
|
||||
}
|
||||
.padding(4)
|
||||
}
|
||||
}
|
||||
.frame(width: 120, height: 120)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(Color.gray.opacity(0.3), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 预览
|
||||
#Preview {
|
||||
MediaUploadDemo()
|
||||
.environmentObject(AuthState.shared)
|
||||
}
|
||||
@ -1,54 +1,161 @@
|
||||
import SwiftUI
|
||||
import os.log
|
||||
|
||||
/// 媒体上传示例视图
|
||||
struct ExampleView: View {
|
||||
/// 是否显示媒体选择器
|
||||
@State private var showMediaPicker = false
|
||||
/// 已选媒体文件
|
||||
@State private var selectedMedia: [MediaType] = []
|
||||
/// 上传状态
|
||||
@State private var uploadStatus: [String: UploadStatus] = [:]
|
||||
/// 上传管理器
|
||||
private let uploader = ImageUploadService()
|
||||
/// 媒体上传状态
|
||||
public enum MediaUploadStatus: Equatable {
|
||||
case pending
|
||||
case uploading(progress: Double)
|
||||
case completed(fileId: String)
|
||||
case failed(Error)
|
||||
|
||||
/// 上传状态
|
||||
private enum UploadStatus: Equatable {
|
||||
case pending
|
||||
case uploading(progress: Double)
|
||||
case completed(fileId: String)
|
||||
case failed(Error)
|
||||
|
||||
static func == (lhs: UploadStatus, rhs: UploadStatus) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.pending, .pending):
|
||||
return true
|
||||
case (.uploading(let lhsProgress), .uploading(let rhsProgress)):
|
||||
return lhsProgress == rhsProgress
|
||||
case (.completed(let lhsId), .completed(let rhsId)):
|
||||
return lhsId == rhsId
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .pending: return "等待上传"
|
||||
case .uploading(let progress): return "上传中 \(Int(progress * 100))%"
|
||||
case .completed(let fileId): return "上传完成 (ID: \(fileId.prefix(8))...)"
|
||||
case .failed(let error): return "上传失败: \(error.localizedDescription)"
|
||||
}
|
||||
public static func == (lhs: MediaUploadStatus, rhs: MediaUploadStatus) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.pending, .pending):
|
||||
return true
|
||||
case (.uploading(let lhsProgress), .uploading(let rhsProgress)):
|
||||
return lhsProgress == rhsProgress
|
||||
case (.completed(let lhsId), .completed(let rhsId)):
|
||||
return lhsId == rhsId
|
||||
case (.failed, .failed):
|
||||
return false // Errors don't need to be equatable
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .pending: return "等待上传"
|
||||
case .uploading(let progress): return "上传中 \(Int(progress * 100))%"
|
||||
case .completed(let fileId): return "上传完成 (ID: \(fileId.prefix(8))...)"
|
||||
case .failed(let error): return "上传失败: \(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 媒体上传管理器
|
||||
public class MediaUploadManager: ObservableObject {
|
||||
/// 已选媒体文件
|
||||
@Published public var selectedMedia: [MediaType] = []
|
||||
/// 上传状态
|
||||
@Published public var uploadStatus: [String: MediaUploadStatus] = [:]
|
||||
|
||||
private let uploader = ImageUploadService()
|
||||
|
||||
public init() {}
|
||||
|
||||
/// 添加上传媒体
|
||||
public func addMedia(_ media: [MediaType]) {
|
||||
selectedMedia.append(contentsOf: media)
|
||||
}
|
||||
|
||||
/// 移除指定索引的媒体
|
||||
public func removeMedia(at index: Int) {
|
||||
guard index < selectedMedia.count else { return }
|
||||
selectedMedia.remove(at: index)
|
||||
// 更新状态字典
|
||||
var newStatus: [String: MediaUploadStatus] = [:]
|
||||
uploadStatus.forEach { key, value in
|
||||
if let keyInt = Int(key), keyInt < index {
|
||||
newStatus[key] = value
|
||||
} else if let keyInt = Int(key), keyInt > index {
|
||||
newStatus["\(keyInt - 1)"] = value
|
||||
}
|
||||
}
|
||||
uploadStatus = newStatus
|
||||
}
|
||||
|
||||
/// 清空所有媒体
|
||||
public func clearAllMedia() {
|
||||
selectedMedia.removeAll()
|
||||
uploadStatus.removeAll()
|
||||
}
|
||||
|
||||
/// 开始上传所有选中的媒体
|
||||
public func startUpload() {
|
||||
print("🔄 开始批量上传 \(selectedMedia.count) 个文件")
|
||||
// 重置上传状态
|
||||
uploadStatus.removeAll()
|
||||
|
||||
for (index, media) in selectedMedia.enumerated() {
|
||||
let id = "\(index)"
|
||||
uploadStatus[id] = .pending
|
||||
|
||||
// Convert MediaType to ImageUploadService.MediaType
|
||||
let uploadMediaType: ImageUploadService.MediaType
|
||||
switch media {
|
||||
case .image(let image):
|
||||
uploadMediaType = .image(image)
|
||||
case .video(let url, let thumbnail):
|
||||
uploadMediaType = .video(url, thumbnail)
|
||||
}
|
||||
uploadMedia(uploadMediaType, id: id)
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取上传结果
|
||||
public func getUploadResults() -> [String: String] {
|
||||
var results: [String: String] = [:]
|
||||
for (id, status) in uploadStatus {
|
||||
if case .completed(let fileId) = status {
|
||||
results[id] = fileId
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
/// 检查是否所有上传都已完成
|
||||
public var isAllUploaded: Bool {
|
||||
guard !selectedMedia.isEmpty else { return false }
|
||||
return uploadStatus.allSatisfy { _, status in
|
||||
if case .completed = status { return true }
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
||||
private func uploadMedia(_ media: ImageUploadService.MediaType, id: String) {
|
||||
print("🔄 开始处理媒体: \(id)")
|
||||
uploadStatus[id] = .uploading(progress: 0)
|
||||
|
||||
uploader.uploadMedia(
|
||||
media,
|
||||
progress: { progress in
|
||||
print("📊 上传进度 (\(id)): \(progress.current)%")
|
||||
DispatchQueue.main.async {
|
||||
self.uploadStatus[id] = .uploading(progress: progress.progress)
|
||||
}
|
||||
},
|
||||
completion: { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
DispatchQueue.main.async {
|
||||
switch result {
|
||||
case .success(let uploadResult):
|
||||
print("✅ 上传成功 (\(id)): \(uploadResult.fileId)")
|
||||
self.uploadStatus[id] = .completed(fileId: uploadResult.fileId)
|
||||
case .failure(let error):
|
||||
print("❌ 上传失败 (\(id)): \(error.localizedDescription)")
|
||||
self.uploadStatus[id] = .failed(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Preview Helper
|
||||
|
||||
/// 示例视图,展示如何使用 MediaUploadManager
|
||||
struct MediaUploadExample: View {
|
||||
@StateObject private var uploadManager = MediaUploadManager()
|
||||
@State private var showMediaPicker = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack(spacing: 20) {
|
||||
// 选择媒体按钮
|
||||
Button(action: {
|
||||
showMediaPicker = true
|
||||
}) {
|
||||
Button(action: { showMediaPicker = true }) {
|
||||
Label("选择媒体", systemImage: "photo.on.rectangle")
|
||||
.font(.headline)
|
||||
.frame(maxWidth: .infinity)
|
||||
@ -59,121 +166,95 @@ struct ExampleView: View {
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
// 显示已选媒体数量
|
||||
if !selectedMedia.isEmpty {
|
||||
VStack(spacing: 10) {
|
||||
Text("已选择 \(selectedMedia.count) 个媒体文件")
|
||||
.font(.headline)
|
||||
|
||||
// 显示媒体缩略图和上传状态
|
||||
List {
|
||||
ForEach(0..<selectedMedia.count, id: \.self) { index in
|
||||
let media = selectedMedia[index]
|
||||
let mediaId = "\(index)"
|
||||
let status = uploadStatus[mediaId] ?? .pending
|
||||
|
||||
HStack {
|
||||
// 缩略图
|
||||
MediaThumbnailView(media: media, onDelete: nil)
|
||||
.frame(width: 60, height: 60)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(media.isVideo ? "视频" : "图片")
|
||||
.font(.subheadline)
|
||||
|
||||
// 上传状态
|
||||
Text(status.description)
|
||||
.font(.caption)
|
||||
.foregroundColor(statusColor(status))
|
||||
|
||||
// 上传进度条
|
||||
if case .uploading(let progress) = status {
|
||||
ProgressView(value: progress, total: 1.0)
|
||||
.progressViewStyle(LinearProgressViewStyle())
|
||||
.frame(height: 4)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
}
|
||||
.frame(height: 300)
|
||||
}
|
||||
.padding(.top)
|
||||
} else {
|
||||
Text("未选择任何媒体")
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.top, 50)
|
||||
// 显示已选媒体
|
||||
MediaSelectionView(uploadManager: uploadManager)
|
||||
|
||||
// 上传按钮
|
||||
Button(action: { uploadManager.startUpload() }) {
|
||||
Text("开始上传")
|
||||
.font(.headline)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(uploadManager.selectedMedia.isEmpty ? Color.gray : Color.green)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.disabled(uploadManager.selectedMedia.isEmpty)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.navigationTitle("媒体上传")
|
||||
.sheet(isPresented: $showMediaPicker) {
|
||||
MediaPickerWithLogging(
|
||||
selectedMedia: $selectedMedia,
|
||||
selectedMedia: $uploadManager.selectedMedia,
|
||||
selectionLimit: 5,
|
||||
onDismiss: {
|
||||
// 媒体选择完成后的处理
|
||||
showMediaPicker = false
|
||||
// 开始上传所有选中的文件
|
||||
uploadAllMedia()
|
||||
}
|
||||
onDismiss: { showMediaPicker = false }
|
||||
)
|
||||
}
|
||||
.onChange(of: selectedMedia) { _ in
|
||||
// 当 selectedMedia 变化时,重置上传状态
|
||||
uploadStatus.removeAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 媒体选择视图组件
|
||||
struct MediaSelectionView: View {
|
||||
@ObservedObject var uploadManager: MediaUploadManager
|
||||
|
||||
var body: some View {
|
||||
if !uploadManager.selectedMedia.isEmpty {
|
||||
VStack(spacing: 10) {
|
||||
Text("已选择 \(uploadManager.selectedMedia.count) 个媒体文件")
|
||||
.font(.headline)
|
||||
|
||||
// 显示媒体缩略图和上传状态
|
||||
List {
|
||||
ForEach(0..<uploadManager.selectedMedia.count, id: \.self) { index in
|
||||
let media = uploadManager.selectedMedia[index]
|
||||
let mediaId = "\(index)"
|
||||
let status = uploadManager.uploadStatus[mediaId] ?? .pending
|
||||
|
||||
HStack {
|
||||
// 缩略图
|
||||
MediaThumbnailView(media: media, onDelete: nil)
|
||||
.frame(width: 60, height: 60)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(media.isVideo ? "视频" : "图片")
|
||||
.font(.subheadline)
|
||||
|
||||
// 上传状态
|
||||
Text(status.description)
|
||||
.font(.caption)
|
||||
.foregroundColor(statusColor(status))
|
||||
|
||||
// 上传进度条
|
||||
if case .uploading(let progress) = status {
|
||||
ProgressView(value: progress, total: 1.0)
|
||||
.progressViewStyle(LinearProgressViewStyle())
|
||||
.frame(height: 4)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.onDelete { indexSet in
|
||||
indexSet.forEach { index in
|
||||
uploadManager.removeMedia(at: index)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: 300)
|
||||
}
|
||||
.padding(.top)
|
||||
} else {
|
||||
Text("未选择任何媒体")
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.top, 50)
|
||||
}
|
||||
}
|
||||
|
||||
// 上传单个媒体文件
|
||||
private func uploadMedia(_ media: ImageUploadService.MediaType, id: String) {
|
||||
print("🔄 开始处理媒体: \(id)")
|
||||
uploadStatus[id] = .uploading(progress: 0)
|
||||
|
||||
uploader.uploadMedia(
|
||||
media,
|
||||
progress: { progress in
|
||||
print("📊 上传进度 (\(id)): \(progress.current)%")
|
||||
uploadStatus[id] = .uploading(progress: progress.progress)
|
||||
},
|
||||
completion: { result in
|
||||
switch result {
|
||||
case .success(let uploadResult):
|
||||
print("✅ 上传成功 (\(id)): \(uploadResult.fileId)")
|
||||
uploadStatus[id] = .completed(fileId: uploadResult.fileId)
|
||||
case .failure(let error):
|
||||
print("❌ 上传失败 (\(id)): \(error.localizedDescription)")
|
||||
uploadStatus[id] = .failed(error)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 上传所有媒体文件
|
||||
private func uploadAllMedia() {
|
||||
print("🔄 开始批量上传 \(selectedMedia.count) 个文件")
|
||||
for (index, media) in selectedMedia.enumerated() {
|
||||
let id = "\(index)"
|
||||
if case .pending = uploadStatus[id] ?? .pending {
|
||||
// Convert MediaType to ImageUploadService.MediaType
|
||||
let uploadMediaType: ImageUploadService.MediaType
|
||||
switch media {
|
||||
case .image(let image):
|
||||
uploadMediaType = .image(image)
|
||||
case .video(let url, let thumbnail):
|
||||
uploadMediaType = .video(url, thumbnail)
|
||||
}
|
||||
uploadMedia(uploadMediaType, id: id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取状态对应的颜色
|
||||
private func statusColor(_ status: UploadStatus) -> Color {
|
||||
private func statusColor(_ status: MediaUploadStatus) -> Color {
|
||||
switch status {
|
||||
case .pending: return .secondary
|
||||
case .uploading: return .blue
|
||||
@ -212,6 +293,6 @@ private struct MediaThumbnailView: View {
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ExampleView()
|
||||
MediaUploadExample()
|
||||
.environmentObject(AuthState.shared)
|
||||
}
|
||||
@ -46,7 +46,7 @@ struct WakeApp: App {
|
||||
// 已登录:显示userInfo页面
|
||||
// UserInfo()
|
||||
// .environmentObject(authState)
|
||||
ExampleView()
|
||||
MediaUploadDemo()
|
||||
.environmentObject(authState)
|
||||
} else {
|
||||
// 未登录:显示登录界面
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user