紧抱豆包大腿,开发了一个iOS实用小工具(二维码照片清理app)
手上没有可用的Mac设备,就折腾虚拟机吧。步骤也很简单:在Xcode里新建一个iOS App工程,接口类型选了默认的SwiftUI,然后语言就只能选Swfit,把豆包给我写好的Swfit源码贴到主源码文件里就行了。(刚开始换MacOS和Xcode高版本把这一茬给忘了,程序一跑就报错,把我困惑了好几天,还以为是高版本系统不兼容代码,几乎放弃)。我自己没有买Apple的开发者订阅,毕竟一年要99美元,
手机相册里存了很多随身码,核算码啥的图片(身在上海,经历过2020~2022年的都懂)。一直没有耐心好好去清理。但总惦记着这个事情,想写个工具app来清理相册里这种图片。
身为一名非典型性程序猿,之前只会用一些非主流的开发工具,想学iOS开发,却是举步维艰:一看object-c的代码就一个头两个大(那时还不知道swfit-UI)。曾经想过用XMarain for iOS做,也写了一个雏形,但是受限于时间和没有Mac电脑等等(都是借口而已),一拖又是几年过去了。
2025年,AI突然发力,甚至可以跟程序猿抢桃子了,我就动了用豆包帮我写这个iOS程序的念头。豆包真的很牛逼,我提了设想,它二话不说就把代码给我写完了,是个SwiftUI程序。代码很简洁,虽然也看不懂,但是好像比较偏向于高级语言。顺便多说两句灌个水,给非程序猿看官科普一下,高级语言这个“高级”不日常用语里高级感满满那个褒义词,是指编程语言更贴近自然语言,是相对于更贴近机器指令的低级语言(如C语言、汇编语言)而言。
手上没有可用的Mac设备,就折腾虚拟机吧。之前主要玩VirtualBox,因为免费。折腾了很久,装MacOS遇到各种问题。网上一看很多人推荐用VM-ware,又突然发现VM-ware workstation已经对个人免费了,于是果断弃暗投明。在VM-ware上装了一个MacOS虚拟机,再加上XCode,居然很顺利地在仿真iOS设备上把这个程序给调试通过了。步骤也很简单:在Xcode里新建一个iOS App工程,接口类型选了默认的SwiftUI,然后语言就只能选Swfit,把豆包给我写好的Swfit源码贴到主源码文件里就行了。
上真机测试的历程有点坎坷,一开始是因为我的Xcode版本太低(好像是XCode 14吧),我的手机偏偏又已经升级到了iOS 18.7,适配不了。一顿折腾,好容易升级到了MacOS 15.7.4 (Sequoia)和Xcode 16.4,终于能上真机调试了。程序跑一会儿就闪退。仿真设备相册里我只放进去40多张照片测试。真机里照片太多,估计是扫描过程中内存没及时释放,爆了,就闪退了。想调优代码,又看不太懂。反复测试,发现如果每次扫描的照片不超过70张,不会闪退。好在这个工具我主要是自己用,我自己能凑合,就改成分批次扫描,每次扫描64张。相册照片按时间从早到晚顺序排队接受检阅。每一批扫描之后,把本批次最后一张照片的日期时间记下来,下一轮扫描从这个日期时间开始的照片。扫描发现了二维码占主体的图片呢,就放到画廊里陈列出来,橱窗里显示缩略图,用户可以点开看大图,可以取消勾选。完成选择之后,工具就提示是否删除照片。说到这里,不得不表达对SwiftUI的敬佩,这些复杂的图形化UI交互,人家一个源码文件就全部搞定,代码还只有600多行。
除了这个SwfitUI的主源码文件(完全是豆包捉刀代笔),我还按照豆包的指导在XCode的工程理加了对相册权限的请求声明。(刚开始换MacOS和Xcode高版本把这一茬给忘了,程序一跑就报错,把我困惑了好几天,还以为是高版本系统不兼容代码,几乎放弃)。首先,在XCode的Project树形导航栏选择最上层的工程节点,然后在中间导航栏选择下面TARGETS区域第一个与工程同名的节点,最后在右侧的Custom iOS Target Properties列表中点+号按钮新增2项(Value对应界面提示文字,可以根据自己偏好调整):
|
Key |
Type |
Value |
|---|---|---|
|
Privacy - Photo Library Usage Description |
String |
需要访问相册以扫描二维码照片 |
|
Privacy - Photo Library Additions Usage Description |
String |
需要访问相册以删除照片 |
最后,作为一个App,在桌面上得有一个Icon,随意用AI生成了一张图标,1024×1024的PNG图片。左侧工程树形导航栏里选Assets,中部导航栏选AppIcon,右侧左上角有个Any Apperance,把图片从Finder里拖进来就OK了。
以下附上主文件Swift源码,其他工程文件就不必要了。我自己没有买Apple的开发者订阅,毕竟一年要99美元,我又不是职业iOS开发程序猿,如果哪位土豪看官或者热心人想让我把这个App上架,可以赞助或者帮我众筹这笔经费。亲们如果有任何问题或者要求想跟我交流,欢迎跟我联系,eMail:zongchao@sina.com,微信号:zong_chao。
import SwiftUI
import Photos
import CoreImage
import CoreImage.CIFilterBuiltins
// MARK: - 配置Key
private let kLastScannedDateKey = "kLastScannedDateKey"
private let kQRThresholdKey = "kQRThresholdKey"
private let kTotalScanCountKey = "kTotalScanCount"
// MARK: - 全局设置
class AppSettings: ObservableObject {
@Published var qrThreshold: Double {
didSet {
UserDefaults.standard.set(qrThreshold, forKey: kQRThresholdKey)
}
}
init() {
let saved = UserDefaults.standard.double(forKey: kQRThresholdKey)
self.qrThreshold = saved == 0 ? 0.3 : saved
}
}
// MARK: - 二维码检测模型
private let ciContext = CIContext(options: [.useSoftwareRenderer: false])
struct QRResult {
let found: Bool
let boundingBox: CGRect
let imageSize: CGSize
var widthRatio: Double {
imageSize.width > 0 ? Double(boundingBox.width / imageSize.width) : 0.0
}
var heightRatio: Double {
imageSize.height > 0 ? Double(boundingBox.height / imageSize.height) : 0.0
}
func isMainQRCode(threshold: Double) -> Bool {
widthRatio >= threshold || heightRatio >= threshold
}
}
extension UIImage {
func detectQRCode(context: CIContext) -> QRResult {
guard let detector = CIDetector(
ofType: CIDetectorTypeQRCode,
context: context,
options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]
) else {
return QRResult(found: false, boundingBox: .zero, imageSize: size)
}
guard let ci = autoreleasepool(invoking: { CIImage(image: self) }),
let features = detector.features(in: ci) as? [CIQRCodeFeature] else {
return QRResult(found: false, boundingBox: .zero, imageSize: size)
}
guard let f = features.first else {
return QRResult(found: false, boundingBox: .zero, imageSize: size)
}
let t = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -size.height)
return QRResult(found: true, boundingBox: f.bounds.applying(t), imageSize: size)
}
}
// MARK: - 扫描管理器
class PhotoScanner: ObservableObject {
@Published var isScanning = false
@Published var totalPhotos = 0
@Published var currentIndex = 0
@Published var foundCount = 0
@Published var foundAssets: [PHAsset] = []
@Published var showPermissionAlert = false
@Published var lastScannedDate: Date?
@Published var showNoMorePhotosAlert = false
// 👇 只加了这一个:累计扫描总数
@Published var totalScanCount = 0
private var stopFlag = false
private let ciContext = CIContext(options: [.useSoftwareRenderer: false])
private let defaults = UserDefaults.standard
private let batchSize = 64
init() {
lastScannedDate = defaults.object(forKey: kLastScannedDateKey) as? Date
totalScanCount = defaults.integer(forKey: kTotalScanCountKey)
}
func stopScan() {
stopFlag = true
}
private func saveBreakpoint(date: Date) {
lastScannedDate = date
defaults.set(date, forKey: kLastScannedDateKey)
}
func clearBreakpoint() {
lastScannedDate = nil
defaults.removeObject(forKey: kLastScannedDateKey)
foundAssets.removeAll()
foundCount = 0
currentIndex = 0
// 👇 清除记忆点时重置累计
totalScanCount = 0
defaults.set(0, forKey: kTotalScanCountKey)
}
func requestPermission(completion: @escaping (Bool) -> Void) {
PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
DispatchQueue.main.async {
completion(status == .authorized || status == .limited)
}
}
}
func startScan(threshold: Double) {
guard !isScanning else { return }
isScanning = true
stopFlag = false
currentIndex = 0
totalPhotos = 0
let opt = PHFetchOptions()
opt.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
opt.fetchLimit = batchSize
if let lastDate = lastScannedDate {
opt.predicate = NSPredicate(format: "creationDate > %@", lastDate as CVarArg)
}
let assets = PHAsset.fetchAssets(with: .image, options: opt)
totalPhotos = assets.count
// 👇 累加累计数
totalScanCount += assets.count
defaults.set(totalScanCount, forKey: kTotalScanCountKey)
if assets.count == 0 {
DispatchQueue.main.async {
self.isScanning = false
self.currentIndex = 1
self.showNoMorePhotosAlert = true
}
return
}
func next(at i: Int) {
if stopFlag || i >= assets.count {
if i > 0, let date = assets.object(at: i-1).creationDate {
saveBreakpoint(date: date)
}
DispatchQueue.main.async {
self.isScanning = false
if self.currentIndex == 0 {
self.currentIndex = 1
}
}
return
}
DispatchQueue.main.async {
self.currentIndex = i + 1
}
let asset = assets.object(at: i)
loadImage(asset: asset) { img in
guard let img = img else {
next(at: i + 1)
return
}
let res = img.detectQRCode(context: self.ciContext)
if res.found && res.isMainQRCode(threshold: threshold) {
DispatchQueue.main.async {
self.foundCount += 1
self.foundAssets.append(asset)
}
}
next(at: i + 1)
}
}
DispatchQueue.global(qos: .userInitiated).async {
next(at: 0)
}
}
private func loadImage(asset: PHAsset, completion: @escaping (UIImage?) -> Void) {
let opt = PHImageRequestOptions()
opt.isSynchronous = true
opt.deliveryMode = .highQualityFormat
opt.resizeMode = .fast
let targetSize = CGSize(width: 1200, height: 1200)
PHImageManager.default().requestImage(
for: asset,
targetSize: targetSize,
contentMode: .aspectFit,
options: opt
) { img, _ in
autoreleasepool(invoking: {
completion(img)
})
}
}
}
// MARK: - 参数调节弹窗
struct QRThresholdSettingView: View {
@ObservedObject var settings: AppSettings
@Environment(\.dismiss) var dismiss
var body: some View {
NavigationStack {
VStack(spacing: 30) {
Text("二维码占比阈值设置")
.font(.title2.bold())
Text("当前值:\(Int(settings.qrThreshold * 100))%")
.font(.headline)
Slider(value: $settings.qrThreshold, in: 0.05...0.95, step: 0.05)
.padding(.horizontal, 30)
Text("调节范围 5% ~ 95%,步长 5%")
.font(.caption)
.foregroundColor(.gray)
Spacer()
}
.padding(.top, 20)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("完成") {
dismiss()
}
}
}
}
}
}
// MARK: - 详情页
struct ResultDetailView: View {
let assets: [PHAsset]
@Binding var showDetail: Bool
var onDeleteCompleted: () -> Void
@State private var currentPage = 0
@State private var selectedItems: Set<Int> = []
@State private var showAlert = false
@State private var alertMessage = ""
@State private var isDeleteAlert = false
@State private var selectedImage: UIImage?
@State private var showFullScreen = false
private let itemsPerPage = 9
private let columns = Array(repeating: GridItem(.flexible()), count: 3)
private var totalPages: Int {
max(1, Int(ceil(Double(assets.count) / Double(itemsPerPage))))
}
private var pageItems: [PHAsset] {
let start = currentPage * itemsPerPage
let end = min(start + itemsPerPage, assets.count)
return Array(assets[start..<end])
}
private var pageIndices: [Int] {
let start = currentPage * itemsPerPage
return (0..<pageItems.count).map { start + $0 }
}
init(assets: [PHAsset], showDetail: Binding<Bool>, onDeleteCompleted: @escaping () -> Void) {
self.assets = assets
self._showDetail = showDetail
self.onDeleteCompleted = onDeleteCompleted
self._selectedItems = State(initialValue: Set(0..<assets.count))
}
var body: some View {
NavigationStack {
VStack(spacing: 0) {
Text("请选择要删除的照片:")
.font(.subheadline)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 12)
Text("第 \(currentPage+1)/\(totalPages) 页")
.font(.caption)
ScrollView(.vertical, showsIndicators: false) {
LazyVGrid(columns: columns, spacing: 8) {
ForEach(Array(zip(pageIndices, pageItems)), id: \.0) { idx, asset in
PhotoCheckItemView(
asset: asset,
isChecked: selectedItems.contains(idx),
onToggle: {
if selectedItems.contains(idx) {
selectedItems.remove(idx)
} else {
selectedItems.insert(idx)
}
},
onTapImage: {
loadFullImage(asset: asset)
}
)
}
}
.padding(.horizontal, 12)
}
HStack(spacing: 16) {
Button("上一页") {
if currentPage > 0 {
currentPage -= 1
}
}
.disabled(currentPage <= 0)
Button("下一页") {
if currentPage < totalPages - 1 {
currentPage += 1
}
}
.disabled(currentPage >= totalPages - 1)
Button("完成选择") {
checkSelectionAndShowAlert()
}
.foregroundColor(.blue)
}
.padding(.vertical, 4)
}
.navigationTitle("二维码主体照片")
.navigationBarTitleDisplayMode(.inline)
.padding(.top, 0)
.alert("提示", isPresented: $showAlert) {
if isDeleteAlert {
Button("是", role: .destructive) {
deleteSelected()
}
Button("否", role: .cancel) {
showDetail = false
}
} else {
Button("确定") {
onDeleteCompleted()
showDetail = false
}
}
} message: {
Text(alertMessage)
}
.overlay {
if showFullScreen, let selectedImage {
FullScreenImageView(image: selectedImage) {
self.selectedImage = nil
self.showFullScreen = false
}
}
}
}
}
private func loadFullImage(asset: PHAsset) {
let options = PHImageRequestOptions()
options.deliveryMode = .highQualityFormat
options.isNetworkAccessAllowed = true
PHImageManager.default().requestImage(
for: asset,
targetSize: PHImageManagerMaximumSize,
contentMode: .aspectFit,
options: options
) { image, _ in
guard let image = image else { return }
DispatchQueue.main.async {
self.selectedImage = image
self.showFullScreen = true
}
}
}
private func checkSelectionAndShowAlert() {
if selectedItems.isEmpty {
alertMessage = "共选中0张照片"
isDeleteAlert = false
} else {
alertMessage = "共选中 \(selectedItems.count) 张照片,是否删除所选照片?"
isDeleteAlert = true
}
showAlert = true
}
private func deleteSelected() {
let toDelete = selectedItems.compactMap { assets.indices.contains($0) ? assets[$0] : nil }
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.deleteAssets(toDelete as NSArray)
}, completionHandler: { success, error in
DispatchQueue.main.async {
if success {
onDeleteCompleted()
showDetail = false
} else {
alertMessage = "删除失败:\(error?.localizedDescription ?? "未知错误")"
isDeleteAlert = false
showAlert = true
}
}
})
}
}
struct FullScreenImageView: View {
let image: UIImage
let onTap: () -> Void
var body: some View {
ZStack {
Color.black.ignoresSafeArea()
Image(uiImage: image)
.resizable()
.scaledToFit()
.onTapGesture {
onTap()
}
}
}
}
struct PhotoCheckItemView: View {
let asset: PHAsset
let isChecked: Bool
let onToggle: () -> Void
let onTapImage: () -> Void
var body: some View {
VStack(spacing: 4) {
PhotoThumbnailView(asset: asset)
.frame(width: 100, height: 100)
.clipped()
.onTapGesture {
onTapImage()
}
Toggle(isOn: Binding(get: { isChecked }, set: { _ in onToggle() })) {
EmptyView()
}
.labelsHidden()
.frame(height: 30)
}
.frame(width: 100)
}
}
struct PhotoThumbnailView: View {
let asset: PHAsset
@State private var image: UIImage?
var body: some View {
ZStack {
if let image = image {
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(width: 100, height: 100)
} else {
Color.gray.opacity(0.4)
}
}
.frame(width: 100, height: 100)
.clipped()
.onAppear {
let opt = PHImageRequestOptions()
opt.isNetworkAccessAllowed = true
opt.deliveryMode = .highQualityFormat
PHImageManager.default().requestImage(
for: asset,
targetSize: CGSize(width: 240, height: 240),
contentMode: .aspectFit,
options: opt
) { img, _ in
DispatchQueue.main.async {
image = img
}
}
}
}
}
// MARK: - 主界面(已修复:所有文字强制黑色)
struct ContentView: View {
@StateObject private var scanner = PhotoScanner()
@StateObject private var settings = AppSettings()
@State private var showDetail = false
@State private var autoContinueWhenNotFound = false
@State private var showClearSuccessAlert = false
@State private var showSettingPage = false
private var progress: Double {
guard scanner.totalPhotos > 0 else { return 0 }
return Double(scanner.currentIndex) / Double(scanner.totalPhotos)
}
private var lastScanDateText: String {
guard let date = scanner.lastScannedDate else {
return "已扫描照片最晚日期时间:无(将从最早照片开始)"
}
let fmt = DateFormatter()
fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
return "已扫描照片最晚日期时间:\n\(fmt.string(from: date))"
}
var body: some View {
VStack(spacing: 8) {
Spacer().frame(height: 60)
Text("本应用可以从相册中查找二维码占主体的照片")
.font(.title2)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 20)
Text("(为防止占用过多系统资源导致应用闪退,每次仅扫描64张照片,随后可继续扫描)")
.font(.subheadline)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 20)
Button {
scanner.requestPermission { granted in
if granted {
scanner.currentIndex = 1
scanner.startScan(threshold: settings.qrThreshold)
} else {
scanner.showPermissionAlert = true
}
}
} label: {
Text("扫描二维码照片")
.frame(maxWidth: .infinity)
.padding()
}
.buttonStyle(.borderedProminent)
.font(.title2.bold())
.cornerRadius(12)
.padding(.horizontal, 30)
Spacer().frame(height: 24)
Text(lastScanDateText)
.font(.caption)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
Button {
scanner.clearBreakpoint()
showClearSuccessAlert = true
} label: {
Text("清除记忆点(从头扫描)")
.frame(maxWidth: .infinity)
.padding()
}
.buttonStyle(.borderedProminent)
.tint(.gray)
.cornerRadius(12)
.padding(.horizontal, 30)
Spacer()
HStack {
Text("二维码占比阈值:\(Int(settings.qrThreshold * 100))%")
.font(.subheadline)
Spacer()
Button("调整参数") {
showSettingPage = true
}
.buttonStyle(.borderedProminent)
.controlSize(.small)
}
.padding(.horizontal, 30)
.padding(.bottom, 16)
}
.overlay {
if scanner.isScanning || scanner.currentIndex > 0 {
Color.white.ignoresSafeArea()
ScrollView {
VStack(spacing: 16) {
if scanner.isScanning {
Text("正在扫描相册...")
.font(.title.bold())
.minimumScaleFactor(0.5)
.foregroundColor(.black) // 👈 修复
ProgressView(value: progress)
.progressViewStyle(.linear)
.frame(height: 12)
.padding(.horizontal, 40)
Text("\(Int(progress * 100))%")
.font(.headline)
.foregroundColor(.blue)
.minimumScaleFactor(0.5)
Text("累计扫描:\(scanner.totalScanCount) 张")
.minimumScaleFactor(0.5)
.foregroundColor(.black) // 👈 修复
Text("当前:第 \(scanner.currentIndex) 张")
.minimumScaleFactor(0.5)
.foregroundColor(.black) // 👈 修复
Text("有 \(scanner.foundCount) 张二维码照片待处理")
.foregroundColor(.green)
.minimumScaleFactor(0.5)
Button {
scanner.stopScan()
autoContinueWhenNotFound = false
} label: {
Text("🛑 停止扫描")
.frame(maxWidth: 120)
.frame(height: 40)
}
.buttonStyle(.borderedProminent)
.tint(.red)
} else {
Text("本批次扫描完成")
.font(.title.bold())
.minimumScaleFactor(0.5)
.lineLimit(1)
.foregroundColor(.black) // 👈 修复
Text("本批次扫描:\(scanner.totalPhotos) 张")
.font(.title3)
.minimumScaleFactor(0.5)
.lineLimit(1)
.foregroundColor(.black) // 👈 修复
Text("累计扫描:\(scanner.totalScanCount) 张")
.font(.title3)
.minimumScaleFactor(0.5)
.foregroundColor(.black) // 👈 修复
Text("有 \(scanner.foundCount) 张二维码照片待处理")
.font(.title)
.foregroundColor(.green)
.minimumScaleFactor(0.5)
.lineLimit(1)
Text(lastScanDateText)
.font(.caption)
.foregroundColor(.gray)
.multilineTextAlignment(.center)
.minimumScaleFactor(0.5)
VStack(spacing: 14) {
Button {
scanner.currentIndex = 0
} label: {
Text("返回")
.frame(maxWidth: .infinity)
.frame(height: 40)
}
.buttonStyle(.borderedProminent)
.tint(.gray)
if scanner.foundCount > 0 {
Button {
showDetail = true
} label: {
Text("查看详情")
.frame(maxWidth: .infinity)
.frame(height: 40)
}
.buttonStyle(.borderedProminent)
}
Button {
scanner.currentIndex = 1
scanner.startScan(threshold: settings.qrThreshold)
} label: {
Text("继续扫描")
.frame(maxWidth: .infinity)
.frame(height: 40)
}
.buttonStyle(.borderedProminent)
}
.frame(width: 200)
Toggle(isOn: $autoContinueWhenNotFound) {
Text("累计不足9张自动续扫")
.foregroundColor(.black)
.minimumScaleFactor(0.5)
}
.padding(.horizontal, 30)
}
}
.padding(.top, 20)
.onAppear {
if !scanner.isScanning, scanner.totalPhotos == 0 {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
scanner.showNoMorePhotosAlert = true
autoContinueWhenNotFound = false
}
}
}
}
}
}
.sheet(isPresented: $showSettingPage) {
QRThresholdSettingView(settings: settings)
}
.sheet(isPresented: $showDetail) {
ResultDetailView(
assets: scanner.foundAssets,
showDetail: $showDetail
) {
scanner.foundCount = 0
scanner.foundAssets.removeAll()
}
}
.onChange(of: scanner.isScanning) { oldValue, newValue in
if !newValue, autoContinueWhenNotFound, scanner.foundCount < 9 {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
guard !scanner.isScanning else { return }
scanner.currentIndex = 1
scanner.startScan(threshold: settings.qrThreshold)
}
}
}
.alert("提示", isPresented: $scanner.showNoMorePhotosAlert) {
Button("确定", role: .cancel) { }
} message: {
Text("没有更多照片可扫描了")
}
.onChange(of: scanner.showNoMorePhotosAlert) { oldValue, newValue in
if newValue {
autoContinueWhenNotFound = false
}
}
.alert("需要相册权限", isPresented: $scanner.showPermissionAlert) {
Button("去设置") {
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
}
Button("取消", role: .cancel) {}
} message: {
Text("请允许访问相册")
}
.alert("清除成功", isPresented: $showClearSuccessAlert) {
Button("确定", role: .cancel) {}
} message: {
Text("已清除记忆点,下次从头扫描")
}
}
}
// MARK: - 预览(已修复)
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
更多推荐



所有评论(0)