perf: 使用 prettier 格式化代码;优化搜索组件

main
Jimmy Liow 9 months ago
parent f0be26199f
commit 58247055f1

@ -0,0 +1,8 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": false
}

@ -1,61 +1,74 @@
import { Outlet, Link, NavLink } from 'react-router-dom'; import { Outlet, Link, NavLink } from 'react-router-dom'
import { Layout, Menu, ConfigProvider, theme, Row, Col, Typography, Flex, App as AntApp } from 'antd'; import {
import 'antd/dist/reset.css'; Layout,
import AppLogo from '@/assets/logo-gh.png'; Menu,
import 'dayjs/locale/zh-cn'; ConfigProvider,
import ErrorBoundary from '@/components/ErrorBoundary'; theme,
import { BUILD_VERSION, } from '@/config'; Row,
import { useThemeContext } from '@/stores/ThemeContext'; Col, App as AntApp
} from 'antd'
import 'antd/dist/reset.css'
import AppLogo from '@/assets/logo-gh.png'
import 'dayjs/locale/zh-cn'
import ErrorBoundary from '@/components/ErrorBoundary'
import { BUILD_VERSION } from '@/config'
import { useThemeContext } from '@/stores/ThemeContext'
const { Header, Content, Footer } = Layout
const { Header, Content, Footer } = Layout;
const { Title } = Typography;
function App() { function App() {
const { colorPrimary } = useThemeContext() const { colorPrimary } = useThemeContext()
console.info('theme: ', theme) return (
<ConfigProvider
return ( theme={{
<ConfigProvider token: {
theme={{ colorPrimary: colorPrimary,
token: { },
colorPrimary: colorPrimary, algorithm: theme.defaultAlgorithm,
}, }}
algorithm: theme.defaultAlgorithm, >
}}> <AntApp>
<AntApp> <ErrorBoundary>
<ErrorBoundary> <Layout className="min-h-screen">
<Layout className='min-h-screen'> <Header className="sticky top-0 z-10 w-full">
<Header className='sticky top-0 z-10 w-full'> <Row gutter={{ md: 24 }} justify="start" align="middle">
<Row gutter={{ md: 24 }} justify='start' align='middle'> <Col span={15}>
<Col span={15}> <NavLink to="/">
<NavLink to='/'> <img
<img src={AppLogo} className='float-left h-9 my-4 mr-6 ml-0 bg-white/30' alt='App logo' /> src={AppLogo}
</NavLink> className="float-left h-9 my-4 mr-6 ml-0 bg-white/30"
<Menu alt="App logo"
theme='dark' />
mode='horizontal' </NavLink>
selectedKeys={['hotel']} <Menu
items={[ theme="dark"
{ key: 'hotel', label: <Link to='/hotel/list'>喜玩酒店</Link> } mode="horizontal"
]} selectedKeys={['hotel']}
/> items={[
</Col> {
</Row> key: 'hotel',
</Header> label: <Link to="/hotel/list">喜玩酒店</Link>,
<Content className='p-6 m-0 min-h-72 bg-white flex justify-center'> },
<div className='max-w-5xl w-full'> ]}
<Outlet /> />
</Col>
</Row>
</Header>
<Content className="p-6 m-0 min-h-72 bg-white flex justify-center">
<div className="max-w-5xl w-full">
<Outlet />
</div> </div>
</Content> </Content>
<Footer>China Highlights International Travel Service Co., LTD, Version: {BUILD_VERSION}</Footer> <Footer>
</Layout> China Highlights International Travel Service Co., LTD, Version:{' '}
{BUILD_VERSION}
</Footer>
</Layout>
</ErrorBoundary> </ErrorBoundary>
</AntApp> </AntApp>
</ConfigProvider> </ConfigProvider>
) )
} }
export default App export default App

@ -2,29 +2,27 @@ import { useState, useEffect } from 'react'
import { useParams, useNavigate } from 'react-router-dom' import { useParams, useNavigate } from 'react-router-dom'
import { Modal, InputNumber, Form, Typography, DatePicker, Button } from 'antd' import { Modal, InputNumber, Form, Typography, DatePicker, Button } from 'antd'
import useHotelStore from '@/stores/Hotel' import useHotelStore from '@/stores/Hotel'
import { RoomList } from "./HotelComponents" import { RoomList } from './HotelComponents'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { isEmpty } from '@/utils/commons' import { isEmpty } from '@/utils/commons'
function Detail() { function Detail() {
const { hotelId, checkin, checkout } = useParams() const { hotelId, checkin, checkout } = useParams()
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [hotelQuotation, setHotelQuotation] = useState({ const [hotelQuotation, setHotelQuotation] = useState({
hotelName: '', hotelName: '',
roomName: '', roomName: '',
price: '' price: '',
}) })
const navigate = useNavigate() const navigate = useNavigate()
const [searchForm] = Form.useForm() const [searchForm] = Form.useForm()
const [selectedHotel, getRoomListByHotel, roomList] = const [selectedHotel, getRoomListByHotel, roomList] = useHotelStore(
useHotelStore(state => (state) => [state.selectedHotel, state.getRoomListByHotel, state.roomList],
[state.selectedHotel, state.getRoomListByHotel, state.roomList]) )
useEffect (() => {
useEffect(() => {
// http://localhost:5175/hotel/416980/2024-09-10/2024-09-11 // http://localhost:5175/hotel/416980/2024-09-10/2024-09-11
if (isEmpty(hotelId) || isEmpty(checkin) || isEmpty(checkout)) { if (isEmpty(hotelId) || isEmpty(checkin) || isEmpty(checkout)) {
console.info('criteria is null') console.info('criteria is null')
@ -33,13 +31,13 @@ function Detail() {
searchForm.setFieldsValue({ searchForm.setFieldsValue({
dataRange: [dayjs(checkin), dayjs(checkout)], dataRange: [dayjs(checkin), dayjs(checkout)],
adultCount: 2, adultCount: 2,
roomCount: 1 roomCount: 1,
}) })
setLoading(true) setLoading(true)
getRoomListByHotel(hotelId, checkin, checkout) getRoomListByHotel(hotelId, checkin, checkout).finally(() =>
.finally(() => setLoading(false)) setLoading(false),
)
} }
}, []) }, [])
const handelFormFinish = () => { const handelFormFinish = () => {
@ -51,18 +49,20 @@ function Detail() {
// }) // })
setLoading(true) setLoading(true)
getRoomListByHotel(hotelId, getRoomListByHotel(
hotelId,
formValue.dataRange[0].format('YYYY-MM-DD'), formValue.dataRange[0].format('YYYY-MM-DD'),
formValue.dataRange[1].format('YYYY-MM-DD'), formValue.dataRange[1].format('YYYY-MM-DD'),
formValue.adultCount, formValue.roomCount) formValue.adultCount,
.finally(() => setLoading(false)) formValue.roomCount,
).finally(() => setLoading(false))
} }
const handleRoomChange = (room, plan) => { const handleRoomChange = (room, plan) => {
const forHtJson = { const forHtJson = {
hotelName: selectedHotel.hotel_name, hotelName: selectedHotel.hotel_name,
roomName: room.RoomName, roomName: room.RoomName,
price: plan.Price price: plan.Price,
} }
setHotelQuotation(forHtJson) setHotelQuotation(forHtJson)
showModal() showModal()
@ -82,58 +82,64 @@ function Detail() {
return ( return (
<div> <div>
<input type='hidden' id='forHtJson' /> <input type="hidden" id="forHtJson" />
<Modal title='你选择了' open={isModalOpen} onOk={handleOk} onCancel={handleCancel} footer={null}> <Modal
title="你选择了"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
footer={null}
>
<p>酒店{hotelQuotation.hotelName}</p> <p>酒店{hotelQuotation.hotelName}</p>
<p>房型{hotelQuotation.roomName}</p> <p>房型{hotelQuotation.roomName}</p>
<p>价格{hotelQuotation.price}</p> <p>价格{hotelQuotation.price}</p>
</Modal> </Modal>
<Typography.Title level={4}> <Typography.Title level={4}>{selectedHotel.hotel_name}</Typography.Title>
{selectedHotel.hotel_name} <div className="p-2">
</Typography.Title> <Form
<div className='p-2'> name="searchForm"
<Form form={searchForm}
name='searchForm' layout="inline"
form={searchForm} onFinish={handelFormFinish}
layout='inline' autoComplete="off"
onFinish={handelFormFinish} >
autoComplete='off' <Form.Item
> label={'入住时间'}
<Form.Item name="dataRange"
label={'入住时间'} allowclear="false"
name='dataRange' rules={[
allowClear={false} {
rules={[ required: true,
{ message: '必填',
required: true, },
message: '必填', ]}
}, >
]} <DatePicker.RangePicker placeholder={['入住日期', '退房日期']} />
> </Form.Item>
<DatePicker.RangePicker placeholder={['入住日期', '退房日期']} /> <Form.Item label={'房间数'} name="roomCount">
</Form.Item> <InputNumber min={1} max={100} />
<Form.Item </Form.Item>
label={'房间数'} <Form.Item label={'人数'} name="adultCount">
name='roomCount' <InputNumber min={1} max={100} />
> </Form.Item>
<InputNumber min={1} max={100} /> <Form.Item>
</Form.Item> <Button type="primary" htmlType="submit">
<Form.Item 搜索
label={'人数'} </Button>
name='adultCount' </Form.Item>
> <Form.Item>
<InputNumber min={1} max={100} /> <Button onClick={() => navigate(-1)}>返回</Button>
</Form.Item> </Form.Item>
<Form.Item> </Form>
<Button type='primary' htmlType='submit'>搜索</Button> </div>
</Form.Item> <RoomList
<Form.Item> loading={loading}
<Button onClick={() => navigate(-1)}>返回</Button> onChange={({ room, plan }) => {
</Form.Item> handleRoomChange(room, plan)
</Form> }}
</div> dataSource={roomList}
<RoomList loading={loading} onChange={({room, plan}) => {handleRoomChange(room, plan)}} dataSource={roomList}></RoomList> ></RoomList>
</div> </div>
) )
} }

@ -1,102 +1,56 @@
import { createContext, useContext, useState } from 'react' import { createContext, useContext } from 'react'
import { Flex, Button, Image, Divider, Typography, Empty, Skeleton, Row, Col, InputNumber, Form, DatePicker, Input } from 'antd' import {
Flex,
Button,
Image,
Divider,
Typography,
Empty,
Skeleton,
Row,
Col,
} from 'antd'
const HotelContext = createContext() const HotelContext = createContext()
export function SearchForm({ onSearch, onInit }) {
const [searchForm] = Form.useForm()
onInit(searchForm)
const handelFormFinish = () => {
const formValue = searchForm.getFieldValue()
// setSearchParams({
// hotel: formValue.hotelName,
// checkin: formValue.dataRange[0].format('YYYY-MM-DD'),
// checkout: formValue.dataRange[1].format('YYYY-MM-DD')
// })
onSearch(formValue)
// setLoading(true)
// searchByCriteria(formValue)
// .finally(() => setLoading(false))
}
return (
<Form
name='searchForm'
form={searchForm}
layout='inline'
onFinish={handelFormFinish}
onFinishFailed={() => console.info('onFinishFailed')}
autoComplete='off'
>
<Form.Item
label={'酒店名称'}
name='hotelName'
rules={[
{
required: true,
message: '必填',
},
]}
>
<Input placeholder={'不能为空'} />
</Form.Item>
<Form.Item
label={'入住时间'}
name='dataRange'
allowClear={false}
rules={[
{
required: true,
message: '必填',
},
]}
>
<DatePicker.RangePicker placeholder={['入住日期', '退房日期']} />
</Form.Item>
<Form.Item>
<Button type='primary' htmlType='submit'>搜索</Button>
</Form.Item>
</Form>
)
}
export function HotelList({ dataSource, loading, onChange }) { export function HotelList({ dataSource, loading, onChange }) {
if (dataSource && dataSource.length > 0) { if (dataSource && dataSource.length > 0) {
const itemList = dataSource.map((data, index) => { const itemList = dataSource.map((data, index) => {
return ( return (
<>
<Skeleton active loading={loading} key={index}> <Skeleton active loading={loading} key={index}>
<Row gutter={16} className='rounded shadow-md hover:shadow-lg hover:shadow-gray-400 p-2 border-solid border border-gray-100'> <Row
gutter={16}
className="rounded shadow-md hover:shadow-lg hover:shadow-gray-400 p-2 border-solid border border-gray-100"
>
<Col span={24}> <Col span={24}>
<Flex <Flex
vertical gap='small' vertical
align='flex-start' gap="small"
justify='flex-start' align="flex-start"
justify="flex-start"
> >
<Typography.Title level={5}> <Typography.Title level={5}>{data.hotel_name}</Typography.Title>
{data.hotel_name} <Typography.Text type="secondary">
</Typography.Title> {data.address}
<Typography.Text type='secondary'>{data.address}</Typography.Text> </Typography.Text>
<span>{data.base_price.Price??0} </span> <span>{data.base_price.Price ?? 0} </span>
<Button size='small' type='primary' onClick={() => onChange(data)}> <Button
size="small"
type="primary"
onClick={() => onChange(data)}
>
查看房型 查看房型
</Button> </Button>
</Flex> </Flex>
</Col> </Col>
</Row> </Row>
</Skeleton> </Skeleton>
</>
) )
}) })
return ( return (
<HotelContext.Provider value={{ onChange }}> <HotelContext.Provider value={{ onChange }}>
<Flex vertical gap='small'> <Flex vertical gap="small">
{itemList} {itemList}
</Flex> </Flex>
</HotelContext.Provider> </HotelContext.Provider>
@ -107,51 +61,46 @@ export function HotelList({ dataSource, loading, onChange }) {
} }
export function RoomList({ dataSource, loading, onChange }) { export function RoomList({ dataSource, loading, onChange }) {
const triggerChange = (changedValue) => { const triggerChange = (changedValue) => {
onChange?.( onChange?.(changedValue)
changedValue
)
} }
if (dataSource && dataSource.length > 0) { if (dataSource && dataSource.length > 0) {
const itemList = dataSource.map((room, index) => { const itemList = dataSource.map((room, index) => {
let roomImage = <Empty description={false}/> let roomImage = <Empty description={false} />
if (room?.Images && room?.Images.length > 0) { if (room?.Images && room?.Images.length > 0) {
roomImage = <Image src={room?.Images[0].Url} /> roomImage = <Image src={room?.Images[0].Url} />
} }
return ( return (
<Skeleton active loading={loading} key={index}> <Skeleton active loading={loading} key={index}>
<div className='rounded shadow-md hover:shadow-lg hover:shadow-gray-400 p-2 border-solid border border-gray-100'> <div className="rounded shadow-md hover:shadow-lg hover:shadow-gray-400 p-2 border-solid border border-gray-100">
<Typography.Title level={5}> <Typography.Title level={5}>
{room.RoomName}, {room.BedTypeDesc} {room.RoomName}, {room.BedTypeDesc}
</Typography.Title> </Typography.Title>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={4}> <Col span={4}>
<Flex vertical> <Flex vertical>
{roomImage} {roomImage}
<span>大小: {room.Area??0}</span> <span>大小: {room.Area ?? 0}</span>
</Flex> </Flex>
</Col> </Col>
<Col span={20}> <Col span={20}>
{ {room.RatePlans.map((plan, index) => {
room.RatePlans.map((plan, index) => {
return ( return (
<PlanItem key={index} room={room} plan={plan}></PlanItem> <PlanItem key={index} room={room} plan={plan}></PlanItem>
) )
}) })}
} </Col>
</Col> </Row>
</Row> </div>
</div>
</Skeleton> </Skeleton>
) )
}) })
return ( return (
<HotelContext.Provider value={{ triggerChange }}> <HotelContext.Provider value={{ triggerChange }}>
<Flex gap='middle' vertical> <Flex gap="middle" vertical>
{itemList} {itemList}
</Flex> </Flex>
</HotelContext.Provider> </HotelContext.Provider>
@ -164,7 +113,8 @@ export function RoomList({ dataSource, loading, onChange }) {
const getMealDesc = (plan) => { const getMealDesc = (plan) => {
const type = plan.MealType const type = plan.MealType
let desc = '未知' let desc = '未知'
if (type === 1) desc = '早' + plan.Breakfast + '中' + plan.Lunch + '晚' + plan.Dinner if (type === 1)
desc = '早' + plan.Breakfast + '中' + plan.Lunch + '晚' + plan.Dinner
else if (type === 2) desc = '半包' else if (type === 2) desc = '半包'
else if (type === 3) desc = '全包' else if (type === 3) desc = '全包'
else if (type === 4) desc = '午/晚二选一' else if (type === 4) desc = '午/晚二选一'
@ -173,20 +123,30 @@ const getMealDesc = (plan) => {
return desc return desc
} }
const PlanItem = ({room, plan}) => { const PlanItem = ({ room, plan }) => {
const { triggerChange } = useContext(HotelContext) const { triggerChange } = useContext(HotelContext)
return ( return (
<> <>
<Row gutter={[16, 16]} className=' divide-y divide-slate-700'> <Row gutter={[16, 16]} className=" divide-y divide-slate-700">
<Col span={6}><span>: {getMealDesc(plan)}</span></Col> <Col span={6}>
<Col span={6}><span>{plan.Cancelable ? plan.CancelRulesText.join('') : plan.CancelableText}</span></Col> <span>: {getMealDesc(plan)}</span>
</Col>
<Col span={6}>
<span>
{plan.Cancelable
? plan.CancelRulesText.join('')
: plan.CancelableText}
</span>
</Col>
<Col span={6}> <Col span={6}>
<b>{plan.PriceUnit}</b> <b>{plan.PriceUnit}</b>
<small>{plan.Currency}</small> <small>{plan.Currency}</small>
</Col> </Col>
<Col span={4}><Button size='small' onClick={() => triggerChange({room, plan})}> <Col span={4}>
选他 <Button size="small" onClick={() => triggerChange({ room, plan })}>
</Button></Col> 选他
</Button>
</Col>
</Row> </Row>
<Divider /> <Divider />
</> </>

@ -1,19 +1,20 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom' import { useNavigate, useSearchParams } from 'react-router-dom'
import { Space, Typography } from 'antd' import { Space, Typography, Form, Button, Input, DatePicker } from 'antd'
import useHotelStore from '@/stores/Hotel' import useHotelStore from '@/stores/Hotel'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { HotelList, SearchForm } from './HotelComponents' import { HotelList } from './HotelComponents'
import { isEmpty } from '@/utils/commons' import { isEmpty } from '@/utils/commons'
const { Title } = Typography const { Title } = Typography
const List = () => { const List = () => {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
let searchForm = null const [searchForm] = Form.useForm()
const [searchByCriteria, hotelList, selectHotel] = const [searchByCriteria, hotelList, selectHotel] = useHotelStore((state) => [
useHotelStore(state => state.searchByCriteria,
[state.searchByCriteria, state.hotelList, state.selectHotel]) state.hotelList,
state.selectHotel,
])
const navigate = useNavigate() const navigate = useNavigate()
const [searchParams, setSearchParams] = useSearchParams() const [searchParams, setSearchParams] = useSearchParams()
@ -25,46 +26,101 @@ const List = () => {
useEffect(() => { useEffect(() => {
// Hotel Skypark Kingstown Dongdaemun // Hotel Skypark Kingstown Dongdaemun
// http://localhost:5175/hotel/list?hotel=s&checkin=2024-8-20&checkout=2024-8-21 // http://localhost:5175/hotel/list?hotel=s&checkin=2024-8-20&checkout=2024-8-21
if (isEmpty(hotelName) || isEmpty(checkinDateString) || isEmpty(checkoutDateString)) { if (
console.info('criteria is null') isEmpty(hotelName) ||
isEmpty(checkinDateString) ||
isEmpty(checkoutDateString)
) {
console.error('criteria is null')
} else { } else {
searchForm.setFieldsValue({ searchForm.setFieldsValue({
dataRange: [dayjs(checkinDateString), dayjs(checkoutDateString)], dataRange: [dayjs(checkinDateString), dayjs(checkoutDateString)],
hotelName: hotelName hotelName: hotelName,
}) })
setLoading(true) setLoading(true)
searchByCriteria(searchForm.getFieldValue()) searchByCriteria(searchForm.getFieldValue()).finally(() =>
.finally(() => setLoading(false)) setLoading(false),
)
} }
}, []) }, [])
const handelSearch = (formValue) => { const handelFormFinish = () => {
const formValue = searchForm.getFieldValue()
setSearchParams({ setSearchParams({
hotel: formValue.hotelName, hotel: formValue.hotelName,
checkin: formValue.dataRange[0].format('YYYY-MM-DD'), checkin: formValue.dataRange[0].format('YYYY-MM-DD'),
checkout: formValue.dataRange[1].format('YYYY-MM-DD') checkout: formValue.dataRange[1].format('YYYY-MM-DD'),
}) })
setLoading(true) setLoading(true)
searchByCriteria(formValue) searchByCriteria(formValue).finally(() => setLoading(false))
.finally(() => setLoading(false))
} }
const handleHotelChange = (hotel) => { const handleHotelChange = (hotel) => {
selectHotel(hotel) selectHotel(hotel)
navigate(`/hotel/${hotel.hotel_id}/${searchForm.getFieldValue().dataRange[0].format('YYYY-MM-DD')}/${searchForm.getFieldValue().dataRange[1].format('YYYY-MM-DD')}`) navigate(
`/hotel/${hotel.hotel_id}/${searchForm
.getFieldValue()
.dataRange[0].format('YYYY-MM-DD')}/${searchForm
.getFieldValue()
.dataRange[1].format('YYYY-MM-DD')}`,
)
}
const SearchForm = () => {
return (
<Form
name="searchForm"
form={searchForm}
layout="inline"
onFinish={handelFormFinish}
onFinishFailed={() => console.info('onFinishFailed')}
autoComplete="off"
>
<Form.Item
label={'酒店名称'}
name="hotelName"
rules={[
{
required: true,
message: '必填',
},
]}
>
<Input placeholder={'不能为空'} />
</Form.Item>
<Form.Item
label={'入住时间'}
name="dataRange"
allowclear="false"
rules={[
{
required: true,
message: '必填',
},
]}
>
<DatePicker.RangePicker placeholder={['入住日期', '退房日期']} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
搜索
</Button>
</Form.Item>
</Form>
)
} }
return ( return (
<> <>
<Space direction='vertical' className='w-full'> <Space direction="vertical" className="w-full">
<Title level={3}>酒店列表</Title> <Title level={3}>酒店列表</Title>
<SearchForm <SearchForm></SearchForm>
onSearch={handelSearch} <HotelList
onInit={form => searchForm = form} loading={loading}
> dataSource={hotelList}
</SearchForm> onChange={(h) => handleHotelChange(h)}
<HotelList loading={loading} dataSource={hotelList} onChange={h => handleHotelChange(h)}></HotelList> ></HotelList>
</Space> </Space>
</> </>
) )

Loading…
Cancel
Save