feat: 门店费用分析模块 + 成本分析筛选排序功能
- 新增门店费用分析模块(9个Tab组件 + 后端路由) - 门店贡献利润、房租租约风险、外卖佣金分析、人效坪效 - 固定/变动费用、盈亏平衡分析、亏损门店诊断 - 关停/续租/改造评估、门店排名 - 门店费用分析9个Tab均支持筛选、排序、正倒序 - 成本分析8个Tab添加筛选、排序、正倒序功能 - 后端10个端点添加filter/sort/order参数支持 - 前端8个组件添加UI控件 - 修复门店评估分类逻辑和日期格式化 - 修复盈亏平衡销售额计算(贡献毛利率≤0时设为NULL)
This commit is contained in:
@@ -37,12 +37,16 @@ export function AdjustmentTab() {
|
||||
const [adjPage, setAdjPage] = useState(1)
|
||||
const [diagPriority, setDiagPriority] = useState('')
|
||||
const [adjStatus, setAdjStatus] = useState('')
|
||||
const [diagSort, setDiagSort] = useState('priority')
|
||||
const [diagOrder, setDiagOrder] = useState('asc')
|
||||
const [adjSort, setAdjSort] = useState('effective_date')
|
||||
const [adjOrder, setAdjOrder] = useState('desc')
|
||||
const [showForm, setShowForm] = useState(false)
|
||||
const [verifyId, setVerifyId] = useState<number | null>(null)
|
||||
const [formData, setFormData] = useState<any>({})
|
||||
|
||||
const { data: diag, isLoading: ld } = useQuery({ queryKey: ['ca/diagnosis', diagPage, diagPriority], queryFn: () => api.get(`/cost-analysis/diagnosis?page=${diagPage}&page_size=${PAGE_SIZE}${diagPriority ? `&priority=${diagPriority}` : ''}`) })
|
||||
const { data: adj, isLoading: la } = useQuery({ queryKey: ['ca/adjustment', adjPage, adjStatus], queryFn: () => api.get(`/cost-analysis/adjustment?page=${adjPage}&page_size=${PAGE_SIZE}${adjStatus ? `&status=${adjStatus}` : ''}`) })
|
||||
const { data: diag, isLoading: ld } = useQuery({ queryKey: ['ca/diagnosis', diagPage, diagPriority, diagSort, diagOrder], queryFn: () => api.get(`/cost-analysis/diagnosis?page=${diagPage}&page_size=${PAGE_SIZE}&sort=${diagSort}&order=${diagOrder}${diagPriority ? `&priority=${diagPriority}` : ''}`) })
|
||||
const { data: adj, isLoading: la } = useQuery({ queryKey: ['ca/adjustment', adjPage, adjStatus, adjSort, adjOrder], queryFn: () => api.get(`/cost-analysis/adjustment?page=${adjPage}&page_size=${PAGE_SIZE}&sort=${adjSort}&order=${adjOrder}${adjStatus ? `&status=${adjStatus}` : ''}`) })
|
||||
const { data: verify, isLoading: lv } = useQuery({ queryKey: ['ca/adjustment/verify', verifyId], queryFn: () => api.get(`/cost-analysis/adjustment/${verifyId}/verify`), enabled: verifyId !== null })
|
||||
|
||||
const generateDiagnosis = useMutation({
|
||||
@@ -120,6 +124,19 @@ export function AdjustmentTab() {
|
||||
<option value="P2">P2</option>
|
||||
<option value="P3">P3</option>
|
||||
</select>
|
||||
<span className="mx-2 text-muted-foreground">|</span>
|
||||
{[
|
||||
{ key: 'priority', label: '优先级' },
|
||||
{ key: 'cost_variance_amount', label: '成本差异' },
|
||||
{ key: 'sales_amount', label: '销售额' },
|
||||
{ key: 'actual_margin_pct', label: '实际毛利率' },
|
||||
].map(s => (
|
||||
<button key={s.key} onClick={() => { setDiagSort(s.key); setDiagPage(1) }} className={`px-3 py-1 rounded text-xs ${diagSort === s.key ? 'bg-primary text-primary-foreground' : 'bg-muted'}`}>按{s.label}</button>
|
||||
))}
|
||||
<span className="mx-2 text-muted-foreground">|</span>
|
||||
<button onClick={() => { setDiagOrder(diagOrder === 'asc' ? 'desc' : 'asc'); setDiagPage(1) }} className="px-3 py-1 rounded text-xs bg-muted">
|
||||
{diagOrder === 'asc' ? '↑ 升序' : '↓ 降序'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3 md:grid-cols-4">
|
||||
@@ -189,7 +206,7 @@ export function AdjustmentTab() {
|
||||
|
||||
{subTab === 'adjustment' && (
|
||||
<>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<select value={adjStatus} onChange={(e) => { setAdjStatus(e.target.value); setAdjPage(1) }} className="rounded border px-2 py-1.5 text-sm">
|
||||
<option value="">全部状态</option>
|
||||
<option value="planned">待执行</option>
|
||||
@@ -197,6 +214,18 @@ export function AdjustmentTab() {
|
||||
<option value="completed">已完成</option>
|
||||
<option value="cancelled">已取消</option>
|
||||
</select>
|
||||
<span className="mx-2 text-muted-foreground">|</span>
|
||||
{[
|
||||
{ key: 'effective_date', label: '执行日期' },
|
||||
{ key: 'dish_name', label: '菜品名' },
|
||||
{ key: 'adjustment_type', label: '调整类型' },
|
||||
].map(s => (
|
||||
<button key={s.key} onClick={() => { setAdjSort(s.key); setAdjPage(1) }} className={`px-3 py-1 rounded text-xs ${adjSort === s.key ? 'bg-primary text-primary-foreground' : 'bg-muted'}`}>按{s.label}</button>
|
||||
))}
|
||||
<span className="mx-2 text-muted-foreground">|</span>
|
||||
<button onClick={() => { setAdjOrder(adjOrder === 'asc' ? 'desc' : 'asc'); setAdjPage(1) }} className="px-3 py-1 rounded text-xs bg-muted">
|
||||
{adjOrder === 'asc' ? '↑ 升序' : '↓ 降序'}
|
||||
</button>
|
||||
</div>
|
||||
{la ? <LoadingSpinner text="加载调整记录..." /> : (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user