From 8d7507e2d8cc732f3c9a42b96845a6bc6a173c1f Mon Sep 17 00:00:00 2001 From: jacy <18049821889@163.com> Date: Thu, 16 Jul 2026 08:28:46 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=9D=9C=E5=BA=B7=E5=8F=91=E5=B8=83=20skil?= =?UTF-8?q?ls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/skills/dukang-release/SKILL.md | 89 ++++++++++++++++++++++++++ AGENTS.md | 2 + 2 files changed, 91 insertions(+) create mode 100644 .cursor/skills/dukang-release/SKILL.md diff --git a/.cursor/skills/dukang-release/SKILL.md b/.cursor/skills/dukang-release/SKILL.md new file mode 100644 index 0000000..61eede8 --- /dev/null +++ b/.cursor/skills/dukang-release/SKILL.md @@ -0,0 +1,89 @@ +--- +name: dukang-release +description: >- + 杜康好客生产发布流程:dev_jacy 提交合并到 dev,并用 deploy.sh 发版。 + Use when the user says 发布、上传、deploy、上线、发版, or @杜康发布 / @dukang-release. +--- + +# 杜康发布 + +将已完成改动发到生产(`aliyun-smarthr`)。**仅用户明确要求发布时执行。** + +## 分支约定 + +| 分支 | 用途 | +|------|------| +| `dev_jacy` | 日常开发 / 提交 | +| `dev` | **发布分支**(服务器拉这个) | + +默认:`dev_jacy` → merge → `dev` → 部署。勿 force push `main`/`dev`。 + +## 标准流程(Windows) + +PowerShell 下 **分支名必须加引号**(否则 `checkout` 与 `dev` 会被粘成 `checkoutdev`)。 + +### 1. 提交(在 `dev_jacy`) + +```powershell +git status -sb +git add +git commit -m @" +fix(scope): summary + +Optional body. +"@ +``` + +- Conventional Commits;不提交 `.env` / `dist` / `node_modules` +- 用户未要求 commit 时不要擅自提交 + +### 2. 推送并合并到 `dev` + +```powershell +git push -u origin HEAD +git checkout "dev" +git pull origin "dev" +git merge "dev_jacy" -m "merge(dev_jacy): <简述>" +git push origin "dev" +git checkout "dev_jacy" +``` + +### 3. 远程发版 + +本机无 WSL bash 时用 **Git Bash**: + +```powershell +& "C:\Program Files\Git\bin\bash.exe" "/deploy/deploy.sh" --branch "dev" -- --skip-db +``` + +- 默认加 `--skip-db`(无迁移时) +- 有 Prisma 迁移时去掉 `--skip-db`,按用户确认是否 `--seed` / `--accept-data-loss` +- `deploy/deploy.env` 提供 `DEPLOY_HOST` 等(勿提交密钥) + +### 4. 等待完成 + +后台跑部署时轮询终端输出,成功标志: + +- 出现 `发版完成` +- `exit_code: 0` +- `mini-user(h5): 200`(或对应前端 200) + +说明:API 健康检查短暂 `FAIL`/`000` 多为重启中,以 `发版完成` + `exit_code: 0` 为准。 + +### 5. 回复用户 + +一句说明:已合并 commit、发版结果、建议微信强刷验证。 + +## 常用变体 + +| 场景 | 做法 | +|------|------| +| 仅部署已推送的 `dev` | 跳过 commit/merge,直接跑 `deploy.sh --branch "dev" -- --skip-db` | +| 检查服务器 | `deploy.sh --branch "dev" --check` | +| 含 DB | `deploy.sh --branch "dev" --`(无 skip-db)或按用户给 seed 参数 | + +## 禁止 + +- 不 amend 已推送 commit;不 force push `main`/`master`/`dev` +- 不把 `.env` / 私钥打进仓库 +- 用户只说「修好」未说发布时 → **只改代码,不发版** diff --git a/AGENTS.md b/AGENTS.md index d5ac38f..a150ea3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -90,6 +90,7 @@ Mock 验证码:`123456`。测试账号见 [`README.md`](./README.md)。 | 需求 Skill | `.cursor/skills/dukang-project/` | @dukang-project | | preV1 Skill | `.cursor/skills/dukang-prev1/` | Mock / Feature Flag | | 任务卡 Skill | `.cursor/skills/dukang-task-card/` | DLV-W* / P1-* / M*-* | +| 发布 Skill | `.cursor/skills/dukang-release/` | @杜康发布 / @dukang-release | ## 子 Agent(按职责选用) @@ -115,6 +116,7 @@ Mock 验证码:`123456`。测试账号见 [`README.md`](./README.md)。 | `dukang-project` | PRD/需求评审、原型对照、任务卡规划(**不用于日常编码**) | | `dukang-prev1` | Mock 开关、preV1 裁剪、Feature Flag | | `dukang-task-card` | 领取任务卡 `DLV-W*-*` / `P1-*` / `M*-*` 并按 DoD 交付 | +| `dukang-release`(杜康发布) | `dev_jacy`→`dev` 合并 + `deploy.sh` 发版 | ## 快速指令 From 22845e5d5f0a05c284507536bc5f8f55c519c343 Mon Sep 17 00:00:00 2001 From: jacy <18049821889@163.com> Date: Thu, 16 Jul 2026 08:30:04 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E9=97=A8=E5=BA=97=E7=AB=AF=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E7=BB=91=E5=AE=9A=E5=BE=AE=E4=BF=A1=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/h5-shop/src/lib/wechat-auth.ts | 27 ++++++++- apps/h5-shop/src/pages/MinePage.tsx | 92 ++++++++++++++++++++++++++++- apps/h5-shop/src/styles.css | 32 +++++++++- 3 files changed, 146 insertions(+), 5 deletions(-) diff --git a/apps/h5-shop/src/lib/wechat-auth.ts b/apps/h5-shop/src/lib/wechat-auth.ts index a9c3f15..562bd3e 100644 --- a/apps/h5-shop/src/lib/wechat-auth.ts +++ b/apps/h5-shop/src/lib/wechat-auth.ts @@ -9,6 +9,8 @@ export type ShopAccountProfile = { storeId: string; name: string; phone: string; + /** 是否已绑定微信(来自 /shop/auth/me account.hasWechat) */ + hasWechat?: boolean; wxOpenId?: string | null; store?: { name: string }; }; @@ -18,7 +20,27 @@ export async function fetchClientConfig(): Promise { } export async function fetchShopAccount(): Promise { - return request('SHOP_H5', '/shop/auth/me'); + const me = await request<{ + id?: string; + storeId?: string; + name?: string; + phone?: string; + account?: { + id?: string; + name?: string; + phone?: string; + hasWechat?: boolean; + }; + store?: { name?: string } | null; + }>('SHOP_H5', '/shop/auth/me'); + return { + id: String(me.account?.id ?? me.id ?? ''), + storeId: String(me.storeId ?? ''), + name: String(me.account?.name ?? me.name ?? ''), + phone: String(me.account?.phone ?? me.phone ?? ''), + hasWechat: !!me.account?.hasWechat, + store: me.store?.name ? { name: String(me.store.name) } : undefined, + }; } export function needsWechatAuth( @@ -26,7 +48,8 @@ export function needsWechatAuth( config?: Pick | null, ): boolean { if (config && !isWxAuthorizeEnabled(config)) return false; - return isWechatEnv() && !!profile && !profile.wxOpenId; + const unbound = !!profile && !(profile.hasWechat || profile.wxOpenId); + return isWechatEnv() && unbound; } export async function checkNeedsWechatAuth(profile: ShopAccountProfile | null): Promise { diff --git a/apps/h5-shop/src/pages/MinePage.tsx b/apps/h5-shop/src/pages/MinePage.tsx index 1600bc4..e8b659e 100644 --- a/apps/h5-shop/src/pages/MinePage.tsx +++ b/apps/h5-shop/src/pages/MinePage.tsx @@ -1,21 +1,95 @@ import { useEffect, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { isWxAuthorizeEnabled } from '@dukang/shared-types'; +import { stripOAuthParamsFromLocation } from '@dukang/weixin-sdk'; import { useStoreSession } from '../contexts/StoreSessionContext'; import { getStoreProfile, request } from '../lib/api'; +import { + authorizeShopWechat, + fetchClientConfig, + fetchShopAccount, + handleShopWechatCallback, + handleShopWechatLoginResult, +} from '../lib/wechat-auth'; +import { isWechatEnv } from '../lib/weixin'; export default function MinePage() { const navigate = useNavigate(); - const { resetSession, store: sessionStore } = useStoreSession(); + const [searchParams, setSearchParams] = useSearchParams(); + const { resetSession, store: sessionStore, applySession } = useStoreSession(); const profile = sessionStore ?? getStoreProfile(); const [store, setStore] = useState | null>(null); + const [hasWechat, setHasWechat] = useState(null); + const [wxAuthorize, setWxAuthorize] = useState(false); + const [binding, setBinding] = useState(false); + const [bindMsg, setBindMsg] = useState(''); useEffect(() => { request('SHOP_H5', '/shop/store').then(setStore).catch(() => setStore(null)); }, []); + useEffect(() => { + fetchClientConfig() + .then((config) => setWxAuthorize(isWxAuthorizeEnabled(config))) + .catch(() => setWxAuthorize(false)); + }, []); + + useEffect(() => { + fetchShopAccount() + .then((me) => setHasWechat(!!(me.hasWechat || me.wxOpenId))) + .catch(() => setHasWechat(null)); + }, []); + + useEffect(() => { + if (!isWechatEnv() || !wxAuthorize || !searchParams.get('code')) return; + void handleShopWechatCallback() + .then((result) => { + if (!result) return; + const session = handleShopWechatLoginResult(result); + if (session) { + applySession(session); + setHasWechat(true); + setBindMsg('微信绑定成功'); + } + stripOAuthParamsFromLocation(); + setSearchParams({}, { replace: true }); + }) + .catch((e) => setBindMsg(e instanceof Error ? e.message : '微信绑定失败')); + }, [applySession, searchParams, setSearchParams, wxAuthorize]); + const openTime = String(store?.openTime || '09:30'); const closeTime = String(store?.closeTime || '22:00'); const multiStore = (profile?.stores?.length ?? 0) > 1; + const showBindWechat = wxAuthorize && hasWechat === false; + + async function bindWechat() { + if (binding) return; + setBindMsg(''); + if (!isWechatEnv()) { + setBindMsg('请在微信内打开门店端以绑定微信'); + return; + } + setBinding(true); + try { + const result = await authorizeShopWechat(); + if (!result) { + // 跳转公众号 OAuth,回跳后由上面 useEffect 处理 + return; + } + const session = handleShopWechatLoginResult(result); + if (session) { + applySession(session); + setHasWechat(true); + setBindMsg('微信绑定成功'); + } else { + setBindMsg('绑定未完成,请重试'); + } + } catch (e) { + setBindMsg(e instanceof Error ? e.message : '微信绑定失败'); + } finally { + setBinding(false); + } + } return (
@@ -77,6 +151,20 @@ export default function MinePage() {

如需修改信息请联系城市合伙人

+ {bindMsg ?

{bindMsg}

: null} + + {showBindWechat ? ( + + ) : null} + + -
- - +
+

快捷入口

+
+ +
+
store
+ 门店管理 +
+ chevron_right + + +
+
add_business
+ 录入新门店 +
+ chevron_right + +
+
+ + + +
+

传承千年 · 杜康好客

); diff --git a/apps/h5-partner/src/pages/StoreCreatePage.tsx b/apps/h5-partner/src/pages/StoreCreatePage.tsx index 51d01cc..363c0f4 100644 --- a/apps/h5-partner/src/pages/StoreCreatePage.tsx +++ b/apps/h5-partner/src/pages/StoreCreatePage.tsx @@ -20,7 +20,6 @@ import { fetchPartnerCities, type OpenCityOption } from '../lib/upload'; import { isWechatEnv, weixinSdk } from '../lib/weixin'; import { usePartnerSession } from '../contexts/PartnerSessionContext'; -import { isSubAccount } from '../lib/partnerAccess'; import { @@ -82,8 +81,6 @@ export default function StoreCreatePage() { const { account, refresh } = usePartnerSession(); - const subAccountLayout = isSubAccount(account); - const accountId = account?.id; const wechatReady = !!account?.hasWechat; @@ -610,7 +607,7 @@ export default function StoreCreatePage() { return ( -
+
navigate('/stores')} /> @@ -1094,7 +1091,7 @@ export default function StoreCreatePage() { -