Files
zkxw/mobile-ui/zkxw-mobile-wechat-notify-menu-demo.html
T
zlwuandCursor 26fe6182df docs: design WeChat notify menu entry consolidating follow and bind
Capture the approved UX for a single「开启微信通知」menu after 抄送我的, with step 1/2 on one page and removal of login/home header entry points.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-14 09:39:30 +08:00

773 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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, viewport-fit=cover" />
<title>中科兴皖 · 微信审批通知菜单 Demo</title>
<style>
:root {
--brand-green: #0a9f6d;
--brand-green-dark: #087f57;
--brand-green-soft: #e9f9f2;
--barley-yellow: #d6a343;
--barley-yellow-soft: #fff7e8;
--bg-page: #f5f8f7;
--surface: #ffffff;
--text-primary: #1f2a26;
--text-muted: #6b7b74;
--border: #dcebe4;
--danger: #d14343;
--warning: #cf8a19;
--success: #189b65;
--radius-card: 14px;
--radius-lg: 20px;
--shadow-soft: 0 4px 16px rgba(8, 127, 87, 0.08);
}
* { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
min-height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", sans-serif;
color: var(--text-primary);
background:
radial-gradient(900px 420px at 10% -10%, rgba(10, 159, 109, 0.14), transparent 55%),
radial-gradient(700px 380px at 90% 0%, rgba(214, 163, 67, 0.12), transparent 50%),
linear-gradient(180deg, #eef6f2 0%, #f7faf9 40%, #f3f6f5 100%);
}
body {
display: flex;
justify-content: center;
padding: 24px 16px 40px;
}
#app {
width: 100%;
max-width: 980px;
display: grid;
grid-template-columns: 280px 1fr;
gap: 24px;
align-items: start;
}
@media (max-width: 860px) {
#app { grid-template-columns: 1fr; }
.doc-panel { position: static; }
}
.doc-panel {
position: sticky;
top: 24px;
background: rgba(255, 255, 255, 0.92);
border: 1px solid var(--border);
border-radius: 18px;
padding: 20px 18px;
box-shadow: var(--shadow-soft);
}
.doc-panel h1 {
margin: 0 0 6px;
font-size: 20px;
line-height: 1.3;
color: var(--brand-green-dark);
}
.doc-panel .sub {
margin: 0 0 16px;
font-size: 12px;
color: var(--text-muted);
line-height: 1.5;
}
.section-title {
margin: 16px 0 8px;
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
letter-spacing: 0.04em;
text-transform: uppercase;
}
.screen-nav {
display: flex;
flex-direction: column;
gap: 8px;
}
.screen-nav button {
appearance: none;
border: 1px solid var(--border);
background: #fff;
border-radius: 10px;
padding: 10px 12px;
text-align: left;
font-size: 13px;
color: var(--text-primary);
cursor: pointer;
}
.screen-nav button.is-on {
border-color: var(--brand-green);
background: var(--brand-green-soft);
color: var(--brand-green-dark);
font-weight: 600;
}
.note {
margin-top: 14px;
padding: 10px 12px;
border-radius: 10px;
background: var(--barley-yellow-soft);
border: 1px solid #f0dfb4;
font-size: 12px;
line-height: 1.55;
color: #7a5a12;
}
.stage {
display: flex;
justify-content: center;
}
.phone {
width: 375px;
max-width: 100%;
background: var(--bg-page);
border-radius: 28px;
border: 1px solid var(--border);
box-shadow: var(--shadow-soft);
overflow: hidden;
min-height: 720px;
display: flex;
flex-direction: column;
}
.status-bar {
height: 28px;
background: linear-gradient(180deg, #0a9f6d 0%, #087f57 100%);
}
.screen { display: none; flex: 1; flex-direction: column; }
.screen.is-on { display: flex; }
/* —— 首页 —— */
.home-header {
background: linear-gradient(180deg, #0a9f6d 0%, #087f57 100%);
color: #fff;
padding: 8px 16px 22px;
border-radius: 0 0 22px 22px;
}
.brand { font-size: 13px; opacity: 0.9; }
.hello { margin-top: 10px; font-size: 22px; font-weight: 700; }
.meta {
margin-top: 8px;
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12px;
font-size: 12px;
opacity: 0.92;
}
.meta__right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 6px;
flex-shrink: 0;
}
.home-logout {
text-decoration: underline;
text-underline-offset: 2px;
cursor: pointer;
opacity: 0.9;
}
.home-mp-legacy {
font-size: 11px;
text-decoration: underline;
opacity: 0.75;
cursor: pointer;
}
.home-mp-legacy.is-hidden { display: none; }
.home-body { padding: 14px 12px 24px; flex: 1; }
.panel {
background: var(--surface);
border-radius: var(--radius-card);
border: 1px solid var(--border);
padding: 14px 12px 8px;
}
.panel-hd {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 12px;
padding: 0 4px;
}
.panel-title { font-size: 15px; font-weight: 700; }
.hint { font-size: 12px; color: var(--text-muted); }
.menu-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px 4px;
}
.menu-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: 8px 4px;
border: 0;
background: transparent;
cursor: pointer;
border-radius: 10px;
}
.menu-item:hover { background: var(--brand-green-soft); }
.menu-item.is-new .menu-icon {
outline: 2px solid var(--barley-yellow);
outline-offset: 2px;
}
.menu-icon {
width: 44px;
height: 44px;
border-radius: 12px;
display: grid;
place-items: center;
font-size: 18px;
color: #fff;
font-weight: 700;
overflow: hidden;
}
.menu-icon img {
width: 44px;
height: 44px;
object-fit: contain;
display: block;
}
.menu-icon.is-plain {
background: transparent !important;
border-radius: 0;
}
.menu-title {
font-size: 11px;
line-height: 1.3;
text-align: center;
color: var(--text-primary);
}
.c1 { background: #0a9f6d; }
.c2 { background: #2f8f7a; }
.c3 { background: #3d8bb8; }
.c4 { background: #5b7fbf; }
.c5 { background: #7a6bb8; }
.c6 { background: #cf8a19; }
.c7 { background: transparent; }
/* —— 通知设置页 —— */
.nav {
background: linear-gradient(180deg, #0a9f6d 0%, #087f57 100%);
color: #fff;
padding: 10px 12px 14px;
display: flex;
align-items: center;
gap: 8px;
}
.nav__back {
width: 36px;
height: 36px;
border: 0;
background: rgba(255,255,255,0.16);
color: #fff;
border-radius: 10px;
cursor: pointer;
font-size: 18px;
}
.nav__title { font-size: 17px; font-weight: 700; }
.notify-body { padding: 14px 12px 28px; flex: 1; }
.status-banner {
display: none;
}
.step-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-card);
padding: 14px;
margin-bottom: 10px;
}
.step-card.is-done {
border-color: #b7ebcf;
background: linear-gradient(180deg, #f6fffb 0%, #fff 60%);
}
.step-hd {
display: flex;
align-items: flex-start;
gap: 10px;
}
.step-num {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--brand-green);
color: #fff;
display: grid;
place-items: center;
font-size: 14px;
font-weight: 700;
flex-shrink: 0;
}
.step-card.is-done .step-num {
background: var(--success);
}
.step-title {
font-size: 15px;
font-weight: 700;
line-height: 1.35;
}
.step-desc {
margin-top: 4px;
font-size: 12px;
color: var(--text-muted);
line-height: 1.5;
}
.step-badge {
margin-left: auto;
font-size: 11px;
padding: 2px 8px;
border-radius: 999px;
background: #eef2f0;
color: var(--text-muted);
flex-shrink: 0;
}
.step-badge.is-done {
background: var(--brand-green-soft);
color: var(--brand-green-dark);
}
.step-actions { margin-top: 12px; padding-left: 38px; }
.btn {
appearance: none;
border: 0;
border-radius: 10px;
padding: 10px 14px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
min-height: 40px;
}
.btn-primary {
background: var(--brand-green);
color: #fff;
width: 100%;
}
.btn-primary:disabled {
background: #a7d7c4;
cursor: default;
}
.btn-ghost {
background: #fff;
color: var(--brand-green-dark);
border: 1px solid var(--border);
width: 100%;
}
.qr-box {
margin-top: 10px;
display: none;
padding: 12px;
border-radius: 12px;
border: 1px dashed #a7f3d0;
background: #f8fffb;
text-align: center;
}
.qr-box.is-on { display: block; }
.qr-fake {
width: 140px;
height: 140px;
margin: 0 auto 8px;
border-radius: 8px;
background:
repeating-linear-gradient(0deg, #1f2a26 0 6px, transparent 6px 12px),
repeating-linear-gradient(90deg, #1f2a26 0 6px, transparent 6px 12px);
opacity: 0.18;
}
.qr-tip { font-size: 12px; color: var(--text-muted); }
/* —— 登录页(对照:入口已移除) —— */
.login-page {
padding: 36px 20px 28px;
flex: 1;
background: linear-gradient(180deg, #e9f9f2 0%, #f5f8f7 42%, #f5f8f7 100%);
}
.login-brand { text-align: center; margin-bottom: 22px; }
.login-logo {
width: 72px;
height: 72px;
margin: 0 auto 10px;
border-radius: 18px;
background: var(--brand-green);
color: #fff;
display: grid;
place-items: center;
font-size: 28px;
font-weight: 800;
}
.login-sub { font-size: 13px; color: var(--text-muted); }
.login-card {
background: #fff;
border-radius: 16px;
border: 1px solid var(--border);
padding: 16px;
box-shadow: var(--shadow-soft);
}
.field { margin-bottom: 12px; }
.field label {
display: block;
font-size: 12px;
color: var(--text-muted);
margin-bottom: 6px;
}
.field .box {
height: 42px;
border: 1px solid var(--border);
border-radius: 10px;
background: #fafcfb;
padding: 0 12px;
display: flex;
align-items: center;
color: #9aaba3;
font-size: 14px;
}
.login-submit {
margin-top: 8px;
height: 44px;
border-radius: 10px;
background: var(--brand-green);
color: #fff;
display: grid;
place-items: center;
font-weight: 700;
}
.login-follow-removed {
margin-top: 16px;
text-align: center;
font-size: 12px;
color: var(--text-muted);
text-decoration: line-through;
opacity: 0.55;
}
.login-follow-note {
margin-top: 6px;
text-align: center;
font-size: 11px;
color: var(--warning);
}
.toast {
position: fixed;
left: 50%;
bottom: 48px;
transform: translateX(-50%);
background: rgba(31, 42, 38, 0.88);
color: #fff;
padding: 10px 16px;
border-radius: 10px;
font-size: 13px;
opacity: 0;
pointer-events: none;
transition: opacity .2s;
z-index: 20;
}
.toast.is-on { opacity: 1; }
</style>
</head>
<body>
<div id="app">
<aside class="doc-panel">
<h1>微信审批通知 · 菜单方案</h1>
<p class="sub">
在「抄送我的」后新增「开启微信通知」菜单(铃铛图标),
把「关注服务号」与「账号绑定」合并为步骤 1 / 2。
<strong>登录页「关注服务号」与首页顶栏入口均不展示</strong>,只保留新菜单。
</p>
<div class="section-title">页面</div>
<div class="screen-nav" id="nav">
<button type="button" data-screen="home" class="is-on">① 首页(新菜单)</button>
<button type="button" data-screen="notify">② 开启微信通知(1 / 2)</button>
<button type="button" data-screen="login">③ 登录页(入口移除示意)</button>
</div>
<div class="section-title">模拟状态</div>
<div class="screen-nav">
<button type="button" id="resetState">重置为未开启</button>
<button type="button" id="markFollowed">仅完成步骤 1</button>
<button type="button" id="markBound">完成步骤 1+2</button>
</div>
<div class="note">
交互说明:首页点「开启微信通知」进入设置页。<br />
图标:mobile-h5/static/menu-icons/oa-wechat-notify.png<br />
步骤 1:模拟关注(可弹二维码)。<br />
步骤 2:未关注时禁用;关注后可点「去绑定」。
</div>
</aside>
<main class="stage">
<div class="phone">
<div class="status-bar"></div>
<!-- 首页 -->
<section class="screen is-on" data-screen="home">
<div class="home-header">
<div class="brand">中科兴皖 · 移动工作台</div>
<div class="hello">您好,张三</div>
<div class="meta">
<div>产品经理 · 研发中心</div>
<div class="meta__right">
<span class="home-mp-legacy is-hidden" id="legacyHomeEntry">开启微信审批通知</span>
<span class="home-logout">退出登录</span>
</div>
</div>
</div>
<div class="home-body">
<div class="panel">
<div class="panel-hd">
<span class="panel-title">功能菜单</span>
<span class="hint" id="menusHint">共 7 项</span>
</div>
<div class="menu-grid" id="menuGrid"></div>
</div>
</div>
</section>
<!-- 通知设置 -->
<section class="screen" data-screen="notify">
<div class="nav">
<button type="button" class="nav__back" id="backHome" aria-label="返回">‹</button>
<div class="nav__title">开启微信通知</div>
</div>
<div class="notify-body">
<div class="step-card" id="step1Card">
<div class="step-hd">
<div class="step-num">1</div>
<div style="flex:1;min-width:0">
<div class="step-title">关注服务号</div>
<div class="step-desc">关注后才能接收微信模板消息。可跳转服务号主页,或长按识别二维码。</div>
</div>
<span class="step-badge" id="step1Badge">未完成</span>
</div>
<div class="step-actions">
<button type="button" class="btn btn-primary" id="btnFollow">去关注</button>
<div class="qr-box" id="qrBox">
<div class="qr-fake" aria-hidden="true"></div>
<div class="qr-tip">长按识别关注(Demo 占位图)</div>
<button type="button" class="btn btn-ghost" id="btnFollowDone" style="margin-top:10px">我已关注</button>
</div>
</div>
</div>
<div class="step-card" id="step2Card">
<div class="step-hd">
<div class="step-num">2</div>
<div style="flex:1;min-width:0">
<div class="step-title">绑定当前账号</div>
<div class="step-desc">将服务号身份与系统账号关联,审批待办 / 抄送才能推送到你。</div>
</div>
<span class="step-badge" id="step2Badge">未完成</span>
</div>
<div class="step-actions">
<button type="button" class="btn btn-primary" id="btnBind">去绑定</button>
</div>
</div>
</div>
</section>
<!-- 登录页对照 -->
<section class="screen" data-screen="login">
<div class="login-page">
<div class="login-brand">
<div class="login-logo">皖</div>
<div class="login-sub">员工工作台登录</div>
</div>
<div class="login-card">
<div class="field">
<label>账号</label>
<div class="box">请输入用户名 / 工号</div>
</div>
<div class="field">
<label>密码</label>
<div class="box">请输入密码</div>
</div>
<div class="login-submit">登录</div>
</div>
<div class="login-follow-note" style="margin-top:16px">登录页不再展示「关注服务号」入口</div>
</div>
</section>
</div>
</main>
</div>
<div class="toast" id="toast"></div>
<script>
const MENUS = [
{ title: '发起审批', cls: 'c1', icon: '发' },
{ title: '草稿箱', cls: 'c2', icon: '草' },
{ title: '待办中心', cls: 'c3', icon: '待' },
{ title: '已办中心', cls: 'c4', icon: '已' },
{ title: '我发起的', cls: 'c5', icon: '我' },
{ title: '抄送我的', cls: 'c6', icon: '抄' },
{
title: '开启微信通知',
cls: 'c7',
iconImg: './oa-wechat-notify.png',
isNew: true,
openNotify: true,
plain: true
}
]
const state = {
followed: false,
bound: false,
showQr: false
}
const els = {
nav: document.getElementById('nav'),
screens: document.querySelectorAll('.screen'),
menuGrid: document.getElementById('menuGrid'),
menusHint: document.getElementById('menusHint'),
step1Card: document.getElementById('step1Card'),
step2Card: document.getElementById('step2Card'),
step1Badge: document.getElementById('step1Badge'),
step2Badge: document.getElementById('step2Badge'),
btnFollow: document.getElementById('btnFollow'),
btnBind: document.getElementById('btnBind'),
qrBox: document.getElementById('qrBox'),
toast: document.getElementById('toast')
}
function showToast(msg) {
els.toast.textContent = msg
els.toast.classList.add('is-on')
clearTimeout(showToast._t)
showToast._t = setTimeout(() => els.toast.classList.remove('is-on'), 1600)
}
function showScreen(name) {
els.screens.forEach((s) => s.classList.toggle('is-on', s.dataset.screen === name))
els.nav.querySelectorAll('button').forEach((b) => {
b.classList.toggle('is-on', b.dataset.screen === name)
})
}
function renderMenus() {
els.menusHint.textContent = '共 ' + MENUS.length + ' 项'
els.menuGrid.innerHTML = MENUS.map((m) => {
const extra = m.isNew ? ' is-new' : ''
const iconCls = 'menu-icon ' + m.cls + (m.plain ? ' is-plain' : '')
const iconInner = m.iconImg
? '<img src="' + m.iconImg + '" alt="" />'
: m.icon
return (
'<button type="button" class="menu-item' + extra + '" data-open="' + (m.openNotify ? '1' : '0') + '">' +
'<span class="' + iconCls + '">' + iconInner + '</span>' +
'<span class="menu-title">' + m.title + '</span>' +
'</button>'
)
}).join('')
}
function renderNotify() {
els.step1Card.classList.toggle('is-done', state.followed)
els.step2Card.classList.toggle('is-done', state.bound)
els.step1Badge.textContent = state.followed ? '已完成' : '未完成'
els.step2Badge.textContent = state.bound ? '已完成' : '未完成'
els.step1Badge.classList.toggle('is-done', state.followed)
els.step2Badge.classList.toggle('is-done', state.bound)
els.btnFollow.textContent = state.followed ? '重新打开关注页' : '去关注'
els.qrBox.classList.toggle('is-on', state.showQr && !state.followed)
els.btnBind.disabled = !state.followed || state.bound
els.btnBind.textContent = state.bound ? '已绑定' : (state.followed ? '去绑定' : '请先完成步骤 1')
}
els.nav.addEventListener('click', (e) => {
const btn = e.target.closest('button[data-screen]')
if (!btn) return
showScreen(btn.dataset.screen)
})
document.getElementById('backHome').addEventListener('click', () => showScreen('home'))
els.menuGrid.addEventListener('click', (e) => {
const item = e.target.closest('.menu-item')
if (!item) return
if (item.dataset.open === '1') {
showScreen('notify')
return
}
showToast(item.querySelector('.menu-title').textContent)
})
els.btnFollow.addEventListener('click', () => {
state.showQr = true
renderNotify()
showToast('Demo:打开服务号主页 / 二维码')
})
document.getElementById('btnFollowDone').addEventListener('click', () => {
state.followed = true
state.showQr = false
renderNotify()
showToast('步骤 1 已完成')
})
els.btnBind.addEventListener('click', () => {
if (!state.followed || state.bound) return
state.bound = true
renderNotify()
showToast('步骤 2 已完成 · 通知已开启')
})
document.getElementById('resetState').addEventListener('click', () => {
state.followed = false
state.bound = false
state.showQr = false
renderNotify()
showScreen('notify')
showToast('已重置')
})
document.getElementById('markFollowed').addEventListener('click', () => {
state.followed = true
state.bound = false
state.showQr = false
renderNotify()
showScreen('notify')
})
document.getElementById('markBound').addEventListener('click', () => {
state.followed = true
state.bound = true
state.showQr = false
renderNotify()
showScreen('notify')
})
renderMenus()
renderNotify()
</script>
</body>
</html>