feat(mobile-h5): add WeChat notify setup page with follow and bind steps

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-08-14 12:15:47 +08:00
1 parent b79ff2d830
commit df42730cc8
2 files changed
+310

No files matched your search

+8
View File
@@ -72,6 +72,14 @@
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/oa/wechat-notify/index",
"style": {
"navigationBarTitleText": "开启微信通知",
"navigationBarBackgroundColor": "#ecfdf5",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/oa/pdf-preview/index",
"style": {
+302
View File
@@ -0,0 +1,302 @@
<template>
<view class="notify-page">
<view class="notify-body">
<!-- 步骤 1:关注服务号 -->
<view class="step-card" :class="{ 'is-done': step1Done }">
<view class="step-hd">
<view class="step-num">1</view>
<view class="step-main">
<view class="step-title">关注服务号</view>
<view class="step-desc">
关注后才能接收微信模板消息。可跳转服务号主页,或长按识别二维码。
</view>
</view>
<text class="step-badge" :class="{ 'is-done': step1Done }">
{{ step1Done ? '已完成' : '未完成' }}
</text>
</view>
<view class="step-actions">
<button class="btn btn-primary" @click="onFollow">
{{ step1Done ? '重新打开关注页' : '去关注' }}
</button>
<view v-if="showQr && !step1Done" class="qr-box">
<image
class="qr-img"
:src="mpQrcodeUrl"
mode="aspectFit"
show-menu-by-longpress
/>
<text class="qr-tip">长按识别关注</text>
<button class="btn btn-ghost" @click="onConfirmFollowed">我已关注</button>
</view>
</view>
</view>
<!-- 步骤 2:绑定账号 -->
<view class="step-card" :class="{ 'is-done': step2Done }">
<view class="step-hd">
<view class="step-num">2</view>
<view class="step-main">
<view class="step-title">绑定当前账号</view>
<view class="step-desc">
将服务号身份与系统账号关联,审批待办 / 抄送才能推送到你。
</view>
</view>
<text class="step-badge" :class="{ 'is-done': step2Done }">
{{ step2Done ? '已完成' : '未完成' }}
</text>
</view>
<view class="step-actions">
<button
class="btn btn-primary"
:disabled="!step1Done || step2Done"
@click="onBind"
>
{{
step2Done
? '已绑定'
: step1Done
? '去绑定'
: '请先完成步骤 1'
}}
</button>
</view>
</view>
</view>
</view>
</template>
<script>
import {
fetchMpBindStatus,
getUser,
forceRelogin,
getToken
} from '../../../utils/auth.js'
import {
isFollowConfirmed,
setFollowConfirmed,
resolveStepDone
} from '../../../utils/mp-follow-confirm.js'
import { MP_GH_USERNAME, MP_QRCODE_URL } from '../../../utils/config.js'
function currentUserId() {
const u = getUser() || {}
return u.id || u.userId || u.username || ''
}
export default {
data() {
return {
bound: false,
followConfirmed: false,
showQr: false,
mpQrcodeUrl: MP_QRCODE_URL,
step1Done: false,
step2Done: false
}
},
onShow() {
if (!getToken()) {
forceRelogin('请先登录')
return
}
this.followConfirmed = isFollowConfirmed(currentUserId())
this.refreshBind()
},
methods: {
applySteps() {
const r = resolveStepDone({
bound: this.bound,
followConfirmed: this.followConfirmed
})
this.step1Done = r.step1Done
this.step2Done = r.step2Done
},
async refreshBind() {
try {
const res = await fetchMpBindStatus()
this.bound = !!(res && res.bound)
} catch (_) {
this.bound = false
}
this.applySteps()
},
onFollow() {
const username = (MP_GH_USERNAME || '').trim()
const openProfile =
typeof wx !== 'undefined' && typeof wx.openOfficialAccountProfile === 'function'
if (openProfile && username) {
wx.openOfficialAccountProfile({
username,
fail: () => {
this.showQr = true
}
})
return
}
this.showQr = true
},
onConfirmFollowed() {
setFollowConfirmed(currentUserId(), true)
this.followConfirmed = true
this.showQr = false
this.applySteps()
uni.showToast({ title: '已标记关注', icon: 'none' })
},
onBind() {
if (!this.step1Done || this.step2Done) return
uni.navigateTo({ url: '/pages/oa/mp-bind/index' })
}
}
}
</script>
<style lang="scss" scoped>
.notify-page {
min-height: 100vh;
background: #f5f8f7;
box-sizing: border-box;
}
.notify-body {
padding: 28rpx 24rpx 56rpx;
}
.step-card {
background: #fff;
border: 1px solid #dcebe4;
border-radius: 28rpx;
padding: 28rpx;
margin-bottom: 20rpx;
&.is-done {
border-color: #b7ebcf;
background: linear-gradient(180deg, #f6fffb 0%, #fff 60%);
}
}
.step-hd {
display: flex;
align-items: flex-start;
gap: 20rpx;
}
.step-num {
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background: #0a9f6d;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: 700;
flex-shrink: 0;
}
.step-card.is-done .step-num {
background: #189b65;
}
.step-main {
flex: 1;
min-width: 0;
}
.step-title {
font-size: 30rpx;
font-weight: 700;
line-height: 1.35;
color: #1f2a26;
}
.step-desc {
margin-top: 8rpx;
font-size: 24rpx;
color: #6b7b74;
line-height: 1.5;
}
.step-badge {
margin-left: auto;
font-size: 22rpx;
padding: 4rpx 16rpx;
border-radius: 999rpx;
background: #eef2f0;
color: #6b7b74;
flex-shrink: 0;
line-height: 1.4;
&.is-done {
background: #e9f9f2;
color: #087f57;
}
}
.step-actions {
margin-top: 24rpx;
padding-left: 76rpx;
}
.btn {
appearance: none;
border: 0;
border-radius: 20rpx;
padding: 20rpx 28rpx;
font-size: 28rpx;
font-weight: 600;
min-height: 80rpx;
line-height: 1.3;
box-sizing: border-box;
&::after {
border: none;
}
}
.btn-primary {
background: #0a9f6d;
color: #fff;
width: 100%;
&[disabled] {
background: #a7d7c4;
color: #fff;
}
}
.btn-ghost {
margin-top: 20rpx;
background: #fff;
color: #087f57;
border: 1px solid #dcebe4;
width: 100%;
}
.qr-box {
margin-top: 20rpx;
padding: 24rpx;
border-radius: 24rpx;
border: 1px dashed #a7f3d0;
background: #f8fffb;
text-align: center;
}
.qr-img {
display: block;
width: 280rpx;
height: 280rpx;
margin: 0 auto;
border-radius: 16rpx;
background: #fff;
}
.qr-tip {
display: block;
margin-top: 16rpx;
font-size: 24rpx;
color: #6b7b74;
}
</style>