feat: 客服: 三峡游船

main
Lei OT 2 months ago
parent 7cfd79a0d2
commit 08aa01e33c

@ -3,7 +3,7 @@ import { toJS } from 'mobx';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT, stores_Context } from './../../config'; import { DATE_FORMAT, SMALL_DATETIME_FORMAT, stores_Context } from './../../config';
import { SearchOutlined } from '@ant-design/icons'; import { SearchOutlined } from '@ant-design/icons';
import { Form, Row, Col, Select, Button, Space, DatePicker, Input } from 'antd'; import { Form, Row, Col, Select, Button, Space, DatePicker, Input, InputNumber } from 'antd';
import moment from 'moment'; import moment from 'moment';
// import locale from 'antd/es/date-picker/locale/zh_CN'; // import locale from 'antd/es/date-picker/locale/zh_CN';
import BusinessSelect from './BusinessSelect'; import BusinessSelect from './BusinessSelect';
@ -181,6 +181,11 @@ export default observer((props) => {
transform: (value) => value?.value || value?.key || '', transform: (value) => value?.value || value?.key || '',
default: '', default: '',
}, },
'cruiseDirection': {
key: 'cruiseDirection',
transform: (value) => value?.key || '',
default: '',
},
}; };
let dest = {}; let dest = {};
const { departureDateType, applyDate, applyDate2, year, yearDiff, dates, months, date, ...omittedValue } = values; const { departureDateType, applyDate, applyDate2, year, yearDiff, dates, months, date, ...omittedValue } = values;
@ -275,14 +280,14 @@ function getFields(props) {
}; };
let baseChildren = []; let baseChildren = [];
baseChildren = [ baseChildren = [
item( item(
"keyword", // {...fieldComProps.keyword} 'keyword', // {...fieldComProps.keyword}
99, 99,
<Form.Item name="keyword" {...fieldProps.keyword}> <Form.Item name="keyword" {...fieldProps.keyword}>
<Input allowClear {...fieldProps.keyword} /> <Input allowClear {...fieldProps.keyword} />
</Form.Item>, </Form.Item>,
fieldProps?.keyword?.col || 6 fieldProps?.keyword?.col || 6
), ),
item( item(
'agency', 'agency',
99, 99,
@ -377,11 +382,11 @@ function getFields(props) {
item( item(
'orderStatus', 'orderStatus',
99, 99,
<Form.Item name={`orderStatus`} initialValue={at(props, 'initialValue.orderStatus')[0] || (fieldProps?.orderStatus?.show_all ? { key: '-1', label: '所有' } : undefined)}> <Form.Item name={`orderStatus`} initialValue={at(props, 'initialValue.orderStatus')[0] || (fieldProps?.orderStatus?.show_all ? { key: '-1', label: '成行状态' } : undefined)}>
<Select style={{ width: '100%' }} placeholder="所有状态" labelInValue> <Select style={{ width: '100%' }} placeholder="成行状态" labelInValue allowClear>
{fieldProps?.orderStatus?.show_all && ( {fieldProps?.orderStatus?.show_all && (
<Select.Option key="所有" value="-1"> <Select.Option key="-1" value="-1" disabled>
所有 成行状态
</Select.Option> </Select.Option>
)} )}
<Select.Option key="已成行" value="1"> <Select.Option key="已成行" value="1">
@ -407,10 +412,7 @@ function getFields(props) {
'departureDateType', 'departureDateType',
99, 99,
<Form.Item name={`departureDateType`} initialValue={at(props, 'initialValue.departureDateType')[0] || { key: 'departureDate', label: '抵达日期' }}> <Form.Item name={`departureDateType`} initialValue={at(props, 'initialValue.departureDateType')[0] || { key: 'departureDate', label: '抵达日期' }}>
<Select labelInValue={true} <Select labelInValue={true} style={{ width: '100%' }} placeholder="选择日期类型">
style={{ width: '100%' }}
placeholder="选择日期类型"
>
{departureDateTypes.map((ele) => ( {departureDateTypes.map((ele) => (
<Select.Option key={ele.key} value={ele.key} disabled={fieldProps?.departureDateType?.disabledKeys.includes(ele.key)}> <Select.Option key={ele.key} value={ele.key} disabled={fieldProps?.departureDateType?.disabledKeys.includes(ele.key)}>
{ele.label} {ele.label}
@ -502,28 +504,80 @@ function getFields(props) {
</Form.Item> </Form.Item>
), ),
item( item(
'cruiseType', 'cruiseDirection',
99, 99,
<Form.Item name={`cruiseType`} initialValue={at(props, 'initialValue.cruiseType')[0] || (fieldProps?.cruiseType?.show_all ? { key: 'all', label: '所有行程' } : undefined)}> <Form.Item name={`cruiseDirection`} initialValue={at(props, 'initialValue.cruiseDirection')[0] || undefined}>
<Select style={{ width: '100%' }} placeholder="所有行程" labelInValue> <Select style={{ width: '100%' }} placeholder="上下水" labelInValue allowClear>
{fieldProps?.cruiseType?.show_all && ( {fieldProps?.cruiseDirection?.show_all && (
<Option key="all" value=""> <Option key="all" value="" disabled>
所有 上下水
</Option> </Option>
)} )}
<Option key="upstream" value="upstream"> <Option key="1" value="1">
上水 上水
</Option> </Option>
<Option key="downstream" value="downstream"> <Option key="2" value="2">
下水 下水
</Option> </Option>
<Option key="long" value="long"> {/* <Option key="long" value="long">
长线 长线
</Option> */}
</Select>
</Form.Item>,
3
),
item(
'cruiseBookType',
99,
<Form.Item name={`cruiseBookType`} initialValue={at(props, 'initialValue.cruiseBookType')[0] || (fieldProps?.cruiseBookType?.show_all ? { key: 'all', label: '预定类型' } : undefined)}>
<Select style={{ width: '100%' }} placeholder="预定类型" labelInValue allowClear>
{fieldProps?.cruiseBookType?.show_all && (
<Option key="all" value="" disabled>
预定类型
</Option>
)}
<Option key="1" value="1">
单订三峡
</Option>
<Option key="0" value="0">
其他
</Option> </Option>
</Select> </Select>
</Form.Item>, </Form.Item>,
3 3
), ),
item(
'roomsRange',
99,
<Form.Item noStyle>
<Input.Group compact>
<Form.Item name={'RoomNumStart'} noStyle>
<InputNumber style={{ width: 'calc(50% - 15px)', textAlign: 'center' }} placeholder="房间数" />
</Form.Item>
<Input style={{ width: 30, borderLeft: 0, borderRight: 0, pointerEvents: 'none', }} placeholder="~" disabled />
<Form.Item name={'RoomNumEnd'} noStyle>
<InputNumber style={{ width: 'calc(50% - 15px)', textAlign: 'center', borderLeft: 0 }} placeholder="房间数" />
</Form.Item>
</Input.Group>
</Form.Item>,
fieldProps?.roomsRange?.col || 4
),
item(
'personRange',
99,
<Form.Item noStyle>
<Input.Group compact>
<Form.Item name={'PersonNumStart'} noStyle>
<InputNumber style={{ width: 'calc(50% - 15px)', textAlign: 'center' }} placeholder="人数" />
</Form.Item>
<Input style={{ width: 30, borderLeft: 0, borderRight: 0, pointerEvents: 'none', }} placeholder="~" disabled />
<Form.Item name={'PersonNumEnd'} noStyle>
<InputNumber style={{ width: 'calc(50% - 15px)', textAlign: 'center', borderLeft: 0 }} placeholder="人数" />
</Form.Item>
</Input.Group>
</Form.Item>,
fieldProps?.personRange?.col || 4
),
item( item(
'bookType', 'bookType',
99, 99,

@ -0,0 +1,147 @@
import { makeAutoObservable, runInAction, toJS } from 'mobx';
import { fetchJSON } from '../utils/request';
import { isEmpty, sortDescBy, objectMapper, groupBy, pick, unique, cloneDeep, omit, fixTo2Decimals } from '../utils/commons';
import { groupsMappedByCode, dataFieldAlias } from './../libs/ht';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from './../config';
import moment from 'moment';
const fetchHotelData = async (param) => {
const defaultParam = {
DEI_SN:'',
City:'',
OrderState:'',
BookingType:'-1',
RecommendedLevel:'-1',
Star:'-1',
ArriveDateCheck:'0',
ArriveDateStart:'',
ArriveDateEnd:'',
ConfirmDateCheck:'0',
ConfirmDateStart:'',
ConfirmDateEnd:'',
Compare:'0',
CompareDateStart:'',
CompareDateEnd:'',
};
const json = await fetchJSON('/service-Analyse2/HotelReservation', { ...defaultParam, ...param });
return json.errcode === 0 ? json.result : [];
};
const fetchCruiseData = async (param) => {
const defaultParam = {
DEI_SN: '',
OrderState: '', // 0: 不成行 1: 成行
ArriveDateStart: '',
ArriveDateEnd: '',
Compare: '',
CompareDateStart: '',
CompareDateEnd: '',
BookingType: '', // 0: 非单订三峡1: 单订三峡
ProductName: '',
Direction: '', // 1: 上水 2: 下水
VEI_SN: '-1', // 只要列出常用游船供应商选择
RoomNumStart: '0',
RoomNumEnd: '',
PersonNumStart: '0',
PersonNumEnd: '',
Country: '-1',
};
const json = await fetchJSON('/service-Analyse2/CruiseReservation', { ...defaultParam, ...param });
return json.errcode === 0 ? json.result : [];
};
const keyMapped = {
'applyDate': { key: '' },
'startDate': { key: 'ArriveDateCheck'},
'comfirmDate': { key: 'ConfirmDateCheck'},
'DepartmentList': { key: 'DEI_SN' },
'orderStatus': { key: 'OrderState' },
'Date1': { key: 'ArriveDateStart' },
'Date2': { key: 'ArriveDateEnd' },
'DateDiff1': { key: 'CompareDateStart' },
'DateDiff2': { key: 'CompareDateEnd' },
'keyword': { key: 'ProductName' },
'cruiseDirection': { key: 'Direction' },
};
class HotelCruise {
constructor(appStore) {
this.appStore = appStore;
makeAutoObservable(this);
}
async getCruiseData(param = {}) {
this.cruise.loading = true;
this.cruise.dataSource = [];
const _queryParam = objectMapper(param, {
'DepartmentList': { key: 'DEI_SN' },
'orderStatus': { key: 'OrderState' },
'Date1': { key: 'ArriveDateStart' },
'Date2': { key: 'ArriveDateEnd' },
'DateDiff1': { key: 'CompareDateStart' },
'DateDiff2': { key: 'CompareDateEnd' },
'keyword': { key: 'ProductName' },
'cruiseDirection': { key: 'Direction' },
});
const queryParam = omit({ ...this.searchValuesToSub, ..._queryParam }, ['DepartmentList', 'orderStatus', 'keyword', 'Date1', 'Date2', 'DateDiff1', 'DateDiff2', 'cruiseDirection']);
queryParam.Compare = isEmpty(param.DateDiff1) ? '' : '1';
const res = await fetchCruiseData(queryParam);
const resCP =
queryParam.Compare === ''
? res
: (res || []).map((ele) => ({
...ele,
TotalNumPercent: ele.CPTotalNum ? fixTo2Decimals(((ele.TotalNum - ele.CPTotalNum) / ele.CPTotalNum) * 100) : '-',
TotalProfitPercent: ele.CPTotalProfit ? fixTo2Decimals(((ele.TotalProfit - ele.CPTotalProfit) / ele.CPTotalProfit) * 100) : '-',
}));
runInAction(() => {
this.cruise.loading = false;
this.cruise.dataSource = resCP;
});
return this.cruise;
}
async getHotelData(param = {}) {
this.hotel.loading = true;
this.hotel.dataSource = [];
const res = await fetchHotelData({ ...this.searchValuesToSub, ...param });
runInAction(() => {
this.hotel.loading = false;
this.hotel.dataSource = [].concat(this.hotel.dataSource, res);
});
}
searchValues = {
date: moment(),
DateType: { key: 'applyDate', label: '提交日期' },
WebCode: { key: '', label: '所有来源' },
// IncludeTickets: { key: '1', label: '含门票'},
DepartmentList: [{ key: '', label: '所有小组' }],
operator: '-1',
opisn: '-1',
};
searchValuesToSub = {};
setSearchValues(obj, values) {
this.searchValues = { ...this.searchValues, ...values };
this.searchValuesToSub = obj;
}
cruise = { loading: false, dataSource: [] };
hotel = { loading: false, dataSource: [] };
resetData = () => {
this.results.loading = false;
for (const key of Object.keys(this.results)) {
if (key !== 'loading') {
this.results[key] = [];
}
}
for (const key of Object.keys(this.hotel)) {
if (key !== 'loading') {
this.hotel[key] = [];
}
}
};
}
export default HotelCruise;

@ -18,6 +18,7 @@ import DataPivot from './DataPivot';
import MeetingData from './MeetingData2024'; import MeetingData from './MeetingData2024';
import MeetingData2025 from './MeetingData2025'; import MeetingData2025 from './MeetingData2025';
import SalesCRMData from './SalesCRMData'; import SalesCRMData from './SalesCRMData';
import HotelCruise from './HotelCruise';
class Index { class Index {
constructor() { constructor() {
this.dashboard_store = new DashboardStore(this); this.dashboard_store = new DashboardStore(this);
@ -39,6 +40,7 @@ class Index {
this.MeetingDataStore = new MeetingData(this); this.MeetingDataStore = new MeetingData(this);
this.MeetingData2025Store = new MeetingData2025(this); this.MeetingData2025Store = new MeetingData2025(this);
this.SalesCRMDataStore = new SalesCRMData(this); this.SalesCRMDataStore = new SalesCRMData(this);
this.HotelCruiseStore = new HotelCruise(this);
makeAutoObservable(this); makeAutoObservable(this);
} }

@ -4,23 +4,54 @@ import { stores_Context } from '../config';
import moment from 'moment'; import moment from 'moment';
import { Row, Col, Table, Select, Space, Typography, Progress, Spin, Divider, Button, Switch } from 'antd'; import { Row, Col, Table, Select, Space, Typography, Progress, Spin, Divider, Button, Switch } from 'antd';
import SearchForm from './../components/search/SearchForm'; import SearchForm from './../components/search/SearchForm';
import { VSTag, TableExportBtn } from './../components/Data';
const {Text} = Typography;
export default observer((props) => { export default observer((props) => {
const { sale_store, date_picker_store: searchFormStore } = useContext(stores_Context); const { sale_store, date_picker_store: searchFormStore } = useContext(stores_Context);
const { customerServicesStore, date_picker_store } = useContext(stores_Context); const { customerServicesStore, HotelCruiseStore, date_picker_store } = useContext(stores_Context);
const { loading, dataSource } = HotelCruiseStore.cruise;
const { formValues, siderBroken } = searchFormStore; const { formValues, siderBroken } = searchFormStore;
const tableProps = { const tableProps = {
size: 'small', size: 'small',
bordered: true, pagination: false,
columns: [ columns: [
{ title: '产品', dataIndex: 'op', key: 'op' }, { title: '产品', dataIndex: 'ProductName', key: 'ProductName' },
{ title: '房间数', dataIndex: 'action', key: 'action' }, {
{ title: '人数', dataIndex: 'action', key: 'action' }, title: '房间数',
{ title: '总利润', dataIndex: 'action', key: 'action' }, dataIndex: 'TotalNum',
{ title: '单订船', dataIndex: 'action', key: 'action' }, key: 'TotalNum',
{ title: '订单含行程', dataIndex: 'action', key: 'action' }, render: (v, r) => (
{ title: '国籍', dataIndex: 'action', key: 'action' }, <>
<Space direction={'vertical'}>
<Text strong>
{v}
{r.CPTotalNum && <Text type="secondary"> VS {r.CPTotalNum}</Text>}
</Text>
{r.CPTotalNum && <VSTag diffPercent={r.TotalNumPercent} />}
</Space>
</>
),
},
{ title: '人数', dataIndex: 'TotalPersonNum', key: 'TotalPersonNum' },
{ title: '总利润', dataIndex: 'TotalProfit', key: 'TotalProfit',
render: (v, r) => (
<>
<Space direction={'vertical'}>
<Text strong>
{v}
{r.CPTotalProfit && <Text type="secondary"> VS {r.CPTotalProfit}</Text>}
</Text>
{r.CPTotalNum && <VSTag diffPercent={r.TotalProfitPercent} />}
</Space>
</>
), },
// { title: '', dataIndex: '', key: '' },
// { title: '', dataIndex: '', key: '' },
// { title: '', dataIndex: '', key: '' },
], ],
}; };
@ -32,27 +63,31 @@ export default observer((props) => {
defaultValue={{ defaultValue={{
initialValue: { initialValue: {
...date_picker_store.formValues, ...date_picker_store.formValues,
...customerServicesStore.searchValues, ...HotelCruiseStore.searchValues,
}, },
// 'countryArea', // 'countryArea',
shows: ['DepartmentList', 'orderStatus', 'years', 'keyword', 'agency', 'cruiseType'], shows: [
sort: { keyword: 101, cruiseType: 102, agency: 103 }, 'DepartmentList', 'orderStatus', 'dates', 'keyword', 'cruiseDirection', 'agency',
// 'cruiseBookType', 'country', 'roomsRange', 'personRange'
],
sort: { keyword: 101, agency: 103, cruiseDirection: 102, country: 104 },
fieldProps: { fieldProps: {
keyword: { placeholder: '产品名', col: 4 }, keyword: { placeholder: '产品名', col: 4 },
DepartmentList: { show_all: true, mode: 'multiple' }, DepartmentList: { show_all: true, mode: 'multiple' },
orderStatus: { show_all: true }, orderStatus: { show_all: true },
cruiseDirection: { show_all: true },
// years: { hide_vs: false }, // years: { hide_vs: false },
}, },
}} }}
onSubmit={(_err, obj, form) => { onSubmit={(_err, obj, form) => {
customerServicesStore.setSearchValues(obj, form); HotelCruiseStore.setSearchValues(obj, form);
// customerServicesStore.fetchDestinationGroupCount(); HotelCruiseStore.getCruiseData(obj);
}} }}
/> />
</Col> </Col>
</Row> </Row>
<section> <section>
<Table {...tableProps} bordered /> <Table {...tableProps} {...{loading, dataSource}} rowKey={(record) => record.ProductName} />
</section> </section>
</> </>
); );

Loading…
Cancel
Save