import SwiftUI // MARK: - 通用列表组件 struct ListView: View where Data.Element: Identifiable { let data: Data let content: (Data.Element) -> Content let spacing: CGFloat let padding: EdgeInsets let backgroundColor: Color init( data: Data, spacing: CGFloat = 12, padding: EdgeInsets = EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0), backgroundColor: Color = Color(.systemGroupedBackground), @ViewBuilder content: @escaping (Data.Element) -> Content ) { self.data = data self.spacing = spacing self.padding = padding self.backgroundColor = backgroundColor self.content = content } var body: some View { ScrollView { LazyVStack(spacing: spacing) { ForEach(data) { item in content(item) } } .padding(padding) } .background(backgroundColor) } } // MARK: - 列表项组件 struct ListItem: View { let content: Content let padding: EdgeInsets let cornerRadius: CGFloat let backgroundColor: Color let shadowColor: Color let shadowRadius: CGFloat let shadowOffset: CGSize let onTap: (() -> Void)? init( padding: EdgeInsets = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16), cornerRadius: CGFloat = 12, backgroundColor: Color = Color(.systemBackground), shadowColor: Color = .black.opacity(0.05), shadowRadius: CGFloat = 4, shadowOffset: CGSize = CGSize(width: 0, height: 2), onTap: (() -> Void)? = nil, @ViewBuilder content: () -> Content ) { self.padding = padding self.cornerRadius = cornerRadius self.backgroundColor = backgroundColor self.shadowColor = shadowColor self.shadowRadius = shadowRadius self.shadowOffset = shadowOffset self.onTap = onTap self.content = content() } var body: some View { Group { if let onTap = onTap { Button(action: onTap) { content .padding(padding) .background(backgroundColor) .cornerRadius(cornerRadius) .shadow( color: shadowColor, radius: shadowRadius, x: shadowOffset.width, y: shadowOffset.height ) } .buttonStyle(PlainButtonStyle()) } else { content .padding(padding) .background(backgroundColor) .cornerRadius(cornerRadius) .shadow( color: shadowColor, radius: shadowRadius, x: shadowOffset.width, y: shadowOffset.height ) } } } } // MARK: - 预定义的列表项样式 extension ListItem { static func standard( onTap: (() -> Void)? = nil, @ViewBuilder content: () -> U ) -> ListItem { ListItem(onTap: onTap, content: content) } static func compact( onTap: (() -> Void)? = nil, @ViewBuilder content: () -> U ) -> ListItem { ListItem( padding: EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12), cornerRadius: 8, onTap: onTap, content: content ) } static func elevated( onTap: (() -> Void)? = nil, @ViewBuilder content: () -> U ) -> ListItem { ListItem( shadowColor: .black.opacity(0.1), shadowRadius: 8, shadowOffset: CGSize(width: 0, height: 4), onTap: onTap, content: content ) } } // MARK: - 空状态组件 struct EmptyStateView: View { let icon: String let title: String let subtitle: String? let actionTitle: String? let action: (() -> Void)? init( icon: String, title: String, subtitle: String? = nil, actionTitle: String? = nil, action: (() -> Void)? = nil ) { self.icon = icon self.title = title self.subtitle = subtitle self.actionTitle = actionTitle self.action = action } var body: some View { VStack(spacing: 20) { Image(systemName: icon) .font(.system(size: 60)) .foregroundColor(.secondary) VStack(spacing: 8) { Text(title) .font(.title2) .fontWeight(.semibold) .foregroundColor(.primary) if let subtitle = subtitle { Text(subtitle) .font(.body) .foregroundColor(.secondary) .multilineTextAlignment(.center) } } if let actionTitle = actionTitle, let action = action { Button(action: action) { Text(actionTitle) .font(.subheadline) .fontWeight(.medium) .foregroundColor(.blue) .padding(.horizontal, 20) .padding(.vertical, 12) .background( RoundedRectangle(cornerRadius: 8) .fill(Color.blue.opacity(0.1)) ) } } } .padding(40) .frame(maxWidth: .infinity, maxHeight: .infinity) } } // MARK: - 加载状态组件 struct LoadingStateView: View { let message: String init(message: String = NSLocalizedString("loading", comment: "Loading")) { self.message = message } var body: some View { VStack(spacing: 16) { ProgressView() .scaleEffect(1.2) Text(message) .font(.subheadline) .foregroundColor(.secondary) } .padding(40) .frame(maxWidth: .infinity, maxHeight: .infinity) } } // MARK: - 错误状态组件 struct ErrorStateView: View { let title: String let message: String let retryAction: (() -> Void)? init( title: String = NSLocalizedString("error_occurred", comment: "Error occurred"), message: String = NSLocalizedString("load_failed_retry", comment: "Load failed, please retry"), retryAction: (() -> Void)? = nil ) { self.title = title self.message = message self.retryAction = retryAction } var body: some View { VStack(spacing: 20) { Image(systemName: "exclamationmark.triangle") .font(.system(size: 60)) .foregroundColor(.orange) VStack(spacing: 8) { Text(title) .font(.title2) .fontWeight(.semibold) .foregroundColor(.primary) Text(message) .font(.body) .foregroundColor(.secondary) .multilineTextAlignment(.center) } if let retryAction = retryAction { Button(action: retryAction) { Text(NSLocalizedString("retry", comment: "Retry")) .font(.subheadline) .fontWeight(.medium) .foregroundColor(.blue) .padding(.horizontal, 20) .padding(.vertical, 12) .background( RoundedRectangle(cornerRadius: 8) .fill(Color.blue.opacity(0.1)) ) } } } .padding(40) .frame(maxWidth: .infinity, maxHeight: .infinity) } } #Preview { VStack(spacing: 20) { // 列表视图 ListView(data: Array(1...5).map { ListItemData(id: $0, title: String(format: NSLocalizedString("item_format", comment: "Item format"), $0)) }) { item in ListItem.standard { AnyView( HStack { Text(item.title) .font(.body) Spacer() Image(systemName: "chevron.right") .font(.caption) .foregroundColor(.secondary) } ) } } .frame(height: 300) // 空状态 EmptyStateView( icon: "tray", title: NSLocalizedString("no_data", comment: "No data"), subtitle: NSLocalizedString("no_content_yet", comment: "No content yet"), actionTitle: NSLocalizedString("add_content", comment: "Add content"), action: {} ) // 加载状态 LoadingStateView(message: NSLocalizedString("loading_data", comment: "Loading data")) // 错误状态 ErrorStateView( title: NSLocalizedString("network_error", comment: "Network error"), message: NSLocalizedString("connection_failed_check_network", comment: "Cannot connect to server, please check network connection"), retryAction: {} ) } } // MARK: - 示例数据 struct ListItemData: Identifiable { let id: Int let title: String }