merge(dev_jacy): partner UX, shop wechat bind, remove H5 title whitespace
This commit is contained in:
@@ -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 <files>
|
||||
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" "<repo>/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` / 私钥打进仓库
|
||||
- 用户只说「修好」未说发布时 → **只改代码,不发版**
|
||||
@@ -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` 发版 |
|
||||
|
||||
## 快速指令
|
||||
|
||||
|
||||
@@ -50,10 +50,11 @@ function SubAccountRoutes() {
|
||||
<Routes>
|
||||
<Route element={<SubAccountLayout />}>
|
||||
<Route path="/stores" element={<StoreListPage />} />
|
||||
<Route path="/stores/new" element={<StoreCreatePage />} />
|
||||
<Route path="/stores/:id" element={<StoreDetailPage />} />
|
||||
<Route path="/me" element={<PartnerMePage />} />
|
||||
</Route>
|
||||
{/* 与主账号一致:录入门店全屏,避免 sticky 底栏被 TabBar 挡住 */}
|
||||
<Route path="/stores/new" element={<StoreCreatePage />} />
|
||||
<Route path="*" element={<Navigate to="/stores/new?step=1" replace />} />
|
||||
</Routes>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { PARTNER_STAFF_ROLE_LABELS, type PartnerStaffRole } from '@dukang/shared-types';
|
||||
import { request } from '../lib/api';
|
||||
import { usePartnerSession } from '../contexts/PartnerSessionContext';
|
||||
@@ -43,21 +44,31 @@ export default function PartnerMePage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="page partner-me-page">
|
||||
<section className="partner-profile-card" style={{ margin: '16px var(--space-page)' }}>
|
||||
<div className="page partner-center-page partner-me-page">
|
||||
<header className="header app-page-header">
|
||||
<h1 className="app-page-title">杜康好客</h1>
|
||||
</header>
|
||||
|
||||
<section className="partner-profile-card">
|
||||
<div className="partner-profile-avatar">
|
||||
<span className="material-symbols-outlined">person</span>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<h2 className="headline-lg" style={{ fontSize: 20 }}>{account?.name || '我的'}</h2>
|
||||
<p className="label-md text-muted" style={{ marginTop: 4 }}>{roleLabel}</p>
|
||||
{account?.companyName ? (
|
||||
<p className="body-md text-muted" style={{ marginTop: 4 }}>{account.companyName}</p>
|
||||
) : null}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<h2 className="headline-lg" style={{ fontSize: 20 }}>{account?.name || '合伙人'}</h2>
|
||||
<span className="partner-role-badge">{roleLabel}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4, marginTop: 4, color: 'var(--color-subtle-gray)' }}>
|
||||
<span className="material-symbols-outlined" style={{ fontSize: 16 }}>location_on</span>
|
||||
<span className="body-md">{account?.companyName || '—'}</span>
|
||||
</div>
|
||||
<p className="label-md text-muted" style={{ marginTop: 4 }}>{account?.phone || ''}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="partner-form-section" style={{ padding: '0 var(--space-page)' }}>
|
||||
<section className="partner-menu-section">
|
||||
<h3 className="headline-md" style={{ marginBottom: 8 }}>账号资料</h3>
|
||||
<div className="partner-menu-card" style={{ padding: '16px' }}>
|
||||
<label className="partner-form-label" htmlFor="me-name">姓名</label>
|
||||
<input
|
||||
id="me-name"
|
||||
@@ -75,27 +86,49 @@ export default function PartnerMePage() {
|
||||
readOnly
|
||||
disabled
|
||||
/>
|
||||
<p className="label-md text-muted" style={{ marginTop: 4 }}>手机号由管理员维护,如需修改请联系总部</p>
|
||||
</section>
|
||||
<p className="label-md text-muted" style={{ marginTop: 4 }}>
|
||||
手机号由管理员维护,如需修改请联系总部
|
||||
</p>
|
||||
|
||||
<div style={{ padding: '24px var(--space-page) 100px' }}>
|
||||
<button
|
||||
type="button"
|
||||
className="partner-btn-primary"
|
||||
style={{ width: '100%' }}
|
||||
style={{ width: '100%', marginTop: 16 }}
|
||||
disabled={saving}
|
||||
onClick={() => void handleSave()}
|
||||
>
|
||||
{saving ? '保存中…' : '保存'}
|
||||
{saving ? '保存中…' : '保存资料'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="partner-btn-outline"
|
||||
style={{ width: '100%', marginTop: 12 }}
|
||||
onClick={logout}
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="partner-menu-section">
|
||||
<h3 className="headline-md" style={{ marginBottom: 8 }}>快捷入口</h3>
|
||||
<div className="partner-menu-card">
|
||||
<Link to="/stores" className="partner-menu-item">
|
||||
<div className="partner-menu-item-left">
|
||||
<div className="partner-menu-icon"><span className="material-symbols-outlined">store</span></div>
|
||||
<span className="body-md" style={{ fontSize: 16 }}>门店管理</span>
|
||||
</div>
|
||||
<span className="material-symbols-outlined text-muted">chevron_right</span>
|
||||
</Link>
|
||||
<Link to="/stores/new?step=1" className="partner-menu-item">
|
||||
<div className="partner-menu-item-left">
|
||||
<div className="partner-menu-icon"><span className="material-symbols-outlined">add_business</span></div>
|
||||
<span className="body-md" style={{ fontSize: 16 }}>录入新门店</span>
|
||||
</div>
|
||||
<span className="material-symbols-outlined text-muted">chevron_right</span>
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button type="button" className="partner-logout-btn" onClick={logout}>
|
||||
<span className="material-symbols-outlined">logout</span>
|
||||
退出登录
|
||||
</button>
|
||||
|
||||
<div style={{ textAlign: 'center', opacity: 0.3, padding: '32px 0' }}>
|
||||
<p className="label-md text-muted">传承千年 · 杜康好客</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
|
||||
<div className={`partner-page-sticky${subAccountLayout ? ' partner-page-sticky--tabbar' : ''}`}>
|
||||
<div className="partner-page-sticky">
|
||||
|
||||
<PageHeader title="录入新门店" onBack={() => navigate('/stores')} />
|
||||
|
||||
@@ -1094,7 +1091,7 @@ export default function StoreCreatePage() {
|
||||
|
||||
|
||||
|
||||
<footer className={`partner-sticky-footer${subAccountLayout ? ' partner-sticky-footer--above-tabbar' : ''}`}>
|
||||
<footer className="partner-sticky-footer">
|
||||
|
||||
{step > 1 && (
|
||||
|
||||
|
||||
@@ -1296,7 +1296,7 @@ nav.app-tabbar .app-tabbar-label {
|
||||
|
||||
.partner-sticky-footer--above-tabbar {
|
||||
bottom: calc(56px + env(safe-area-inset-bottom, 0px));
|
||||
z-index: 90;
|
||||
z-index: 110;
|
||||
}
|
||||
|
||||
.partner-stepper {
|
||||
@@ -1752,12 +1752,13 @@ nav.app-tabbar .app-tabbar-label {
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
transform: none;
|
||||
background: rgba(250, 249, 247, 0.92);
|
||||
background: rgba(250, 249, 247, 0.96);
|
||||
backdrop-filter: blur(8px);
|
||||
border-top: 1px solid rgba(226, 190, 188, 0.2);
|
||||
box-shadow: 0 -4px 20px rgba(166, 29, 36, 0.05);
|
||||
padding: 16px var(--space-page) calc(16px + env(safe-area-inset-bottom, 0px));
|
||||
z-index: 60;
|
||||
/* 高于 .app-tabbar(100),防止偶发叠层遮挡「下一步/提交」 */
|
||||
z-index: 110;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -1801,7 +1802,7 @@ nav.app-tabbar .app-tabbar-label {
|
||||
|
||||
.partner-sticky-filter {
|
||||
position: sticky;
|
||||
top: 56px;
|
||||
top: 0;
|
||||
z-index: 40;
|
||||
background: rgba(250, 249, 247, 0.95);
|
||||
backdrop-filter: blur(8px);
|
||||
@@ -3066,10 +3067,21 @@ body {
|
||||
border-top: 1px dashed rgba(166, 29, 36, 0.15);
|
||||
}
|
||||
|
||||
/* 微信 H5 系统标题已展示:隐藏页内重复标题,保留返回键与顶栏布局 */
|
||||
/* 微信 H5 系统标题已展示:隐藏页内重复标题,保留返回键与操作区 */
|
||||
.app-page-title {
|
||||
visibility: hidden;
|
||||
font-size: 0 !important;
|
||||
line-height: 0 !important;
|
||||
color: transparent !important;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 仅含标题的顶栏一并收起,去掉空白条;有返回/操作的顶栏保留 */
|
||||
header:has(> .app-page-title:only-child),
|
||||
.header.app-page-header:has(> .app-page-title:only-child),
|
||||
.app-page-header:has(> .app-page-title:only-child) {
|
||||
display: none !important;
|
||||
height: 0 !important;
|
||||
min-height: 0 !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
overflow: hidden;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
@@ -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<ClientRuntimeConfig> {
|
||||
}
|
||||
|
||||
export async function fetchShopAccount(): Promise<ShopAccountProfile> {
|
||||
return request<ShopAccountProfile>('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<ClientRuntimeConfig, 'wxAuthorize'> | 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<boolean> {
|
||||
|
||||
@@ -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<Record<string, unknown> | null>(null);
|
||||
const [hasWechat, setHasWechat] = useState<boolean | null>(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 (
|
||||
<div className="shop-mine-page">
|
||||
@@ -77,6 +151,20 @@ export default function MinePage() {
|
||||
<p>如需修改信息请联系城市合伙人</p>
|
||||
</div>
|
||||
|
||||
{bindMsg ? <p className="shop-mine-bind-msg">{bindMsg}</p> : null}
|
||||
|
||||
{showBindWechat ? (
|
||||
<button
|
||||
type="button"
|
||||
className="shop-mine-bind-wechat"
|
||||
disabled={binding}
|
||||
onClick={() => void bindWechat()}
|
||||
>
|
||||
<span className="material-symbols-outlined" style={{ fontSize: 20 }}>chat</span>
|
||||
{binding ? '绑定中…' : '绑定微信'}
|
||||
</button>
|
||||
) : null}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="shop-mine-logout"
|
||||
|
||||
+53
-22
@@ -1489,25 +1489,13 @@
|
||||
}
|
||||
|
||||
.shop-records-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 var(--space-page);
|
||||
background: rgba(250, 249, 247, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
z-index: 50;
|
||||
/* 页内标题已隐藏:不再占位;保留类名以免改 JSX */
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.shop-records-filters {
|
||||
position: sticky;
|
||||
top: 56px;
|
||||
top: 0;
|
||||
z-index: 40;
|
||||
background: var(--color-surface);
|
||||
border-bottom: 1px solid var(--color-surface-container-highest);
|
||||
@@ -1578,7 +1566,7 @@
|
||||
}
|
||||
|
||||
.shop-records-main {
|
||||
padding-top: 56px;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.shop-records-summary {
|
||||
@@ -2153,8 +2141,38 @@
|
||||
color: var(--color-subtle-gray);
|
||||
}
|
||||
|
||||
.shop-mine-bind-msg {
|
||||
margin: 0 0 8px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: var(--color-heritage-red);
|
||||
}
|
||||
|
||||
.shop-mine-bind-wechat {
|
||||
margin-top: 8px;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
font-family: var(--font-headline);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.shop-mine-bind-wechat:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.shop-mine-logout {
|
||||
margin-top: 24px;
|
||||
margin-top: 12px;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
border: 1px solid var(--color-heritage-red);
|
||||
@@ -2702,11 +2720,24 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 微信 H5 系统标题已展示:隐藏页内重复标题,保留返回键与顶栏布局 */
|
||||
/* 微信 H5 系统标题已展示:隐藏页内重复标题,保留返回键与操作区 */
|
||||
.app-page-title {
|
||||
visibility: hidden;
|
||||
font-size: 0 !important;
|
||||
line-height: 0 !important;
|
||||
color: transparent !important;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 仅含标题的顶栏一并收起,去掉空白条;有返回/操作的顶栏保留 */
|
||||
header:has(> .app-page-title:only-child),
|
||||
.shop-home-header:has(> .app-page-title:only-child),
|
||||
.shop-mine-header:has(> .app-page-title:only-child),
|
||||
.shop-records-header:has(> .app-page-title:only-child),
|
||||
.app-page-header:has(> .app-page-title:only-child) {
|
||||
display: none !important;
|
||||
height: 0 !important;
|
||||
min-height: 0 !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
overflow: hidden;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,10 @@ type SubPageHeaderProps = {
|
||||
|
||||
export default function SubPageHeader({ title, onBack }: SubPageHeaderProps) {
|
||||
return (
|
||||
<header className="sub-page-header">
|
||||
<header className="sub-page-header" aria-label={title}>
|
||||
<button type="button" className="sub-page-header-back" aria-label="返回" onClick={onBack}>
|
||||
<span className="material-symbols-outlined">arrow_back</span>
|
||||
</button>
|
||||
<h1 className="app-page-title">{title}</h1>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,9 +7,13 @@ type TabMainHeaderProps = {
|
||||
};
|
||||
|
||||
export default function TabMainHeader({ title, extra, className = '' }: TabMainHeaderProps) {
|
||||
// H5:系统标题已展示;无右侧内容时整栏不渲染,避免顶部留白
|
||||
if (!extra) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<header className={`tab-main-header${className ? ` ${className}` : ''}`}>
|
||||
<h1 className="app-page-title">{title}</h1>
|
||||
<header className={`tab-main-header${className ? ` ${className}` : ''}`} aria-label={title}>
|
||||
{extra ? <div className="tab-main-header-extra">{extra}</div> : null}
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -917,6 +917,11 @@
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
/* 首页有城市栏顶栏时 top=56;无额外内容已不渲染顶栏时贴顶 */
|
||||
.home-page:not(:has(.tab-main-header)) .home-aroma-nav {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.home-aroma-tab {
|
||||
border: none;
|
||||
background: none;
|
||||
@@ -5926,3 +5931,20 @@
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* 微信 H5 系统标题已展示:隐藏页内重复标题;仅标题顶栏收起 */
|
||||
.app-page-title {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
header:has(> .app-page-title:only-child),
|
||||
.app-page-header:has(> .app-page-title:only-child) {
|
||||
display: none !important;
|
||||
height: 0 !important;
|
||||
min-height: 0 !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
overflow: hidden;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,11 @@ const isH5 = process.env.TARO_ENV === 'h5';
|
||||
export default function TabMainHeader({ title, extra }: TabMainHeaderProps) {
|
||||
const metrics = useNavBarMetrics();
|
||||
|
||||
// H5:系统标题已展示;无右侧内容时整栏不渲染,避免顶部留白
|
||||
if (isH5 && !extra) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="tab-main-header" style={navBarStyle(metrics)} aria-label={title}>
|
||||
{!isH5 ? <Text className="tab-main-header__title">{title}</Text> : null}
|
||||
|
||||
@@ -65,7 +65,10 @@ export default function HomePage() {
|
||||
const onSale = tab === 'QINGXIANG';
|
||||
|
||||
return (
|
||||
<PageShell variant="tab" className="home-page">
|
||||
<PageShell
|
||||
variant="tab"
|
||||
className={`home-page${process.env.TARO_ENV === 'h5' ? ' h5-no-tab-header' : ''}`}
|
||||
>
|
||||
<TabMainHeader title="杜康好客" />
|
||||
|
||||
<View className="home-aroma-nav">
|
||||
|
||||
@@ -210,7 +210,10 @@ export default function MinePage() {
|
||||
|
||||
if (!authed) {
|
||||
return (
|
||||
<PageShell variant="tab" className="mine-page">
|
||||
<PageShell
|
||||
variant="tab"
|
||||
className={`mine-page${process.env.TARO_ENV === 'h5' ? ' h5-no-tab-header' : ''}`}
|
||||
>
|
||||
<TabMainHeader title="我的" />
|
||||
<View className="mine-header">
|
||||
<View className="mine-header-texture" />
|
||||
@@ -247,7 +250,10 @@ export default function MinePage() {
|
||||
const memberLabel = hasWechat ? '好客会员' : canWxBind ? '微信未授权' : '未授权微信';
|
||||
|
||||
return (
|
||||
<PageShell variant="tab" className="mine-page">
|
||||
<PageShell
|
||||
variant="tab"
|
||||
className={`mine-page${process.env.TARO_ENV === 'h5' ? ' h5-no-tab-header' : ''}`}
|
||||
>
|
||||
<TabMainHeader title="我的" />
|
||||
<View className="mine-header">
|
||||
<View className="mine-header-texture" />
|
||||
|
||||
@@ -73,7 +73,10 @@ export default function StoresPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<PageShell variant="tab" className="store-page">
|
||||
<PageShell
|
||||
variant="tab"
|
||||
className={`store-page${process.env.TARO_ENV === 'h5' ? ' h5-no-tab-header' : ''}`}
|
||||
>
|
||||
<TabMainHeader title="门店" />
|
||||
<View className="store-toolbar">
|
||||
<View className="store-location" onClick={() => setRegionOpen(true)}>
|
||||
|
||||
@@ -20,14 +20,13 @@
|
||||
padding-bottom: calc(88px + env(safe-area-inset-bottom, 0px));
|
||||
}
|
||||
|
||||
/* ── Tab 主页面顶栏(实底 sticky) ── */
|
||||
.tab-main-header {
|
||||
position: sticky;
|
||||
/* H5 无 Tab 顶栏:贴顶,去掉标题栏留白 */
|
||||
.page-shell.h5-no-tab-header {
|
||||
--nav-bar-height: 0px;
|
||||
}
|
||||
|
||||
.page-shell.h5-no-tab-header .home-aroma-nav {
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
background: var(--color-background);
|
||||
box-shadow: var(--shadow-card);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tab-main-header__content {
|
||||
|
||||
@@ -534,7 +534,8 @@ button, input, select, textarea { font: inherit; }
|
||||
.page-header-title--spacer {
|
||||
font-size: 0;
|
||||
line-height: 0;
|
||||
min-height: 26px;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Coupon badge (ticket notch) */
|
||||
|
||||
Reference in New Issue
Block a user