feat(web-admin): unify CRM confirm dialogs for customer and product.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
5c791b00cb
commit
4fd178499e
17 files changed
+4186
-206
No files matched your search
@@ -0,0 +1,49 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { describe, it } from 'node:test'
|
||||
import { buildCrmConfirmOptions } from './mp-crm-confirm'
|
||||
|
||||
describe('buildCrmConfirmOptions', () => {
|
||||
it('builds scheme A for non-delete confirms', () => {
|
||||
const opts = buildCrmConfirmOptions({
|
||||
kind: 'default',
|
||||
title: '放入公海',
|
||||
message: '确定将客户放入公海吗?'
|
||||
})
|
||||
assert.equal(opts.title, '放入公海')
|
||||
assert.equal(opts.message, '确定将客户放入公海吗?')
|
||||
assert.equal(opts.allowHtml, false)
|
||||
assert.equal(opts.className, 'mp-crm-dialog')
|
||||
assert.equal(opts.confirmButtonText, '确认')
|
||||
assert.equal(opts.confirmButtonColor, '#0a9f6d')
|
||||
assert.equal(opts.theme, undefined)
|
||||
})
|
||||
|
||||
it('builds scheme B for delete confirms with warn html', () => {
|
||||
const opts = buildCrmConfirmOptions({
|
||||
kind: 'danger',
|
||||
title: '删除客户',
|
||||
message: '确认删除该客户吗?'
|
||||
})
|
||||
assert.equal(opts.title, '')
|
||||
assert.equal(opts.allowHtml, true)
|
||||
assert.equal(opts.className, 'mp-crm-dialog mp-crm-dialog--danger')
|
||||
assert.equal(opts.confirmButtonText, '确认删除')
|
||||
assert.equal(opts.confirmButtonColor, '#dc2626')
|
||||
assert.match(String(opts.message), /mp-crm-dialog__warn/)
|
||||
assert.match(String(opts.message), /mp-crm-dialog__title/)
|
||||
assert.match(String(opts.message), /删除客户/)
|
||||
assert.match(String(opts.message), /确认删除该客户吗?/)
|
||||
assert.doesNotMatch(String(opts.message), /<script/)
|
||||
assert.equal(opts.theme, undefined)
|
||||
})
|
||||
|
||||
it('escapes html in danger title and message body', () => {
|
||||
const opts = buildCrmConfirmOptions({
|
||||
kind: 'danger',
|
||||
title: '删 <b>除</b>',
|
||||
message: 'a <b>x</b> & "y"'
|
||||
})
|
||||
assert.match(String(opts.message), /删 <b>除<\/b>/)
|
||||
assert.match(String(opts.message), /a <b>x<\/b> & "y"/)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,44 @@
|
||||
import { showConfirmDialog, type DialogOptions } from 'vant'
|
||||
|
||||
export type CrmConfirmKind = 'default' | 'danger'
|
||||
|
||||
export type BuildCrmConfirmInput = {
|
||||
kind: CrmConfirmKind
|
||||
title: string
|
||||
message: string
|
||||
confirmButtonText?: string
|
||||
}
|
||||
|
||||
function escapeHtml(text: string): string {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
/** 方案 B:图标在上、标题居中(对齐 Demo),标题写入 message HTML */
|
||||
function dangerMessageHtml(title: string, message: string): string {
|
||||
return (
|
||||
`<div class="mp-crm-dialog__warn"><span class="mp-crm-dialog__warn-icon">!</span></div>` +
|
||||
`<div class="mp-crm-dialog__title">${escapeHtml(title)}</div>` +
|
||||
`<div class="mp-crm-dialog__msg">${escapeHtml(message)}</div>`
|
||||
)
|
||||
}
|
||||
|
||||
export function buildCrmConfirmOptions(input: BuildCrmConfirmInput): DialogOptions {
|
||||
const isDanger = input.kind === 'danger'
|
||||
return {
|
||||
title: isDanger ? '' : input.title,
|
||||
message: isDanger ? dangerMessageHtml(input.title, input.message) : input.message,
|
||||
allowHtml: isDanger,
|
||||
className: isDanger ? 'mp-crm-dialog mp-crm-dialog--danger' : 'mp-crm-dialog',
|
||||
confirmButtonText: input.confirmButtonText ?? (isDanger ? '确认删除' : '确认'),
|
||||
confirmButtonColor: isDanger ? '#dc2626' : '#0a9f6d'
|
||||
}
|
||||
}
|
||||
|
||||
export function showCrmConfirmDialog(input: BuildCrmConfirmInput) {
|
||||
return showConfirmDialog(buildCrmConfirmOptions(input))
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict'
|
||||
import { describe, it } from 'node:test'
|
||||
import { buildProductConfirmOptions } from './mp-crm-product-confirm'
|
||||
|
||||
describe('buildProductConfirmOptions', () => {
|
||||
describe('buildProductConfirmOptions (alias of CRM confirm)', () => {
|
||||
it('builds scheme A for non-delete confirms', () => {
|
||||
const opts = buildProductConfirmOptions({
|
||||
kind: 'default',
|
||||
@@ -10,50 +10,21 @@ describe('buildProductConfirmOptions', () => {
|
||||
message: '确认下架该产品?'
|
||||
})
|
||||
assert.equal(opts.title, '下架产品')
|
||||
assert.equal(opts.message, '确认下架该产品?')
|
||||
assert.equal(opts.allowHtml, false)
|
||||
assert.equal(opts.className, 'mp-product-dialog')
|
||||
assert.equal(opts.confirmButtonText, '确认')
|
||||
assert.equal(opts.className, 'mp-crm-dialog')
|
||||
assert.equal(opts.confirmButtonColor, '#0a9f6d')
|
||||
assert.equal(opts.theme, undefined)
|
||||
})
|
||||
|
||||
it('builds scheme B for delete confirms with warn html', () => {
|
||||
it('builds scheme B for delete confirms', () => {
|
||||
const opts = buildProductConfirmOptions({
|
||||
kind: 'danger',
|
||||
title: '删除产品',
|
||||
message: '确认删除该产品?删除后不可恢复。'
|
||||
})
|
||||
assert.equal(opts.title, '')
|
||||
assert.equal(opts.allowHtml, true)
|
||||
assert.equal(opts.className, 'mp-product-dialog mp-product-dialog--danger')
|
||||
assert.equal(opts.className, 'mp-crm-dialog mp-crm-dialog--danger')
|
||||
assert.equal(opts.confirmButtonText, '确认删除')
|
||||
assert.equal(opts.confirmButtonColor, '#dc2626')
|
||||
assert.match(String(opts.message), /mp-product-dialog__warn/)
|
||||
assert.match(String(opts.message), /mp-product-dialog__title/)
|
||||
assert.match(String(opts.message), /mp-crm-dialog__warn/)
|
||||
assert.match(String(opts.message), /删除产品/)
|
||||
assert.match(String(opts.message), /确认删除该产品?删除后不可恢复。/)
|
||||
assert.doesNotMatch(String(opts.message), /<script/)
|
||||
assert.equal(opts.theme, undefined)
|
||||
})
|
||||
|
||||
it('escapes html in danger title and message body', () => {
|
||||
const opts = buildProductConfirmOptions({
|
||||
kind: 'danger',
|
||||
title: '删 <b>除</b>',
|
||||
message: 'a <b>x</b> & "y"'
|
||||
})
|
||||
assert.match(String(opts.message), /删 <b>除<\/b>/)
|
||||
assert.match(String(opts.message), /a <b>x<\/b> & "y"/)
|
||||
})
|
||||
|
||||
it('allows override confirmButtonText', () => {
|
||||
const opts = buildProductConfirmOptions({
|
||||
kind: 'default',
|
||||
title: '上架产品',
|
||||
message: '确认上架该产品?',
|
||||
confirmButtonText: '立即上架'
|
||||
})
|
||||
assert.equal(opts.confirmButtonText, '立即上架')
|
||||
})
|
||||
})
|
||||
@@ -1,45 +1,10 @@
|
||||
import { showConfirmDialog, type DialogOptions } from 'vant'
|
||||
|
||||
export type ProductConfirmKind = 'default' | 'danger'
|
||||
|
||||
export type BuildProductConfirmInput = {
|
||||
kind: ProductConfirmKind
|
||||
title: string
|
||||
message: string
|
||||
confirmButtonText?: string
|
||||
}
|
||||
|
||||
function escapeHtml(text: string): string {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
/** 方案 B:图标在上、标题居中(对齐 Demo),故标题写入 message HTML,不走 Vant header */
|
||||
function dangerMessageHtml(title: string, message: string): string {
|
||||
return (
|
||||
`<div class="mp-product-dialog__warn"><span class="mp-product-dialog__warn-icon">!</span></div>` +
|
||||
`<div class="mp-product-dialog__title">${escapeHtml(title)}</div>` +
|
||||
`<div class="mp-product-dialog__msg">${escapeHtml(message)}</div>`
|
||||
)
|
||||
}
|
||||
|
||||
export function buildProductConfirmOptions(input: BuildProductConfirmInput): DialogOptions {
|
||||
const isDanger = input.kind === 'danger'
|
||||
return {
|
||||
// 危险态用空 title,避免 Vant header 左对齐且排在图标上方
|
||||
title: isDanger ? '' : input.title,
|
||||
message: isDanger ? dangerMessageHtml(input.title, input.message) : input.message,
|
||||
allowHtml: isDanger,
|
||||
className: isDanger ? 'mp-product-dialog mp-product-dialog--danger' : 'mp-product-dialog',
|
||||
confirmButtonText: input.confirmButtonText ?? (isDanger ? '确认删除' : '确认'),
|
||||
confirmButtonColor: isDanger ? '#dc2626' : '#0a9f6d'
|
||||
}
|
||||
}
|
||||
|
||||
export function showProductConfirmDialog(input: BuildProductConfirmInput) {
|
||||
return showConfirmDialog(buildProductConfirmOptions(input))
|
||||
}
|
||||
/**
|
||||
* 产品管理确认弹窗 — 转发至 CRM 共用实现(方案 A/B)。
|
||||
* 保留本文件避免既有 import 断裂。
|
||||
*/
|
||||
export {
|
||||
buildCrmConfirmOptions as buildProductConfirmOptions,
|
||||
showCrmConfirmDialog as showProductConfirmDialog,
|
||||
type BuildCrmConfirmInput as BuildProductConfirmInput,
|
||||
type CrmConfirmKind as ProductConfirmKind
|
||||
} from './mp-crm-confirm'
|
||||
Reference in new issue
Block a user