diff --git a/.umirc.ts b/.umirc.ts index dbc477a..8d7c0e6 100644 --- a/.umirc.ts +++ b/.umirc.ts @@ -164,6 +164,11 @@ export default defineConfig({ path: '/product/attribute', component: './Product/Attribute', }, + { + name: '产品品牌空间', + path: '/product/brandspace', + component: './Product/BrandSpace', + }, // sync { name: '同步产品', @@ -174,7 +179,7 @@ export default defineConfig({ name: '产品CSV 工具', path: '/product/csvtool', component: './Product/CsvTool', - } + }, ], }, { diff --git a/src/hooks/useSites.ts b/src/hooks/useSites.ts index 9378a44..36d317f 100644 --- a/src/hooks/useSites.ts +++ b/src/hooks/useSites.ts @@ -1,5 +1,5 @@ -import { useState, useEffect } from 'react'; import { sitecontrollerAll } from '@/servers/api/site'; +import { useEffect, useState } from 'react'; // 站点数据的类型定义 interface Site { @@ -89,4 +89,4 @@ const useSites = () => { }; // 导出 useSites Hook -export default useSites; \ No newline at end of file +export default useSites; diff --git a/src/pages/Customer/List/index.tsx b/src/pages/Customer/List/index.tsx index 0283315..48b97c3 100644 --- a/src/pages/Customer/List/index.tsx +++ b/src/pages/Customer/List/index.tsx @@ -116,7 +116,6 @@ const CustomerList: React.FC = () => { const { message } = App.useApp(); const [syncModalVisible, setSyncModalVisible] = useState(false); - const columns: ProColumns[] = [ { title: 'ID', @@ -177,7 +176,7 @@ const CustomerList: React.FC = () => { }, { title: '用户名', - dataIndex: 'username', + dataIndex: 'username', copyable: true, sorter: true, }, @@ -196,7 +195,7 @@ const CustomerList: React.FC = () => { title: '账单地址', dataIndex: 'billing', hideInSearch: true, - + width: 200, render: (billing) => , }, @@ -318,25 +317,27 @@ const CustomerList: React.FC = () => { actionRef={actionRef} columns={columns} rowKey="id" - request={async (params, sorter,filter) => { - console.log('custoemr request',params, sorter,filter) + request={async (params, sorter, filter) => { + console.log('custoemr request', params, sorter, filter); const { current, pageSize, ...restParams } = params; - const orderBy:any = {} + const orderBy: any = {}; Object.entries(sorter).forEach(([key, value]) => { orderBy[key] = value === 'ascend' ? 'asc' : 'desc'; - }) + }); // 构建查询参数 const queryParams: any = { page: current || 1, per_page: pageSize || 20, where: { - ...filter, - ...restParams + ...filter, + ...restParams, }, - orderBy + orderBy, }; - const result = await customercontrollerGetcustomerlist({params: queryParams}); + const result = await customercontrollerGetcustomerlist({ + params: queryParams, + }); console.log(queryParams, result); return { total: result?.data?.total || 0, @@ -344,7 +345,6 @@ const CustomerList: React.FC = () => { success: true, }; }} - search={{ labelWidth: 'auto', span: 6, diff --git a/src/pages/Customer/Statistic/index.tsx b/src/pages/Customer/Statistic/index.tsx index f16c106..09d098d 100644 --- a/src/pages/Customer/Statistic/index.tsx +++ b/src/pages/Customer/Statistic/index.tsx @@ -137,7 +137,8 @@ const ListPage: React.FC = () => { { title: '账单地址', dataIndex: 'billing', - render: (_, record) => JSON.stringify(record?.billing || record?.shipping), + render: (_, record) => + JSON.stringify(record?.billing || record?.shipping), }, { title: '标签', diff --git a/src/pages/Order/Items/index.tsx b/src/pages/Order/Items/index.tsx index 8bcede6..ec4c857 100644 --- a/src/pages/Order/Items/index.tsx +++ b/src/pages/Order/Items/index.tsx @@ -152,7 +152,7 @@ const OrderItemsPage: React.FC = () => { columns={columns} request={request} pagination={{ - showSizeChanger: true, + showSizeChanger: true, showQuickJumper: true, }} search={{ labelWidth: 90, span: 6 }} diff --git a/src/pages/Order/List/index.tsx b/src/pages/Order/List/index.tsx index 3ae3da8..e2ace00 100644 --- a/src/pages/Order/List/index.tsx +++ b/src/pages/Order/List/index.tsx @@ -56,6 +56,7 @@ import { ProFormTextArea, ProTable, } from '@ant-design/pro-components'; +import { request } from '@umijs/max'; import { App, Button, @@ -76,7 +77,6 @@ import { } from 'antd'; import React, { useMemo, useRef, useState } from 'react'; import RelatedOrders from '../../Subscription/Orders/RelatedOrders'; -import { request } from '@umijs/max'; const ListPage: React.FC = () => { const actionRef = useRef(); @@ -191,7 +191,7 @@ const ListPage: React.FC = () => { request: async () => { try { const result = await sitecontrollerAll(); - const {success, data}= result + const { success, data } = result; if (success && data) { return data.map((site: any) => ({ label: site.name, @@ -275,9 +275,15 @@ const ListPage: React.FC = () => { {(record as any)?.fulfillments?.map((item: any) => { if (!item) return; return ( -
- 物流供应商: {item.shipping_provider} - 物流单号: {item.tracking_number} +
+ 物流供应商: {item.shipping_provider} + 物流单号: {item.tracking_number}
); })} @@ -513,10 +519,12 @@ const ListPage: React.FC = () => { method: 'POST', data: { ids: selectedRowKeys, - } + }, }); if (res?.success && res.data) { - const blob = new Blob([res.data], { type: 'text/csv;charset=utf-8;' }); + const blob = new Blob([res.data], { + type: 'text/csv;charset=utf-8;', + }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; @@ -533,10 +541,7 @@ const ListPage: React.FC = () => { } }} > - , @@ -628,36 +633,36 @@ const Detail: React.FC<{ ) ? [] : [ - , - , - ]), + }} + > + 同步订单 + , + ]), // ...(['processing', 'pending_reshipment'].includes(record.orderStatus) // ? [ // , @@ -676,152 +681,152 @@ const Detail: React.FC<{ 'pending_refund', ].includes(record.orderStatus) ? [ - , - { - try { - if (!record.id) { - message.error('订单ID不存在'); - return; + , + { + try { + if (!record.id) { + message.error('订单ID不存在'); + return; + } + const { success, message: errMsg } = + await ordercontrollerChangestatus( + { + id: record.id, + }, + { + status: 'after_sale_pending', + }, + ); + if (!success) { + throw new Error(errMsg); + } + tableRef.current?.reload(); + } catch (error: any) { + message.error(error.message); } - const { success, message: errMsg } = - await ordercontrollerChangestatus( - { - id: record.id, - }, - { - status: 'after_sale_pending', - }, - ); - if (!success) { - throw new Error(errMsg); - } - tableRef.current?.reload(); - } catch (error: any) { - message.error(error.message); - } - }} - > - - , - ] + }} + > + + , + ] : []), ...(record.orderStatus === 'after_sale_pending' ? [ - , - { - try { - if (!record.id) { - message.error('订单ID不存在'); - return; - } - const { success, message: errMsg } = - await ordercontrollerCancelorder({ - id: record.id, - }); - if (!success) { - throw new Error(errMsg); - } - tableRef.current?.reload(); - } catch (error: any) { - message.error(error.message); - } - }} - > - - , - , - { - try { - if (!record.id) { - message.error('订单ID不存在'); - return; - } - const { success, message: errMsg } = - await ordercontrollerRefundorder({ - id: record.id, - }); - if (!success) { - throw new Error(errMsg); - } - tableRef.current?.reload(); - } catch (error: any) { - message.error(error.message); - } - }} - > - - , - , - { - try { - if (!record.id) { - message.error('订单ID不存在'); - return; - } - const { success, message: errMsg } = - await ordercontrollerCompletedorder({ - id: record.id, - }); - if (!success) { - throw new Error(errMsg); - } - tableRef.current?.reload(); - } catch (error: any) { - message.error(error.message); - } - }} - > - - , - , - { - try { - const { success, message: errMsg } = - await ordercontrollerChangestatus( - { + , + { + try { + if (!record.id) { + message.error('订单ID不存在'); + return; + } + const { success, message: errMsg } = + await ordercontrollerCancelorder({ id: record.id, - }, - { - status: 'pending_reshipment', - }, - ); - if (!success) { - throw new Error(errMsg); + }); + if (!success) { + throw new Error(errMsg); + } + tableRef.current?.reload(); + } catch (error: any) { + message.error(error.message); } - tableRef.current?.reload(); - } catch (error: any) { - message.error(error.message); - } - }} - > - - , - ] + }} + > + + , + , + { + try { + if (!record.id) { + message.error('订单ID不存在'); + return; + } + const { success, message: errMsg } = + await ordercontrollerRefundorder({ + id: record.id, + }); + if (!success) { + throw new Error(errMsg); + } + tableRef.current?.reload(); + } catch (error: any) { + message.error(error.message); + } + }} + > + + , + , + { + try { + if (!record.id) { + message.error('订单ID不存在'); + return; + } + const { success, message: errMsg } = + await ordercontrollerCompletedorder({ + id: record.id, + }); + if (!success) { + throw new Error(errMsg); + } + tableRef.current?.reload(); + } catch (error: any) { + message.error(error.message); + } + }} + > + + , + , + { + try { + const { success, message: errMsg } = + await ordercontrollerChangestatus( + { + id: record.id, + }, + { + status: 'pending_reshipment', + }, + ); + if (!success) { + throw new Error(errMsg); + } + tableRef.current?.reload(); + } catch (error: any) { + message.error(error.message); + } + }} + > + + , + ] : []), ]} > @@ -1083,31 +1088,31 @@ const Detail: React.FC<{ } actions={ v.state === 'waiting-for-scheduling' || - v.state === 'waiting-for-transit' + v.state === 'waiting-for-transit' ? [ - { - try { - const { success, message: errMsg } = - await logisticscontrollerDelshipment({ - id: v.id, - }); - if (!success) { - throw new Error(errMsg); + { + try { + const { success, message: errMsg } = + await logisticscontrollerDelshipment({ + id: v.id, + }); + if (!success) { + throw new Error(errMsg); + } + tableRef.current?.reload(); + initRequest(); + } catch (error: any) { + message.error(error.message); } - tableRef.current?.reload(); - initRequest(); - } catch (error: any) { - message.error(error.message); - } - }} - > - - 取消运单 - , - ] + }} + > + + 取消运单 + , + ] : [] } > @@ -1495,16 +1500,16 @@ const Shipping: React.FC<{ - // value && value.length > 0 - // ? Promise.resolve() - // : Promise.reject('至少需要一个商品'), - // }, - // ]} + // rules={[ + // { + // required: true, + // message: '至少需要一个商品', + // validator: (_, value) => + // value && value.length > 0 + // ? Promise.resolve() + // : Promise.reject('至少需要一个商品'), + // }, + // ]} > diff --git a/src/pages/Product/Attribute/index.tsx b/src/pages/Product/Attribute/index.tsx index 1479fda..1d0fd32 100644 --- a/src/pages/Product/Attribute/index.tsx +++ b/src/pages/Product/Attribute/index.tsx @@ -2,6 +2,7 @@ import * as dictApi from '@/servers/api/dict'; import { ActionType, PageContainer, + ProColumns, ProTable, } from '@ant-design/pro-components'; import { request } from '@umijs/max'; @@ -219,11 +220,35 @@ const AttributePage: React.FC = () => { ]; // 右侧字典项列表列定义(紧凑样式) - const dictItemColumns: any[] = [ - { title: '名称', dataIndex: 'name', key: 'name', copyable: true }, - { title: '标题', dataIndex: 'title', key: 'title', copyable: true }, - { title: '中文标题', dataIndex: 'titleCN', key: 'titleCN', copyable: true }, - { title: '简称', dataIndex: 'shortName', key: 'shortName', copyable: true }, + const dictItemColumns: ProColumns[] = [ + { + title: '名称', + dataIndex: 'name', + key: 'name', + copyable: true, + sorter: true, + }, + { + title: '标题', + dataIndex: 'title', + key: 'title', + copyable: true, + sorter: true, + }, + { + title: '中文标题', + dataIndex: 'titleCN', + key: 'titleCN', + copyable: true, + sorter: true, + }, + { + title: '简称', + dataIndex: 'shortName', + key: 'shortName', + copyable: true, + sorter: true, + }, { title: '图片', dataIndex: 'image', diff --git a/src/pages/Product/BrandSpace/index.tsx b/src/pages/Product/BrandSpace/index.tsx new file mode 100644 index 0000000..7d90a55 --- /dev/null +++ b/src/pages/Product/BrandSpace/index.tsx @@ -0,0 +1,452 @@ +import { PageContainer } from '@ant-design/pro-components'; +import { request } from '@umijs/max'; +import { + Card, + Col, + Image, + Layout, + Row, + Select, + Space, + Typography, + message, +} from 'antd'; +import React, { useEffect, useState } from 'react'; + +const { Sider, Content } = Layout; +const { Title, Text } = Typography; +const { Option } = Select; + +// Define interfaces +interface Brand { + id: number; + name: string; + shortName?: string; + image?: string; +} + +interface Attribute { + id: number; + name: string; + title: string; +} + +interface AttributeValue { + id: number; + name: string; + title: string; + titleCN?: string; + value?: string; + image?: string; +} + +interface Product { + id: number; + sku: string; + name: string; + image?: string; + brandId: number; + brandName: string; + attributes: { [key: string]: any }; +} + +const BrandSpace: React.FC = () => { + // State management + const [brands, setBrands] = useState([]); + const [selectedBrand, setSelectedBrand] = useState(null); + + const [attributes, setAttributes] = useState([]); + const [selectedAttribute, setSelectedAttribute] = useState( + null, + ); + + const [attributeValues, setAttributeValues] = useState([]); + const [selectedAttributeValue, setSelectedAttributeValue] = useState< + number | null + >(null); + + const [products, setProducts] = useState([]); + const [loading, setLoading] = useState(false); + + // Fetch brands list + const fetchBrands = async () => { + try { + const response = await request('/dict/items', { + params: { dictId: 'brand' }, // Assuming brand is a dict + }); + const brandList = Array.isArray(response) + ? response + : response?.data || []; + setBrands(brandList); + + // Set default brand to "yoone" if exists + const defaultBrand = brandList.find((brand) => brand.name === 'yoone'); + if (defaultBrand) { + setSelectedBrand(defaultBrand.id); + } + } catch (error) { + console.error('Failed to fetch brands:', error); + message.error('获取品牌列表失败'); + } + }; + + // Fetch attributes list + const fetchAttributes = async () => { + try { + // Get all dicts that are attributes (excluding non-attribute dicts) + const response = await request('/dict/list'); + const dictList = Array.isArray(response) + ? response + : response?.data || []; + + // Filter out non-attribute dicts (assuming attributes are specific dicts) + const attributeDicts = dictList.filter((dict: any) => + ['strength', 'flavor', 'humidity', 'size', 'version'].includes( + dict.name, + ), + ); + + setAttributes(attributeDicts); + + // Set default attribute to strength if exists + const defaultAttribute = attributeDicts.find( + (attr) => attr.name === 'strength', + ); + if (defaultAttribute) { + setSelectedAttribute(defaultAttribute.name); + } + } catch (error) { + console.error('Failed to fetch attributes:', error); + message.error('获取属性列表失败'); + } + }; + + // Fetch attribute values based on selected attribute + const fetchAttributeValues = async (attributeName: string) => { + try { + const response = await request('/dict/items', { + params: { dictId: attributeName }, + }); + const values = Array.isArray(response) ? response : response?.data || []; + setAttributeValues(values); + } catch (error) { + console.error('Failed to fetch attribute values:', error); + message.error('获取属性值列表失败'); + } + }; + + // Fetch products based on filters + const fetchProducts = async () => { + if (!selectedBrand) return; + + setLoading(true); + try { + const params: any = { + brandId: selectedBrand, + }; + + // Add attribute filter if selected + if (selectedAttribute) { + // If attribute value is selected, filter by both attribute and value + if (selectedAttributeValue) { + params[selectedAttribute] = selectedAttributeValue; + } else { + // If only attribute is selected, filter by attribute presence + params[selectedAttribute] = 'hasValue'; + } + } + + const response = await request('/product/list', { + params, + }); + + const productList = Array.isArray(response) + ? response + : response?.data || []; + setProducts(productList); + } catch (error) { + console.error('Failed to fetch products:', error); + message.error('获取产品列表失败'); + } finally { + setLoading(false); + } + }; + + // Initial data fetch + useEffect(() => { + fetchBrands(); + fetchAttributes(); + }, []); + + // Fetch attribute values when attribute changes + useEffect(() => { + if (selectedAttribute) { + fetchAttributeValues(selectedAttribute); + setSelectedAttributeValue(null); // Reset selected value when attribute changes + } + }, [selectedAttribute]); + + // Fetch products when filters change + useEffect(() => { + fetchProducts(); + }, [selectedBrand, selectedAttribute, selectedAttributeValue]); + + // Handle brand selection change + const handleBrandChange = (value: number) => { + setSelectedBrand(value); + }; + + // Handle attribute selection change + const handleAttributeChange = (value: string) => { + setSelectedAttribute(value); + }; + + // Handle attribute value selection change + const handleAttributeValueChange = (value: number) => { + setSelectedAttributeValue(value); + }; + + return ( + + + {/* Top Brand Selection */} +
+ + + 选择品牌 + + + +
+ + + {/* Left Attribute Selection */} + +
+ + + 选择属性 + + + + {selectedAttribute && ( + <> + + 属性值 + + + + )} + + {/* Filter Summary */} + {selectedBrand && ( +
+ 当前筛选条件: +
+ 品牌: + + {brands.find((b) => b.id === selectedBrand)?.name} + +
+ {selectedAttribute && ( +
+ + { + attributes.find((a) => a.name === selectedAttribute) + ?.title + } + : + + + {selectedAttributeValue + ? attributeValues.find( + (v) => v.id === selectedAttributeValue, + )?.titleCN || + attributeValues.find( + (v) => v.id === selectedAttributeValue, + )?.title + : '所有值'} + +
+ )} +
+ )} +
+
+
+ + {/* Main Content - Product List */} + +
+ + 产品列表 + <Text type="secondary" style={{ fontSize: 16, marginLeft: 8 }}> + ({products.length} 个产品) + </Text> + +
+ + {loading ? ( +
+ 加载中... +
+ ) : products.length > 0 ? ( + + {products.map((product) => ( + + +
+ {product.name} +
+
+ + {product.sku} + + + {product.name} + +
+ + ¥{product.price || '--'} + +
+
+
+ + ))} +
+ ) : ( +
+ 暂无符合条件的产品 +
+ )} +
+
+
+
+ ); +}; + +export default BrandSpace; diff --git a/src/pages/Product/Category/index.tsx b/src/pages/Product/Category/index.tsx index 254387a..7ad802d 100644 --- a/src/pages/Product/Category/index.tsx +++ b/src/pages/Product/Category/index.tsx @@ -255,7 +255,9 @@ const CategoryPage: React.FC = () => { {selectedCategory ? ( 添加关联属性 diff --git a/src/pages/Product/CsvTool/index.tsx b/src/pages/Product/CsvTool/index.tsx index 35350f6..6e2f019 100644 --- a/src/pages/Product/CsvTool/index.tsx +++ b/src/pages/Product/CsvTool/index.tsx @@ -1,3 +1,4 @@ +import { productcontrollerGetcategoriesall } from '@/servers/api/product'; import { UploadOutlined } from '@ant-design/icons'; import { PageContainer, @@ -8,8 +9,6 @@ import { request } from '@umijs/max'; import { Button, Card, Checkbox, Col, Input, message, Row, Upload } from 'antd'; import React, { useEffect, useState } from 'react'; import * as XLSX from 'xlsx'; -import { productcontrollerGetcategoriesall } from '@/servers/api/product'; - // 定义站点接口 interface Site { @@ -21,7 +20,7 @@ interface Site { // 定义选项接口,用于下拉选择框的选项 interface Option { - name: string; // 显示名称 + name: string; // 显示名称 shortName: string; // 短名称,用于生成SKU } @@ -66,7 +65,8 @@ const CsvTool: React.FC = () => { const [isProcessing, setIsProcessing] = useState(false); const [sites, setSites] = useState([]); const [selectedSites, setSelectedSites] = useState([]); // 现在使用多选 - const [generateBundleSkuForSingle, setGenerateBundleSkuForSingle] = useState(true); // 是否为type为single的记录生成包含quantity的bundle SKU + const [generateBundleSkuForSingle, setGenerateBundleSkuForSingle] = + useState(true); // 是否为type为single的记录生成包含quantity的bundle SKU const [config, setConfig] = useState({ brands: [], categories: [], @@ -78,16 +78,18 @@ const CsvTool: React.FC = () => { quantities: [], }); // 所有属性名称到shortName的映射 - const [attributeMappings, setAttributeMappings] = useState({ - brands: {}, - categories: {}, - flavors: {}, - strengths: {}, - humidities: {}, - versions: {}, - sizes: {}, - quantities: {}, - }); + const [attributeMappings, setAttributeMappings] = useState( + { + brands: {}, + categories: {}, + flavors: {}, + strengths: {}, + humidities: {}, + versions: {}, + sizes: {}, + quantities: {}, + }, + ); // 在组件加载时获取站点列表和字典数据 useEffect(() => { @@ -118,19 +120,19 @@ const CsvTool: React.FC = () => { params: { dictId: dict.id }, }); const items = itemsResponse?.data || itemsResponse || []; - + // 创建完整的选项数组 const options = items.map((item: any) => ({ name: item.name, - shortName: item.shortName || item.name + shortName: item.shortName || item.name, })); - + // 创建name到shortName的映射 const mapping = items.reduce((acc: AttributeMapping, item: any) => { acc[item.name] = item.shortName || item.name; return acc; }, {}); - + return { options, mapping }; } catch (error) { console.error(`Failed to fetch items for ${dictName}:`, error); @@ -139,7 +141,15 @@ const CsvTool: React.FC = () => { }; // 4. 获取所有字典项(品牌、口味、强度、湿度、版本、尺寸、数量) - const [brandResult, flavorResult, strengthResult, humidityResult, versionResult, sizeResult, quantityResult] = await Promise.all([ + const [ + brandResult, + flavorResult, + strengthResult, + humidityResult, + versionResult, + sizeResult, + quantityResult, + ] = await Promise.all([ getDictItems('brand'), getDictItems('flavor'), getDictItems('strength'), @@ -151,16 +161,21 @@ const CsvTool: React.FC = () => { // 5. 获取商品分类列表 const categoriesResponse = await productcontrollerGetcategoriesall(); - const categoryOptions = categoriesResponse?.data?.map((category: any) => ({ - name: category.name, - shortName: category.shortName || category.name - })) || []; - + const categoryOptions = + categoriesResponse?.data?.map((category: any) => ({ + name: category.name, + shortName: category.shortName || category.name, + })) || []; + // 商品分类的映射(如果分类有shortName的话) - const categoryMapping = categoriesResponse?.data?.reduce((acc: AttributeMapping, category: any) => { - acc[category.name] = category.shortName || category.name; - return acc; - }, {}) || {}; + const categoryMapping = + categoriesResponse?.data?.reduce( + (acc: AttributeMapping, category: any) => { + acc[category.name] = category.shortName || category.name; + return acc; + }, + {}, + ) || {}; // 6. 设置所有属性映射 setAttributeMappings({ @@ -171,7 +186,7 @@ const CsvTool: React.FC = () => { humidities: humidityResult.mapping, versions: versionResult.mapping, sizes: sizeResult.mapping, - quantities: quantityResult.mapping + quantities: quantityResult.mapping, }); // 更新配置状态 @@ -188,21 +203,24 @@ const CsvTool: React.FC = () => { setConfig(newConfig); // 设置表单值时只需要name数组 form.setFieldsValue({ - brands: brandResult.options.map(opt => opt.name), - categories: categoryOptions.map(opt => opt.name), - flavors: flavorResult.options.map(opt => opt.name), - strengths: strengthResult.options.map(opt => opt.name), - humidities: humidityResult.options.map(opt => opt.name), - versions: versionResult.options.map(opt => opt.name), - sizes: sizeResult.options.map(opt => opt.name), - quantities: quantityResult.options.map(opt => opt.name), + brands: brandResult.options.map((opt) => opt.name), + categories: categoryOptions.map((opt) => opt.name), + flavors: flavorResult.options.map((opt) => opt.name), + strengths: strengthResult.options.map((opt) => opt.name), + humidities: humidityResult.options.map((opt) => opt.name), + versions: versionResult.options.map((opt) => opt.name), + sizes: sizeResult.options.map((opt) => opt.name), + quantities: quantityResult.options.map((opt) => opt.name), generateBundleSkuForSingle: true, }); message.success({ content: '数据加载成功', key: 'loading' }); } catch (error) { console.error('Failed to fetch data:', error); - message.error({ content: '数据加载失败,请刷新页面重试', key: 'loading' }); + message.error({ + content: '数据加载失败,请刷新页面重试', + key: 'loading', + }); } }; @@ -221,21 +239,21 @@ const CsvTool: React.FC = () => { setFile(uploadedFile); const reader = new FileReader(); - + // 检查是否为CSV文件 const isCsvFile = uploadedFile.name.match(/\.csv$/i); - + if (isCsvFile) { // 对于CSV文件,使用readAsText并指定UTF-8编码以正确处理中文 reader.onload = (e) => { try { const textData = e.target?.result as string; // 使用XLSX.read处理CSV文本数据,指定type为'csv'并设置编码 - const workbook = XLSX.read(textData, { + const workbook = XLSX.read(textData, { type: 'string', codepage: 65001, // UTF-8 encoding cellText: true, - cellDates: true + cellDates: true, }); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; @@ -305,7 +323,7 @@ const CsvTool: React.FC = () => { }; reader.readAsArrayBuffer(uploadedFile); } - + reader.onerror = (error) => { message.error('文件读取失败!'); console.error('File Read Error:', error); @@ -350,7 +368,7 @@ const CsvTool: React.FC = () => { humidity: string, size: string, quantity?: any, - type?: string + type?: string, ): string => { // 构建SKU组件,不包含站点前缀 const skuComponents: string[] = []; @@ -368,7 +386,8 @@ const CsvTool: React.FC = () => { } if (category) { // 使用分类的shortName,如果没有则使用分类名称 - const categoryShortName = attributeMappings.categories[category] || category; + const categoryShortName = + attributeMappings.categories[category] || category; skuComponents.push(categoryShortName); } if (flavor) { @@ -378,12 +397,14 @@ const CsvTool: React.FC = () => { } if (strength) { // 使用强度的shortName,如果没有则使用强度名称 - const strengthShortName = attributeMappings.strengths[strength] || strength; + const strengthShortName = + attributeMappings.strengths[strength] || strength; skuComponents.push(strengthShortName); } if (humidity) { // 使用湿度的shortName,如果没有则使用湿度名称 - const humidityShortName = attributeMappings.humidities[humidity] || humidity; + const humidityShortName = + attributeMappings.humidities[humidity] || humidity; skuComponents.push(humidityShortName); } @@ -392,22 +413,67 @@ const CsvTool: React.FC = () => { const sizeShortName = attributeMappings.sizes[size] || size; skuComponents.push(sizeShortName); } - - // 如果type为single且启用了生成bundle SKU,则添加quantity - if ((!type || type === 'single') && generateBundleSkuForSingle && quantity) { - // 将quantity转换为数字,然后格式化为四位数(前导零) - const formattedQuantity = Number(quantity).toString().padStart(4, '0'); - skuComponents.push(formattedQuantity); - } else if (type === 'bundle' && quantity) { - // 对于bundle类型,始终添加quantity - const formattedQuantity = Number(quantity).toString().padStart(4, '0'); - skuComponents.push(formattedQuantity); - } + // 如果type为single且启用了生成bundle SKU,则添加quantity + if ( + quantity + ) { + console.log(quantity, attributeMappings.quantities[quantity]) + // 使用quantity的shortName,如果没有则使用quantity但匹配 4 个零 + const quantityShortName = attributeMappings.quantities[quantity] || Number(quantity).toString().padStart(4, '0'); + skuComponents.push(quantityShortName); + } // 合并所有组件,使用短横线分隔 return skuComponents.join('-').toUpperCase(); }; + /** + * @description 根据配置生成产品名称(使用属性的完整名称,空格分隔) + * @param {string} brand - 品牌 + * @param {string} version - 版本 + * @param {string} category - 分类 + * @param {string} flavor - 口味 + * @param {string} strength - 强度 + * @param {string} humidity - 湿度 + * @param {string} size - 型号 + * @param {any} quantity - 数量 + * @param {string} type - 产品类型 + * @returns {string} 生成的产品名称 + */ + const generateName = ( + brand: string, + version: string, + category: string, + flavor: string, + strength: string, + humidity: string, + size: string, + quantity?: any, + type?: string, + ): string => { + // 构建产品名称组件数组 + const nameComponents: string[] = []; + + // 按顺序添加组件:品牌 -> 版本 -> 品类 -> 风味 -> 毫克数(强度) -> 湿度 -> 型号 -> 数量 + if (brand) nameComponents.push(brand); + if (version) nameComponents.push(version); + if (category) nameComponents.push(category); + if (flavor) nameComponents.push(flavor); + if (strength) nameComponents.push(strength); + if (humidity) nameComponents.push(humidity); + if (size) nameComponents.push(size); + + // 如果有数量且类型为bundle或者生成bundle的single产品,则添加数量 + if ( + type==='bundle' && quantity + ) { + nameComponents.push(String(quantity)); + } + + // 使用空格连接所有组件 + return nameComponents.join(' '); + }; + /** * @description 为所有站点生成带前缀的siteSkus * @param {string} baseSku - 基础SKU(不包含站点前缀) @@ -418,7 +484,7 @@ const CsvTool: React.FC = () => { if (selectedSites.length === 0 || !baseSku) return ''; // 为每个站点生成siteSku - const siteSkus = selectedSites.map(site => { + const siteSkus = selectedSites.map((site) => { // 如果站点有shortName,则添加前缀,否则使用基础SKU if (site.skuPrefix) { return `${site.skuPrefix}-${baseSku}`; @@ -427,7 +493,7 @@ const CsvTool: React.FC = () => { }); // 使用分号分隔所有站点的siteSkus - return siteSkus.join(';').toUpperCase(); + return [baseSku, ...siteSkus].join(';').toUpperCase(); }; /** @@ -466,36 +532,58 @@ const CsvTool: React.FC = () => { const type = row.type || ''; // 生成基础SKU(不包含站点前缀) - const baseSku = generateSku(brand, version, category, flavor, strength, humidity, size, quantity, type); - + const baseSku = generateSku( + brand, + version, + category, + flavor, + strength, + humidity, + size, + quantity, + type, + ); + const name = generateName( + brand, + version, + category, + flavor, + strength, + humidity, + size, + quantity, + type, + ); // 为所有站点生成带前缀的siteSkus const siteSkus = generateSiteSkus(baseSku); - // 返回包含新SKU和siteSkus的行数据,将SKU直接保存到sku字段 - return { - ...row, + // 返回包含新SKU和siteSkus的行数据,将SKU直接保存到sku栏 + return { + ...row, sku: baseSku, // 直接生成在sku栏 + generatedName: name, + // name: name, // 生成的产品名称 siteSkus, - attribute_quantity: quantity // 确保quantity保存到attribute_quantity + attribute_quantity: quantity, // 确保quantity保存到attribute_quantity }; }); // Determine which data to use for processing and download let finalData = dataWithSku; - + // If generateBundleSkuForSingle is enabled, generate bundle products for single products - if(generateBundleSkuForSingle) { + if (generateBundleSkuForSingle) { // Filter out single records - const singleRecords = dataWithSku.filter(row => row.type === 'single'); - + const singleRecords = dataWithSku.filter( + (row) => row.type === 'single', + ); + // Get quantity values from the config (same source as other attributes like brand) - const quantityValues = config.quantities - .map(quantity => Number(quantity.name)) // Extract name and convert to number - .filter(quantity => !isNaN(quantity)); // Filter out invalid numbers - + const quantityValues = config.quantities.map(quantity=>quantity.name) + // Generate bundle products for each single record and quantity - const generatedBundleRecords = singleRecords.flatMap(singleRecord => { - return quantityValues.map(quantity => { + const generatedBundleRecords = singleRecords.flatMap((singleRecord) => { + return quantityValues.map((quantity) => { // Extract all necessary attributes from the single record const brand = singleRecord.attribute_brand || ''; const version = singleRecord.attribute_version || ''; @@ -504,47 +592,74 @@ const CsvTool: React.FC = () => { const strength = singleRecord.attribute_strength || ''; const humidity = singleRecord.attribute_humidity || ''; const size = singleRecord.attribute_size || singleRecord.size || ''; - // Generate bundle SKU with the quantity - const bundleSku = generateSku(brand, version, category, flavor, strength, humidity, size, quantity, 'bundle'); - + const bundleSku = generateSku( + brand, + version, + category, + flavor, + strength, + humidity, + size, + quantity, + 'bundle', + ); + + // Generate bundle name with the quantity + const bundleName = generateName( + brand, + version, + category, + flavor, + strength, + humidity, + size, + quantity, + 'bundle', + ); + // Generate siteSkus for the bundle const bundleSiteSkus = generateSiteSkus(bundleSku); - + // Create the bundle record return { ...singleRecord, type: 'bundle', // Change type to bundle sku: bundleSku, // Use the new bundle SKU + name: bundleName, // Use the new bundle name siteSkus: bundleSiteSkus, attribute_quantity: quantity, // Set the attribute_quantity component_1_sku: singleRecord.sku, // Set component_1_sku to the single product's sku - component_1_quantity: quantity, // Set component_1_quantity to the same as attribute_quantity + component_1_quantity: Number(quantity), // Set component_1_quantity to the same as attribute_quantity }; }); }); - + // Combine original dataWithSku with generated bundle records finalData = [...dataWithSku, ...generatedBundleRecords]; } - + // Set the processed data setProcessedData(finalData); - message.success({ content: 'SKU生成成功!正在自动下载...', key: 'processing' }); + message.success({ + content: 'SKU生成成功!正在自动下载...', + key: 'processing', + }); // 自动下载 the final data (with or without generated bundle products) downloadData(finalData); } catch (error) { - message.error({ content: '处理失败,请检查配置或文件.', key: 'processing' }); + message.error({ + content: '处理失败,请检查配置或文件.', + key: 'processing', + }); console.error('Processing Error:', error); } finally { setIsProcessing(false); } }; - - return ( @@ -564,9 +679,9 @@ const CsvTool: React.FC = () => { placeholder="请输入品牌,按回车确认" rules={[{ required: true, message: '至少需要一个品牌' }]} tooltip="品牌名称会作为SKU的第一个组成部分" - options={config.brands.map(opt => ({ + options={config.brands.map((opt) => ({ label: `${opt.name} (${opt.shortName})`, - value: opt.name + value: opt.name, }))} /> { placeholder="请输入分类,按回车确认" rules={[{ required: true, message: '至少需要一个分类' }]} tooltip="分类名称会作为SKU的第二个组成部分" - options={config.categories.map(opt => ({ + options={config.categories.map((opt) => ({ label: `${opt.name} (${opt.shortName})`, - value: opt.name + value: opt.name, }))} /> { placeholder="请输入口味,按回车确认" rules={[{ required: true, message: '至少需要一个口味' }]} tooltip="口味名称会作为SKU的第三个组成部分" - options={config.flavors.map(opt => ({ + options={config.flavors.map((opt) => ({ label: `${opt.name} (${opt.shortName})`, - value: opt.name + value: opt.name, }))} /> { mode="tags" placeholder="请输入强度,按回车确认" tooltip="强度信息会作为SKU的第四个组成部分" - options={config.strengths.map(opt => ({ + options={config.strengths.map((opt) => ({ label: `${opt.name} (${opt.shortName})`, - value: opt.name + value: opt.name, }))} /> { mode="tags" placeholder="请输入湿度,按回车确认" tooltip="湿度信息会作为SKU的第五个组成部分" - options={config.humidities.map(opt => ({ + options={config.humidities.map((opt) => ({ label: `${opt.name} (${opt.shortName})`, - value: opt.name + value: opt.name, }))} /> { mode="tags" placeholder="请输入版本,按回车确认" tooltip="版本信息会作为SKU的第六个组成部分" - options={config.versions.map(opt => ({ + options={config.versions.map((opt) => ({ label: `${opt.name} (${opt.shortName})`, - value: opt.name + value: opt.name, }))} /> { mode="tags" placeholder="请输入尺寸,按回车确认" tooltip="尺寸信息会作为SKU的第七个组成部分" - options={config.sizes.map(opt => ({ + options={config.sizes.map((opt) => ({ label: `${opt.name} (${opt.shortName})`, - value: opt.name + value: opt.name, }))} /> - + ({ + options={config.quantities.map((opt) => ({ label: `${opt.name} (${opt.shortName})`, - value: opt.name + value: opt.name, }))} fieldProps={{ allowClear: true }} /> - + { - - + + - {sites.map(site => ( + {sites.map((site) => ( - - + + ))}
站点名称ShortName + 站点名称 + + ShortName +
{site.name}{site.skuPrefix} + {site.name} + + {site.skuPrefix} +
) : ( -

暂无站点信息

+

+ 暂无站点信息 +

)}
-

说明:所有站点的shortName将作为前缀添加到生成的SKU中,以分号分隔。

+

+ 说明:所有站点的shortName将作为前缀添加到生成的SKU中,以分号分隔。 +

@@ -719,16 +869,27 @@ const CsvTool: React.FC = () => { - + {/* 显示处理结果摘要 */} {processedData.length > 0 && ( -
+

已成功为 {processedData.length} 条产品记录生成SKU!

@@ -741,4 +902,4 @@ const CsvTool: React.FC = () => { ); }; -export default CsvTool; \ No newline at end of file +export default CsvTool; diff --git a/src/pages/Product/List/CreateForm.tsx b/src/pages/Product/List/CreateForm.tsx index 99c5108..5ece0ad 100644 --- a/src/pages/Product/List/CreateForm.tsx +++ b/src/pages/Product/List/CreateForm.tsx @@ -77,7 +77,7 @@ const CreateForm: React.FC<{ const strengthName: string = String(strengthValues?.[0] || ''); const flavorName: string = String(flavorValues?.[0] || ''); const humidityName: string = String(humidityValues?.[0] || ''); - console.log(formValues) + console.log(formValues); // 调用模板渲染API来生成SKU const { data: rendered, @@ -86,25 +86,25 @@ const CreateForm: React.FC<{ } = await templatecontrollerRendertemplate( { name: 'product.sku' }, { - category: formValues.category, + category: formValues.category, attributes: [ { - dict: {name: "brand"}, + dict: { name: 'brand' }, shortName: brandName || '', }, { - dict: {name: "flavor"}, + dict: { name: 'flavor' }, shortName: flavorName || '', }, { - dict: {name: "strength"}, + dict: { name: 'strength' }, shortName: strengthName || '', }, { - dict: {name: "humidity"}, + dict: { name: 'humidity' }, shortName: humidityName ? capitalize(humidityName) : '', }, - ] + ], }, ); if (!success) { @@ -153,8 +153,8 @@ const CreateForm: React.FC<{ humidityName === 'dry' ? 'Dry' : humidityName === 'moisture' - ? 'Moisture' - : capitalize(humidityName), + ? 'Moisture' + : capitalize(humidityName), }, ); if (!success) { @@ -219,20 +219,21 @@ const CreateForm: React.FC<{ // 根据产品类型决定是否组装 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 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, diff --git a/src/pages/Product/List/EditForm.tsx b/src/pages/Product/List/EditForm.tsx index aa0515f..29614d2 100644 --- a/src/pages/Product/List/EditForm.tsx +++ b/src/pages/Product/List/EditForm.tsx @@ -4,7 +4,6 @@ import { productcontrollerGetcategoryattributes, productcontrollerGetproductcomponents, productcontrollerGetproductlist, - productcontrollerGetproductsiteskus, productcontrollerUpdateproduct, } from '@/servers/api/product'; import { sitecontrollerAll } from '@/servers/api/site'; @@ -121,7 +120,7 @@ const EditForm: React.FC<{ categoryId: (record as any).categoryId || (record as any).category?.id, // 初始化站点SKU为字符串数组 // 修改后代码: - siteSkus:(record.siteSkus||[]).map(code => ({ code })), + siteSkus: (record.siteSkus || []).map((code) => ({ code })), }; }, [record, components, type]); return ( @@ -188,7 +187,7 @@ const EditForm: React.FC<{ attributes, type: values.type, // 直接使用 type categoryId: values.categoryId, - siteSkus: values.siteSkus.map((v: {code: string}) => (v.code)) || [], // 直接传递字符串数组 + siteSkus: values.siteSkus.map((v: { code: string }) => v.code) || [], // 直接传递字符串数组 // 连带更新 components components: values.type === 'bundle' @@ -316,9 +315,17 @@ const EditForm: React.FC<{ rules={[{ required: true, message: '请输入单品SKU' }]} request={async ({ keyWords }) => { const params = keyWords - ? { where: {sku: keyWords, name: keyWords, type: 'single'} } - : { 'per_page': 9999 , where: {type: 'single'} }; - const { data } = await productcontrollerGetproductlist(params); + ? { + where: { + sku: keyWords, + name: keyWords, + type: 'single', + }, + } + : { per_page: 9999, where: { type: 'single' } }; + const { data } = await productcontrollerGetproductlist( + params, + ); if (!data || !data.items) { return []; } diff --git a/src/pages/Product/List/SyncToSiteModal/index.tsx b/src/pages/Product/List/SyncToSiteModal/index.tsx index 0f74a9f..84b4898 100644 --- a/src/pages/Product/List/SyncToSiteModal/index.tsx +++ b/src/pages/Product/List/SyncToSiteModal/index.tsx @@ -1,11 +1,11 @@ -import { showBatchOperationResult } from '@/utils/showResult'; +import { productcontrollerBatchsynctosite } from '@/servers/api/product'; import { sitecontrollerAll } from '@/servers/api/site'; import { templatecontrollerRendertemplate } from '@/servers/api/template'; -import { productcontrollerBatchsynctosite } from '@/servers/api/product'; +import { showBatchOperationResult } from '@/utils/showResult'; import { ModalForm, - ProFormSelect, ProFormDependency, + ProFormSelect, ProFormText, } from '@ant-design/pro-components'; import { App, Button, Tag } from 'antd'; @@ -36,12 +36,14 @@ const SyncToSiteModal: React.FC = ({ product: API.Product, ): Promise => { try { - console.log('site', currentSite) + console.log('site', currentSite); const { data: renderedSku } = await templatecontrollerRendertemplate( { name: 'site.product.sku' }, { site: currentSite, product }, ); - return renderedSku || `${currentSite.skuPrefix || ''}${product.sku || ''}`; + return ( + renderedSku || `${currentSite.skuPrefix || ''}${product.sku || ''}` + ); } catch (error) { return `${currentSite.skuPrefix || ''}${product.sku || ''}`; } @@ -95,15 +97,16 @@ const SyncToSiteModal: React.FC = ({ } }} onFinish={async (values) => { - console.log(`values`,values) + console.log(`values`, values); if (!values.siteId) return false; try { const siteSkusMap = values.siteSkus || {}; const data = products.map((product) => ({ productId: product.id, - siteSku: siteSkusMap[product.id] || `${values.siteId}-${product.sku}`, + siteSku: + siteSkusMap[product.id] || `${values.siteId}-${product.sku}`, })); - console.log(`data`,data) + console.log(`data`, data); const result = await productcontrollerBatchsynctosite({ siteId: values.siteId, data, @@ -128,7 +131,14 @@ const SyncToSiteModal: React.FC = ({ {({ siteId }) => (
-
+
原始SKU: {row.sku || '-'}
已有商品SKU:{' '} @@ -150,9 +160,12 @@ const SyncToSiteModal: React.FC = ({ fieldProps={{ onChange: (e) => { // 手动输入时更新表单值 - const currentValues = formRef.current?.getFieldValue('siteSkus') || {}; + const currentValues = + formRef.current?.getFieldValue('siteSkus') || {}; currentValues[row.id] = e.target.value; - formRef.current?.setFieldsValue({ siteSkus: currentValues }); + formRef.current?.setFieldsValue({ + siteSkus: currentValues, + }); }, }} /> @@ -162,11 +175,18 @@ const SyncToSiteModal: React.FC = ({ size="small" onClick={async () => { if (siteId) { - const currentSite = sites.find((s: any) => s.id === siteId) || {}; - const siteSku = await generateSingleSiteSku(currentSite, row); - const currentValues = formRef.current?.getFieldValue('siteSkus') || {}; + const currentSite = + sites.find((s: any) => s.id === siteId) || {}; + const siteSku = await generateSingleSiteSku( + currentSite, + row, + ); + const currentValues = + formRef.current?.getFieldValue('siteSkus') || {}; currentValues[row.id] = siteSku; - formRef.current?.setFieldsValue({ siteSkus: currentValues }); + formRef.current?.setFieldsValue({ + siteSkus: currentValues, + }); } }} > diff --git a/src/pages/Product/List/index.tsx b/src/pages/Product/List/index.tsx index 9869278..53de10b 100644 --- a/src/pages/Product/List/index.tsx +++ b/src/pages/Product/List/index.tsx @@ -3,9 +3,8 @@ import { productcontrollerBatchupdateproduct, productcontrollerDeleteproduct, productcontrollerGetcategoriesall, - productcontrollerGetproductcomponents, productcontrollerGetproductlist, - productcontrollerUpdatenamecn + productcontrollerUpdatenamecn, } from '@/servers/api/product'; import { ActionType, @@ -14,7 +13,7 @@ import { ProColumns, ProFormSelect, ProFormText, - ProTable + ProTable, } from '@ant-design/pro-components'; import { request } from '@umijs/max'; import { App, Button, Modal, Popconfirm, Tag, Upload } from 'antd'; @@ -154,13 +153,18 @@ const BatchEditModal: React.FC<{ ); }; -const ProductList = ({ filter, columns }: { filter: { skus: string[] }, columns: any[] }) => { - +const ProductList = ({ + filter, + columns, +}: { + filter: { skus: string[] }; + columns: any[]; +}) => { return ( { const { data, success } = await productcontrollerGetproductlist({ - where: filter + where: filter, }); if (!success) return []; return data || []; @@ -170,7 +174,7 @@ const ProductList = ({ filter, columns }: { filter: { skus: string[] }, columns: rowKey="id" bordered size="small" - scroll={{ x: "max-content" }} + scroll={{ x: 'max-content' }} headerTitle={null} toolBarRender={false} /> @@ -219,6 +223,7 @@ const List: React.FC = () => { { title: '关联商品', dataIndex: 'siteSkus', + width: 200, render: (_, record) => ( <> {record.siteSkus?.map((siteSku, index) => ( @@ -244,7 +249,6 @@ const List: React.FC = () => { }, }, - { title: '价格', dataIndex: 'price', @@ -257,7 +261,7 @@ const List: React.FC = () => { hideInSearch: true, sorter: true, }, - { + { title: '商品类型', dataIndex: 'category', render: (_, record: any) => { @@ -443,7 +447,6 @@ const List: React.FC = () => { onError?.(error); } }} - > , @@ -506,8 +509,8 @@ const List: React.FC = () => { sortField = field; sortOrder = sort[field]; } - const { current, pageSize, ...where } = params - console.log(`params`, params) + const { current, pageSize, ...where } = params; + console.log(`params`, params); const { data, success } = await productcontrollerGetproductlist({ where, page: current || 1, @@ -573,5 +576,4 @@ const List: React.FC = () => { ); }; - export default List; diff --git a/src/pages/Product/Sync/index.tsx b/src/pages/Product/Sync/index.tsx index 734f048..c13ed77 100644 --- a/src/pages/Product/Sync/index.tsx +++ b/src/pages/Product/Sync/index.tsx @@ -1,4 +1,3 @@ -import { showBatchOperationResult } from '@/utils/showResult'; import { productcontrollerBatchsynctosite, productcontrollerGetproductlist, diff --git a/src/pages/Site/List/index.tsx b/src/pages/Site/List/index.tsx index 5a04a56..4cee73f 100644 --- a/src/pages/Site/List/index.tsx +++ b/src/pages/Site/List/index.tsx @@ -5,14 +5,29 @@ import { sitecontrollerList, sitecontrollerUpdate, } from '@/servers/api/site'; -import { subscriptioncontrollerSync } from '@/servers/api/subscription'; 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 { subscriptioncontrollerSync } from '@/servers/api/subscription'; +import { + ActionType, + DrawerForm, + ProColumns, + ProFormSelect, + ProFormSwitch, + ProTable, +} from '@ant-design/pro-components'; +import { + Button, + Form, + message, + notification, + Popconfirm, + Space, + Tag, +} from 'antd'; import * as countries from 'i18n-iso-countries'; import zhCN from 'i18n-iso-countries/langs/zh'; +import React, { useEffect, useRef, useState } from 'react'; +import EditSiteForm from '../Shop/EditSiteForm'; // 引入重构后的表单组件 // 区域数据项类型 interface AreaItem { @@ -49,7 +64,6 @@ const SiteList: React.FC = () => { const [batchEditForm] = Form.useForm(); countries.registerLocale(zhCN); - const handleSync = async (ids: number[]) => { if (!ids.length) return; const hide = message.loading('正在同步...', 0); @@ -204,8 +218,8 @@ const SiteList: React.FC = () => { }, { // 地区列配置 - title: "地区", - dataIndex: "areas", + title: '地区', + dataIndex: 'areas', hideInSearch: true, render: (_, row) => { // 如果没有关联地区,显示"全局"标签 @@ -308,10 +322,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 { diff --git a/src/pages/Site/Shop/Customers/index.tsx b/src/pages/Site/Shop/Customers/index.tsx index e6fcfc7..28814b8 100644 --- a/src/pages/Site/Shop/Customers/index.tsx +++ b/src/pages/Site/Shop/Customers/index.tsx @@ -465,7 +465,11 @@ const CustomerPage: React.FC = () => { = ({ // 如果是编辑模式并且有初始值 if (isEdit && initialValues) { // 编辑模式下, 设置表单值为初始值 - const { token, consumerKey, consumerSecret, ...safeInitialValues } = initialValues; + const { token, consumerKey, consumerSecret, ...safeInitialValues } = + initialValues; // 清空敏感字段, 让用户输入最新的数据 form.setFieldsValue({ ...safeInitialValues, diff --git a/src/pages/Site/Shop/Orders/index.tsx b/src/pages/Site/Shop/Orders/index.tsx index 9c224b0..22a1ae1 100644 --- a/src/pages/Site/Shop/Orders/index.tsx +++ b/src/pages/Site/Shop/Orders/index.tsx @@ -68,7 +68,7 @@ const OrdersPage: React.FC = () => { dataIndex: 'id', }, { - title:'订单号', + title: '订单号', dataIndex: 'number', }, { @@ -127,7 +127,7 @@ const OrdersPage: React.FC = () => { title: '客户姓名', dataIndex: 'customer_name', }, - { + { title: '客户IP', dataIndex: 'customer_ip_address', }, @@ -190,7 +190,7 @@ const OrdersPage: React.FC = () => { ellipsis: true, copyable: true, }, - { + { title: '发货状态', dataIndex: 'fulfillment_status', // hideInSearch: true, @@ -384,8 +384,7 @@ const OrdersPage: React.FC = () => { setSelectedRowKeys={setSelectedRowKeys} siteId={siteId} />, - - , + , , + setIsSource(!isSource), + }} + />, + ]} + /> +
+
+ YOONE:{' '} + {(yooneTotal.yoone3Quantity || 0) + + (yooneTotal.yoone6Quantity || 0) + + (yooneTotal.yoone9Quantity || 0) + + (yooneTotal.yoone12Quantity || 0) + + (yooneTotal.yoone15Quantity || 0) + + (yooneTotal.yoone18Quantity || 0) + + (yooneTotal.zexQuantity || 0)} +
+
YOONE 3MG: {yooneTotal.yoone3Quantity || 0}
+
YOONE 6MG: {yooneTotal.yoone6Quantity || 0}
+
YOONE 9MG: {yooneTotal.yoone9Quantity || 0}
+
YOONE 12MG新: {yooneTotal.yoone12QuantityNew || 0}
+
+ YOONE 12MG白:{' '} + {(yooneTotal.yoone12Quantity || 0) - + (yooneTotal.yoone12QuantityNew || 0)} +
+
YOONE 15MG: {yooneTotal.yoone15Quantity || 0}
+
YOONE 18MG: {yooneTotal.yoone18Quantity || 0}
+
ZEX: {yooneTotal.zexQuantity || 0}
+
+ + ); +}; + +export default ListPage; diff --git a/src/pages/Statistics/Sales/index.tsx b/src/pages/Statistics/Sales/index.tsx index 903c182..a8ab853 100644 --- a/src/pages/Statistics/Sales/index.tsx +++ b/src/pages/Statistics/Sales/index.tsx @@ -41,6 +41,10 @@ const ListPage: React.FC = () => { valueType: 'switch', hideInTable: true, }, + { + title: '产品名称', + dataIndex: 'sku', + }, { title: '产品名称', dataIndex: 'name', @@ -73,37 +77,31 @@ const ListPage: React.FC = () => { title: '一单订单数', dataIndex: 'firstOrderCount', hideInSearch: true, - render(_, record) { - if (isSource) return record.firstOrderCount; - return `${record.firstOrderCount}(${record.firstOrderYOONEBoxCount})`; - }, + }, + { + title: '一单YOONE盒数', + dataIndex: 'firstOrderYOONEBoxCount', + hideInSearch: true, }, { title: '两单订单数', dataIndex: 'secondOrderCount', hideInSearch: true, - render(_, record) { - if (isSource) return record.secondOrderCount; - return `${record.secondOrderCount}(${record.secondOrderYOONEBoxCount})`; - }, + }, + { + title: '两单YOONE盒数', + dataIndex: 'secondOrderYOONEBoxCount', + hideInSearch: true, }, { title: '三单订单数', dataIndex: 'thirdOrderCount', hideInSearch: true, - render(_, record) { - if (isSource) return record.thirdOrderCount; - return `${record.thirdOrderCount}(${record.thirdOrderYOONEBoxCount})`; - }, }, { - title: '三单以上订单数', - dataIndex: 'moreThirdOrderCount', + title: '三单YOONE盒数', + dataIndex: 'thirdOrderYOONEBoxCount', hideInSearch: true, - render(_, record) { - if (isSource) return record.moreThirdOrderCount; - return `${record.moreThirdOrderCount}(${record.moreThirdOrderYOONEBoxCount})`; - }, }, { title: '订单数', diff --git a/src/pages/Stock/Warehouse/index.tsx b/src/pages/Stock/Warehouse/index.tsx index 604f726..7081c9c 100644 --- a/src/pages/Stock/Warehouse/index.tsx +++ b/src/pages/Stock/Warehouse/index.tsx @@ -16,9 +16,9 @@ import { ProTable, } from '@ant-design/pro-components'; import { App, Button, Divider, Popconfirm, Space, Tag } from 'antd'; -import { useRef } from 'react'; import * as countries from 'i18n-iso-countries'; import zhCN from 'i18n-iso-countries/langs/zh'; +import { useRef } from 'react'; // 初始化中文语言包 countries.registerLocale(zhCN); diff --git a/src/pages/Subscription/Orders/index.tsx b/src/pages/Subscription/Orders/index.tsx index 68b9730..5f6e885 100644 --- a/src/pages/Subscription/Orders/index.tsx +++ b/src/pages/Subscription/Orders/index.tsx @@ -161,7 +161,7 @@ const OrdersPage: React.FC = () => { rowKey="id" columns={columns} request={request} - pagination={{ + pagination={{ showSizeChanger: true, showQuickJumper: true, }}