完善合作获客员备注名:保存时校验长度,弹窗展示 saler_real_name。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-02 17:57:17 +08:00
parent 328c4321f4
commit c7f557f361
8 changed files with 97 additions and 5 deletions

View File

@ -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`)仍展示真实获客员名,不使用备注名
## 与订单模块关系

View File

@ -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

View File

@ -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
}
}

View File

@ -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 }
}

View File

@ -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("")) 设置备注名")
}
}
}

View File

@ -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")

View 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(" 张哥 "), "张哥")
}
}

View File

@ -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",