完善合作获客员备注名:保存时校验长度,弹窗展示 saler_real_name。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -34,9 +34,11 @@
|
||||
### 获客员备注名
|
||||
|
||||
1. 合作获客员列表每行右上角「修改备注」→ 弹窗编辑备注名
|
||||
2. 保存 → `POST .../sale-user/update-remark`,Body:`{ "sale_user_id": Int, "photog_remark_name": String }`
|
||||
3. 展示规则:优先 `photog_remark_name`,为空时回退获客员原名(`name` / `saler_name` / `sale_user_name`)
|
||||
4. 绑定确认页(`BindAcquirerView`)仍展示真实获客员名,不使用备注名
|
||||
2. 备注名最多 20 个字符;点击保存时校验,超出则提示且不提交
|
||||
3. 修改备注弹窗展示 `saler_real_name`:「为 {获客员真实姓名} 设置备注名」
|
||||
4. 保存 → `POST .../sale-user/update-remark`,Body:`{ "sale_user_id": Int, "photog_remark_name": String }`
|
||||
5. 展示规则:优先 `photog_remark_name`,为空时回退获客员原名(`name` / `saler_name` / `sale_user_name`)
|
||||
6. 绑定确认页(`BindAcquirerView`)仍展示真实获客员名,不使用备注名
|
||||
|
||||
## 与订单模块关系
|
||||
|
||||
|
||||
@ -65,6 +65,8 @@ struct CooperativeSaler: Decodable, Identifiable {
|
||||
let bindingId: Int
|
||||
let name: String
|
||||
let salerName: String
|
||||
let salerRealName: String
|
||||
let saleUserName: String
|
||||
let photogRemarkName: String
|
||||
let phone: String
|
||||
let phoneMasked: String
|
||||
@ -95,6 +97,8 @@ struct CooperativeSaler: Decodable, Identifiable {
|
||||
case bindingId = "binding_id"
|
||||
case name
|
||||
case salerName = "saler_name"
|
||||
case salerRealName = "saler_real_name"
|
||||
case saleUserName = "sale_user_name"
|
||||
case photogRemarkName = "photog_remark_name"
|
||||
case phone
|
||||
case phoneMasked = "phone_masked"
|
||||
@ -111,6 +115,8 @@ struct CooperativeSaler: Decodable, Identifiable {
|
||||
bindingId = try container.decodeLossyInt(forKey: .bindingId) ?? 0
|
||||
name = try container.decodeLossyString(forKey: .name)
|
||||
salerName = try container.decodeLossyString(forKey: .salerName)
|
||||
salerRealName = try container.decodeLossyString(forKey: .salerRealName)
|
||||
saleUserName = try container.decodeLossyString(forKey: .saleUserName)
|
||||
photogRemarkName = try container.decodeLossyString(forKey: .photogRemarkName)
|
||||
phone = try container.decodeLossyString(forKey: .phone)
|
||||
phoneMasked = try container.decodeLossyString(forKey: .phoneMasked)
|
||||
@ -128,6 +134,8 @@ struct CooperativeSaler: Decodable, Identifiable {
|
||||
bindingId: bindingId,
|
||||
name: name,
|
||||
salerName: salerName,
|
||||
salerRealName: salerRealName,
|
||||
saleUserName: saleUserName,
|
||||
photogRemarkName: remark,
|
||||
phone: phone,
|
||||
phoneMasked: phoneMasked,
|
||||
@ -144,6 +152,8 @@ struct CooperativeSaler: Decodable, Identifiable {
|
||||
bindingId: Int,
|
||||
name: String,
|
||||
salerName: String,
|
||||
salerRealName: String,
|
||||
saleUserName: String,
|
||||
photogRemarkName: String,
|
||||
phone: String,
|
||||
phoneMasked: String,
|
||||
@ -157,6 +167,8 @@ struct CooperativeSaler: Decodable, Identifiable {
|
||||
self.bindingId = bindingId
|
||||
self.name = name
|
||||
self.salerName = salerName
|
||||
self.salerRealName = salerRealName
|
||||
self.saleUserName = saleUserName
|
||||
self.photogRemarkName = photogRemarkName
|
||||
self.phone = phone
|
||||
self.phoneMasked = phoneMasked
|
||||
|
||||
@ -16,3 +16,22 @@ enum CooperationOrderFeature {
|
||||
permissionContext.canAccess(uri)
|
||||
}
|
||||
}
|
||||
|
||||
/// 合作获客员备注名校验规则,与 Android 端保持一致。
|
||||
enum CooperationOrderRemarkName {
|
||||
/// 备注名最大字符数。
|
||||
static let maxLength = 20
|
||||
|
||||
/// 去除首尾空白后的备注名。
|
||||
static func trimmed(_ remark: String) -> String {
|
||||
remark.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
/// 保存前校验备注名,超限时返回错误提示。
|
||||
static func validationMessage(for remark: String) -> String? {
|
||||
guard trimmed(remark).count <= maxLength else {
|
||||
return "备注名不能超过\(maxLength)个字符"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,7 +35,10 @@ final class CooperationAcquirerViewModel: ObservableObject {
|
||||
api: CooperationOrderServing
|
||||
) async throws {
|
||||
guard saleUserId > 0 else { return }
|
||||
let trimmedRemark = remark.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if let message = CooperationOrderRemarkName.validationMessage(for: remark) {
|
||||
throw CooperationOrderRemarkValidationError(message: message)
|
||||
}
|
||||
let trimmedRemark = CooperationOrderRemarkName.trimmed(remark)
|
||||
try await api.saleUserUpdateRemark(saleUserId: saleUserId, photogRemarkName: trimmedRemark)
|
||||
acquirers = acquirers.map { acquirer in
|
||||
guard acquirer.saleUserId == saleUserId else { return acquirer }
|
||||
@ -43,3 +46,10 @@ final class CooperationAcquirerViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 合作获客员备注名校验失败错误。
|
||||
struct CooperationOrderRemarkValidationError: LocalizedError {
|
||||
let message: String
|
||||
|
||||
var errorDescription: String? { message }
|
||||
}
|
||||
|
||||
@ -88,6 +88,10 @@ struct CooperationAcquirerView: View {
|
||||
Button("保存") {
|
||||
// 必须在弹窗关闭前同步捕获,避免 alert dismiss 清空 state 导致接口未调用。
|
||||
guard let acquirer = remarkEditingAcquirer else { return }
|
||||
if let message = CooperationOrderRemarkName.validationMessage(for: remarkDraft) {
|
||||
toastCenter.show(message)
|
||||
return
|
||||
}
|
||||
let saleUserId = acquirer.saleUserId
|
||||
let draft = remarkDraft
|
||||
showRemarkAlert = false
|
||||
@ -98,7 +102,7 @@ struct CooperationAcquirerView: View {
|
||||
}
|
||||
} message: {
|
||||
if let acquirer = remarkEditingAcquirer {
|
||||
Text("为 \(acquirer.salerName.cooperationNonEmptyOrDefault(acquirer.name)) 设置备注名")
|
||||
Text("为 \(acquirer.salerRealName.cooperationNonEmptyOrDefault("—")) 设置备注名")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ final class CooperationOrderAPITests: XCTestCase {
|
||||
XCTAssertEqual(response.list.first?.displayId, 101)
|
||||
XCTAssertEqual(response.list.first?.displayName, "张哥")
|
||||
XCTAssertEqual(response.list.first?.photogRemarkName, "张哥")
|
||||
XCTAssertEqual(response.list.first?.salerRealName, "管铜彬")
|
||||
XCTAssertEqual(response.list.first?.displayPhone, "19281871413")
|
||||
XCTAssertEqual(response.list.first?.bindingId, 5)
|
||||
XCTAssertEqual(response.list.first?.shareCode, "INV-000101")
|
||||
|
||||
43
suixinkanTests/CooperationOrderRemarkNameTests.swift
Normal file
43
suixinkanTests/CooperationOrderRemarkNameTests.swift
Normal file
@ -0,0 +1,43 @@
|
||||
//
|
||||
// CooperationOrderRemarkNameTests.swift
|
||||
// suixinkanTests
|
||||
//
|
||||
// Created by Codex on 2026/7/2.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import suixinkan
|
||||
|
||||
/// 合作获客员备注名校验规则测试。
|
||||
final class CooperationOrderRemarkNameTests: XCTestCase {
|
||||
/// 测试超出上限时返回错误提示。
|
||||
func testValidationMessageWhenExceedsMaxLength() {
|
||||
let input = String(repeating: "名", count: CooperationOrderRemarkName.maxLength + 1)
|
||||
XCTAssertEqual(
|
||||
CooperationOrderRemarkName.validationMessage(for: input),
|
||||
"备注名不能超过\(CooperationOrderRemarkName.maxLength)个字符"
|
||||
)
|
||||
}
|
||||
|
||||
/// 测试合法长度时不返回错误。
|
||||
func testValidationMessageAcceptsValidRemark() {
|
||||
XCTAssertNil(CooperationOrderRemarkName.validationMessage(for: "张哥"))
|
||||
XCTAssertNil(
|
||||
CooperationOrderRemarkName.validationMessage(
|
||||
for: String(repeating: "名", count: CooperationOrderRemarkName.maxLength)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// 测试保存前会去除首尾空白后再校验。
|
||||
func testValidationUsesTrimmedLength() {
|
||||
let inner = String(repeating: "张", count: CooperationOrderRemarkName.maxLength)
|
||||
XCTAssertNil(CooperationOrderRemarkName.validationMessage(for: " \(inner) "))
|
||||
XCTAssertNotNil(CooperationOrderRemarkName.validationMessage(for: " \(inner)多 "))
|
||||
}
|
||||
|
||||
/// 测试去除首尾空白。
|
||||
func testTrimmedRemovesSurroundingWhitespace() {
|
||||
XCTAssertEqual(CooperationOrderRemarkName.trimmed(" 张哥 "), "张哥")
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,7 @@
|
||||
"binding_id": 5,
|
||||
"bound_at": "2026-06-25 17:11:01",
|
||||
"name": "管铜彬",
|
||||
"saler_real_name": "管铜彬",
|
||||
"photog_remark_name": "张哥",
|
||||
"phone": "19281871413",
|
||||
"phone_masked": "192****1413",
|
||||
|
||||
Reference in New Issue
Block a user