From bdf0515f2a1ddb9701bc6c6e119567218f765ec9 Mon Sep 17 00:00:00 2001 From: weijuesen Date: Wed, 12 Aug 2026 17:29:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(miniapp):=20=E6=B6=88=E6=81=AF=E8=AE=A2?= =?UTF-8?q?=E9=98=85=E8=AE=BE=E7=BD=AE=E9=A1=B5=EF=BC=88=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=20ID=20=E5=8D=A0=E4=BD=8D=EF=BC=8C#11=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- miniapp/src/api/wechat.ts | 18 +++ miniapp/src/app.config.ts | 1 + .../src/pages/notification/index.config.ts | 3 + .../src/pages/notification/index.module.scss | 95 +++++++++++++ miniapp/src/pages/notification/index.tsx | 126 ++++++++++++++++++ miniapp/src/pages/settings/index.tsx | 8 ++ 6 files changed, 251 insertions(+) create mode 100644 miniapp/src/api/wechat.ts create mode 100644 miniapp/src/pages/notification/index.config.ts create mode 100644 miniapp/src/pages/notification/index.module.scss create mode 100644 miniapp/src/pages/notification/index.tsx diff --git a/miniapp/src/api/wechat.ts b/miniapp/src/api/wechat.ts new file mode 100644 index 0000000..4bbfee4 --- /dev/null +++ b/miniapp/src/api/wechat.ts @@ -0,0 +1,18 @@ +import { get, post } from './request'; + +// 订阅消息模板 ID 占位:申请到模板后替换为真实 ID +export const SUBSCRIBE_TEMPLATES: { key: string; label: string; id: string }[] = [ + { key: 'inspection', label: '巡检风险提醒', id: '' }, + { key: 'alarm', label: '告警通知', id: '' }, +]; + +export interface WechatBinding { + bound: boolean; + openId?: string; + authorized?: string[]; +} + +export const bindWechat = (code: string) => post<{ bound: boolean; openId: string }>('/wechat/bind', { code }); +export const getWechatBinding = () => get('/wechat/binding'); +export const updateWechatSubscribe = (template: string, authorized: boolean) => + post<{ authorized: string[] }>('/wechat/subscribe', { template, authorized }); diff --git a/miniapp/src/app.config.ts b/miniapp/src/app.config.ts index 09d618f..0c82a2a 100644 --- a/miniapp/src/app.config.ts +++ b/miniapp/src/app.config.ts @@ -14,6 +14,7 @@ export default defineAppConfig({ 'pages/knowledge/index', 'pages/knowledge/detail/index', 'pages/inspection/index', + 'pages/notification/index', ], window: { backgroundTextStyle: 'dark', diff --git a/miniapp/src/pages/notification/index.config.ts b/miniapp/src/pages/notification/index.config.ts new file mode 100644 index 0000000..f3c98ad --- /dev/null +++ b/miniapp/src/pages/notification/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '消息订阅', +}); diff --git a/miniapp/src/pages/notification/index.module.scss b/miniapp/src/pages/notification/index.module.scss new file mode 100644 index 0000000..bf0af76 --- /dev/null +++ b/miniapp/src/pages/notification/index.module.scss @@ -0,0 +1,95 @@ +@use '@/styles/variables.scss' as *; + +.page { + min-height: 100vh; + background: $color-bg-page; + padding: $spacing-md $spacing-lg; + box-sizing: border-box; +} + +.card { + background: $color-bg-card; + border-radius: $radius-lg; + padding: $spacing-lg; + box-shadow: $shadow-card; + margin-bottom: $spacing-md; +} + +.cardTitle { + display: block; + font-size: $font-size-lg; + font-weight: $font-weight-semibold; + color: $color-text-primary; + margin-bottom: $spacing-sm; +} + +.cardDesc { + display: block; + font-size: $font-size-sm; + color: $color-text-secondary; + line-height: $line-height-normal; + margin-bottom: $spacing-md; +} + +.statusRow { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: $spacing-sm; +} + +.statusLabel { + font-size: $font-size-md; + color: $color-text-primary; +} + +.statusOk { + font-size: $font-size-sm; + color: $color-success; +} + +.statusNo { + font-size: $font-size-sm; + color: $color-text-tertiary; +} + +.openId { + display: block; + font-size: $font-size-xs; + color: $color-text-tertiary; + margin-bottom: $spacing-sm; + word-break: break-all; +} + +.btn { + margin: $spacing-sm 0 0; + @include button-reset; + background: $color-primary; + color: $color-text-white; + border-radius: $radius-button; + height: $button-height-md; + font-size: $font-size-md; +} + +.tplRow { + display: flex; + justify-content: space-between; + align-items: center; + padding: $spacing-sm 0; + border-top: 1rpx solid rgba(0, 0, 0, 0.06); +} + +.tplLabel { + font-size: $font-size-md; + color: $color-text-primary; +} + +.tplReady { + font-size: $font-size-xs; + color: $color-success; +} + +.tplPending { + font-size: $font-size-xs; + color: $color-warning; +} diff --git a/miniapp/src/pages/notification/index.tsx b/miniapp/src/pages/notification/index.tsx new file mode 100644 index 0000000..272182d --- /dev/null +++ b/miniapp/src/pages/notification/index.tsx @@ -0,0 +1,126 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { Button, Text, View } from '@tarojs/components'; +import Taro from '@tarojs/taro'; +import styles from './index.module.scss'; +import { + SUBSCRIBE_TEMPLATES, + bindWechat, + getWechatBinding, + updateWechatSubscribe, + type WechatBinding, +} from '@/api/wechat'; + +const NotificationPage: React.FC = () => { + const [binding, setBinding] = useState(null); + const [loading, setLoading] = useState(false); + + const refresh = useCallback(async () => { + const data = await getWechatBinding().catch(() => ({ bound: false } as WechatBinding)); + setBinding(data); + }, []); + + useEffect(() => { + refresh(); + }, [refresh]); + + const handleBind = async () => { + setLoading(true); + try { + const { code } = await Taro.login(); + if (!code) throw new Error('获取登录 code 失败'); + await bindWechat(code); + Taro.showToast({ title: '微信绑定成功', icon: 'success' }); + refresh(); + } catch (err) { + Taro.showToast({ + title: err instanceof Error ? err.message : '绑定失败', + icon: 'none', + }); + } finally { + setLoading(false); + } + }; + + const handleSubscribe = async () => { + const configured = SUBSCRIBE_TEMPLATES.filter((t) => t.id); + if (configured.length === 0) { + Taro.showModal({ + title: '模板未配置', + content: '订阅消息模板 ID 尚未配置,申请模板后填入 miniapp/src/api/wechat.ts 即可启用。', + showCancel: false, + confirmText: '知道了', + }); + return; + } + setLoading(true); + try { + const res = await Taro.requestSubscribeMessage({ + tmplIds: configured.map((t) => t.id), + }); + for (const t of configured) { + if (res[t.id] === 'accept') { + await updateWechatSubscribe(t.key, true); + } + } + Taro.showToast({ title: '订阅设置已更新', icon: 'success' }); + refresh(); + } catch (err) { + Taro.showToast({ + title: err instanceof Error ? err.message : '订阅失败', + icon: 'none', + }); + } finally { + setLoading(false); + } + }; + + return ( + + + 微信订阅消息 + + 绑定微信后,巡检风险(黄/橙/红)和告警发生时可通过订阅消息提醒你。注意:微信一次性订阅消息每次触发都需重新授权。 + + + 绑定状态 + + {binding?.bound ? '已绑定' : '未绑定'} + + + {binding?.bound && binding.openId ? ( + openid:{binding.openId} + ) : null} + {binding?.bound && binding.authorized && binding.authorized.length > 0 ? ( + 已授权:{binding.authorized.join('、')} + ) : null} + + + + + 可订阅场景 + {SUBSCRIBE_TEMPLATES.map((t) => ( + + {t.label} + + {t.id ? '已配置' : '待配置模板 ID'} + + + ))} + + + ); +}; + +export default NotificationPage; diff --git a/miniapp/src/pages/settings/index.tsx b/miniapp/src/pages/settings/index.tsx index 02f91ab..2b92e62 100644 --- a/miniapp/src/pages/settings/index.tsx +++ b/miniapp/src/pages/settings/index.tsx @@ -53,6 +53,9 @@ const SettingsPage: React.FC = () => { case 'inspection': Taro.navigateTo({ url: '/pages/inspection/index' }); break; + case 'notification': + Taro.navigateTo({ url: '/pages/notification/index' }); + break; case 'about': Taro.showModal({ title: '关于', @@ -174,6 +177,11 @@ const SettingsPage: React.FC = () => { 拍照巡检 + handleMenuTap('notification')}> + 🔔 + 消息订阅 + + handleMenuTap('wsStatus')}> 🔗 连接状态