docs: add mp CRM product confirm dialog design (delete B, other A).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-10-18 18:07:45 +08:00
1 parent ef71e842ee
commit 3138666ae2
2 files changed
+524

No files matched your search

@@ -0,0 +1,443 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>山峡化工 · 产品管理 · 删除确认弹窗 Demo</title>
<style>
:root {
--brand: #0a9f6d;
--brand-dark: #087f57;
--brand-soft: #e9f9f2;
--bg: #f5f8f7;
--surface: #fff;
--text: #1f2a26;
--muted: #6b7b74;
--border: #dcebe4;
--danger: #dc2626;
--danger-soft: #fef2f2;
--header: linear-gradient(180deg, #087f57 0%, #087f57 28%, #0a9f6d 78%, #0a9f6d 100%);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
button { font: inherit; cursor: pointer; }
body {
background: #cfd9d4;
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
font-size: 14px;
line-height: 1.45;
min-height: 100vh;
}
.stage { max-width: 1100px; margin: 0 auto; padding: 16px 12px 48px; }
.banner {
background: #1f2a26; color: #fff; border-radius: 12px; padding: 12px 14px;
margin-bottom: 12px; font-size: 12px; line-height: 1.55;
}
.banner strong { color: #9fe3c4; }
.banner li { margin-left: 1.1em; margin-top: 4px; }
.mode-bar {
display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px;
}
.mode-bar button {
border: 1px solid #b7c9c0; background: #fff; border-radius: 10px;
padding: 10px 14px; font-size: 13px; font-weight: 600; color: var(--text);
}
.mode-bar button.active {
background: var(--brand-soft); border-color: #b7ebce; color: var(--brand-dark);
}
.phones {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
@media (min-width: 900px) {
.phones.side-by-side { grid-template-columns: 1fr 1fr; }
.phones.side-by-side-3 { grid-template-columns: 1fr 1fr 1fr; }
}
.phone-wrap { display: none; }
.phone-wrap.show { display: block; }
.phones.side-by-side .phone-wrap,
.phones.side-by-side-3 .phone-wrap { display: block; }
.phone-label {
font-size: 12px; font-weight: 700; color: #24312c; margin-bottom: 8px;
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.phone-label .pill {
font-size: 10px; font-weight: 600; padding: 2px 8px; border-radius: 999px;
}
.pill--old { background: #fee2e2; color: #b91c1c; }
.pill--rec { background: var(--brand-soft); color: var(--brand-dark); }
.pill--alt { background: #fff7e8; color: #a16207; }
.phone {
position: relative;
background: var(--bg);
border-radius: 28px;
overflow: hidden;
border: 8px solid #24312c;
height: 640px;
box-shadow: 0 18px 40px rgba(0,0,0,.16);
display: flex;
flex-direction: column;
}
.status-bar {
flex: 0 0 auto;
height: 28px; background: var(--brand-dark); color: rgba(255,255,255,.85);
display: flex; align-items: center; justify-content: space-between;
padding: 0 14px; font-size: 11px; font-weight: 600;
}
.header {
flex: 0 0 auto;
background: var(--header);
color: #fff;
padding: 10px 12px 14px;
border-bottom-left-radius: 22px;
border-bottom-right-radius: 22px;
}
.header-row {
display: flex; align-items: center; gap: 8px; min-height: 44px;
}
.header-row h1 { font-size: 17px; font-weight: 700; }
.header-back { font-size: 20px; line-height: 1; opacity: .95; }
.body {
flex: 1; overflow: hidden; padding: 12px 10px; position: relative;
}
.card {
background: #fff; border: 1px solid var(--border); border-radius: 14px;
padding: 12px; box-shadow: 0 2px 8px rgba(34,64,52,.04);
}
.card-title { font-size: 15px; font-weight: 700; margin-bottom: 6px; }
.card-meta { font-size: 12px; color: var(--muted); margin-bottom: 10px; }
.card-actions { display: flex; gap: 8px; }
.mini-btn {
flex: 1; height: 30px; border-radius: 8px; font-size: 13px; font-weight: 500;
border: 1px solid var(--border); background: #fff; color: var(--text);
}
.mini-btn.danger {
color: var(--danger); border-color: #fecaca;
}
.mask {
position: absolute; inset: 0;
background: rgba(15, 23, 20, .45);
display: flex; align-items: center; justify-content: center;
padding: 20px 18px;
}
/* ── 现状:丑(纵向堆叠、无层级)── */
.dlg-old {
width: 100%; max-width: 280px;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 4px;
overflow: hidden;
}
.dlg-old__title {
padding: 14px 14px 0;
font-size: 15px; font-weight: 600; text-align: left;
}
.dlg-old__msg {
padding: 10px 14px 16px;
font-size: 14px; color: #111; text-align: left; line-height: 1.5;
}
.dlg-old__btn {
display: block; width: 100%;
height: 44px; background: #fff; border: 0;
border-top: 1px solid #e5e7eb;
font-size: 15px; color: #111;
}
/* ── 方案 A:横排 + 三墙绿确认(推荐,对齐客户管理)── */
.dlg-a {
width: 100%; max-width: 300px;
background: #fff;
border-radius: 16px;
box-shadow: 0 16px 40px rgba(15, 40, 30, .22);
overflow: hidden;
}
.dlg-a__head {
padding: 20px 20px 4px;
text-align: center;
}
.dlg-a__title {
font-size: 16px; font-weight: 700; color: var(--text);
}
.dlg-a__msg {
padding: 10px 20px 20px;
font-size: 14px; color: var(--muted); text-align: center; line-height: 1.55;
}
.dlg-a__foot {
display: flex; gap: 10px;
padding: 0 16px 16px;
}
.dlg-a__btn {
flex: 1; height: 40px; border-radius: 10px;
font-size: 14px; font-weight: 600;
border: 1px solid var(--border); background: #fff; color: var(--text);
}
.dlg-a__btn.ok {
background: var(--brand); border-color: var(--brand); color: #fff;
}
/* ── 方案 B:横排 + 危险红确认(强调不可恢复)── */
.dlg-b {
width: 100%; max-width: 300px;
background: #fff;
border-radius: 16px;
box-shadow: 0 16px 40px rgba(15, 40, 30, .22);
overflow: hidden;
}
.dlg-b__icon {
width: 44px; height: 44px; margin: 20px auto 0;
border-radius: 50%; background: var(--danger-soft);
display: flex; align-items: center; justify-content: center;
color: var(--danger); font-size: 22px; font-weight: 700;
}
.dlg-b__title {
padding: 12px 20px 0;
font-size: 16px; font-weight: 700; color: var(--text); text-align: center;
}
.dlg-b__msg {
padding: 8px 20px 20px;
font-size: 14px; color: var(--muted); text-align: center; line-height: 1.55;
}
.dlg-b__foot {
display: flex; gap: 10px;
padding: 0 16px 16px;
}
.dlg-b__btn {
flex: 1; height: 40px; border-radius: 10px;
font-size: 14px; font-weight: 600;
border: 1px solid var(--border); background: #fff; color: var(--text);
}
.dlg-b__btn.ok {
background: var(--danger); border-color: var(--danger); color: #fff;
}
/* ── 方案 C:底栏毛发线横排(贴近原生微信,确认字用品牌绿)── */
.dlg-c {
width: 100%; max-width: 300px;
background: #fff;
border-radius: 14px;
overflow: hidden;
box-shadow: 0 12px 32px rgba(15, 40, 30, .18);
}
.dlg-c__title {
padding: 22px 20px 6px;
font-size: 17px; font-weight: 700; text-align: center; color: var(--text);
}
.dlg-c__msg {
padding: 0 22px 22px;
font-size: 14px; color: #4b5c55; text-align: center; line-height: 1.55;
}
.dlg-c__foot {
display: flex;
border-top: 1px solid #e8f0ec;
}
.dlg-c__btn {
flex: 1; height: 48px; border: 0; background: #fff;
font-size: 16px; font-weight: 500; color: var(--text);
}
.dlg-c__btn + .dlg-c__btn {
border-left: 1px solid #e8f0ec;
}
.dlg-c__btn.ok {
color: var(--brand); font-weight: 700;
}
.note {
margin-top: 14px; padding: 12px 14px; background: #fff; border-radius: 12px;
border: 1px solid #b7c9c0; font-size: 12px; color: #33443d; line-height: 1.6;
}
.note strong { color: var(--brand-dark); }
</style>
</head>
<body>
<div class="stage">
<div class="banner">
<strong>产品管理 · 删除确认弹窗美化 Demo</strong>
<ul>
<li>背景:当前 Vant <code>showConfirmDialog</code> 在 WebView 中呈现为纵向堆叠、无主次、圆角/间距差。</li>
<li>Token:三墙绿 <code>#0a9f6d</code> / <code>#087f57</code>;危险红 <code>#dc2626</code>;禁止 Fresh Mint <code>#10b981</code>。</li>
<li>推荐落地:方案 A(与客户管理 Dialog 确认色一致);删除场景也可选 B。</li>
</ul>
</div>
<div class="mode-bar">
<button type="button" class="active" data-mode="compare">三方案对照</button>
<button type="button" data-mode="old">仅现状</button>
<button type="button" data-mode="a">仅方案 A</button>
<button type="button" data-mode="b">仅方案 B</button>
<button type="button" data-mode="c">仅方案 C</button>
</div>
<div class="phones side-by-side-3" id="phones">
<!-- 现状 -->
<div class="phone-wrap show" data-id="old">
<div class="phone-label">现状 <span class="pill pill--old">丑</span></div>
<div class="phone">
<div class="status-bar"><span>9:41</span><span>产品管理</span></div>
<div class="header">
<div class="header-row">
<span class="header-back">‹</span>
<h1>产品管理</h1>
</div>
</div>
<div class="body">
<div class="card">
<div class="card-title">工业乙醇 95%</div>
<div class="card-meta">上架 · P20261018001</div>
<div class="card-actions">
<button class="mini-btn" type="button">下架</button>
<button class="mini-btn danger" type="button">删除</button>
</div>
</div>
<div class="mask">
<div class="dlg-old">
<div class="dlg-old__title">提示</div>
<div class="dlg-old__msg">确认删除该产品?删除后不可恢复。</div>
<button class="dlg-old__btn" type="button">取消</button>
<button class="dlg-old__btn" type="button">确认</button>
</div>
</div>
</div>
</div>
</div>
<!-- 方案 A -->
<div class="phone-wrap show" data-id="a">
<div class="phone-label">方案 A · 横排绿确认 <span class="pill pill--rec">推荐</span></div>
<div class="phone">
<div class="status-bar"><span>9:41</span><span>产品管理</span></div>
<div class="header">
<div class="header-row">
<span class="header-back">‹</span>
<h1>产品管理</h1>
</div>
</div>
<div class="body">
<div class="card">
<div class="card-title">工业乙醇 95%</div>
<div class="card-meta">上架 · P20261018001</div>
<div class="card-actions">
<button class="mini-btn" type="button">下架</button>
<button class="mini-btn danger" type="button">删除</button>
</div>
</div>
<div class="mask">
<div class="dlg-a">
<div class="dlg-a__head">
<div class="dlg-a__title">删除产品</div>
</div>
<div class="dlg-a__msg">确认删除该产品?删除后不可恢复。</div>
<div class="dlg-a__foot">
<button class="dlg-a__btn" type="button">取消</button>
<button class="dlg-a__btn ok" type="button">确认删除</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 方案 B -->
<div class="phone-wrap show" data-id="b">
<div class="phone-label">方案 B · 横排红确认 <span class="pill pill--alt">危险强调</span></div>
<div class="phone">
<div class="status-bar"><span>9:41</span><span>产品管理</span></div>
<div class="header">
<div class="header-row">
<span class="header-back">‹</span>
<h1>产品管理</h1>
</div>
</div>
<div class="body">
<div class="card">
<div class="card-title">工业乙醇 95%</div>
<div class="card-meta">上架 · P20261018001</div>
<div class="card-actions">
<button class="mini-btn" type="button">下架</button>
<button class="mini-btn danger" type="button">删除</button>
</div>
</div>
<div class="mask">
<div class="dlg-b">
<div class="dlg-b__icon">!</div>
<div class="dlg-b__title">删除产品</div>
<div class="dlg-b__msg">确认删除该产品?删除后不可恢复。</div>
<div class="dlg-b__foot">
<button class="dlg-b__btn" type="button">取消</button>
<button class="dlg-b__btn ok" type="button">确认删除</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 方案 C(对照时隐藏,点「仅方案 C」或可在 compare 时通过 JS 切换) -->
<div class="phone-wrap" data-id="c">
<div class="phone-label">方案 C · 原生毛发线横排 <span class="pill pill--alt">轻量</span></div>
<div class="phone">
<div class="status-bar"><span>9:41</span><span>产品管理</span></div>
<div class="header">
<div class="header-row">
<span class="header-back">‹</span>
<h1>产品管理</h1>
</div>
</div>
<div class="body">
<div class="card">
<div class="card-title">工业乙醇 95%</div>
<div class="card-meta">上架 · P20261018001</div>
<div class="card-actions">
<button class="mini-btn" type="button">下架</button>
<button class="mini-btn danger" type="button">删除</button>
</div>
</div>
<div class="mask">
<div class="dlg-c">
<div class="dlg-c__title">删除产品</div>
<div class="dlg-c__msg">确认删除该产品?删除后不可恢复。</div>
<div class="dlg-c__foot">
<button class="dlg-c__btn" type="button">取消</button>
<button class="dlg-c__btn ok" type="button">确认</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="note">
<strong>落地说明(待你选方案后写进 spec):</strong>
产品页 <code>web-admin/src/views/m/crm/product/*.vue</code> 仍用 Vant <code>showConfirmDialog</code>,
在 <code>product.scss</code> 覆盖 Dialog 圆角/内边距/横排按钮样式(对齐客户页
<code>body .van-dialog .van-button</code>),删除调用传 <code>confirmButtonText: '确认删除'</code>、
标题改为「删除产品」。方案 B 额外设 <code>confirmButtonColor: '#dc2626'</code> 或 danger class。
上架/下架确认可共用同一外壳,确认色用品牌绿。
</div>
</div>
<script>
const phones = document.getElementById('phones')
const wraps = [...document.querySelectorAll('.phone-wrap')]
const buttons = [...document.querySelectorAll('.mode-bar button')]
function setMode(mode) {
buttons.forEach((b) => b.classList.toggle('active', b.dataset.mode === mode))
phones.classList.remove('side-by-side', 'side-by-side-3')
wraps.forEach((w) => w.classList.remove('show'))
if (mode === 'compare') {
phones.classList.add('side-by-side-3')
wraps.filter((w) => ['old', 'a', 'b'].includes(w.dataset.id)).forEach((w) => w.classList.add('show'))
return
}
wraps.find((w) => w.dataset.id === mode)?.classList.add('show')
}
buttons.forEach((b) => b.addEventListener('click', () => setMode(b.dataset.mode)))
setMode('compare')
</script>
</body>
</html>
@@ -0,0 +1,81 @@
# 小程序 WebView · 产品管理确认弹窗美化
日期:2026-10-18
状态:已确认(删除 = 方案 B;其它确认 = 方案 A)
范围:`web-admin` 小程序 WebView 产品管理 `/m/crm/product*`
## 1. 目标与边界
### 1.1 目标
1. 美化产品管理中 Vant `showConfirmDialog` 的默认纵向堆叠、无主次样式。
2. **删除类确认**采用方案 B(危险红确认 + 警示图标感)。
3. **非删除确认**(上架 / 下架)采用方案 A(三墙绿确认)。
4. 文案与视觉对齐 Demo:`bak/mobile-ui/shanxia-mp-crm-product-delete-confirm-demo.html`。
### 1.2 范式权威
| 维度 | 权威 |
|------|------|
| 视觉 Token | 三墙绿 `#0a9f6d` / `#087f57` / `#e9f9f2`;危险红 `#dc2626`;禁止 Fresh Mint `#10b981` |
| 交互示意 | `bak/mobile-ui/shanxia-mp-crm-product-delete-confirm-demo.html` 方案 A / B |
| 客户页 Dialog 按钮覆盖先例 | `web-admin/src/views/m/crm/customer/customer.scss` 中 `body .van-dialog .van-button` |
### 1.3 不做
- 不改客户 / 报价 / 合同等其它 CRM 模块确认弹窗。
- 不改 PC 端 `crm/product`。
- 不更换组件库;继续使用 Vant `showConfirmDialog`。
- 不引入独立自定义 Dialog 组件(除非 Vant 样式无法达到 Demo 效果,再单开变更)。
## 2. 视觉规格
### 2.1 公共外壳(A / B 共用)
- 白底圆角卡片(约 14–16px)、居中标题与说明。
- 遮罩半透明深色;无 backdrop-blur。
- 底部**横排**两个按钮(取消 | 确认),等宽,圆角约 10px,高度约 40px。
- 禁止纵向堆叠「取消 / 确认」。
### 2.2 方案 A(非删除)
- 确认按钮:三墙绿实心(`#0a9f6d`)+ 白字。
- 取消按钮:白底 + `#dcebe4` 描边 + 深色字。
- 无危险图标。
### 2.3 方案 B(删除)
- 标题上方或标题区呈现危险警示视觉(圆形浅红底 +「!」或等价警示符号,对齐 Demo 方案 B)。
- 确认按钮:危险红实心(`#dc2626`)+ 白字;文案为「确认删除」。
- 取消按钮:同方案 A。
## 3. 调用点与文案
| 文件 | 场景 | 方案 | 标题 | 确认文案 |
|------|------|------|------|----------|
| `product/index.vue` | 删除产品 | B | 删除产品 | 确认删除 |
| `product/index.vue` | 上架 / 下架 | A | 上架产品 / 下架产品 | 确认 |
| `product/detail.vue` | 删除产品 | B | 删除产品 | 确认删除 |
| `product/detail.vue` | 上架 / 下架 | A | 上架产品 / 下架产品 | 确认 |
| `product/categories.vue` | 删除分类 | B | 删除分类 | 确认删除 |
说明文案保持现有语义(删除含「不可恢复」;上下架为「确认上架/下架该产品?」)。
## 4. 实现要点
说明:Vant 默认 `theme` 为横排文字按钮;`theme: 'round-button'` 会纵向堆叠(正是现状丑态,**禁止使用**)。`confirmButtonColor` 只改文字色,**不能单独达到 Demo 实心按钮**。落地必须以 CSS 覆盖 footer 为「内边距 + 横排实心键」。
1. **样式入口**:在 `product.scss` 写 `body .van-dialog.mp-product-dialog`(及 `--danger` 修饰),只影响产品管理传入 `className` 的 Dialog,避免误伤客户等其它页。
2. **公共外壳 CSS**:圆角约 16px、标题/正文居中、footer 去毛发线全宽拉伸,改为 `padding + gap` 横排两键(高约 40px、圆角约 10px),对齐 Demo A/B。
3. **方案 A 调用**:`className: 'mp-product-dialog'`;确认键 CSS 实心 `#0a9f6d`;可选 `confirmButtonColor: '#0a9f6d'` 作兜底。
4. **方案 B 调用**:`className: 'mp-product-dialog mp-product-dialog--danger'`;确认键 CSS 实心 `#dc2626`;`confirmButtonText: '确认删除'`;`confirmButtonColor: '#dc2626'` 作兜底。
5. **警示图标**:删除 Dialog 用 `allowHtml: true`,在 `message` 内拼一段含圆形「!」图标 + 说明文字的 HTML(结构对齐 Demo B);非删除不传 HTML。
6. **回归**:对照 Demo;列表/详情/分类删除为 B;列表/详情上下架为 A;确认无纵向堆叠。
## 5. 验收标准
1. 删除确认:横排按钮;确认键红色;有明显危险警示;文案为「删除产品/分类」+「确认删除」。
2. 上架/下架确认:横排按钮;确认键三墙绿;无红色危险样式。
3. 取消或点遮罩关闭不执行业务(保持 Vant 现有 reject 行为)。
4. 不出现纵向堆叠的丑默认态(对照截图与 Demo「现状」)。
5. 色值不混用 Fresh Mint `#10b981`。