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
5.3 KiB
TypeScript
156 lines
5.3 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
import type { StorePackagesResponse } from '@dukang/shared-types';
|
|
import { normalizeStorePackageImageUrls } from '@dukang/shared-types';
|
|
import StorePackagesForm from '../components/StorePackagesForm';
|
|
import { request } from '../lib/api';
|
|
import { toastError, toastSuccess } from '../lib/toast';
|
|
import {
|
|
emptyPackage,
|
|
normalizePackageFormItems,
|
|
validatePackageFormItems,
|
|
type PackageFormItem,
|
|
} from '../lib/storePackages';
|
|
|
|
export default function StorePackagesPage() {
|
|
const { id } = useParams();
|
|
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 [error, setError] = useState('');
|
|
|
|
useEffect(() => {
|
|
if (!id) return;
|
|
setLoading(true);
|
|
request<StorePackagesResponse>('PARTNER_H5', `/partner/stores/${id}/packages`)
|
|
.then((data) => {
|
|
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);
|
|
})
|
|
.catch((e) => setError(e instanceof Error ? e.message : '加载失败'))
|
|
.finally(() => setLoading(false));
|
|
}, [id]);
|
|
|
|
async function submitAudit() {
|
|
if (!id) return;
|
|
const msg = validatePackageFormItems(items);
|
|
if (msg) {
|
|
toastError(msg);
|
|
return;
|
|
}
|
|
if (pending?.status === 'PENDING') {
|
|
toastError('已有套餐变更审核中,请等待总部处理');
|
|
return;
|
|
}
|
|
setSubmitting(true);
|
|
try {
|
|
const packages = normalizePackageFormItems(items).map((p) => ({
|
|
...p,
|
|
price: Number(p.price).toFixed(2),
|
|
}));
|
|
await request('PARTNER_H5', `/partner/stores/${id}/packages`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify({ packages }),
|
|
});
|
|
toastSuccess('已提交审核');
|
|
navigate(`/stores/${id}`);
|
|
} catch (e) {
|
|
toastError(e instanceof Error ? e.message : '提交失败');
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="partner-detail-page partner-home--flush-top">
|
|
<main style={{ padding: '24px 20px' }}>
|
|
<p className="label-md text-muted">加载中…</p>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="partner-page-sticky partner-home--flush-top">
|
|
<header className="partner-packages-page-header">
|
|
<button type="button" className="partner-packages-back" onClick={() => navigate(`/stores/${id}`)}>
|
|
<span className="material-symbols-outlined">arrow_back</span>
|
|
</button>
|
|
<h1 className="headline-md">门店套餐</h1>
|
|
</header>
|
|
|
|
<main className="partner-packages-page-main">
|
|
{error ? <p className="partner-form-error" role="alert">{error}</p> : null}
|
|
|
|
{pending?.status === 'PENDING' ? (
|
|
<div className="partner-info-banner">
|
|
<span className="material-symbols-outlined" style={{ color: 'var(--color-heritage-red)' }}>hourglass_top</span>
|
|
<p className="body-md">套餐变更审核中,C 端仍展示上一版生效套餐。</p>
|
|
</div>
|
|
) : null}
|
|
|
|
{pending?.status === 'REJECTED' && pending.rejectReason ? (
|
|
<div className="partner-info-banner" style={{ background: 'rgba(180,35,24,0.06)', borderColor: 'rgba(180,35,24,0.15)' }}>
|
|
<span className="material-symbols-outlined" style={{ color: 'var(--color-heritage-red)' }}>error</span>
|
|
<p className="body-md">上次驳回:{pending.rejectReason}</p>
|
|
</div>
|
|
) : null}
|
|
|
|
<section className="partner-form-card">
|
|
<div className="partner-section-title">
|
|
<div className="partner-section-bar" />
|
|
<h2 className="headline-md">套餐信息</h2>
|
|
</div>
|
|
<p className="label-md text-muted" style={{ marginBottom: 16 }}>
|
|
提交后将由总部审核,通过后 C 端展示生效套餐。
|
|
</p>
|
|
<StorePackagesForm
|
|
items={items}
|
|
onChange={setItems}
|
|
disabled={pending?.status === 'PENDING' || submitting}
|
|
embedded
|
|
/>
|
|
</section>
|
|
</main>
|
|
|
|
<footer className="partner-sticky-footer">
|
|
<button
|
|
type="button"
|
|
className="partner-btn-outline"
|
|
onClick={() => navigate(`/stores/${id}`)}
|
|
disabled={submitting}
|
|
>
|
|
返回
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="partner-btn-primary"
|
|
disabled={submitting || pending?.status === 'PENDING'}
|
|
onClick={() => void submitAudit()}
|
|
>
|
|
{submitting ? '提交中…' : '提交审核'}
|
|
</button>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|