新增 CooperationOrder 模块(双 Tab 列表、获客员绑定、主 Tab 扫码)、订单来源/带客单绑定及配套 API 测试;同步引入 roleCode 权限匹配与相关单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
108 lines
3.6 KiB
Swift
108 lines
3.6 KiB
Swift
//
|
||
// SaleUserQrParser.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/29.
|
||
//
|
||
|
||
import Foundation
|
||
|
||
/// 获客员绑定二维码解析器,从扫码内容提取 sale_user_id。
|
||
enum SaleUserQrParser {
|
||
/// 标准获客员绑定二维码 method,与后端约定一致。
|
||
static let methodSaleUserBind = "sale_user_bind"
|
||
|
||
/// 从获客员二维码内容解析 sale_user_id。
|
||
/// 标准格式:`{"method":"sale_user_bind","sale_user_id":2}`
|
||
/// 兼容:纯数字、URL query、无 method 的历史 JSON。
|
||
static func parseSaleUserId(_ content: String) -> Int? {
|
||
let trimmed = content.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
if trimmed.isEmpty { return nil }
|
||
|
||
if trimmed.hasPrefix("{") {
|
||
if let value = parseFromJSON(trimmed) {
|
||
return value
|
||
}
|
||
}
|
||
|
||
if let value = Int(trimmed), value > 0 {
|
||
return value
|
||
}
|
||
|
||
if trimmed.contains("://") || trimmed.contains("?") {
|
||
return parseFromURL(trimmed)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
/// 判断扫码内容是否为获客员绑定专用二维码。
|
||
static func isSaleUserBindQR(_ content: String) -> Bool {
|
||
guard let object = jsonObject(from: content) else { return false }
|
||
let method = (object["method"] as? String)?
|
||
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||
return method == methodSaleUserBind
|
||
}
|
||
|
||
private static func parseFromJSON(_ json: String) -> Int? {
|
||
guard let object = jsonObject(from: json) else { return nil }
|
||
return parseFromJSONObject(object)
|
||
}
|
||
|
||
private static func jsonObject(from json: String) -> [String: Any]? {
|
||
guard let data = json.data(using: .utf8),
|
||
let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
||
else { return nil }
|
||
return object
|
||
}
|
||
|
||
private static func parseFromJSONObject(_ object: [String: Any]) -> Int? {
|
||
let method = (object["method"] as? String)?
|
||
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||
if method == methodSaleUserBind {
|
||
return readIntValue(object["sale_user_id"])
|
||
}
|
||
return readSaleUserId(object)
|
||
}
|
||
|
||
private static func readSaleUserId(_ object: [String: Any]) -> Int? {
|
||
let keys = ["sale_user_id", "saleUserId", "user_id", "id"]
|
||
for key in keys {
|
||
if let value = readIntValue(object[key]) {
|
||
return value
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
private static func readIntValue(_ value: Any?) -> Int? {
|
||
switch value {
|
||
case let intValue as Int where intValue > 0:
|
||
return intValue
|
||
case let number as NSNumber where number.intValue > 0:
|
||
return number.intValue
|
||
case let stringValue as String:
|
||
let trimmed = stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
if let intValue = Int(trimmed), intValue > 0 {
|
||
return intValue
|
||
}
|
||
return nil
|
||
default:
|
||
return nil
|
||
}
|
||
}
|
||
|
||
private static func parseFromURL(_ urlString: String) -> Int? {
|
||
guard let components = URLComponents(string: urlString) else { return nil }
|
||
let keys = ["sale_user_id", "saleUserId", "user_id", "id"]
|
||
for key in keys {
|
||
if let value = components.queryItems?.first(where: { $0.name == key })?.value,
|
||
let intValue = Int(value.trimmingCharacters(in: .whitespacesAndNewlines)),
|
||
intValue > 0 {
|
||
return intValue
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
}
|