Browse Source

fix(web-admin): align product danger confirm layout with demo B.

Co-authored-by: Cursor <cursoragent@cursor.com>
wuzilong 1 week ago
parent
commit
5c791b00cb

+ 3 - 0
web-admin/scripts/test-mp-crm-product-confirm-dom.mjs

@@ -11,6 +11,7 @@ const categories = await readFile(new URL('categories.vue', root), 'utf8')
 assert.match(util, /ProductConfirmKind\s*=\s*'default'\s*\|\s*'danger'/, 'helper 必须支持 danger kind')
 assert.match(util, /kind\s*===\s*'danger'/, 'helper 必须分支处理 danger')
 assert.match(util, /mp-product-dialog--danger/, 'helper 必须带 danger className')
+assert.match(util, /mp-product-dialog__title/, '方案 B 标题须写入 HTML(图标在上)')
 assert.match(util, /#dc2626/, '删除确认色必须为危险红')
 assert.match(util, /#0a9f6d/, '普通确认色必须为三墙绿')
 assert.doesNotMatch(util, /round-button/, '禁止 round-button 主题')
@@ -19,6 +20,8 @@ assert.doesNotMatch(util, /#10b981/, '禁止 Fresh Mint')
 assert.match(scss, /body \.van-dialog\.mp-product-dialog/, 'scss 必须限定产品 Dialog')
 assert.match(scss, /mp-product-dialog--danger/, 'scss 必须覆盖危险确认键')
 assert.match(scss, /mp-product-dialog__warn-icon/, 'scss 必须有警示图标')
+assert.match(scss, /mp-product-dialog__title/, 'scss 必须有居中标题')
+assert.match(scss, /text-align:\s*center/, '标题/文案须居中')
 assert.doesNotMatch(scss, /#10b981/, 'scss 禁止 Fresh Mint')
 
 for (const [name, src] of [

+ 6 - 3
web-admin/src/utils/mp-crm-product-confirm.test.ts

@@ -24,23 +24,26 @@ describe('buildProductConfirmOptions', () => {
       title: '删除产品',
       message: '确认删除该产品?删除后不可恢复。'
     })
-    assert.equal(opts.title, '删除产品')
+    assert.equal(opts.title, '')
     assert.equal(opts.allowHtml, true)
     assert.equal(opts.className, 'mp-product-dialog mp-product-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), /删除产品/)
     assert.match(String(opts.message), /确认删除该产品?删除后不可恢复。/)
     assert.doesNotMatch(String(opts.message), /<script/)
     assert.equal(opts.theme, undefined)
   })
 
-  it('escapes html in danger message body', () => {
+  it('escapes html in danger title and message body', () => {
     const opts = buildProductConfirmOptions({
       kind: 'danger',
-      title: '删除产品',
+      title: '删 <b>除</b>',
       message: 'a <b>x</b> & "y"'
     })
+    assert.match(String(opts.message), /删 &lt;b&gt;除&lt;\/b&gt;/)
     assert.match(String(opts.message), /a &lt;b&gt;x&lt;\/b&gt; &amp; &quot;y&quot;/)
   })
 

+ 6 - 3
web-admin/src/utils/mp-crm-product-confirm.ts

@@ -18,9 +18,11 @@ function escapeHtml(text: string): string {
     .replace(/'/g, '&#39;')
 }
 
-function dangerMessageHtml(message: string): string {
+/** 方案 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>`
   )
 }
@@ -28,8 +30,9 @@ function dangerMessageHtml(message: string): string {
 export function buildProductConfirmOptions(input: BuildProductConfirmInput): DialogOptions {
   const isDanger = input.kind === 'danger'
   return {
-    title: input.title,
-    message: isDanger ? dangerMessageHtml(input.message) : input.message,
+    // 危险态用空 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 ? '确认删除' : '确认'),

+ 28 - 9
web-admin/src/views/m/crm/product/product.scss

@@ -291,19 +291,23 @@ body > .van-dropdown-item {
   z-index: 3000 !important;
 }
 
-/* ── 确认弹窗:方案 A / B(挂到 body,须用 className 限定)── */
+/* ── 确认弹窗:方案 A / B(挂到 body,须用 className 限定;对齐 Demo)── */
 body .van-dialog.mp-product-dialog {
   overflow: hidden;
+  width: 300px !important;
+  max-width: calc(100vw - 48px);
   border-radius: 16px;
   box-shadow: 0 16px 40px rgb(15 40 30 / 22%);
 }
 
+/* 方案 A:Vant header 居中 */
 body .van-dialog.mp-product-dialog .van-dialog__header {
-  padding-top: 20px;
+  padding: 20px 20px 0;
   font-size: 16px;
   font-weight: 700;
   color: #1f2a26;
   line-height: 1.4;
+  text-align: center;
 }
 
 body .van-dialog.mp-product-dialog .van-dialog__content {
@@ -311,17 +315,26 @@ body .van-dialog.mp-product-dialog .van-dialog__content {
 }
 
 body .van-dialog.mp-product-dialog .van-dialog__message {
-  padding: 10px 20px 4px;
+  padding: 10px 20px 20px;
   font-size: 14px;
   line-height: 1.55;
   color: #6b7b74;
   text-align: center;
 }
 
+/* 方案 B:图标 → 标题 → 文案(标题在 HTML 内,无 Vant header) */
+body .van-dialog.mp-product-dialog.mp-product-dialog--danger .van-dialog__header {
+  display: none;
+}
+
+body .van-dialog.mp-product-dialog.mp-product-dialog--danger .van-dialog__message {
+  padding: 20px 20px 20px;
+}
+
 body .van-dialog.mp-product-dialog .mp-product-dialog__warn {
   display: flex;
   justify-content: center;
-  margin: 4px 0 10px;
+  margin: 0 0 12px;
 }
 
 body .van-dialog.mp-product-dialog .mp-product-dialog__warn-icon {
@@ -332,11 +345,21 @@ body .van-dialog.mp-product-dialog .mp-product-dialog__warn-icon {
   height: 44px;
   font-size: 22px;
   font-weight: 700;
+  line-height: 1;
   color: #dc2626;
   background: #fef2f2;
   border-radius: 50%;
 }
 
+body .van-dialog.mp-product-dialog .mp-product-dialog__title {
+  margin: 0 0 8px;
+  font-size: 16px;
+  font-weight: 700;
+  line-height: 1.4;
+  color: #1f2a26;
+  text-align: center;
+}
+
 body .van-dialog.mp-product-dialog .mp-product-dialog__msg {
   color: #6b7b74;
   text-align: center;
@@ -345,7 +368,7 @@ body .van-dialog.mp-product-dialog .mp-product-dialog__msg {
 body .van-dialog.mp-product-dialog .van-dialog__footer {
   display: flex;
   gap: 10px;
-  padding: 16px;
+  padding: 0 16px 16px;
   border-top: none !important;
 }
 
@@ -384,7 +407,3 @@ body .van-dialog.mp-product-dialog.mp-product-dialog--danger .van-dialog__confir
   background: #dc2626 !important;
   border-color: #dc2626 !important;
 }
-
-body .van-dialog.mp-product-dialog.mp-product-dialog--danger .van-dialog__header {
-  padding-top: 12px;
-}