76375eff83
Allow HQ to replace/delete store photos; support multi-image packages with a 20-image cap. Co-authored-by: Cursor <cursoragent@cursor.com>
156 lines
4.9 KiB
TypeScript
156 lines
4.9 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import type { StorePackagesResponse } from '@dukang/shared-types';
|
|
import { normalizeStorePackageImageUrls } from '@dukang/shared-types';
|
|
import PullToRefresh from '@dukang/shared-ui/PullToRefresh';
|
|
import ShopPackagesForm from '../components/ShopPackagesForm';
|
|
import { request } from '../lib/api';
|
|
import { useStorePageView } from '../lib/usePageView';
|
|
import {
|
|
emptyPackage,
|
|
normalizePackageFormItems,
|
|
validatePackageFormItems,
|
|
type PackageFormItem,
|
|
} from '../lib/storePackages';
|
|
|
|
export default function PackagesPage() {
|
|
useStorePageView('store_packages_view');
|
|
const navigate = useNavigate();
|
|
const [items, setItems] = useState<PackageFormItem[]>([emptyPackage()]);
|
|
const [pending, setPending] = useState<StorePackagesResponse['pendingRequest']>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [msg, setMsg] = useState('');
|
|
|
|
async function load() {
|
|
setLoading(true);
|
|
try {
|
|
const data = await request<StorePackagesResponse>('SHOP_H5', '/shop/store/packages');
|
|
const base = data.pendingRequest?.packages?.length
|
|
? data.pendingRequest.packages
|
|
: data.live?.length
|
|
? data.live
|
|
: [emptyPackage()];
|
|
setItems(
|
|
base.map((p, i) => {
|
|
const imageUrls = normalizeStorePackageImageUrls(p);
|
|
return {
|
|
...p,
|
|
price: String(p.price),
|
|
imageUrl: imageUrls[0] ?? '',
|
|
imageUrls,
|
|
sortOrder: i,
|
|
};
|
|
}),
|
|
);
|
|
setPending(data.pendingRequest ?? null);
|
|
setMsg('');
|
|
} catch (e) {
|
|
setMsg(e instanceof Error ? e.message : '加载失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
void load();
|
|
}, []);
|
|
|
|
async function submitAudit() {
|
|
const validationMsg = validatePackageFormItems(items);
|
|
if (validationMsg) {
|
|
setMsg(validationMsg);
|
|
return;
|
|
}
|
|
if (pending?.status === 'PENDING') {
|
|
setMsg('已有套餐变更审核中,请等待总部处理');
|
|
return;
|
|
}
|
|
setSubmitting(true);
|
|
setMsg('');
|
|
try {
|
|
const packages = normalizePackageFormItems(items).map((p) => ({
|
|
...p,
|
|
price: Number(p.price).toFixed(2),
|
|
}));
|
|
await request('SHOP_H5', '/shop/store/packages', {
|
|
method: 'PUT',
|
|
body: JSON.stringify({ packages }),
|
|
});
|
|
setMsg('已提交审核,请等待总部处理');
|
|
await load();
|
|
} catch (e) {
|
|
setMsg(e instanceof Error ? e.message : '提交失败');
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
}
|
|
|
|
const disabled = pending?.status === 'PENDING' || submitting;
|
|
|
|
return (
|
|
<PullToRefresh onRefresh={load} className="shop-records-page shop-packages-page">
|
|
<header className="shop-records-header" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
<button
|
|
type="button"
|
|
className="shop-withdraw-back"
|
|
onClick={() => navigate('/mine')}
|
|
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">
|
|
{loading ? <p className="shop-records-empty">加载中…</p> : null}
|
|
|
|
{!loading && pending?.status === 'PENDING' ? (
|
|
<section className="shop-packages-notice">
|
|
<span className="material-symbols-outlined shop-fill-icon" style={{ fontSize: 18 }}>
|
|
hourglass_top
|
|
</span>
|
|
<p>套餐变更审核中,用户端仍展示上一版生效套餐。</p>
|
|
</section>
|
|
) : null}
|
|
|
|
{!loading && pending?.status === 'REJECTED' && pending.rejectReason ? (
|
|
<section className="shop-packages-notice shop-packages-notice--warn">
|
|
<span className="material-symbols-outlined shop-fill-icon" style={{ fontSize: 18 }}>
|
|
error
|
|
</span>
|
|
<p>上次驳回:{pending.rejectReason}</p>
|
|
</section>
|
|
) : null}
|
|
|
|
{!loading ? (
|
|
<>
|
|
<p className="shop-packages-intro">
|
|
维护门店可核销套餐信息,提交后由总部审核通过后在用户端展示。
|
|
</p>
|
|
<ShopPackagesForm items={items} onChange={setItems} disabled={disabled} />
|
|
</>
|
|
) : null}
|
|
|
|
{msg ? <p className="shop-withdraw-msg">{msg}</p> : null}
|
|
|
|
{!loading ? (
|
|
<button
|
|
type="button"
|
|
className="shop-withdraw-btn"
|
|
style={{ marginTop: 16 }}
|
|
disabled={disabled}
|
|
onClick={() => void submitAudit()}
|
|
>
|
|
{submitting ? '提交中…' : '提交审核'}
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
</PullToRefresh>
|
|
);
|
|
}
|