feat(store): v4.0.18 门店多收款账户与子账号继承二维码
C 端门店列表拼接省市区县地址;门店多银行账户与默认打款账户;子账号独立 sa_ 关联码及统计维度;同步 v4.0.18 开发文档。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Button, Form, Input, Modal, Popconfirm, Space, Table, Tag, message } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import type { StoreBankAccountDto } from '@dukang/shared-types';
|
||||
import { request } from '../lib/api';
|
||||
|
||||
type Props = { storeId: string };
|
||||
|
||||
type FormValues = { bankAccountName: string; bankAccountNo: string; bankBranch?: string };
|
||||
|
||||
export default function StoreBankAccountsPanel({ storeId }: Props) {
|
||||
const [form] = Form.useForm<FormValues>();
|
||||
const [items, setItems] = useState<StoreBankAccountDto[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await request<StoreBankAccountDto[]>(`/admin/stores/${storeId}/bank-accounts`);
|
||||
setItems(res ?? []);
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '加载失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [storeId]);
|
||||
|
||||
useEffect(() => {
|
||||
void load();
|
||||
}, [load]);
|
||||
|
||||
function openCreate() {
|
||||
setEditingId(null);
|
||||
form.resetFields();
|
||||
setModalOpen(true);
|
||||
}
|
||||
|
||||
function openEdit(item: StoreBankAccountDto) {
|
||||
setEditingId(item.id);
|
||||
form.setFieldsValue({
|
||||
bankAccountName: item.bankAccountName,
|
||||
bankAccountNo: item.bankAccountNo,
|
||||
bankBranch: item.bankBranch ?? '',
|
||||
});
|
||||
setModalOpen(true);
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
const v = await form.validateFields();
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const body = JSON.stringify({
|
||||
bankAccountName: v.bankAccountName.trim(),
|
||||
bankAccountNo: v.bankAccountNo.replace(/\s+/g, ''),
|
||||
bankBranch: v.bankBranch?.trim() || undefined,
|
||||
});
|
||||
if (editingId) {
|
||||
await request(`/admin/stores/${storeId}/bank-accounts/${editingId}`, { method: 'PUT', body });
|
||||
} else {
|
||||
await request(`/admin/stores/${storeId}/bank-accounts`, { method: 'POST', body });
|
||||
}
|
||||
message.success('已保存');
|
||||
setModalOpen(false);
|
||||
void load();
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '保存失败');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function setDefault(id: string) {
|
||||
try {
|
||||
await request(`/admin/stores/${storeId}/bank-accounts/${id}/default`, { method: 'POST' });
|
||||
message.success('已设为默认打款账户');
|
||||
void load();
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '设置失败');
|
||||
}
|
||||
}
|
||||
|
||||
async function remove(id: string) {
|
||||
try {
|
||||
await request(`/admin/stores/${storeId}/bank-accounts/${id}`, { method: 'DELETE' });
|
||||
message.success('已删除');
|
||||
void load();
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '删除失败');
|
||||
}
|
||||
}
|
||||
|
||||
const columns: ColumnsType<StoreBankAccountDto> = [
|
||||
{ title: '收款人', dataIndex: 'bankAccountName', width: 140 },
|
||||
{ title: '银行账号', dataIndex: 'bankAccountNo', width: 180 },
|
||||
{ title: '开户行', dataIndex: 'bankBranch', render: (v) => v || '—' },
|
||||
{
|
||||
title: '默认',
|
||||
dataIndex: 'isDefault',
|
||||
width: 80,
|
||||
render: (v) => (v ? <Tag color="green">默认</Tag> : null),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 220,
|
||||
render: (_, row) => (
|
||||
<Space size={0} wrap>
|
||||
<Button type="link" size="small" onClick={() => openEdit(row)}>编辑</Button>
|
||||
{!row.isDefault ? (
|
||||
<Button type="link" size="small" onClick={() => void setDefault(row.id)}>设为默认</Button>
|
||||
) : null}
|
||||
{!row.isDefault ? (
|
||||
<Popconfirm
|
||||
title="确认删除该收款账户?"
|
||||
okText="删除"
|
||||
okButtonProps={{ danger: true }}
|
||||
onConfirm={() => void remove(row.id)}
|
||||
>
|
||||
<Button type="link" size="small" danger>删除</Button>
|
||||
</Popconfirm>
|
||||
) : null}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Space style={{ marginBottom: 12, width: '100%', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: '#888' }}>
|
||||
每门店可维护多个收款账户,标记「默认」的账户用于核销结算与提现打款;切换即时生效、无需重启服务。
|
||||
</span>
|
||||
<Button type="primary" size="small" onClick={openCreate}>新增账户</Button>
|
||||
</Space>
|
||||
<Table
|
||||
rowKey="id"
|
||||
size="small"
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
dataSource={items}
|
||||
pagination={false}
|
||||
/>
|
||||
<Modal
|
||||
title={editingId ? '编辑收款账户' : '新增收款账户'}
|
||||
open={modalOpen}
|
||||
confirmLoading={submitting}
|
||||
onCancel={() => setModalOpen(false)}
|
||||
onOk={() => void submit()}
|
||||
okText="保存"
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
name="bankAccountName"
|
||||
label="收款人"
|
||||
rules={[{ required: true, message: '请填写收款人' }]}
|
||||
>
|
||||
<Input maxLength={64} placeholder="户名 / 收款人姓名" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="bankAccountNo"
|
||||
label="银行账号"
|
||||
rules={[
|
||||
{ required: true, message: '请填写银行账号' },
|
||||
{ pattern: /^\d{8,32}$/, message: '请填写 8~32 位数字账号' },
|
||||
]}
|
||||
>
|
||||
<Input maxLength={32} placeholder="请输入银行账号" />
|
||||
</Form.Item>
|
||||
<Form.Item name="bankBranch" label="开户行名称">
|
||||
<Input maxLength={128} placeholder="如 中国工商银行郑州分行" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -51,6 +51,7 @@ import TencentLocPickerModal from '../components/TencentLocPickerModal';
|
||||
import AdminStorePackagesSection, {
|
||||
type AdminStorePackagesHandle,
|
||||
} from '../components/AdminStorePackagesSection';
|
||||
import StoreBankAccountsPanel from '../components/StoreBankAccountsPanel';
|
||||
import StorePackageAuditPanel, {
|
||||
auditStorePackageRequest,
|
||||
} from '../components/StorePackageAuditPanel';
|
||||
@@ -1297,6 +1298,14 @@ export default function StoresPage() {
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'bank-accounts',
|
||||
label: '收款账户',
|
||||
forceRender: true,
|
||||
children: (
|
||||
<StoreBankAccountsPanel storeId={String(detail.id)} />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'media',
|
||||
label: '审核材料',
|
||||
|
||||
@@ -61,7 +61,9 @@ export default function AssocQrcodePage() {
|
||||
<section className="partner-bill-card" style={{ margin: 16 }}>
|
||||
<div style={{ padding: 24, textAlign: 'center' }}>
|
||||
<p className="body-md text-muted" style={{ marginBottom: 16 }}>
|
||||
用户扫码后首次锁定本合伙人,后续酒单按订单佣金结算
|
||||
{summary?.isSubAccount
|
||||
? '子账号专属二维码:用户扫码后锁定主账号(佣金归主账号),额外计入本子账号统计'
|
||||
: '用户扫码后首次锁定本合伙人,后续酒单按订单佣金结算'}
|
||||
</p>
|
||||
{summary?.qrcodeUrl ? (
|
||||
<img
|
||||
@@ -75,6 +77,7 @@ export default function AssocQrcodePage() {
|
||||
)}
|
||||
<p className="label-md text-muted" style={{ marginTop: 12 }}>
|
||||
已关联 {summary?.userCount ?? 0} 人
|
||||
{typeof summary?.scanCount === 'number' ? ` · 已扫码 ${summary.scanCount} 次` : ''}
|
||||
</p>
|
||||
<button type="button" className="partner-btn-primary" style={{ marginTop: 20 }} onClick={() => void downloadQr()}>
|
||||
下载二维码
|
||||
|
||||
@@ -192,7 +192,9 @@ export default function UsersManagePage() {
|
||||
|
||||
<section className="partner-bill-card" style={{ marginBottom: 20, textAlign: 'center', padding: 20 }}>
|
||||
<p className="body-md text-muted" style={{ marginBottom: 12 }}>
|
||||
用户扫码后首次锁定,后续购酒计入关联订单
|
||||
{summary?.isSubAccount
|
||||
? '子账号专属二维码:扫码锁定主账号(佣金归主账号),下方统计仅计本子账号'
|
||||
: '用户扫码后首次锁定,后续购酒计入关联订单'}
|
||||
</p>
|
||||
{summary?.activityPosterId && !hidePoster && (previewUrl || heroUrl) ? (
|
||||
<img
|
||||
|
||||
@@ -13,6 +13,7 @@ import StatusPage from './pages/StatusPage';
|
||||
import MinePage from './pages/MinePage';
|
||||
import StaffPage from './pages/StaffPage';
|
||||
import WithdrawPage from './pages/WithdrawPage';
|
||||
import BankAccountsPage from './pages/BankAccountsPage';
|
||||
import PackagesPage from './pages/PackagesPage';
|
||||
|
||||
export default function App() {
|
||||
@@ -25,6 +26,7 @@ export default function App() {
|
||||
<Route path="/select-store" element={<SelectStorePage />} />
|
||||
<Route path="/staff" element={<StaffPage />} />
|
||||
<Route path="/withdraw" element={<WithdrawPage />} />
|
||||
<Route path="/bank-accounts" element={<BankAccountsPage />} />
|
||||
<Route path="/packages" element={<PackagesPage />} />
|
||||
<Route path="/redeem" element={<RedeemConfirmPage />} />
|
||||
<Route path="/redeem/phone" element={<PhoneRedeemPage />} />
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { StoreBankAccountDto } from '@dukang/shared-types';
|
||||
import { request } from '../lib/api';
|
||||
import { useStoreSession } from '../contexts/StoreSessionContext';
|
||||
import { useStorePageView } from '../lib/usePageView';
|
||||
|
||||
type FormState = {
|
||||
id?: string;
|
||||
bankAccountName: string;
|
||||
bankAccountNo: string;
|
||||
bankBranch: string;
|
||||
};
|
||||
|
||||
const EMPTY_FORM: FormState = { bankAccountName: '', bankAccountNo: '', bankBranch: '' };
|
||||
|
||||
export default function BankAccountsPage() {
|
||||
useStorePageView('store_bank_accounts_view');
|
||||
const navigate = useNavigate();
|
||||
const { store } = useStoreSession();
|
||||
const isPrimary = !!store?.isPrimary;
|
||||
const [items, setItems] = useState<StoreBankAccountDto[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [msg, setMsg] = useState('');
|
||||
const [form, setForm] = useState<FormState>(EMPTY_FORM);
|
||||
const [fieldErrors, setFieldErrors] = useState<Partial<Record<keyof FormState, string>>>({});
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
const res = await request<StoreBankAccountDto[]>('SHOP_H5', '/shop/store/bank-accounts');
|
||||
setItems(res ?? []);
|
||||
} catch (e) {
|
||||
setMsg(e instanceof Error ? e.message : '加载失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void load();
|
||||
}, [load]);
|
||||
|
||||
function startCreate() {
|
||||
setForm(EMPTY_FORM);
|
||||
setFieldErrors({});
|
||||
}
|
||||
|
||||
function startEdit(item: StoreBankAccountDto) {
|
||||
setForm({
|
||||
id: item.id,
|
||||
bankAccountName: item.bankAccountName,
|
||||
bankAccountNo: item.bankAccountNo,
|
||||
bankBranch: item.bankBranch ?? '',
|
||||
});
|
||||
setFieldErrors({});
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
const name = form.bankAccountName.trim();
|
||||
const no = form.bankAccountNo.replace(/\s+/g, '');
|
||||
const branch = form.bankBranch.trim();
|
||||
const next: Partial<Record<keyof FormState, string>> = {};
|
||||
if (!name) next.bankAccountName = '请填写收款人';
|
||||
if (!/^\d{8,32}$/.test(no)) next.bankAccountNo = '请填写 8~32 位数字账号';
|
||||
setFieldErrors(next);
|
||||
if (Object.keys(next).length) return;
|
||||
|
||||
setSubmitting(true);
|
||||
setMsg('');
|
||||
try {
|
||||
const body = JSON.stringify({ bankAccountName: name, bankAccountNo: no, bankBranch: branch });
|
||||
if (form.id) {
|
||||
await request('SHOP_H5', `/shop/store/bank-accounts/${form.id}`, {
|
||||
method: 'PUT',
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await request('SHOP_H5', '/shop/store/bank-accounts', { method: 'POST', body });
|
||||
}
|
||||
setForm(EMPTY_FORM);
|
||||
await load();
|
||||
} catch (e) {
|
||||
setMsg(e instanceof Error ? e.message : '保存失败');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function setDefault(id: string) {
|
||||
setMsg('');
|
||||
try {
|
||||
await request('SHOP_H5', `/shop/store/bank-accounts/${id}/default`, { method: 'POST' });
|
||||
await load();
|
||||
} catch (e) {
|
||||
setMsg(e instanceof Error ? e.message : '设置失败');
|
||||
}
|
||||
}
|
||||
|
||||
async function remove(id: string) {
|
||||
setMsg('');
|
||||
try {
|
||||
await request('SHOP_H5', `/shop/store/bank-accounts/${id}`, { method: 'DELETE' });
|
||||
await load();
|
||||
} catch (e) {
|
||||
setMsg(e instanceof Error ? e.message : '删除失败');
|
||||
}
|
||||
}
|
||||
|
||||
const editing = !!form.id;
|
||||
|
||||
return (
|
||||
<div className="shop-records-page">
|
||||
<header className="shop-records-header" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<button
|
||||
type="button"
|
||||
className="shop-withdraw-back"
|
||||
onClick={() => navigate(-1)}
|
||||
aria-label="返回"
|
||||
>
|
||||
<span className="material-symbols-outlined" style={{ fontSize: 22 }}>arrow_back</span>
|
||||
</button>
|
||||
<h1 className="app-page-title" style={{ margin: 0 }}>收款账户</h1>
|
||||
</header>
|
||||
|
||||
<div className="shop-records-main">
|
||||
<p
|
||||
className="shop-records-summary-note"
|
||||
style={{ marginBottom: 12 }}
|
||||
>
|
||||
用于总部打款,可维护多个账户;标记「默认」的账户用于提现打款,切换即时生效、无需重启。
|
||||
</p>
|
||||
|
||||
{loading ? (
|
||||
<p className="shop-records-empty">加载中…</p>
|
||||
) : items.length === 0 ? (
|
||||
<p className="shop-records-empty">暂无收款账户{isPrimary ? ',请新增' : ''}</p>
|
||||
) : (
|
||||
<div className="shop-records-list">
|
||||
{items.map((it) => (
|
||||
<article key={it.id} className="shop-record-card">
|
||||
<div className="shop-record-card-top">
|
||||
<div>
|
||||
<div className="shop-record-order">
|
||||
<span className="shop-record-time" style={{ margin: 0 }}>收款人</span>
|
||||
<span>{it.bankAccountName}</span>
|
||||
{it.isDefault ? (
|
||||
<span className="shop-record-badge paid">默认</span>
|
||||
) : null}
|
||||
</div>
|
||||
<p className="shop-record-time">账号: {it.bankAccountNo}</p>
|
||||
{it.bankBranch ? <p className="shop-record-time">开户行: {it.bankBranch}</p> : null}
|
||||
</div>
|
||||
</div>
|
||||
{isPrimary ? (
|
||||
<div className="shop-record-footer" style={{ display: 'flex', gap: 12 }}>
|
||||
<button type="button" className="shop-records-chip" onClick={() => startEdit(it)}>
|
||||
编辑
|
||||
</button>
|
||||
{!it.isDefault ? (
|
||||
<button type="button" className="shop-records-chip" onClick={() => void setDefault(it.id)}>
|
||||
设为默认
|
||||
</button>
|
||||
) : null}
|
||||
{!it.isDefault ? (
|
||||
<button type="button" className="shop-records-chip" onClick={() => void remove(it.id)}>
|
||||
删除
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isPrimary ? (
|
||||
<section className="shop-records-summary" style={{ marginTop: 16 }}>
|
||||
<h3 className="shop-records-list-title">
|
||||
{editing ? '编辑账户' : '新增账户'}
|
||||
</h3>
|
||||
<div className="shop-mine-info-card">
|
||||
<label className="shop-record-amount-label">
|
||||
收款人 <span style={{ color: 'red' }}>*</span>
|
||||
</label>
|
||||
<input
|
||||
className="shop-withdraw-input"
|
||||
value={form.bankAccountName}
|
||||
maxLength={64}
|
||||
placeholder="户名 / 收款人姓名"
|
||||
onChange={(e) => setForm({ ...form, bankAccountName: e.target.value })}
|
||||
/>
|
||||
{fieldErrors.bankAccountName ? (
|
||||
<p className="shop-withdraw-msg" role="alert">{fieldErrors.bankAccountName}</p>
|
||||
) : null}
|
||||
|
||||
<label className="shop-record-amount-label" style={{ marginTop: 12 }}>
|
||||
银行账号 <span style={{ color: 'red' }}>*</span>
|
||||
</label>
|
||||
<input
|
||||
className="shop-withdraw-input"
|
||||
inputMode="numeric"
|
||||
value={form.bankAccountNo}
|
||||
maxLength={32}
|
||||
placeholder="请输入银行账号"
|
||||
onChange={(e) => setForm({ ...form, bankAccountNo: e.target.value.replace(/[^\d]/g, '') })}
|
||||
/>
|
||||
{fieldErrors.bankAccountNo ? (
|
||||
<p className="shop-withdraw-msg" role="alert">{fieldErrors.bankAccountNo}</p>
|
||||
) : null}
|
||||
|
||||
<label className="shop-record-amount-label" style={{ marginTop: 12 }}>
|
||||
开户行名称
|
||||
</label>
|
||||
<input
|
||||
className="shop-withdraw-input"
|
||||
value={form.bankBranch}
|
||||
maxLength={128}
|
||||
placeholder="如 中国工商银行郑州分行"
|
||||
onChange={(e) => setForm({ ...form, bankBranch: e.target.value })}
|
||||
/>
|
||||
|
||||
<div style={{ display: 'flex', gap: 8, marginTop: 16 }}>
|
||||
<button
|
||||
type="button"
|
||||
className="shop-withdraw-btn"
|
||||
disabled={submitting}
|
||||
onClick={() => void submit()}
|
||||
>
|
||||
{submitting ? '保存中…' : '保存'}
|
||||
</button>
|
||||
{editing ? (
|
||||
<button
|
||||
type="button"
|
||||
className="shop-records-chip"
|
||||
onClick={startCreate}
|
||||
>
|
||||
取消编辑
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<p className="shop-records-empty">仅主账号可维护收款账户</p>
|
||||
)}
|
||||
|
||||
{msg ? <p className="shop-withdraw-msg">{msg}</p> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -153,6 +153,12 @@ export default function MinePage() {
|
||||
<span className="material-symbols-outlined" style={{ fontSize: 20 }}>account_balance_wallet</span>
|
||||
结算提现
|
||||
</button>
|
||||
{profile?.isPrimary ? (
|
||||
<button type="button" className="shop-mine-action" onClick={() => navigate('/bank-accounts')}>
|
||||
<span className="material-symbols-outlined" style={{ fontSize: 20 }}>account_balance</span>
|
||||
收款账户
|
||||
</button>
|
||||
) : null}
|
||||
{profile?.isPrimary ? (
|
||||
<button type="button" className="shop-mine-action" onClick={() => navigate('/packages')}>
|
||||
<span className="material-symbols-outlined" style={{ fontSize: 20 }}>restaurant_menu</span>
|
||||
|
||||
@@ -1923,6 +1923,18 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.shop-withdraw-input {
|
||||
width: 100%;
|
||||
margin-top: 6px;
|
||||
height: 42px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid var(--color-border, #e5e5e5);
|
||||
border-radius: 10px;
|
||||
font-size: 15px;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ─── 休息中核销·开张确认弹窗 ─── */
|
||||
.shop-redeem-modal {
|
||||
position: fixed;
|
||||
|
||||
@@ -32,7 +32,7 @@ type EnterOptionsLike = {
|
||||
function normalizeAssocScene(raw: unknown): string | null {
|
||||
if (raw == null || raw === '') return null;
|
||||
const s = safeDecode(String(raw)).trim();
|
||||
return /^pa_\d+$/.test(s) ? s : null;
|
||||
return /^(pa|sa)_\d+$/.test(s) ? s : null;
|
||||
}
|
||||
|
||||
function extractAssocSceneFromEnterOptions(opts?: EnterOptionsLike | null): string | null {
|
||||
|
||||
@@ -17,6 +17,22 @@ export function storeStarCount(rating?: number | string | null): number {
|
||||
return Math.min(5, Math.max(1, Math.round(n)));
|
||||
}
|
||||
|
||||
/** 省市区县 + 详细地址 拼接;空则回退占位文案 */
|
||||
export function fullStoreAddress(
|
||||
store: {
|
||||
province?: string | null;
|
||||
cityName?: string | null;
|
||||
city?: string | null;
|
||||
district?: string | null;
|
||||
address?: string | null;
|
||||
},
|
||||
fallback = '地址待完善',
|
||||
): string {
|
||||
const city = store.cityName || store.city || '';
|
||||
const text = `${store.province || ''}${city}${store.district || ''}${store.address || ''}`.trim();
|
||||
return text || fallback;
|
||||
}
|
||||
|
||||
export function storeCategoryTags(
|
||||
store: {
|
||||
tags?: unknown;
|
||||
|
||||
@@ -42,7 +42,7 @@ import {
|
||||
toWeappShareTimeline,
|
||||
} from '../../lib/wechat-share';
|
||||
import BenefitSloganBar from '../../components/BenefitSloganBar';
|
||||
import { storeCategoryTags, storeLeafCategoryIds, storeStarCount } from '../../lib/store-display';
|
||||
import { fullStoreAddress, storeCategoryTags, storeLeafCategoryIds, storeStarCount } from '../../lib/store-display';
|
||||
import openBadgeImg from '../../assets/icons/store-open-badge.png';
|
||||
|
||||
type Store = {
|
||||
@@ -317,7 +317,12 @@ export default function StoresPage() {
|
||||
if (!matchesCategory(s)) return false;
|
||||
if (!keyword.trim()) return true;
|
||||
const q = keyword.trim();
|
||||
return s.name.includes(q) || (s.address ?? '').includes(q) || (s.district ?? '').includes(q);
|
||||
return (
|
||||
s.name.includes(q) ||
|
||||
(s.address ?? '').includes(q) ||
|
||||
(s.district ?? '').includes(q) ||
|
||||
fullStoreAddress(s, '').includes(q)
|
||||
);
|
||||
});
|
||||
const next = [...list];
|
||||
next.sort((a, b) => {
|
||||
@@ -498,7 +503,7 @@ export default function StoresPage() {
|
||||
</View>
|
||||
<View className="store-card-row store-card-row--mid">
|
||||
<Text className="store-card-address" numberOfLines={2}>
|
||||
{s.address || (s.district ? `${s.district}` : '地址待完善')}
|
||||
{fullStoreAddress(s)}
|
||||
</Text>
|
||||
<Text className="store-card-distance">
|
||||
{formatDistanceMeters(s.distanceMeters)}
|
||||
|
||||
Reference in New Issue
Block a user