批量设置价格后马上预览

perf/export-docx
Jimmy Liow 11 months ago
parent e0784eb593
commit b8b679a0ec

@ -91,13 +91,11 @@ const useAccountStore = create((set, get) => ({
return postAccountPassword(formData)
},
newEmptyRole: () => {
return {
newEmptyRole: () => ({
role_id: null,
role_name: '',
role_ids: ''
}
},
}),
newEmptyAccount: () => {
return {

@ -1,6 +1,6 @@
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import dayjs from 'dayjs'
import { fetchJSON, postForm, postJSON } from '@/utils/request';
import { HT_HOST } from '@/config';
import { groupBy } from '@/utils/commons';
@ -107,6 +107,7 @@ const initialState = {
activeAgency: {}, // 审核/编辑 页: 当前的供应商
agencyProducts: {}, // 审核/编辑 页: 供应商产品列表
editingProduct: {}, // 编辑页: 当前编辑的产品
quotationList: [], // 编辑页: 当前产品报价列表
editing: false,
};
export const useProductsStore = create(
@ -120,11 +121,37 @@ export const useProductsStore = create(
setAgencyList: (agencyList) => set({ agencyList }),
setActiveAgency: (activeAgency) => set({ activeAgency }),
setAgencyProducts: (agencyProducts) => set({ agencyProducts }),
setEditingProduct: (editingProduct) => set({ editingProduct }),
setEditingProduct: (product) => {
set(() => ({
editingProduct: product,
quotationList: product.quotation
}))
},
setEditing: (editing) => set({ editing }),
reset: () => set(initialState),
newEmptyQuotation: () => ({
id: null,
adult_cost: 0,
child_cost: 0,
currency: 'RMB',
unit_id: '0',
group_size_min: 1,
group_size_max: 10,
use_dates: [
dayjs().startOf('M'),
dayjs().endOf('M')
],
weekdays: '5, 6'
}),
appendQuotationList: (newList) => {
set((state) => ({
quotationList: [...state.quotationList, ...newList]
}))
},
// side effects
searchAgency: async (param) => {
const { setLoading, setAgencyList } = get();

@ -56,6 +56,7 @@ const BatchImportPrice = ({ onBatchImportData }) => {
currency: definition.currency,
unit: definition.unitName,
// API
use_dates_start: definition.useDate[0],
use_dates_end: definition.useDate[1],
weekdays: definition.weekend.join(','),
@ -315,27 +316,26 @@ const BatchImportPrice = ({ onBatchImportData }) => {
remove(field.name)
}} />}
>
<Form.Item label="类型" name={[field.name, 'unitName']}>
<Select placeholder="选择类型">
<Option value="每人">每人</Option>
<Option value="每团">每团</Option>
</Select>
</Form.Item>
<Form.Item label="币种" name={[field.name, 'currency']}>
<Select placeholder="选择币种">
<Option value="CNY">CNY</Option>
<Option value="USD">USD</Option>
</Select>
</Form.Item>
<Form.Item label="有效期" name={[field.name, 'useDate']}>
<RangePicker style={{width: '100%'}} allowClear={true} inputReadOnly={true} presets={presets} placeholder={['From', 'Thru']} />
<Form.Item label="类型" name={[field.name, 'unitName']}>
<Select placeholder="选择类型">
<Option value="每人">每人</Option>
<Option value="每团">每团</Option>
</Select>
</Form.Item>
<Form.Item label="周末" name={[field.name, 'weekend']}>
<Checkbox.Group
options={['5', '6', '7']}
/>
</Form.Item>
<Form.Item label="有效期" name={[field.name, 'useDate']}>
<RangePicker style={{width: '100%'}} allowClear={true} inputReadOnly={true} presets={presets} placeholder={['From', 'Thru']} />
</Form.Item>
<Form.Item label="人等">
<Form.List name={[field.name, 'priceList']}>
{(priceFieldList, priceOptList) => (

@ -1,104 +1,273 @@
import { useState } from 'react'
import { Table, Form, Modal, Button, Radio, Input, InputNumber, Checkbox, DatePicker, Space } from 'antd'
import { useEffect, useState } from 'react'
import { Table, Form, Modal, Button, Radio, Input, Flex, Card, Select, Typography, InputNumber, Checkbox, DatePicker, Space } from 'antd'
import { useTranslation } from 'react-i18next'
import dayjs from 'dayjs'
import BatchImportPrice from './BatchImportPrice'
import { CloseOutlined, StarTwoTone, PlusOutlined } from '@ant-design/icons';
import { useDatePresets } from '@/hooks/useDatePresets'
import useProductsStore from '@/stores/Products/Index'
const { RangePicker } = DatePicker
const ProductInfoQuotation = ({ editable, ...props }) => {
const { t } = useTranslation()
const [isQuotationModalOpen, setQuotationModalOpen] = useState(false)
const [quotationForm] = Form.useForm()
const datePresets = useDatePresets()
const [editingProduct] = useProductsStore((state) => [state.editingProduct])
const [batchImportPriceVisible, setBatchImportPriceVisible] = useState(false)
const [quotationTableVisible, setQuotationTableVisible] = useState(false)
const [quotation, setQuotation] = useState([])
const [batchImportData, setBatchImportData] = useState([])
const handleBatchImportData = (data) => {
setBatchImportData(data)
}
const handleBatchImportOK = () => {
const tempBatchImportData = batchImportData.map((item) => {
const { tag, validPeriod, ...rest } = item
return rest
const PriceInput = (props) => {
const { id, value = {}, onChange } = props
const [numberStart, setNumberStart] = useState(0)
const [numberEnd, setNumberEnd] = useState(0)
const [audultPrice, setAudultPrice] = useState(0)
const [childrenPrice, setChildrenPrice] = useState(0)
const triggerChange = (changedValue) => {
onChange?.({
numberStart,
numberEnd,
audultPrice,
childrenPrice,
...value,
...changedValue,
})
const newData = [...quotation, ...tempBatchImportData]
const sortedData = [...newData].sort((a, b) => {
if (a.group_size_min !== b.group_size_min) {
return a.group_size_min - b.group_size_min
}
return a.group_size_max - b.group_size_max
const onNumberStartChange = (e) => {
const newNumber = parseInt(e.target.value || '0', 10)
if (Number.isNaN(numberStart)) {
return
}
if (!('numberStart' in value)) {
setNumberStart(newNumber);
}
triggerChange({
numberStart: newNumber,
})
setQuotation(sortedData)
setBatchImportPriceVisible(false)
}
const quotationTableVisibleOK = () => {
const tempQuotation = [...quotation]
const sortedData = [...tempQuotation].sort((a, b) => {
const aValidPeriod = dayjs(a.use_dates_end).diff(dayjs(a.use_dates_start))
const bValidPeriod = dayjs(b.use_dates_end).diff(dayjs(b.use_dates_start))
if (aValidPeriod !== bValidPeriod) {
return aValidPeriod - bValidPeriod
const onNumberEndChange = (e) => {
const newNumber = parseInt(e.target.value || '0', 10)
if (Number.isNaN(numberEnd)) {
return
}
const aGroupSize = a.group_size_max - a.group_size_min
const bGroupSize = b.group_size_max - b.group_size_min
return aGroupSize - bGroupSize
if (!('numberEnd' in value)) {
setNumberEnd(newNumber)
}
triggerChange({
numberEnd: newNumber,
});
};
const onAudultPriceChange = (e) => {
const newNumber = parseInt(e.target.value || '0', 10)
if (Number.isNaN(audultPrice)) {
return
}
if (!('audultPrice' in value)) {
setAudultPrice(newNumber)
}
triggerChange({
audultPrice: newNumber,
})
setQuotation(sortedData)
setQuotationTableVisible(false)
}
const quotationTableVisibleCancel = () => {
setQuotationTableVisible(false)
const onChildrenPriceChange = (e) => {
const newNumber = parseInt(e.target.value || '0', 10)
if (Number.isNaN(childrenPrice)) {
return
}
if (!('childrenPrice' in value)) {
setChildrenPrice(newNumber)
}
triggerChange({
childrenPrice: newNumber,
})
};
return (
<Space.Compact id={id}>
<Input
type='text'
value={value.numberStart || numberStart}
onChange={onNumberStartChange}
style={{
width: '20%',
}}
/>
<Input
type='text'
value={value.numberEnd || numberEnd}
onChange={onNumberEndChange}
style={{
width: '40%',
}}
addonBefore='~'
/>
<Input
type='text'
value={value.audultPrice || audultPrice}
onChange={onAudultPriceChange}
style={{
width: '70%',
}}
addonBefore='成人价'
/>
<Input
type='text'
value={value.childrenPrice || childrenPrice}
onChange={onChildrenPriceChange}
style={{
width: '70%',
}}
addonBefore='儿童价'
/>
</Space.Compact>
)
}
const handleDelete = (index) => {
const newData = [...quotation]
newData.splice(index, 1)
const sortedData = [...newData].sort((a, b) => {
const aValidPeriod = dayjs(a.use_dates_end).diff(dayjs(a.use_dates_start))
const bValidPeriod = dayjs(b.use_dates_end).diff(dayjs(b.use_dates_start))
if (aValidPeriod !== bValidPeriod) {
return aValidPeriod - bValidPeriod
const batchSetupInitialValues = {
'defList': [
//
{
'useDate': [
dayjs().add(1, 'year').startOf('y'), dayjs().add(1, 'year').endOf('y')
],
'unitName': '每人',
'currency': 'CNY',
'weekend': [
'5',
'6'
],
'priceList': [
{
'priceInput': {
'numberStart': 1,
'numberEnd': 2,
'audultPrice': 0,
'childrenPrice': 0
}
},
{
'priceInput': {
'numberStart': 3,
'numberEnd': 4,
'audultPrice': 0,
'childrenPrice': 0
}
},
{
'priceInput': {
'numberStart': 5,
'numberEnd': 6,
'audultPrice': 0,
'childrenPrice': 0
}
},
{
'priceInput': {
'numberStart': 7,
'numberEnd': 9,
'audultPrice': 0,
'childrenPrice': 0
}
}
]
},
//
{
'useDate': [
dayjs().add(1, 'year').subtract(2, 'M').startOf('M'), dayjs().add(1, 'year').endOf('M')
],
'unitName': '每人',
'currency': 'CNY',
'weekend': [
'5',
'6'
],
'priceList': [
{
'priceInput': {
'numberStart': 1,
'numberEnd': 2,
'audultPrice': 0,
'childrenPrice': 0
}
},
{
'priceInput': {
'numberStart': 3,
'numberEnd': 4,
'audultPrice': 0,
'childrenPrice': 0
}
},
{
'priceInput': {
'numberStart': 5,
'numberEnd': 6,
'audultPrice': 0,
'childrenPrice': 0
}
const aGroupSize = a.group_size_max - a.group_size_min
const bGroupSize = b.group_size_max - b.group_size_min
},
{
'priceInput': {
'numberStart': 7,
'numberEnd': 9,
'audultPrice': 0,
'childrenPrice': 0
}
}
]
}
]
}
return aGroupSize - bGroupSize
})
setQuotation(sortedData)
const defaultPriceValue = {
'priceInput': {
'numberStart': 1,
'numberEnd': 2,
'audultPrice': 0,
'childrenPrice': 0
}
}
const defaultDefinitionValue = {
'useDate': [
dayjs().add(1, 'year').subtract(2, 'M').startOf('M'), dayjs().add(1, 'year').endOf('M')
],
'unitName': '每人',
'currency': 'CNY',
'weekend': [
'5',
'6'
],
'priceList': [
defaultPriceValue
]
}
const ProductInfoQuotation = ({ editable, ...props }) => {
const { t } = useTranslation()
const [isQuotationModalOpen, setQuotationModalOpen] = useState(false)
const [isBatchSetupModalOpen, setBatchSetupModalOpen] = useState(false)
const [quotationForm] = Form.useForm()
const [batchSetupForm] = Form.useForm()
const datePresets = useDatePresets()
const [newEmptyQuotation, appendQuotationList, quotationList] =
useProductsStore((state) => [state.newEmptyQuotation, state.appendQuotationList, state.quotationList])
useEffect(() => {
console.info('quotationList: ', quotationList)
}, [quotationList])
const onQuotationSeleted = async (quotation) => {
// RangePicker
// start, end RangePicker
quotation.use_dates = [dayjs(quotation.use_dates_start), dayjs(quotation.use_dates_end)]
quotationForm.setFieldsValue(quotation)
setQuotationModalOpen(true)
}
const onNewQuotation = () => {
// const emptyQuotation = newEmptyQuotation()
// quotationForm.setFieldsValue(emptyQuotation)
// setQuotationModalOpen(true)
const emptyQuotation = newEmptyQuotation()
quotationForm.setFieldsValue(emptyQuotation)
setQuotationModalOpen(true)
}
const onQuotationFinish = (values) => {
console.info(values)
setQuotationModalOpen(false)
// saveOrUpdateQuotation(values)
// .then(() => {
// setQuotationModalOpen(false)
@ -118,6 +287,35 @@ const ProductInfoQuotation = ({ editable, ...props }) => {
// form.resetFields()
}
const onBatchSetupFinish = () => {
// console.info(batchSetupForm.getFieldsValue())
let previewList = []
const defList = batchSetupForm.getFieldsValue().defList
defList.forEach(definition => {
const previewPrice = definition?.priceList.map(price => {
return {
id: 0,
adult_cost: price.priceInput.audultPrice,
child_cost: price.priceInput.childrenPrice,
group_size_min: price.priceInput.numberStart,
group_size_max: price.priceInput.numberEnd,
currency: definition.currency,
unit: definition.unitName,
// API
use_dates_start: definition.useDate[0].format('YYYY-MM-DD'),
use_dates_end: definition.useDate[1].format('YYYY-MM-DD'),
weekdays: definition.weekend.join(','),
}
})
previewList.push(...previewPrice)
})
appendQuotationList(previewList)
setBatchSetupModalOpen(false)
}
const quotationColumns = [
{ title: t('products:adultPrice'), dataIndex: 'adult_cost', width: '4rem' },
{ title: t('products:childrenPrice'), dataIndex: 'child_cost', width: '4rem' },
@ -162,28 +360,110 @@ const ProductInfoQuotation = ({ editable, ...props }) => {
<>
<h2>{t('products:EditComponents.Quotation')}</h2>
<Table
rowKey={'id'}
// rowKey={'id'}
bordered
dataSource={editingProduct.quotation}
dataSource={quotationList}
columns={quotationColumns}
pagination={false}
/>
{
// editable &&
<Button onClick={() => setQuotationModalOpen(true)} type='primary' ghost style={{ marginTop: 16 }}>
<Button onClick={() => onNewQuotation()} type='primary' ghost style={{ marginTop: 16 }}>
{t('products:addQuotation')}
</Button>
}
{
// editable &&
<Button onClick={() => setBatchImportPriceVisible(true)} type='primary' ghost style={{ marginTop: 16, marginLeft: 16 }}>
批量添加
<Button onClick={() => setBatchSetupModalOpen(true)} type='primary' ghost style={{ marginTop: 16, marginLeft: 16 }}>
批量设置
</Button>
}
<Modal title='批量设置价格' open={batchImportPriceVisible} onOk={handleBatchImportOK} onCancel={() => setBatchImportPriceVisible(false)} width={'90%'}>
<BatchImportPrice onBatchImportData={handleBatchImportData} />
<Modal
centered
title='批量设置价格'
width={'640px'}
open={isBatchSetupModalOpen}
onOk={() => onBatchSetupFinish()}
onCancel={() => setBatchSetupModalOpen(false)}
destroyOnClose
forceRender
>
<Form
labelCol={{ span: 3 }}
wrapperCol={{ span: 20 }}
form={batchSetupForm}
name='batchSetupForm'
autoComplete='off'
initialValues={batchSetupInitialValues}
>
<Form.List name='defList'>
{(fields, { add, remove }) => (
<Flex gap='middle' vertical>
{fields.map((field, index) => (
<Card
size='small'
title={index == 0 ? '旺季' : index == 1 ? '淡季' : '其他'}
key={field.key}
extra={index == 0 ? <StarTwoTone twoToneColor='#eb2f96' /> : <CloseOutlined onClick={() => {
remove(field.name)
}} />}
>
<Form.Item label='币种' name={[field.name, 'currency']}>
<Select placeholder='选择币种'>
<Select.Option value='CNY'>CNY</Select.Option>
<Select.Option value='USD'>USD</Select.Option>
</Select>
</Form.Item>
<Form.Item label='类型' name={[field.name, 'unitName']}>
<Select placeholder='选择类型'>
<Select.Option value='每人'>每人</Select.Option>
<Select.Option value='每团'>每团</Select.Option>
</Select>
</Form.Item>
<Form.Item label='周末' name={[field.name, 'weekend']}>
<Checkbox.Group
options={['5', '6', '7']}
/>
</Form.Item>
<Form.Item label='有效期' name={[field.name, 'useDate']}>
<RangePicker style={{ width: '100%' }} allowClear={true} inputReadOnly={true} presets={datePresets} placeholder={['From', 'Thru']} />
</Form.Item>
<Form.Item label='人等'>
<Form.List name={[field.name, 'priceList']}>
{(priceFieldList, priceOptList) => (
<Flex gap='middle' vertical>
{priceFieldList.map((priceField, index) => (
<Space key={priceField.key}>
<Form.Item noStyle name={[priceField.name, 'priceInput']}>
<PriceInput />
</Form.Item>
{index == 0 ? <StarTwoTone twoToneColor='#eb2f96' /> : <CloseOutlined onClick={() => priceOptList.remove(priceField.name)} />}
</Space>
))}
<Button type='dashed' icon={<PlusOutlined />} onClick={() => priceOptList.add(defaultPriceValue)} block>
新增人等
</Button>
</Flex>
)}
</Form.List>
</Form.Item>
</Card>
))}
<Button type='dashed' icon={<PlusOutlined />} onClick={() => add(defaultDefinitionValue)} block>
新增设置
</Button>
</Flex>
)}
</Form.List>
<Form.Item noStyle shouldUpdate>
{() => (
<Typography className='hidden'>
<pre>{JSON.stringify(batchSetupForm.getFieldsValue(), null, 2)}</pre>
</Typography>
)}
</Form.Item>
</Form>
</Modal>
<Modal
@ -222,7 +502,7 @@ const ProductInfoQuotation = ({ editable, ...props }) => {
},
]}
>
<InputNumber style={{width: '100%'}} />
<InputNumber style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label={t('products:childrenPrice')}
@ -234,7 +514,7 @@ const ProductInfoQuotation = ({ editable, ...props }) => {
},
]}
>
<InputNumber style={{width: '100%'}} />
<InputNumber style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label={t('products:currency')}
@ -253,7 +533,7 @@ const ProductInfoQuotation = ({ editable, ...props }) => {
</Form.Item>
<Form.Item
label={t('products:Types')}
name='unit_name'
name='unit_id'
rules={[
{
required: true,
@ -276,7 +556,7 @@ const ProductInfoQuotation = ({ editable, ...props }) => {
},
]}
>
<InputNumber style={{width: '100%'}} />
<InputNumber style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label={t('products:number')}
@ -288,7 +568,7 @@ const ProductInfoQuotation = ({ editable, ...props }) => {
},
]}
>
<InputNumber style={{width: '100%'}} />
<InputNumber style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label={t('products:validityPeriod')}
@ -300,7 +580,7 @@ const ProductInfoQuotation = ({ editable, ...props }) => {
},
]}
>
<RangePicker presets={datePresets} style={{width: '100%'}} />
<RangePicker presets={datePresets} style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label={t('products:Weekdays')}

Loading…
Cancel
Save