import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Alert, Button, Card, Col, Drawer, Dropdown, Image, Input, Row, Select, Segmented, Space, Spin, Statistic, Table, Tabs, Tag, Tooltip, Typography, message, } from 'antd'; import { DownloadOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons'; import { SycmPage } from '../../components/Common'; import { getProductMonitoringList, getProductMonitoringFilters, getProductMonitoringOperators, } from '../../api/productMonitoring'; import { exportTableFile } from '../../utils/tableExport'; import './style.css'; const { Title, Text } = Typography; const number = (value, digits = 2) => value === null || value === undefined || value === '' ? '--' : Number(value).toLocaleString('zh-CN', { minimumFractionDigits: digits, maximumFractionDigits: digits }); const int = (value) => number(value, 0); const text = (value) => (value === null || value === undefined || value === '' ? '--' : value); const COMPANY_TAG_COLOR = { S: 'red', A: 'orange', B: 'blue', C: 'default', D: 'default' }; const DEPARTMENT_TAG_COLOR = { 'G-S': 'red', 'G-A': 'orange', 'G-B': 'blue', 'G-C': 'default' }; const STATUS_TABS = [ { key: '销售', label: '销售' }, { key: '停产', label: '停产' }, { key: '退市', label: '退市' }, { key: '__all__', label: '全部' }, ]; const toOptions = (list = []) => (Array.isArray(list) ? list : []) .filter(Boolean) .map((value) => ({ value, label: value })); const ProductMonitoringPanel = () => { const [status, setStatus] = useState('销售'); const [filters, setFilters] = useState({ category: '', spu: '', sku: '', productName: '', supplier: '', warehouse: '', purchaser: '', companyTag: [], departmentTag: [], departmentOwner: '', }); const [filterOptions, setFilterOptions] = useState({ categories: [], suppliers: [], warehouses: [], purchasers: [] }); const [data, setData] = useState({ rows: [], total: 0, page: 1, pageSize: 50, summary: {}, latestDate: null }); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(50); const [sorter, setSorter] = useState({ field: 'companySales7d', order: 'DESC' }); const [selected, setSelected] = useState(null); const [operators, setOperators] = useState([]); const [operatorsLoading, setOperatorsLoading] = useState(false); useEffect(() => { let cancelled = false; getProductMonitoringFilters() .then((result) => { const payload = result?.data || {}; if (cancelled) return; setFilterOptions({ categories: payload.categories || [], suppliers: payload.suppliers || [], warehouses: payload.warehouses || [], purchasers: payload.purchasers || [], }); }) .catch(() => {}); return () => { cancelled = true; }; }, []); const query = useMemo(() => { const params = { page, pageSize, sortField: sorter.field, sortOrder: sorter.order, }; if (status === '__all__') params.includeAll = '1'; else params.saleStatus = status; Object.entries(filters).forEach(([key, value]) => { if (Array.isArray(value)) { if (value.length) params[key] = value.join(','); } else if (value) { params[key] = value; } }); return params; }, [page, pageSize, sorter, status, filters]); const load = useCallback(async () => { setLoading(true); setError(''); try { const result = await getProductMonitoringList(query); setData(result?.data || { rows: [], total: 0, summary: {} }); } catch (requestError) { setError(requestError?.response?.data?.error || requestError?.message || '加载商品监控数据失败'); } finally { setLoading(false); } }, [query]); useEffect(() => { load(); }, [load]); const changeStatus = (key) => { setStatus(key); setPage(1); }; const changeFilter = (key) => (value) => { setFilters((old) => ({ ...old, [key]: value || '' })); setPage(1); }; const changeMultiFilter = (key) => (value) => { setFilters((old) => ({ ...old, [key]: value || [] })); setPage(1); }; const resetFilters = () => { setFilters({ category: '', spu: '', sku: '', productName: '', supplier: '', warehouse: '', purchaser: '', companyTag: [], departmentTag: [], departmentOwner: '', }); setPage(1); }; const openDetail = async (record) => { setSelected(record); setOperators([]); setOperatorsLoading(true); try { const result = await getProductMonitoringOperators(record.skuCode || record.skuId); setOperators(result?.data?.operators || []); } catch (requestError) { message.error(requestError?.response?.data?.error || requestError?.message || '加载运营明细失败'); } finally { setOperatorsLoading(false); } }; const columns = useMemo(() => [ { title: '图片', dataIndex: 'imageUrl', width: 64, fixed: 'left', exportable: false, render: (value) => (value ? : --) }, { title: '品类', dataIndex: 'category', width: 150, ellipsis: true, sorter: true }, { title: 'SPU', dataIndex: 'spu', width: 130, sorter: true }, { title: 'SKU', dataIndex: 'skuId', width: 150, fixed: 'left', sorter: true, render: (value, row) => }, { title: '条码', dataIndex: 'barcode', width: 150, ellipsis: true }, { title: '产品名称', dataIndex: 'productName', width: 240, ellipsis: true, sorter: true, render: (value) => {text(value)} }, { title: '材质', dataIndex: 'material', width: 100, ellipsis: true, render: text }, { title: '颜色', dataIndex: 'color', width: 100, ellipsis: true, render: text }, { title: '尺寸', dataIndex: 'size', width: 140, ellipsis: true, render: text }, { title: '销售状态', dataIndex: 'saleStatus', width: 90, sorter: true, render: (v) => {text(v)} }, { title: '供应商', dataIndex: 'supplier', width: 180, ellipsis: true, sorter: true, render: text }, { title: '发货仓库', dataIndex: 'warehouse', width: 120, ellipsis: true, sorter: true, render: text }, { title: '公司SKU负责人', dataIndex: 'companyOwner', width: 130, sorter: true, render: text }, { title: '采购负责人', dataIndex: 'purchaser', width: 110, sorter: true, render: text }, { title: '公司近7日日销', dataIndex: 'companySales7d', width: 120, align: 'right', sorter: true, render: (v) => number(v) }, { title: '可使用库存', dataIndex: 'availableStock', width: 110, align: 'right', sorter: true, render: int }, { title: '在途库存', dataIndex: 'inTransitStock', width: 100, align: 'right', render: (v) => {text(v)} }, { title: '可使用天数', dataIndex: 'stockDays', width: 110, align: 'right', sorter: true, render: (v) => number(v, 1) }, { title: '公司商品标签', dataIndex: 'companyTag', width: 110, sorter: true, render: (v) => (v ? {v} : --) }, { title: '部门', dataIndex: 'department', width: 110, ellipsis: true, sorter: true, render: text }, { title: '部门的标签', dataIndex: 'departmentTag', width: 110, sorter: true, render: (v) => (v ? {v} : --) }, { title: '部门SKU负责人', dataIndex: 'departmentOwner', width: 130, sorter: true, render: text }, { title: '近30天销售额', dataIndex: 'performance30d', width: 130, align: 'right', sorter: true, render: (v) => number(v) }, { title: '近7天销售额', dataIndex: 'performance7d', width: 120, align: 'right', sorter: true, render: (v) => number(v) }, { title: '部门近7日日销', dataIndex: 'departmentSales7d', width: 120, align: 'right', sorter: true, render: (v) => number(v) }, { title: '部门未发货数量', dataIndex: 'departmentUnshipped', width: 120, align: 'right', render: (v) => {text(v)} }, { title: '建议备货数量', dataIndex: 'suggestedReplenish', width: 120, align: 'right', sorter: true, render: int }, { title: '部门主销售渠道', dataIndex: 'mainSalesChannel', width: 180, ellipsis: true, sorter: true, render: text }, { title: '部门主销售运营', dataIndex: 'mainSalesOperator', width: 130, sorter: true, render: text }, ], []); const exportColumns = useMemo( () => columns.filter((column) => column.exportable !== false), [columns] ); const handleExport = async (format) => { if (!data.rows.length) { message.warning('暂无数据可导出'); return; } try { await exportTableFile(format, `商品监控_${status === '__all__' ? '全部' : status}_${data.latestDate ? String(data.latestDate).slice(0, 10) : '最新'}`, exportColumns, data.rows, '商品监控'); message.success(`已导出 ${data.rows.length} 条数据(${format.toUpperCase()})`); } catch (exportError) { message.error(exportError?.message || '导出失败'); } }; const summary = data.summary || {}; const operatorColumns = [ { title: '运营人员', dataIndex: 'operator', width: 140 }, { title: '日销(近7天日均件数)', dataIndex: 'dailySales', align: 'right', render: (v) => number(v) }, { title: '近7天销售件数', dataIndex: 'salesQty7d', align: 'right', render: int }, { title: '近7天销售额', dataIndex: 'salesAmount7d', align: 'right', render: (v) => number(v) }, { title: '主销售渠道', dataIndex: 'mainChannel', render: text }, ]; return ( <>
销售状态 ({ label: item.label, value: item.key }))} />
({ value, label: value }))} value={filters.departmentTag} onChange={changeMultiFilter('departmentTag')} style={{ minWidth: 160 }} /> } placeholder="SPU" value={filters.spu} onChange={(e) => changeFilter('spu')(e.target.value)} style={{ width: 150 }} /> } placeholder="SKU / 条码" value={filters.sku} onChange={(e) => changeFilter('sku')(e.target.value)} style={{ width: 160 }} /> } placeholder="产品名称" value={filters.productName} onChange={(e) => changeFilter('productName')(e.target.value)} style={{ width: 180 }} /> } placeholder="部门SKU负责人" value={filters.departmentOwner} onChange={(e) => changeFilter('departmentOwner')(e.target.value)} style={{ width: 160 }} />
{error ? 重试} /> : null} 商品明细({data.total || 0} 条) {data.latestDate ? 统计日 {String(data.latestDate).slice(0, 10)} : null} )} extra={( handleExport(key), }}> )} > { setPage(pagination.current || 1); setPageSize(pagination.pageSize || 50); if (nextSorter?.field) { setSorter({ field: nextSorter.field, order: nextSorter.order === 'ascend' ? 'ASC' : 'DESC', }); } }} pagination={{ current: data.page || page, pageSize: data.pageSize || pageSize, total: data.total || 0, showSizeChanger: true, pageSizeOptions: [50, 100, 200], showTotal: (total) => `共 ${total} 条`, }} scroll={{ x: 3400, y: 'min(520px, calc(100vh - 520px))' }} /> setSelected(null)} > {selected ? ( <> 产品名称:{text(selected.productName)} 部门:{text(selected.department)} 部门标签:{text(selected.departmentTag)} 部门SKU负责人:{text(selected.departmentOwner)}
) : null} ); }; const ProductMonitoring = () => (
商品监控 全公司商品档案、库存、标签分级与部门运营指标;点击 SKU 可下钻查看部门运营明细
}]} />
); export default ProductMonitoring;