You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
GHHub/src/views/products/Detail/ProductInfoQuotation.jsx

609 lines
18 KiB
JavaScript

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 { CloseOutlined, StarTwoTone, PlusOutlined, ExclamationCircleFilled } from '@ant-design/icons';
import { useDatePresets } from '@/hooks/useDatePresets'
import useProductsStore from '@/stores/Products/Index'
const { RangePicker } = DatePicker
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 onNumberStartChange = (e) => {
const newNumber = parseInt(e.target.value || '0', 10)
if (Number.isNaN(numberStart)) {
return
}
if (!('numberStart' in value)) {
setNumberStart(newNumber);
}
triggerChange({
numberStart: newNumber,
})
}
const onNumberEndChange = (e) => {
const newNumber = parseInt(e.target.value || '0', 10)
if (Number.isNaN(numberEnd)) {
return
}
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,
})
}
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 batchSetupInitialValues = {
'defList': [
// 旺季
{
'useDate': [
dayjs().add(1, 'year').startOf('y'), dayjs().add(1, 'year').endOf('y')
],
'unitId': '0',
'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')
],
'unitId': '0',
'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
}
}
]
}
]
}
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')
],
'unitId': '0',
'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) => {
// 把 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 onQuotationFinish = (values) => {
console.info(values)
setQuotationModalOpen(false)
// saveOrUpdateQuotation(values)
// .then(() => {
// setQuotationModalOpen(false)
// })
// .catch(ex => {
// notification.error({
// message: 'Notification',
// description: ex.message,
// placement: 'top',
// duration: 4,
// })
// })
}
const onQuotationFailed = (error) => {
console.log('Failed:', error)
// 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_id: definition.unitId,
// 保持和 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' },
{ title: t('products:currency'), dataIndex: 'currency', width: '4rem' },
{
title: t('products:Types'),
dataIndex: 'unit_id',
width: '4rem',
render: (text) => (text === '0' ? '每人' : text === '1' ? '每团' : text),
},
{
title: t('products:number'),
dataIndex: 'group_size',
width: '4rem',
render: (_, record) => `${record.group_size_min}-${record.group_size_max}`,
},
{
title: t('products:validityPeriod'),
dataIndex: 'use_dates',
width: '6rem',
render: (_, record) => `${record.use_dates_start}-${record.use_dates_end}`,
},
{ title: t('products:Weekdays'), dataIndex: 'weekdays', width: '4rem' },
{
title: t('products:operation'),
dataIndex: 'operation',
width: '3%',
render: (_, quotation) => {
return (
<Space>
<Button type='link' onClick={() => onQuotationSeleted(quotation)}>{t('Edit')}</Button>
<Button type='link' danger onClick={() => {
Modal.confirm({
title: '请确认',
icon: <ExclamationCircleFilled />,
content: '你要删除这条价格吗?',
onOk() {
console.log('OK');
},
onCancel() {
console.log('Cancel');
},
})
}}>{t('Delete')}</Button>
</Space>
)
},
},
]
return (
<>
<h2>{t('products:EditComponents.Quotation')}</h2>
<Table
// rowKey={'id'}
bordered
dataSource={quotationList}
columns={quotationColumns}
pagination={false}
/>
{
// editable &&
<Button onClick={() => onNewQuotation()} type='primary' ghost style={{ marginTop: 16 }}>
{t('products:addQuotation')}
</Button>
}
{
// editable &&
<Button onClick={() => setBatchSetupModalOpen(true)} type='primary' ghost style={{ marginTop: 16, marginLeft: 16 }}>
批量设置
</Button>
}
<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, 'unitId']}>
<Select placeholder='选择类型'>
<Select.Option value='0'>每人</Select.Option>
<Select.Option value='1'>每团</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
centered
okButtonProps={{
autoFocus: true,
htmlType: 'submit',
}}
title={t('account:detail')}
open={isQuotationModalOpen} onCancel={() => setQuotationModalOpen(false)}
destroyOnClose
forceRender
modalRender={(dom) => (
<Form
name='quotationForm'
form={quotationForm}
labelCol={{ span: 4 }}
wrapperCol={{ span: 20 }}
className='max-w-2xl'
onFinish={onQuotationFinish}
onFinishFailed={onQuotationFailed}
autoComplete='off'
>
{dom}
</Form>
)}
>
<Form.Item name='id' className='hidden' ><Input /></Form.Item>
<Form.Item
label={t('products:adultPrice')}
name='adult_cost'
rules={[
{
required: true,
message: t('products:Validation.adultPrice'),
},
]}
>
<InputNumber style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label={t('products:childrenPrice')}
name='child_cost'
rules={[
{
required: true,
message: t('products:Validation.childrenPrice'),
},
]}
>
<InputNumber style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label={t('products:currency')}
name='currency'
rules={[
{
required: true,
message: t('products:Validation.currency'),
},
]}
>
<Radio.Group>
<Radio value='RMB'>RMB</Radio>
<Radio value='USD'>USD</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label={t('products:Types')}
name='unit_id'
rules={[
{
required: true,
message: t('products:Validation.unit_name'),
},
]}
>
<Radio.Group>
<Radio value='0'>每人</Radio>
<Radio value='1'>每团</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label={t('products:number')}
name='group_size_min'
rules={[
{
required: true,
message: t('products:Validation.group_size_min'),
},
]}
>
<InputNumber style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label={t('products:number')}
name='group_size_max'
rules={[
{
required: true,
message: t('products:Validation.group_size_max'),
},
]}
>
<InputNumber style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label={t('products:validityPeriod')}
name='use_dates'
rules={[
{
required: true,
message: t('products:Validation.use_dates'),
},
]}
>
<RangePicker presets={datePresets} style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label={t('products:Weekdays')}
name='weekdays'
>
<Checkbox.Group options={['5', '6', '7']} />
</Form.Item>
</Modal>
</>
)
}
export default ProductInfoQuotation