feat: 套餐折叠、小程序 staging API、首页去筛选、企微日志权限

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-03 20:17:52 +08:00
parent ac2f9f4793
commit ffa753707b
9 changed files with 558 additions and 261 deletions
@@ -1,3 +1,4 @@
import { useState } from 'react';
import type { PackageFormItem } from '../lib/storePackages';
import { STORE_PACKAGE_MAX_COUNT } from '@dukang/shared-types';
import { emptyPackage } from '../lib/storePackages';
@@ -11,6 +12,8 @@ type Props = {
};
export default function StorePackagesForm({ items, onChange, disabled, embedded }: Props) {
const [collapsed, setCollapsed] = useState<Record<number, boolean>>({});
function updateAt(index: number, patch: Partial<PackageFormItem>) {
const next = items.map((item, i) => (i === index ? { ...item, ...patch } : item));
onChange(next);
@@ -23,97 +26,126 @@ export default function StorePackagesForm({ items, onChange, disabled, embedded
function removeAt(index: number) {
onChange(items.filter((_, i) => i !== index).map((item, i) => ({ ...item, sortOrder: i })));
setCollapsed((prev) => {
const next: Record<number, boolean> = {};
Object.entries(prev).forEach(([k, v]) => {
const i = Number(k);
if (i < index) next[i] = v;
else if (i > index) next[i - 1] = v;
});
return next;
});
}
function toggleCollapse(index: number) {
setCollapsed((prev) => ({ ...prev, [index]: !prev[index] }));
}
const list = items.length ? items : [emptyPackage(0)];
return (
<div className={`partner-packages-form${embedded ? ' partner-packages-form--embedded' : ''}`}>
{list.map((item, index) => (
<section
key={index}
className="partner-form-card partner-packages-item"
style={embedded ? { marginLeft: 0, marginRight: 0 } : undefined}
>
<div className="partner-packages-item-head">
<div className="partner-section-title" style={{ marginBottom: 0 }}>
<div className="partner-section-bar" />
<h2 className="headline-md"> {index + 1}</h2>
</div>
{!disabled && list.length > 1 ? (
<button type="button" className="partner-packages-remove" onClick={() => removeAt(index)}>
{list.map((item, index) => {
const isCollapsed = !!collapsed[index];
const displayName = item.name.trim() || `套餐 ${index + 1}`;
return (
<section
key={index}
className={`partner-form-card partner-packages-item${isCollapsed ? ' partner-packages-item--collapsed' : ''}`}
style={embedded ? { marginLeft: 0, marginRight: 0 } : undefined}
>
<div className="partner-packages-item-head">
<button
type="button"
className="partner-packages-toggle"
onClick={() => toggleCollapse(index)}
aria-expanded={!isCollapsed}
aria-label={isCollapsed ? '展开套餐' : '收起套餐'}
>
<span className="material-symbols-outlined">
{isCollapsed ? 'expand_more' : 'expand_less'}
</span>
<span className="headline-md">{displayName}</span>
</button>
{!disabled && list.length > 1 ? (
<button type="button" className="partner-packages-remove" onClick={() => removeAt(index)}>
</button>
) : null}
</div>
{!isCollapsed ? (
<>
<div className="partner-field">
<label> <span className="text-primary">*</span></label>
<div className="partner-field-input">
<span className="material-symbols-outlined">restaurant</span>
<input
placeholder="如:套餐A"
value={item.name}
disabled={disabled}
onChange={(e) => updateAt(index, { name: e.target.value })}
/>
</div>
</div>
<div className="partner-field">
<label> <span className="text-primary">*</span></label>
<div className="partner-field-input">
<span className="material-symbols-outlined">payments</span>
<input
type="number"
min={0}
step={0.01}
placeholder="198"
value={item.price}
disabled={disabled}
onChange={(e) => updateAt(index, { price: e.target.value })}
/>
</div>
</div>
<div className="partner-field">
<label> <span className="text-primary">*</span></label>
<textarea
rows={3}
placeholder="红烧肉、红烧鱼、油焖茄子"
value={item.dishes}
disabled={disabled}
onChange={(e) => updateAt(index, { dishes: e.target.value })}
/>
</div>
<div className="partner-field">
<label>使</label>
<div className="partner-field-input">
<span className="material-symbols-outlined">schedule</span>
<input
placeholder="节假日除外"
value={item.usableTime || ''}
disabled={disabled}
onChange={(e) => updateAt(index, { usableTime: e.target.value })}
/>
</div>
</div>
<div className="partner-field">
<label></label>
<div className="partner-field-input">
<span className="material-symbols-outlined">info</span>
<input
placeholder="不可叠加"
value={item.otherNotes || ''}
disabled={disabled}
onChange={(e) => updateAt(index, { otherNotes: e.target.value })}
/>
</div>
</div>
</>
) : null}
</div>
<div className="partner-field">
<label> <span className="text-primary">*</span></label>
<div className="partner-field-input">
<span className="material-symbols-outlined">restaurant</span>
<input
placeholder="如:套餐A"
value={item.name}
disabled={disabled}
onChange={(e) => updateAt(index, { name: e.target.value })}
/>
</div>
</div>
<div className="partner-field">
<label> <span className="text-primary">*</span></label>
<div className="partner-field-input">
<span className="material-symbols-outlined">payments</span>
<input
type="number"
min={0}
step={0.01}
placeholder="198"
value={item.price}
disabled={disabled}
onChange={(e) => updateAt(index, { price: e.target.value })}
/>
</div>
</div>
<div className="partner-field">
<label> <span className="text-primary">*</span></label>
<textarea
rows={3}
placeholder="红烧肉、红烧鱼、油焖茄子"
value={item.dishes}
disabled={disabled}
onChange={(e) => updateAt(index, { dishes: e.target.value })}
/>
</div>
<div className="partner-field">
<label>使</label>
<div className="partner-field-input">
<span className="material-symbols-outlined">schedule</span>
<input
placeholder="节假日除外"
value={item.usableTime || ''}
disabled={disabled}
onChange={(e) => updateAt(index, { usableTime: e.target.value })}
/>
</div>
</div>
<div className="partner-field">
<label></label>
<div className="partner-field-input">
<span className="material-symbols-outlined">info</span>
<input
placeholder="不可叠加"
value={item.otherNotes || ''}
disabled={disabled}
onChange={(e) => updateAt(index, { otherNotes: e.target.value })}
/>
</div>
</div>
</section>
))}
</section>
);
})}
{!disabled && list.length < STORE_PACKAGE_MAX_COUNT ? (
<button type="button" className="partner-packages-add" onClick={addItem}>