style(store): 统一门店套餐页 UI 与拓店校验弹窗
管理后台套餐 Tab 对齐 Ant Design 表单;合伙人/门店端套餐表单与基本信息页风格一致;拓店下一步校验增加弹窗提示。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { StorePackagesResponse } from '@dukang/shared-types';
|
||||
import { STORE_PACKAGE_MAX_COUNT } from '@dukang/shared-types';
|
||||
import PullToRefresh from '@dukang/shared-ui/PullToRefresh';
|
||||
import ShopPackagesForm from '../components/ShopPackagesForm';
|
||||
import { request } from '../lib/api';
|
||||
import {
|
||||
emptyPackage,
|
||||
@@ -16,49 +17,43 @@ export default function PackagesPage() {
|
||||
const [pending, setPending] = useState<StorePackagesResponse['pendingRequest']>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
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) => ({ ...p, price: String(p.price), sortOrder: i })));
|
||||
setPending(data.pendingRequest ?? null);
|
||||
setMsg('');
|
||||
} catch (e) {
|
||||
setMsg(e instanceof Error ? e.message : '加载失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
request<StorePackagesResponse>('SHOP_H5', '/shop/store/packages')
|
||||
.then((data) => {
|
||||
const base = data.pendingRequest?.packages?.length
|
||||
? data.pendingRequest.packages
|
||||
: data.live?.length
|
||||
? data.live
|
||||
: [emptyPackage()];
|
||||
setItems(base.map((p, i) => ({ ...p, price: String(p.price), sortOrder: i })));
|
||||
setPending(data.pendingRequest ?? null);
|
||||
})
|
||||
.catch((e) => setError(e instanceof Error ? e.message : '加载失败'))
|
||||
.finally(() => setLoading(false));
|
||||
void load();
|
||||
}, []);
|
||||
|
||||
function updateAt(index: number, patch: Partial<PackageFormItem>) {
|
||||
setItems((prev) => prev.map((item, i) => (i === index ? { ...item, ...patch } : item)));
|
||||
}
|
||||
|
||||
function addItem() {
|
||||
if (items.length >= STORE_PACKAGE_MAX_COUNT) return;
|
||||
setItems((prev) => [...prev, emptyPackage(prev.length)]);
|
||||
}
|
||||
|
||||
function removeAt(index: number) {
|
||||
setItems((prev) => prev.filter((_, i) => i !== index).map((item, i) => ({ ...item, sortOrder: i })));
|
||||
}
|
||||
|
||||
async function submitAudit() {
|
||||
const msg = validatePackageFormItems(items);
|
||||
if (msg) {
|
||||
setError(msg);
|
||||
const validationMsg = validatePackageFormItems(items);
|
||||
if (validationMsg) {
|
||||
setMsg(validationMsg);
|
||||
return;
|
||||
}
|
||||
if (pending?.status === 'PENDING') {
|
||||
setError('已有套餐变更审核中,请等待总部处理');
|
||||
setMsg('已有套餐变更审核中,请等待总部处理');
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
setError('');
|
||||
setMsg('');
|
||||
try {
|
||||
const packages = normalizePackageFormItems(items).map((p) => ({
|
||||
...p,
|
||||
@@ -68,105 +63,79 @@ export default function PackagesPage() {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ packages }),
|
||||
});
|
||||
navigate('/mine');
|
||||
setMsg('已提交审核,请等待总部处理');
|
||||
await load();
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : '提交失败');
|
||||
setMsg(e instanceof Error ? e.message : '提交失败');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
const list = items.length ? items : [emptyPackage()];
|
||||
const disabled = pending?.status === 'PENDING' || submitting;
|
||||
|
||||
return (
|
||||
<div className="shop-page">
|
||||
<header className="shop-subheader">
|
||||
<button type="button" className="shop-back" onClick={() => navigate('/mine')}>
|
||||
返回
|
||||
<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>门店套餐</h1>
|
||||
<h1 className="app-page-title" style={{ margin: 0 }}>
|
||||
门店套餐
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
{loading ? <p className="shop-muted">加载中…</p> : null}
|
||||
{error ? <p className="shop-error">{error}</p> : null}
|
||||
{pending?.status === 'PENDING' ? (
|
||||
<p className="shop-hint">套餐变更审核中,用户端仍展示上一版生效套餐。</p>
|
||||
) : null}
|
||||
<div className="shop-records-main">
|
||||
{loading ? <p className="shop-records-empty">加载中…</p> : null}
|
||||
|
||||
{!loading
|
||||
? list.map((item, index) => (
|
||||
<div key={index} className="shop-card" style={{ marginBottom: 12 }}>
|
||||
<div className="shop-card-head">
|
||||
<strong>套餐 {index + 1}</strong>
|
||||
{!disabled && list.length > 1 ? (
|
||||
<button type="button" onClick={() => removeAt(index)}>
|
||||
删除
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
<label>
|
||||
名称
|
||||
<input
|
||||
value={item.name}
|
||||
disabled={disabled}
|
||||
onChange={(e) => updateAt(index, { name: e.target.value })}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
价格(元)
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
value={item.price}
|
||||
disabled={disabled}
|
||||
onChange={(e) => updateAt(index, { price: e.target.value })}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
菜品
|
||||
<textarea
|
||||
rows={2}
|
||||
value={item.dishes}
|
||||
disabled={disabled}
|
||||
onChange={(e) => updateAt(index, { dishes: e.target.value })}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
使用时间
|
||||
<input
|
||||
value={item.usableTime || ''}
|
||||
disabled={disabled}
|
||||
onChange={(e) => updateAt(index, { usableTime: e.target.value })}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
其他说明
|
||||
<input
|
||||
value={item.otherNotes || ''}
|
||||
disabled={disabled}
|
||||
onChange={(e) => updateAt(index, { otherNotes: e.target.value })}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
))
|
||||
: 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}
|
||||
|
||||
{!disabled && list.length < STORE_PACKAGE_MAX_COUNT ? (
|
||||
<button type="button" className="shop-btn-outline" onClick={addItem}>
|
||||
+ 添加套餐
|
||||
</button>
|
||||
) : 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}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="shop-btn-primary"
|
||||
style={{ marginTop: 16 }}
|
||||
disabled={disabled}
|
||||
onClick={() => void submitAudit()}
|
||||
>
|
||||
{submitting ? '提交中…' : '提交审核'}
|
||||
</button>
|
||||
</div>
|
||||
{!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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user