feat;提交管理端和城市合伙人端
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { View, Text, Image, Button } from '@tarojs/components';
|
||||
import Taro, { useRouter } from '@tarojs/taro';
|
||||
import HqHeader from '../../components/HqHeader';
|
||||
import { request, toast } from '../../lib/api';
|
||||
import { STORE_STATUS_LABELS, badgeClass, fmtTime } from '../../lib/constants';
|
||||
|
||||
type StoreDetail = {
|
||||
id: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
status: string;
|
||||
province?: string;
|
||||
cityName?: string;
|
||||
district?: string;
|
||||
address?: string;
|
||||
intro?: string | null;
|
||||
coverUrl?: string | null;
|
||||
redeemCount?: number;
|
||||
createdAt: string;
|
||||
partner?: { companyName: string };
|
||||
account?: { name: string; phone: string };
|
||||
};
|
||||
|
||||
const ACTIONS: Array<{ status: string; label: string }> = [
|
||||
{ status: 'OPEN', label: '通过 / 营业' },
|
||||
{ status: 'PAUSED', label: '暂停营业' },
|
||||
{ status: 'CLOSED', label: '关闭门店' },
|
||||
];
|
||||
|
||||
export default function StoreDetailPage() {
|
||||
const router = useRouter();
|
||||
const id = router.params.id;
|
||||
const [store, setStore] = useState<StoreDetail | null>(null);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
function load() {
|
||||
if (!id) return;
|
||||
request<StoreDetail>(`/admin/stores/${id}`).then(setStore).catch(() => undefined);
|
||||
}
|
||||
|
||||
useEffect(load, [id]);
|
||||
|
||||
async function changeStatus(status: string) {
|
||||
if (!id || saving) return;
|
||||
setSaving(true);
|
||||
try {
|
||||
await request(`/admin/stores/${id}/status`, { method: 'PUT', data: { status } });
|
||||
toast('状态已更新', 'success');
|
||||
setStore((s) => (s ? { ...s, status } : s));
|
||||
} catch (e) {
|
||||
toast(e instanceof Error ? e.message : '更新失败');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="hq-page" style="padding-bottom:calc(96px + var(--hq-safe-bottom))">
|
||||
<HqHeader title="门店详情" back />
|
||||
|
||||
{!store && <View className="hq-empty">加载中…</View>}
|
||||
|
||||
{store && (
|
||||
<>
|
||||
{store.coverUrl ? (
|
||||
<Image className="store-cover" src={store.coverUrl} mode="aspectFill" />
|
||||
) : null}
|
||||
|
||||
<View className="hq-card">
|
||||
<View className="hq-row">
|
||||
<Text style="font-size:18px;font-weight:700">{store.name}</Text>
|
||||
<Text className={`hq-badge ${badgeClass(store.status)}`}>
|
||||
{STORE_STATUS_LABELS[store.status] || store.status}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className="hq-muted" style="display:block;margin-top:8px;font-size:13px">
|
||||
{store.province || ''}{store.cityName || ''}{store.district || ''}{store.address || ''}
|
||||
</Text>
|
||||
<Text className="hq-muted" style="display:block;margin-top:4px;font-size:13px">联系电话:{store.phone}</Text>
|
||||
{store.intro ? (
|
||||
<Text style="display:block;margin-top:8px;font-size:13px;line-height:1.6">{store.intro}</Text>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
<View className="hq-card">
|
||||
<View className="hq-row" style="margin-bottom:8px">
|
||||
<Text className="hq-muted" style="font-size:13px">开城合伙人</Text>
|
||||
<Text style="font-size:14px">{store.partner?.companyName || '—'}</Text>
|
||||
</View>
|
||||
<View className="hq-row" style="margin-bottom:8px">
|
||||
<Text className="hq-muted" style="font-size:13px">店长</Text>
|
||||
<Text style="font-size:14px">{store.account?.name || '—'} {store.account?.phone || ''}</Text>
|
||||
</View>
|
||||
<View className="hq-row" style="margin-bottom:8px">
|
||||
<Text className="hq-muted" style="font-size:13px">累计核销</Text>
|
||||
<Text style="font-size:14px">{store.redeemCount ?? 0} 笔</Text>
|
||||
</View>
|
||||
<View className="hq-row">
|
||||
<Text className="hq-muted" style="font-size:13px">创建时间</Text>
|
||||
<Text style="font-size:14px">{fmtTime(store.createdAt)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text className="hq-section-title">审核操作</Text>
|
||||
<View className="hq-card" style="display:flex;flex-direction:column;gap:10px">
|
||||
{ACTIONS.map((a) => (
|
||||
<Button
|
||||
key={a.status}
|
||||
className={`hq-btn hq-btn--block ${a.status === 'OPEN' ? 'hq-btn--primary' : 'hq-btn--outline'}`}
|
||||
disabled={saving || store.status === a.status}
|
||||
onClick={() => changeStatus(a.status)}
|
||||
>
|
||||
{a.label}
|
||||
</Button>
|
||||
))}
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { View, Text, ScrollView } from '@tarojs/components';
|
||||
import Taro, { useDidShow } from '@tarojs/taro';
|
||||
import HqHeader from '../../components/HqHeader';
|
||||
import HqTabBar from '../../components/HqTabBar';
|
||||
import { request, type Paginated } from '../../lib/api';
|
||||
import { useHqSession } from '../../lib/session';
|
||||
import { STORE_STATUS_LABELS, badgeClass, fmtTime } from '../../lib/constants';
|
||||
|
||||
type StoreRow = {
|
||||
id: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
status: string;
|
||||
cityName?: string;
|
||||
createdAt: string;
|
||||
partner?: { companyName: string };
|
||||
};
|
||||
|
||||
const TABS = [
|
||||
{ key: '', label: '全部' },
|
||||
{ key: 'OPEN', label: '营业中' },
|
||||
{ key: 'PAUSED', label: '暂停' },
|
||||
{ key: 'CLOSED', label: '已关闭' },
|
||||
];
|
||||
|
||||
export default function StoresPage() {
|
||||
useHqSession();
|
||||
const [status, setStatus] = useState('');
|
||||
const [rows, setRows] = useState<StoreRow[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
function load() {
|
||||
setLoading(true);
|
||||
const qs = new URLSearchParams({ pageSize: '50' });
|
||||
if (status) qs.set('status', status);
|
||||
request<Paginated<StoreRow>>(`/admin/stores?${qs.toString()}`)
|
||||
.then((d) => {
|
||||
setRows(d.items);
|
||||
setTotal(d.total);
|
||||
})
|
||||
.catch(() => setRows([]))
|
||||
.finally(() => setLoading(false));
|
||||
}
|
||||
|
||||
useEffect(load, [status]);
|
||||
useDidShow(load);
|
||||
|
||||
return (
|
||||
<View className="hq-page hq-page--tab">
|
||||
<HqHeader title="门店审核" />
|
||||
|
||||
<ScrollView scrollX enhanced showScrollbar={false} className="hq-tabs">
|
||||
{TABS.map((t) => (
|
||||
<View
|
||||
key={t.key}
|
||||
className={`hq-tab${status === t.key ? ' hq-tab--active' : ''}`}
|
||||
onClick={() => setStatus(t.key)}
|
||||
>
|
||||
<Text>{t.label}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
|
||||
<Text className="hq-section-title">
|
||||
<Text>门店列表</Text>
|
||||
<Text className="hq-muted" style="font-size:12px;font-weight:400">共 {total} 家</Text>
|
||||
</Text>
|
||||
|
||||
{loading && <View className="hq-empty">加载中…</View>}
|
||||
{!loading && rows.length === 0 && <View className="hq-empty">暂无门店</View>}
|
||||
|
||||
{rows.map((s) => (
|
||||
<View
|
||||
key={s.id}
|
||||
className="hq-list-item"
|
||||
onClick={() => Taro.navigateTo({ url: `/pages/stores/detail?id=${s.id}` })}
|
||||
>
|
||||
<View className="hq-avatar">
|
||||
<Text className="material-symbols-outlined">storefront</Text>
|
||||
</View>
|
||||
<View style="flex:1;min-width:0">
|
||||
<View className="hq-row">
|
||||
<Text style="font-weight:600;font-size:15px">{s.name}</Text>
|
||||
<Text className={`hq-badge ${badgeClass(s.status)}`}>{STORE_STATUS_LABELS[s.status] || s.status}</Text>
|
||||
</View>
|
||||
<Text className="hq-muted" style="font-size:12px;display:block;margin-top:4px">
|
||||
{s.partner?.companyName || '—'} · {s.cityName || ''} · {s.phone}
|
||||
</Text>
|
||||
<Text className="hq-muted" style="font-size:11px">{fmtTime(s.createdAt)}</Text>
|
||||
</View>
|
||||
<Text className="material-symbols-outlined hq-muted">chevron_right</Text>
|
||||
</View>
|
||||
))}
|
||||
<HqTabBar selected={1} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user