import { useEffect, useState } from 'react' import dayjs, { type Dayjs } from 'dayjs' import { Avatar, Button, Card, DatePicker, Form, Input, InputNumber, Modal, Popconfirm, Select, Space, Table, Typography, } from 'antd' import type { ColumnsType, TablePaginationConfig } from 'antd/es/table' import { toast } from 'sonner' import { apiAdminMerchantPage, apiAdminRecommendMerchantDelete, apiAdminRecommendMerchantPage, apiAdminRecommendMerchantSave, } from '@/apis/apiMallAdmin' import StatusTag from '@/components/StatusTag' import type { AdminRecommendMerchantItem, AdminRecommendMerchantPageParams, AdminRecommendMerchantSaveParams, EnabledStatus, MerchantInfo, RecommendMerchantPositionCode, } from '@/types/mall' import { compactText, formatTimestamp } from '@/utils/format' import { enabledStatusOptions, getRecommendStorePositionLabel, recommendStorePositionOptions, withAllOption, } from '@/utils/mall' const { TextArea } = Input const DEFAULT_PAGE_SIZE = 20 const DEFAULT_STATUS: EnabledStatus = 'enabled' const DEFAULT_POSITION_CODE: RecommendMerchantPositionCode = 'home_featured_store' interface RecommendStoreSearchValues { keyword?: string positionCode?: RecommendMerchantPositionCode | '' status?: EnabledStatus | '' merchantId?: string page: number size: number } interface RecommendStoreFormValues { positionCode: RecommendMerchantPositionCode merchantId?: string mainCategoryName?: string recommendReason?: string rankScore?: number sort?: number status: EnabledStatus startTime?: Dayjs | null endTime?: Dayjs | null } interface MerchantSelectOption { label: string value: string } const normalizeSearchValues = (values: RecommendStoreSearchValues): AdminRecommendMerchantPageParams => ({ keyword: values.keyword?.trim() || undefined, positionCode: values.positionCode || undefined, status: values.status || undefined, merchantId: values.merchantId || undefined, page: values.page, size: values.size, }) const buildMerchantOption = (merchant: Pick): MerchantSelectOption => ({ value: String(merchant.id), label: compactText(merchant.merchantName), }) const mergeMerchantOptions = (current: MerchantSelectOption[], next: MerchantSelectOption[]) => { const merged = new Map() current.forEach(item => merged.set(item.value, item)) next.forEach(item => merged.set(item.value, item)) return Array.from(merged.values()) } export default function RecommendStoreManagementPage() { const [form] = Form.useForm() const [modalForm] = Form.useForm() const [loading, setLoading] = useState(false) const [saving, setSaving] = useState(false) const [merchantLoading, setMerchantLoading] = useState(false) const [list, setList] = useState([]) const [total, setTotal] = useState(0) const [pagination, setPagination] = useState({ current: 1, pageSize: DEFAULT_PAGE_SIZE }) const [modalOpen, setModalOpen] = useState(false) const [editingRecord, setEditingRecord] = useState(null) const [merchantOptions, setMerchantOptions] = useState([]) const loadData = async (page = pagination.current, size = pagination.pageSize) => { try { setLoading(true) const response = await apiAdminRecommendMerchantPage( normalizeSearchValues({ ...form.getFieldsValue(), page, size, }) ) setList(response.data.list) setTotal(response.data.total) setPagination({ current: page, pageSize: size }) } catch (error) { toast.error(error instanceof Error ? error.message : '首页推荐商家列表加载失败') } finally { setLoading(false) } } const loadMerchantOptions = async (keyword?: string) => { try { setMerchantLoading(true) const response = await apiAdminMerchantPage({ keyword: keyword?.trim() || undefined, page: 1, size: 20, }) const nextOptions = response.data.list.map(item => buildMerchantOption(item)) setMerchantOptions(current => mergeMerchantOptions(current, nextOptions)) } catch (error) { toast.error(error instanceof Error ? error.message : '商家选项加载失败') } finally { setMerchantLoading(false) } } useEffect(() => { form.setFieldsValue({ page: 1, size: DEFAULT_PAGE_SIZE, }) void loadData(1, DEFAULT_PAGE_SIZE) }, []) const resetModal = () => { setModalOpen(false) setEditingRecord(null) modalForm.resetFields() } const openCreateModal = () => { setEditingRecord(null) modalForm.setFieldsValue({ positionCode: DEFAULT_POSITION_CODE, rankScore: 0, sort: 0, status: DEFAULT_STATUS, startTime: null, endTime: null, }) setModalOpen(true) if (!merchantOptions.length) { void loadMerchantOptions() } } const openEditModal = (record: AdminRecommendMerchantItem) => { setEditingRecord(record) setMerchantOptions(current => mergeMerchantOptions(current, [ { value: record.merchantId, label: compactText(record.merchantName), }, ]) ) modalForm.setFieldsValue({ positionCode: record.positionCode, merchantId: record.merchantId, mainCategoryName: record.mainCategoryName || '', recommendReason: record.recommendReason || '', rankScore: Number(record.rankScore ?? 0), sort: record.sort ?? 0, status: record.configStatus, startTime: record.startTime ? dayjs.unix(record.startTime) : null, endTime: record.endTime ? dayjs.unix(record.endTime) : null, }) setModalOpen(true) } const handleDelete = async (record: AdminRecommendMerchantItem) => { try { await apiAdminRecommendMerchantDelete({ id: record.id }) toast.success('首页推荐商家已删除') await loadData() } catch (error) { toast.error(error instanceof Error ? error.message : '首页推荐商家删除失败') } } const handleSave = async () => { try { const values = await modalForm.validateFields() setSaving(true) const payload: AdminRecommendMerchantSaveParams = { id: editingRecord?.id, positionCode: values.positionCode, merchantId: values.merchantId || '', mainCategoryName: values.mainCategoryName?.trim() || undefined, recommendReason: values.recommendReason?.trim() || undefined, rankScore: values.rankScore ?? 0, sort: values.sort ?? 0, status: values.status, startTime: values.startTime?.unix(), endTime: values.endTime?.unix(), } await apiAdminRecommendMerchantSave(payload) toast.success(editingRecord ? '首页推荐商家已更新' : '首页推荐商家已新增') resetModal() await loadData() } catch (error) { if (error instanceof Error && error.message) { toast.error(error.message) } } finally { setSaving(false) } } const handleTableChange = (next: TablePaginationConfig) => { void loadData(next.current || 1, next.pageSize || DEFAULT_PAGE_SIZE) } const columns: ColumnsType = [ { title: '配置 ID', dataIndex: 'id', width: 92, }, { title: '位置编码', dataIndex: 'positionCode', width: 140, render: value => getRecommendStorePositionLabel(value), }, { title: '商家 ID', dataIndex: 'merchantId', width: 92, }, { title: '商家名称', dataIndex: 'merchantName', width: 200, render: value => compactText(value), }, { title: '商家 Logo', dataIndex: 'logo', width: 100, render: (_, record) => ( {record.merchantName?.slice(0, 1)} ), }, { title: '商家公告', dataIndex: 'notice', width: 220, render: value => ( {compactText(value)} ), }, { title: '商家状态', dataIndex: 'merchantStatus', width: 96, render: value => , }, { title: '审核状态', dataIndex: 'auditStatus', width: 96, render: value => , }, { title: '主推类目', dataIndex: 'mainCategoryName', width: 160, render: value => compactText(value), }, { title: '推荐理由', dataIndex: 'recommendReason', width: 220, render: value => ( {compactText(value)} ), }, { title: '排行分值', dataIndex: 'rankScore', width: 96, }, { title: '排序', dataIndex: 'sort', width: 76, }, { title: '配置状态', dataIndex: 'configStatus', width: 96, render: value => , }, { title: '开始时间', dataIndex: 'startTime', width: 170, render: value => formatTimestamp(value), }, { title: '结束时间', dataIndex: 'endTime', width: 170, render: value => formatTimestamp(value), }, { title: '更新时间', dataIndex: 'updatedAt', width: 170, render: value => formatTimestamp(value), }, { title: '操作', key: 'action', fixed: 'right', width: 144, render: (_, record) => ( void handleDelete(record)} > ), }, ] return (
首页推荐商家
void loadData(1, pagination.pageSize)}>
rowKey="id" loading={loading} columns={columns} dataSource={list} className="table-top-align" scroll={{ x: 2200 }} pagination={{ current: pagination.current, pageSize: pagination.pageSize, total, showSizeChanger: true, showTotal: count => `共 ${count} 条`, }} onChange={handleTableChange} /> void handleSave()} >
{ if (!merchantOptions.length) { void loadMerchantOptions() } }} onSearch={value => { void loadMerchantOptions(value) }} />