From e23b0b3e3913f289bfd90a3f143c65b6012ea403 Mon Sep 17 00:00:00 2001 From: tikkhun Date: Sun, 4 Jan 2026 21:01:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=AB=99=E7=82=B9=E7=AE=A1=E7=90=86):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=89=B9=E9=87=8F=E7=BC=96=E8=BE=91=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=B9=B6=E4=BC=98=E5=8C=96=E5=8C=BA=E5=9F=9F=E9=80=89?= =?UTF-8?q?=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加站点批量编辑功能,支持同时修改多个站点的区域、关联仓库和禁用状态 使用i18n-iso-countries库替换原有区域选择逻辑,支持中文显示国家名称 优化站点列表查询参数处理,添加分页和排序支持 修复产品创建表单中属性组装逻辑,根据产品类型决定是否组装attributes --- src/pages/Customer/List/HistoryOrders.tsx | 4 +- src/pages/Customer/List/index.tsx | 69 +++------ src/pages/Product/List/CreateForm.tsx | 44 +++--- src/pages/Site/List/index.tsx | 165 ++++++++++++++++++++-- src/pages/Site/Shop/EditSiteForm.tsx | 34 +++-- src/pages/Stock/Warehouse/index.tsx | 61 ++++---- src/servers/api/site.ts | 9 +- src/servers/api/typings.d.ts | 67 ++++++++- 8 files changed, 326 insertions(+), 127 deletions(-) diff --git a/src/pages/Customer/List/HistoryOrders.tsx b/src/pages/Customer/List/HistoryOrders.tsx index 5c05da9..b3b9058 100644 --- a/src/pages/Customer/List/HistoryOrders.tsx +++ b/src/pages/Customer/List/HistoryOrders.tsx @@ -91,7 +91,9 @@ const HistoryOrders: React.FC = ({ customer, siteId }) => { setLoading(true); try { const response = await ordercontrollerGetorders({ - customer_email: customer.email, + where: { + customer_email: customer.email, + }, }); if (response) { diff --git a/src/pages/Customer/List/index.tsx b/src/pages/Customer/List/index.tsx index e993091..d937a9d 100644 --- a/src/pages/Customer/List/index.tsx +++ b/src/pages/Customer/List/index.tsx @@ -152,6 +152,7 @@ const CustomerList: React.FC = () => { { title: '原始 ID', dataIndex: 'origin_id', + sorter: true, }, { title: '站点', @@ -172,10 +173,6 @@ const CustomerList: React.FC = () => { return []; } }, - render(_, record) { - // console.log(`siteId`, record.site_id); - return {getSiteName(record.site_id) || '-'}; - }, }, { title: '头像', @@ -209,16 +206,17 @@ const CustomerList: React.FC = () => { title: '用户名', dataIndex: 'username', hideInSearch: true, + sorter: true, }, { title: '邮箱', dataIndex: 'email', copyable: true, + sorter: true, }, { title: '电话', dataIndex: 'phone', - hideInSearch: true, copyable: true, }, { @@ -240,6 +238,7 @@ const CustomerList: React.FC = () => { dataIndex: 'rate', width: 120, hideInSearch: true, + sorter: true, render: (_, record) => { return ( { headerTitle="查询表格" actionRef={actionRef} rowKey="id" - request={async (params, sorter) => { - // 获取排序字段和排序方向 - const key = Object.keys(sorter)[0]; - - // 构建过滤条件对象 - const where: any = {}; - - // 添加邮箱过滤 - if (params.email) { - where.email = params.email; - } - - // 添加站点ID过滤 - if (params.site_id) { - where.site_id = params.site_id; - } - - // 添加用户名过滤 - if (params.username) { - where.username = params.username; - } - - // 添加电话过滤 - if (params.phone) { - where.phone = params.phone; - } - + request={async (params, sorter,filter) => { + console.log('custoemr request',params, sorter,filter) + const { current, pageSize, ...restParams } = params; + const orderBy:any = {} + Object.entries(sorter).forEach(([key, value]) => { + orderBy[key] = value === 'ascend' ? 'asc' : 'desc'; + }) // 构建查询参数 const queryParams: any = { - page: params.current || 1, - per_page: params.pageSize || 20, + page: current || 1, + per_page: pageSize || 20, + where: { + ...filter, + ...restParams + }, + orderBy }; - // 添加搜索关键词 - if (params.fullname) { - queryParams.search = params.fullname; - } - - // 添加过滤条件(只有在有过滤条件时才添加) - if (Object.keys(where).length > 0) { - queryParams.where = where; - } - - // 添加排序 - if (key) { - queryParams.orderBy = { [key]: sorter[key] as 'asc' | 'desc' }; - } - - const result = await customercontrollerGetcustomerlist(queryParams); + const result = await customercontrollerGetcustomerlist({params: queryParams}); console.log(queryParams, result); return { total: result?.data?.total || 0, diff --git a/src/pages/Product/List/CreateForm.tsx b/src/pages/Product/List/CreateForm.tsx index 2243a7f..cb49ef8 100644 --- a/src/pages/Product/List/CreateForm.tsx +++ b/src/pages/Product/List/CreateForm.tsx @@ -3,6 +3,7 @@ import { productcontrollerCreateproduct, productcontrollerGetcategoriesall, productcontrollerGetcategoryattributes, + productcontrollerGetproductlist, } from '@/servers/api/product'; import { stockcontrollerGetstocks as getStocks } from '@/servers/api/stock'; import { templatecontrollerRendertemplate } from '@/servers/api/template'; @@ -200,19 +201,23 @@ const CreateForm: React.FC<{ } }} onFinish={async (values: any) => { - // 组装 attributes(根据 activeAttributes 动态组装) - const attributes = activeAttributes.flatMap((attr: any) => { - const dictName = attr.name; - const key = `${dictName}Values`; - const vals = values[key]; - if (vals && Array.isArray(vals)) { - return vals.map((v: string) => ({ - dictName: dictName, - name: v, - })); - } - return []; - }); + // 根据产品类型决定是否组装 attributes + // 如果产品类型为 bundle,则 attributes 为空数组 + // 如果产品类型为 single,则根据 activeAttributes 动态组装 attributes + const attributes = values.type === 'bundle' + ? [] + : activeAttributes.flatMap((attr: any) => { + const dictName = attr.name; + const key = `${dictName}Values`; + const vals = values[key]; + if (vals && Array.isArray(vals)) { + return vals.map((v: string) => ({ + dictName: dictName, + name: v, + })); + } + return []; + }); const payload: any = { name: (values as any).name, @@ -315,15 +320,18 @@ const CreateForm: React.FC<{ rules={[{ required: true, message: '请输入子产品SKU' }]} request={async ({ keyWords }) => { const params = keyWords - ? { sku: keyWords, name: keyWords } - : { pageSize: 9999 }; - const { data } = await getStocks(params as any); + ? { sku: keyWords, name: keyWords, type: 'single' } + : { pageSize: 9999, type: 'single' }; + const { data } = await productcontrollerGetproductlist( + params as any, + ); if (!data || !data.items) { return []; } + // 只返回类型为单品的产品 return data.items - .filter((item) => item.sku) - .map((item) => ({ + .filter((item: any) => item.type === 'single' && item.sku) + .map((item: any) => ({ label: `${item.sku} - ${item.name}`, value: item.sku, })); diff --git a/src/pages/Site/List/index.tsx b/src/pages/Site/List/index.tsx index d1baf57..9d5729f 100644 --- a/src/pages/Site/List/index.tsx +++ b/src/pages/Site/List/index.tsx @@ -6,10 +6,13 @@ import { sitecontrollerUpdate, } from '@/servers/api/site'; import { subscriptioncontrollerSync } from '@/servers/api/subscription'; -import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components'; -import { Button, message, notification, Popconfirm, Space, Tag } from 'antd'; -import React, { useRef, useState } from 'react'; +import { stockcontrollerGetallstockpoints } from '@/servers/api/stock'; +import { ActionType, ProColumns, ProTable, DrawerForm, ProFormSelect, ProFormSwitch } from '@ant-design/pro-components'; +import { Button, message, notification, Popconfirm, Space, Tag, Form } from 'antd'; +import React, { useRef, useState, useEffect } from 'react'; import EditSiteForm from '../Shop/EditSiteForm'; // 引入重构后的表单组件 +import * as countries from 'i18n-iso-countries'; +import zhCN from 'i18n-iso-countries/langs/zh'; // 区域数据项类型 interface AreaItem { @@ -42,6 +45,10 @@ const SiteList: React.FC = () => { const [open, setOpen] = useState(false); const [editing, setEditing] = useState(null); const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [batchEditOpen, setBatchEditOpen] = useState(false); + const [batchEditForm] = Form.useForm(); + countries.registerLocale(zhCN); + const handleSync = async (ids: number[]) => { if (!ids.length) return; @@ -96,6 +103,68 @@ const SiteList: React.FC = () => { } }; + // 获取所有国家/地区的选项 + const getCountryOptions = () => { + // 获取所有国家的 ISO 代码 + const countryCodes = countries.getAlpha2Codes(); + // 将国家代码转换为选项数组 + return Object.keys(countryCodes).map((code) => ({ + label: countries.getName(code, 'zh') || code, // 使用中文名称, 如果没有则使用代码 + value: code, + })); + }; + + // 处理批量编辑提交 + const handleBatchEditFinish = async (values: any) => { + if (!selectedRowKeys.length) return; + const hide = message.loading('正在批量更新...', 0); + + try { + // 遍历所有选中的站点 ID + for (const id of selectedRowKeys) { + // 构建更新数据对象,只包含用户填写了值的字段 + const updateData: any = {}; + + // 如果用户选择了区域,则更新区域 + if (values.areas && values.areas.length > 0) { + updateData.areas = values.areas; + } + + // 如果用户选择了仓库,则更新仓库 + if (values.stockPointIds && values.stockPointIds.length > 0) { + updateData.stockPointIds = values.stockPointIds; + } + + // 如果用户设置了禁用状态,则更新状态 + if (values.isDisabled !== undefined) { + updateData.isDisabled = values.isDisabled; + } + + // 如果有需要更新的字段,则调用更新接口 + if (Object.keys(updateData).length > 0) { + await sitecontrollerUpdate({ id: String(id) }, updateData); + } + } + + hide(); + message.success('批量更新成功'); + setBatchEditOpen(false); + setSelectedRowKeys([]); + batchEditForm.resetFields(); + actionRef.current?.reload(); + } catch (error: any) { + hide(); + message.error(error.message || '批量更新失败'); + } + }; + + // 当批量编辑弹窗打开时,重置表单 + useEffect(() => { + if (batchEditOpen) { + batchEditForm.resetFields(); + } + }, [batchEditOpen, batchEditForm]); + // 表格列定义 const columns: ProColumns[] = [ { @@ -122,23 +191,42 @@ const SiteList: React.FC = () => { { title: 'SKU 前缀', dataIndex: 'skuPrefix', - width: 160, hideInSearch: true, }, { title: '平台', dataIndex: 'type', - width: 140, valueType: 'select', request: async () => [ { label: 'WooCommerce', value: 'woocommerce' }, { label: 'Shopyy', value: 'shopyy' }, ], }, + { + // 地区列配置 + title: "地区", + dataIndex: "areas", + hideInSearch: true, + render: (_, row) => { + // 如果没有关联地区,显示"全局"标签 + if (!row.areas || row.areas.length === 0) { + return 全局; + } + // 遍历显示所有关联的地区名称 + return ( + + {row.areas.map((area) => ( + + {area.name} + + ))} + + ); + }, + }, { title: '关联仓库', dataIndex: 'stockPoints', - width: 200, hideInSearch: true, render: (_, row) => { if (!row.stockPoints || row.stockPoints.length === 0) { @@ -220,10 +308,10 @@ const SiteList: React.FC = () => { try { const { current, pageSize, name, type } = params; const resp = await sitecontrollerList({ - current, - pageSize, - keyword: name || undefined, - type: type || undefined, + current, + pageSize, + keyword: name || undefined, + type: type || undefined, }); // 假设 resp 直接就是后端返回的结构,包含 items 和 total return { @@ -267,6 +355,12 @@ const SiteList: React.FC = () => { selectedRowKeys, onChange: setSelectedRowKeys, }} + pagination={{ + defaultPageSize: 20, + showSizeChanger: true, + showQuickJumper: true, + showTotal: (total) => `共 ${total} 条`, + }} toolBarRender={() => [ , + ,