账单页面,搜索功能

perf/export-docx
YCC 10 months ago
parent 31720a2930
commit ecdba1ee09

@ -25,46 +25,40 @@
"Import": "Import",
"Export": "Export",
"Copy": "Copy",
"sureCancel": "Are you sure to cancel?",
"sureDelete":"Are you sure to delete?",
"sureSubmit":"Are you sure to submit?",
"sureDelete": "Are you sure to delete?",
"sureSubmit": "Are you sure to submit?",
"Yes": "Yes",
"No": "No",
"Success": "Success",
"Failed": "Failed",
"All": "All",
"Table": {
"Total": "Total {{total}} items"
},
"Login": "Login",
"Username": "Username",
"Realname": "Realname",
"Password": "Password",
"ChangePassword": "Change password",
"Profile": "Profile",
"Logout": "Logout",
"LoginTimeout": "Login timeout",
"LoginTimeoutTip": "Please input your password",
"userProfile": "User Profile",
"Telephone": "Telephone",
"Email": "Email address",
"Address": "Address",
"Company": "Company",
"Department": "Department",
"datetime": {
"thisWeek": "This Week",
"lastWeek": "Last Week",
"thisMonth": "This Month",
"lastMonth": "Last Month",
"nextMonth": "Next Month",
"lastThreeMonth": "Last Three Month",
"nextThreeMonth": "Next Three Month",
"firstHalfYear": "First Half Year",
"latterHalfYear": "Latter Half Year",
"thisYear": "This Year"
@ -101,5 +95,13 @@
"LoginFailed": "Incorrect password, Login failed.",
"UsernameIsEmpty": "Please input your username",
"PasswordIsEmpty": "Please input your password"
},
"invoiceStatus": {
"Status": "Status",
"Not_submitted": "Not submitted",
"Submitted": "Submitted",
"Travel_advisor_approved": "Travel advisor approved",
"Finance_Dept_arrproved": "Finance Dept arrproved",
"Paid": "Paid"
}
}

@ -25,46 +25,40 @@
"Import": "导入",
"Export": "导出",
"Copy": "复制",
"sureCancel": "确定取消?",
"sureDelete":"确定删除?",
"sureSubmit":"确定提交?",
"sureDelete": "确定删除?",
"sureSubmit": "确定提交?",
"Yes": "是",
"No": "否",
"Success": "成功",
"Failed": "失败",
"All": "所有",
"Table": {
"Total": "共 {{total}} 条"
},
"Login": "登录",
"Username": "账号",
"Realname": "姓名",
"Password": "密码",
"ChangePassword": "修改密码",
"Profile": "账户中心",
"Logout": "退出",
"LoginTimeout": "登录超时",
"LoginTimeoutTip": "请输入密码",
"userProfile": "账号信息",
"Telephone": "联系电话",
"Email": "电子邮箱",
"Address": "公司地址",
"Company": "公司名称",
"Department": "部门",
"datetime": {
"thisWeek": "本周",
"lastWeek": "上周",
"thisMonth": "本月",
"lastMonth": "上月",
"nextMonth": "下月",
"lastThreeMonth": "前三个月",
"nextThreeMonth": "后三个月",
"firstHalfYear": "上半年",
"latterHalfYear": "下半年",
"thisYear": "今年"
@ -101,5 +95,13 @@
"LoginFailed": "密码错误,登陆失败。",
"UsernameIsEmpty": "请输入账号",
"PasswordIsEmpty": "请输入密码"
},
"invoiceStatus": {
"Status": "审核状态",
"Not_submitted": "待提交",
"Submitted": "待审核",
"Travel_advisor_approved": "顾问已审核",
"Finance_Dept_arrproved": "财务已审核",
"Paid": "已打款"
}
}

@ -1,350 +1,347 @@
import { useEffect } from 'react';
import { Form, Input, Row, Col, Select, DatePicker, Space, Button, Checkbox } from 'antd';
import { objectMapper, at } from '@/utils/commons';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from '@/config';
import useFormStore from '@/stores/Form';
import { useDatePresets } from '@/hooks/useDatePresets';
import { useTranslation } from 'react-i18next';
import { useEffect } from "react";
import { Form, Input, Row, Col, Select, DatePicker, Space, Button, Checkbox } from "antd";
import { objectMapper, at } from "@/utils/commons";
import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from "@/config";
import useFormStore from "@/stores/Form";
import { useDatePresets } from "@/hooks/useDatePresets";
import { useTranslation } from "react-i18next";
import SearchInput from './SearchInput';
import AuditStateSelector from './AuditStateSelector';
import DeptSelector from './DeptSelector';
import ProductsTypesSelector from './ProductsTypesSelector';
import CitySelector from '@/components/CitySelector';
import VendorSelector from '@/components/VendorSelector';
import SearchInput from "./SearchInput";
import AuditStateSelector from "./AuditStateSelector";
import DeptSelector from "./DeptSelector";
import ProductsTypesSelector from "./ProductsTypesSelector";
import CitySelector from "@/components/CitySelector";
import VendorSelector from "@/components/VendorSelector";
const { RangePicker } = DatePicker;
const SearchForm = ({ initialValue, onSubmit, onReset, onMounted, confirmText, formName, formLayout, loading, ...props }) => {
const { t } = useTranslation();
const presets = useDatePresets();
const [formValues, setFormValues] = useFormStore((state) => [state.formValues, state.setFormValues]);
const [formValuesToSub, setFormValuesToSub] = useFormStore((state) => [state.formValuesToSub, state.setFormValuesToSub]);
const [form] = Form.useForm();
const { sort, hides, shows, fieldProps, fieldComProps } = {
sort: '',
// initialValue: '',
fieldProps: '',
fieldComProps: '',
hides: [],
shows: [],
...props.fieldsConfig,
};
const readValues = { ...initialValue, ...formValues };
const { t } = useTranslation();
const presets = useDatePresets();
const [formValues, setFormValues] = useFormStore(state => [state.formValues, state.setFormValues]);
const [formValuesToSub, setFormValuesToSub] = useFormStore(state => [state.formValuesToSub, state.setFormValuesToSub]);
const [form] = Form.useForm();
const { sort, hides, shows, fieldProps, fieldComProps } = {
sort: "",
// initialValue: '',
fieldProps: "",
fieldComProps: "",
hides: [],
shows: [],
...props.fieldsConfig,
};
const readValues = { ...initialValue, ...formValues };
const formValuesMapper = (values) => {
const destinationObject = {
'keyword': { key: 'keyword', transform: (value) => value || '' },
'username': { key: 'username', transform: (value) => value || '' },
'referenceNo': { key: 'referenceNo', transform: (value) => value || '' },
'dates': [
{ key: 'startdate', transform: (arrVal) => (arrVal ? arrVal[0].format(DATE_FORMAT) : '') },
{ key: 'enddate', transform: (arrVal) => (arrVal ? arrVal[1].format(DATE_FORMAT) : '') },
{ key: 'starttime', transform: (arrVal) => (arrVal ? arrVal[0].format(DATE_FORMAT) : '') },
{ key: 'endtime', transform: (arrVal) => (arrVal ? arrVal[1].format(SMALL_DATETIME_FORMAT) : '') },
],
'invoiceStatus': { key: 'invoiceStatus', transform: (value) => value?.value || value?.key || '', default: '' },
'audit_state': { key: 'audit_state', transform: (value) => value?.value || value?.key || '', default: '' },
'agency': {
key: 'agency',
transform: (value) => {
return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.value : '';
},
},
'year': [
{ key: 'year', transform: (arrVal) => (arrVal ? arrVal.format('YYYY') : '') },
],
'products_types': {
key: 'products_types',
transform: (value) => {
return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.value : '';
},
},
'dept': {
key: 'dept',
transform: (value) => {
console.log(value);
return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.value : '';
},
},
'city': {
key: 'city',
transform: (value) => {
return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.value : '';
},
},
'unconfirmed': { key: 'unconfirmed', transform: (value) => value ? 1 : 0 },
};
let dest = {};
const { dates, ...omittedValue } = values;
dest = { ...omittedValue, ...objectMapper(values, destinationObject) };
for (const key in dest) {
if (Object.prototype.hasOwnProperty.call(dest, key)) {
dest[key] = typeof dest[key] === 'string' ? (dest[key] || '').trim() : dest[key];
}
}
// omit empty
// Object.keys(dest).forEach((key) => (dest[key] == null || dest[key] === '' || dest[key].length === 0) && delete dest[key]);
return dest;
};
const formValuesMapper = values => {
const destinationObject = {
keyword: { key: "keyword", transform: value => value || "" },
username: { key: "username", transform: value => value || "" },
referenceNo: { key: "referenceNo", transform: value => value || "" },
dates: [
{ key: "startdate", transform: arrVal => (arrVal ? arrVal[0].format(DATE_FORMAT) : "") },
{ key: "enddate", transform: arrVal => (arrVal ? arrVal[1].format(DATE_FORMAT) : "") },
{ key: "starttime", transform: arrVal => (arrVal ? arrVal[0].format(DATE_FORMAT) : "") },
{ key: "endtime", transform: arrVal => (arrVal ? arrVal[1].format(SMALL_DATETIME_FORMAT) : "") },
],
invoiceStatus: { key: "invoiceStatus", transform: value => value?.value || value?.key || "", default: "" },
audit_state: { key: "audit_state", transform: value => value?.value || value?.key || "", default: "" },
agency: {
key: "agency",
transform: value => {
return Array.isArray(value) ? value.map(ele => ele.key).join(",") : value ? value.value : "";
},
},
year: [{ key: "year", transform: arrVal => (arrVal ? arrVal.format("YYYY") : "") }],
products_types: {
key: "products_types",
transform: value => {
return Array.isArray(value) ? value.map(ele => ele.key).join(",") : value ? value.value : "";
},
},
dept: {
key: "dept",
transform: value => {
console.log(value);
return Array.isArray(value) ? value.map(ele => ele.key).join(",") : value ? value.value : "";
},
},
city: {
key: "city",
transform: value => {
return Array.isArray(value) ? value.map(ele => ele.key).join(",") : value ? value.value : "";
},
},
unconfirmed: { key: "unconfirmed", transform: value => (value ? 1 : 0) },
};
let dest = {};
const { dates, ...omittedValue } = values;
dest = { ...omittedValue, ...objectMapper(values, destinationObject) };
for (const key in dest) {
if (Object.prototype.hasOwnProperty.call(dest, key)) {
dest[key] = typeof dest[key] === "string" ? (dest[key] || "").trim() : dest[key];
}
}
// omit empty
// Object.keys(dest).forEach((key) => (dest[key] == null || dest[key] === '' || dest[key].length === 0) && delete dest[key]);
return dest;
};
useEffect(() => {
setFormValues(readValues);
const dest = formValuesMapper(readValues);
setFormValuesToSub(dest);
useEffect(() => {
setFormValues(readValues);
const dest = formValuesMapper(readValues);
setFormValuesToSub(dest);
if (typeof onMounted === 'function') {
onMounted(dest)
}
if (typeof onMounted === "function") {
onMounted(dest);
}
return () => {};
}, []);
return () => {};
}, []);
const onFinish = (values) => {
console.log('Received values of form, origin form value: \n', values);
const dest = formValuesMapper(values);
console.log('form value send to onSubmit:\n', dest);
const str = new URLSearchParams(dest).toString();
setFormValues(values);
setFormValuesToSub(dest);
if (typeof onSubmit === 'function') {
onSubmit(null, dest, values, str);
}
};
const onFinish = values => {
console.log("Received values of form, origin form value: \n", values);
const dest = formValuesMapper(values);
console.log("form value send to onSubmit:\n", dest);
const str = new URLSearchParams(dest).toString();
setFormValues(values);
setFormValuesToSub(dest);
if (typeof onSubmit === "function") {
onSubmit(null, dest, values, str);
}
};
const handleReset = () => {
form.setFieldsValue({
// 'DateType': undefined,
});
if (typeof onReset === 'function') {
onReset();
}
};
const onValuesChange = (changedValues, allValues) => {
const handleReset = () => {
form.setFieldsValue({
// 'DateType': undefined,
});
if (typeof onReset === "function") {
onReset();
}
};
const onValuesChange = (changedValues, allValues) => {
const dest = formValuesMapper(allValues);
setFormValues(allValues);
setFormValuesToSub(dest);
// console.log('form onValuesChange', Object.keys(changedValues), args);
};
const dest = formValuesMapper(allValues);
setFormValues(allValues);
setFormValuesToSub(dest);
// console.log('form onValuesChange', Object.keys(changedValues), args);
};
const onFinishFailed = ({ values, errorFields }) => {
console.log("form validate failed", "\nform values:", values, "\nerrorFields", errorFields);
};
const onFinishFailed = ({ values, errorFields }) => {
console.log('form validate failed', '\nform values:', values, '\nerrorFields', errorFields);
};
return (
<>
<Form form={form} layout={'horizontal'} name={formName || 'advanced_search'} className='orders-search-form' onFinish={onFinish} onValuesChange={onValuesChange} onFinishFailed={onFinishFailed} >
{/* <EditableContext.Provider value={form}> */}
<Row gutter={16}>
{getFields({ sort, initialValue: readValues, hides, shows, fieldProps, fieldComProps, form, presets, t })}
{/* 'textAlign': 'right' */}
<Col flex='1 0 90px' className='flex justify-normal items-start' >
<Space align='center'>
<Button size={'middle'} type='primary' htmlType='submit' loading={loading}>
{confirmText || t('Search')}
</Button>
{/* <Button size="small" onClick={onReset}>
return (
<>
<Form form={form} layout={"horizontal"} name={formName || "advanced_search"} className="orders-search-form" onFinish={onFinish} onValuesChange={onValuesChange} onFinishFailed={onFinishFailed}>
{/* <EditableContext.Provider value={form}> */}
<Row gutter={16}>
{getFields({ sort, initialValue: readValues, hides, shows, fieldProps, fieldComProps, form, presets, t })}
{/* 'textAlign': 'right' */}
<Col flex="1 0 90px" className="flex justify-normal items-start">
<Space align="center">
<Button size={"middle"} type="primary" htmlType="submit" loading={loading}>
{confirmText || t("Search")}
</Button>
{/* <Button size="small" onClick={onReset}>
重置
</Button> */}
</Space>
</Col>
</Row>
{/* </EditableContext.Provider> */}
</Form>
</>
);
</Space>
</Col>
</Row>
{/* </EditableContext.Provider> */}
</Form>
</>
);
};
function getFields(props) {
const { fieldProps, fieldComProps, form, presets, t } = props;
const bigCol = 4 * 2;
const midCol = 6;
const layoutProps = {
// gutter: { xs: 8, sm: 8, lg: 16 },
lg: { span: 4 },
md: { span: 8 },
sm: { span: 12 },
xs: { span: 24 },
};
const item = (name, sort = 0, render, col) => {
const customCol = col || 4;
const mdCol = customCol * 2;
return {
'key': '',
sort,
name,
render,
'hide': false,
'col': { lg: { span: customCol }, md: { span: mdCol < 8 ? 10 : mdCol }, flex: mdCol < 8 ? '1 0' : '' },
};
};
let baseChildren = [];
baseChildren = [
item(
'keyword',
99,
<Form.Item name='keyword' {...fieldProps.keyword}>
<Input allowClear {...fieldComProps.keyword} />
</Form.Item>,
fieldProps?.keyword?.col || 6
),
item(
'referenceNo',
99,
<Form.Item name='referenceNo' label={t('group:RefNo')} {...fieldProps.referenceNo}>
<Input placeholder={t('group:RefNo')} allowClear />
</Form.Item>,
fieldProps?.referenceNo?.col || 6
),
item(
'PNR',
99,
<Form.Item name='PNR' label='PNR'>
<Input placeholder={t('group:PNR')} allowClear />
</Form.Item>,
fieldProps?.PNR?.col || 4
),
item(
'invoiceStatus',
99,
<Form.Item name={`invoiceStatus`} initialValue={at(props, 'initialValue.invoiceStatus')[0] || { value: '0', label: 'Status' }}>
<Select
labelInValue
options={[
{ value: '0', label: 'Status' },
{ value: '1', label: 'Not submitted' },
{ value: '2', label: 'Submitted' },
{ value: '3', label: 'Travel advisor approved' },
{ value: '4', label: 'Finance Dept arrproved' },
{ value: '5', label: 'Paid' },
]}
/>
</Form.Item>,
fieldProps?.invoiceStatus?.col || 3
),
item(
'dates',
99,
<Form.Item name={'dates'} label={t('group:ArrivalDate')} {...fieldProps.dates} initialValue={at(props, 'initialValue.dates')[0]}>
{/* <DatePickerCharts isform={true} {...fieldProps.dates} form={form} /> */}
<RangePicker allowClear={true} inputReadOnly={true} presets={presets} placeholder={['From', 'Thru']} {...fieldComProps.dates} />
</Form.Item>,
fieldProps?.dates?.col || midCol
),
item(
'username',
99,
<Form.Item name='username' label={t('account:username')} {...fieldProps.username}>
<Input placeholder={t('account:username')} allowClear />
</Form.Item>,
fieldProps?.username?.col || 4
),
/**
*
*/
item(
'year',
99,
<Form.Item name={'year'} label={t('products:UseYear')} {...fieldProps.year} initialValue={at(props, 'initialValue.year')[0]}>
<DatePicker picker='year' allowClear {...fieldComProps.year} />
</Form.Item>,
fieldProps?.year?.col || 3
),
item(
'agency',
99,
<Form.Item name='agency' label={t('products:Vendor')} {...fieldProps.agency} initialValue={at(props, 'initialValue.agency')[0]}>
<VendorSelector {...fieldComProps.agency} />
</Form.Item>,
fieldProps?.agency?.col || 6
),
item(
'audit_state',
99,
<Form.Item name={`audit_state`} initialValue={at(props, 'initialValue.audit_state')[0] || { value: '', label: 'Status' }}>
<AuditStateSelector {...fieldComProps.audit_state} />
</Form.Item>,
fieldProps?.audit_state?.col || 3
),
item(
'products_types',
99,
<Form.Item name={`products_types`} label={t('products:ProductType')} {...fieldProps.products_types} initialValue={at(props, 'initialValue.products_types')[0] || undefined}>
<ProductsTypesSelector maxTagCount={1} {...fieldComProps.products_types} />
</Form.Item>,
fieldProps?.products_types?.col || 6
),
item(
'dept',
99,
<Form.Item name={`dept`} label={t('products:Dept')} {...fieldProps.dept} initialValue={at(props, 'initialValue.dept')[0] || undefined}>
<DeptSelector {...fieldComProps.dept} />
</Form.Item>,
fieldProps?.dept?.col || 6
),
item(
'city',
99,
<Form.Item name={`city`} label={t('products:City')} {...fieldProps.city} initialValue={at(props, 'initialValue.city')[0] || undefined}>
<CitySelector {...fieldComProps.city} />
</Form.Item>,
fieldProps?.city?.col || 4
),
item(
'unconfirmed',
99,
<Form.Item name={`unconfirmed`} valuePropName='checked' initialValue={at(props, 'initialValue.unconfirmed') || false}>
<Checkbox>{t('group:unconfirmed')}</Checkbox>
</Form.Item>,
fieldProps?.unconfirmed?.col || 2
),
];
baseChildren = baseChildren
.map((x) => {
x.hide = false;
if (props.sort === undefined) {
return x;
}
const tmpSort = props.sort;
for (const key in tmpSort) {
if (Object.prototype.hasOwnProperty.call(tmpSort, key)) {
if (x.name === key) {
x.sort = tmpSort[key];
}
}
}
return x;
})
.map((x) => {
if (props.hides.length === 0 && props.shows.length === 0) {
return x;
}
if (props.hides.length === 0) {
x.hide = !props.shows.includes(x.name);
} else if (props.shows.length === 0) {
x.hide = props.hides.includes(x.name);
}
return x;
})
.filter((x) => !x.hide)
.sort((a, b) => {
return a.sort < b.sort ? -1 : 1;
});
const children = [];
const leftStyle = {}; // { borderRight: '1px solid #dedede' };
for (let i = 0; i < baseChildren.length; i++) {
let style = {}; // { padding: '0px 2px' };
style = i % 2 === 0 && baseChildren[i].col === 12 ? { ...style, ...leftStyle } : style;
style = !baseChildren[i].hide ? { ...style, display: 'block' } : { ...style, display: 'none' };
const Item = (
<Col key={String(i)} style={style} {...layoutProps} {...baseChildren[i].col}>
{baseChildren[i].render}
</Col>
);
children.push(Item);
}
return children;
const { fieldProps, fieldComProps, form, presets, t } = props;
const bigCol = 4 * 2;
const midCol = 6;
const layoutProps = {
// gutter: { xs: 8, sm: 8, lg: 16 },
lg: { span: 4 },
md: { span: 8 },
sm: { span: 12 },
xs: { span: 24 },
};
const item = (name, sort = 0, render, col) => {
const customCol = col || 4;
const mdCol = customCol * 2;
return {
key: "",
sort,
name,
render,
hide: false,
col: { lg: { span: customCol }, md: { span: mdCol < 8 ? 10 : mdCol }, flex: mdCol < 8 ? "1 0" : "" },
};
};
let baseChildren = [];
baseChildren = [
item(
"keyword",
99,
<Form.Item name="keyword" {...fieldProps.keyword}>
<Input allowClear {...fieldComProps.keyword} />
</Form.Item>,
fieldProps?.keyword?.col || 6
),
item(
"referenceNo",
99,
<Form.Item name="referenceNo" label={t("group:RefNo")} {...fieldProps.referenceNo}>
<Input placeholder={t("group:RefNo")} allowClear />
</Form.Item>,
fieldProps?.referenceNo?.col || 6
),
item(
"PNR",
99,
<Form.Item name="PNR" label="PNR">
<Input placeholder={t("group:PNR")} allowClear />
</Form.Item>,
fieldProps?.PNR?.col || 4
),
item(
"invoiceStatus",
99,
<Form.Item name={`invoiceStatus`} initialValue={at(props, "initialValue.invoiceStatus")[0] || { value: "0", label: t("invoiceStatus.Status") }}>
<Select
labelInValue
options={[
{ value: "0", label: t("invoiceStatus.Status") },
{ value: "1", label: t("invoiceStatus.Not_submitted") },
{ value: "2", label: t("invoiceStatus.Submitted") },
{ value: "3", label: t("invoiceStatus.Travel_advisor_approved") },
{ value: "4", label: t("invoiceStatus.Finance_Dept_arrproved") },
{ value: "5", label: t("invoiceStatus.Paid") },
]}
/>
</Form.Item>,
fieldProps?.invoiceStatus?.col || 3
),
item(
"dates",
99,
<Form.Item name={"dates"} label={t("group:ArrivalDate")} {...fieldProps.dates} initialValue={at(props, "initialValue.dates")[0]}>
{/* <DatePickerCharts isform={true} {...fieldProps.dates} form={form} /> */}
<RangePicker allowClear={true} inputReadOnly={true} presets={presets} placeholder={["From", "Thru"]} {...fieldComProps.dates} />
</Form.Item>,
fieldProps?.dates?.col || midCol
),
item(
"username",
99,
<Form.Item name="username" label={t("account:username")} {...fieldProps.username}>
<Input placeholder={t("account:username")} allowClear />
</Form.Item>,
fieldProps?.username?.col || 4
),
/**
*
*/
item(
"year",
99,
<Form.Item name={"year"} label={t("products:UseYear")} {...fieldProps.year} initialValue={at(props, "initialValue.year")[0]}>
<DatePicker picker="year" allowClear {...fieldComProps.year} />
</Form.Item>,
fieldProps?.year?.col || 3
),
item(
"agency",
99,
<Form.Item name="agency" label={t("products:Vendor")} {...fieldProps.agency} initialValue={at(props, "initialValue.agency")[0]}>
<VendorSelector {...fieldComProps.agency} />
</Form.Item>,
fieldProps?.agency?.col || 6
),
item(
"audit_state",
99,
<Form.Item name={`audit_state`} initialValue={at(props, "initialValue.audit_state")[0] || { value: "", label: "Status" }}>
<AuditStateSelector {...fieldComProps.audit_state} />
</Form.Item>,
fieldProps?.audit_state?.col || 3
),
item(
"products_types",
99,
<Form.Item name={`products_types`} label={t("products:ProductType")} {...fieldProps.products_types} initialValue={at(props, "initialValue.products_types")[0] || undefined}>
<ProductsTypesSelector maxTagCount={1} {...fieldComProps.products_types} />
</Form.Item>,
fieldProps?.products_types?.col || 6
),
item(
"dept",
99,
<Form.Item name={`dept`} label={t("products:Dept")} {...fieldProps.dept} initialValue={at(props, "initialValue.dept")[0] || undefined}>
<DeptSelector {...fieldComProps.dept} />
</Form.Item>,
fieldProps?.dept?.col || 6
),
item(
"city",
99,
<Form.Item name={`city`} label={t("products:City")} {...fieldProps.city} initialValue={at(props, "initialValue.city")[0] || undefined}>
<CitySelector {...fieldComProps.city} />
</Form.Item>,
fieldProps?.city?.col || 4
),
item(
"unconfirmed",
99,
<Form.Item name={`unconfirmed`} valuePropName="checked" initialValue={at(props, "initialValue.unconfirmed") || false}>
<Checkbox>{t("group:unconfirmed")}</Checkbox>
</Form.Item>,
fieldProps?.unconfirmed?.col || 2
),
];
baseChildren = baseChildren
.map(x => {
x.hide = false;
if (props.sort === undefined) {
return x;
}
const tmpSort = props.sort;
for (const key in tmpSort) {
if (Object.prototype.hasOwnProperty.call(tmpSort, key)) {
if (x.name === key) {
x.sort = tmpSort[key];
}
}
}
return x;
})
.map(x => {
if (props.hides.length === 0 && props.shows.length === 0) {
return x;
}
if (props.hides.length === 0) {
x.hide = !props.shows.includes(x.name);
} else if (props.shows.length === 0) {
x.hide = props.hides.includes(x.name);
}
return x;
})
.filter(x => !x.hide)
.sort((a, b) => {
return a.sort < b.sort ? -1 : 1;
});
const children = [];
const leftStyle = {}; // { borderRight: '1px solid #dedede' };
for (let i = 0; i < baseChildren.length; i++) {
let style = {}; // { padding: '0px 2px' };
style = i % 2 === 0 && baseChildren[i].col === 12 ? { ...style, ...leftStyle } : style;
style = !baseChildren[i].hide ? { ...style, display: "block" } : { ...style, display: "none" };
const Item = (
<Col key={String(i)} style={style} {...layoutProps} {...baseChildren[i].col}>
{baseChildren[i].render}
</Col>
);
children.push(Item);
}
return children;
}
export default SearchForm;

@ -1,7 +1,7 @@
export const PROJECT_NAME = "GHHub";
// mode: test内部测试使用
export const HT_HOST = import.meta.env.MODE === 'test' ? 'http://202.103.68.144:890' : import.meta.env.PROD ? "https://p9axztuwd7x8a7.mycht.cn" : 'http://202.103.68.144:890'
export const HT_HOST = import.meta.env.MODE === 'test' ? 'http://202.103.68.144:891' : import.meta.env.PROD ? "https://p9axztuwd7x8a7.mycht.cn" : 'http://202.103.68.144:891'
export const DATE_FORMAT = "YYYY-MM-DD";

@ -25,10 +25,18 @@ export const useDatePresets = () => {
label: t("datetime.lastMonth"),
value: [dayjs().subtract(1, "M").startOf("M"), dayjs().subtract(1, "M").endOf("M")],
},
{
label: t("datetime.nextMonth"),
value: [dayjs().add(1, "M").startOf("M"), dayjs().add(1, "M").endOf("M")],
},
{
label: t("datetime.lastThreeMonth"),
value: [dayjs().subtract(2, "M").startOf("M"), dayjs().endOf("M")],
},
{
label: t("datetime.nextThreeMonth"),
value: [dayjs().startOf("M"), dayjs().add(3,"M").endOf("M")],
},
{
label: t("datetime.firstHalfYear"),
value: [dayjs().startOf("y"), dayjs().endOf("y").subtract(6, "M")],

@ -29,6 +29,7 @@ import InvoicePaid from '@/views/invoice/Paid'
import InvoicePaidDetail from '@/views/invoice/PaidDetail'
import Airticket from '@/views/airticket/Index'
import AirticketPlan from '@/views/airticket/Plan'
import AirticketInvoice from '@/views/airticket/Invoice'
import { ThemeContext } from '@/stores/ThemeContext'
import { usingStorage } from '@/hooks/usingStorage'
import useAuthStore from './stores/Auth'
@ -69,6 +70,7 @@ const initRouter = async () => {
{ path: 'invoice/paid/detail/:flid', element: <RequireAuth subject={PERM_OVERSEA} result={true}><InvoicePaidDetail /></RequireAuth>},
{ path: 'airticket',element: <RequireAuth subject={PERM_AIR_TICKET} result={true}><Airticket /></RequireAuth>},
{ path: 'airticket/plan/:coli_sn/:gri_sn',element:<RequireAuth subject={PERM_AIR_TICKET} result={true}><AirticketPlan /></RequireAuth>},
{ path: 'airticket/invoice',element:<RequireAuth subject={PERM_AIR_TICKET} result={true}><AirticketInvoice /></RequireAuth>},
{ path: "products",element: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT} result={true}><ProductsManage /></RequireAuth>},
{ path: "products/:travel_agency_id/:use_year/:audit_state/audit",element:<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT} result={true}><ProductsAudit /></RequireAuth>},

@ -10,6 +10,7 @@ const airTicketStore = create((set, get) => ({
setPlanList: planList => set({ planList }),
setPlanDetail: planDetail => set({ planDetail }),
setGuestList: guestList => set({ guestList }),
setVEIFlightBill: vEIFlightBill => set({ vEIFlightBill }),
async getPlanList(vei_sn, GRI_Name, TimeStart, TimeEnd) {
const { setLoading, setPlanList } = get();
@ -68,15 +69,21 @@ const airTicketStore = create((set, get) => ({
const _result = errcode !== 0 ? [] : result;
setGuestList(_result);
},
// async getFlightCostList(CLF_SN) {
// const searchParams = {
// CLF_SN: CLF_SN,
// };
// const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/Get_flight_cost`, searchParams);
// const _result = errcode !== 0 ? [] : result;
// console.log(_result);
// return _result;
// },
//获取账单列表
async getVEIFlightBill(VEI_SN, GRI_Name, CheckStatus, FlightDate1, FlightDate2) {
const { setVEIFlightBill } = get();
const searchParams = {
VEI_SN: VEI_SN,
GRI_Name: GRI_Name,
CheckStatus: CheckStatus,
FlightDate1: FlightDate1,
FlightDate2: FlightDate2,
};
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/GetVEIFlightBill`, searchParams);
const _result = errcode !== 0 ? [] : result;
console.log(_result);
setVEIFlightBill(_result);
},
async postFlightCost(values) {
const formData = new FormData();
for (const [key, value] of Object.entries(values)) {
@ -91,6 +98,15 @@ const airTicketStore = create((set, get) => ({
}
});
},
async deleteFlightCost(CLC_SN) {
const searchParams = {
CLC_SN: CLC_SN,
};
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/Delete_flight_cost`, searchParams);
const _result = errcode !== 0 ? [] : result;
return _result;
},
}));
export default airTicketStore;

@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
import { Grid, Divider, Layout, Spin, Input, Col, Row, Space, List, Table } from "antd";
import { PhoneOutlined, CustomerServiceOutlined, AudioOutlined } from "@ant-design/icons";
import { Grid, Divider, Layout, Spin, Input, Col, Row, Space, List, Table, Button } from "antd";
import { PhoneOutlined, CustomerServiceOutlined, AudioOutlined,AuditOutlined } from "@ant-design/icons";
import { useParams, useHref, useNavigate, NavLink } from "react-router-dom";
import { isEmpty, formatColonTime } from "@/utils/commons";
import dayjs from "dayjs";
@ -14,6 +14,7 @@ const planListColumns = [
title: "团名",
key: "GRI_No",
dataIndex: "GRI_No",
// sorter: (a, b) => b.GRI_No - a.GRI_No,
},
{
title: "组团人",
@ -77,35 +78,40 @@ const planListColumns = [
];
const Airticket = props => {
const href = useHref();
const navigate = useNavigate();
const { phonenumber } = useParams();
const { travelAgencyId } = usingStorage();
const [getPlanList, planList, loading] = airTicketStore(state => [state.getPlanList, state.planList, state.loading]);
const [phone_number, setPhone_number] = useState(phonenumber);
const showTotal = total => `合计 ${total} `;
useEffect(() => {}, []);
const oncall = () => {};
return (
<Space direction="vertical" style={{ width: "100%" }}>
<SearchForm
initialValue={{
dates: [dayjs().startOf("M"), dayjs().endOf("M")],
}}
fieldsConfig={{
shows: ["referenceNo", "dates"],
fieldProps: {
referenceNo: { label: "搜索计划" },
dates: { label: "出发日期" },
},
}}
onSubmit={(err, formVal, filedsVal) => {
getPlanList(travelAgencyId, formVal.referenceNo, formVal.startdate, formVal.endtime);
}}
/>
<Row>
<Col md={20} lg={20} xxl={20}>
<SearchForm
initialValue={{
dates: [dayjs().startOf("M"), dayjs().endOf("M")],
}}
fieldsConfig={{
shows: ["referenceNo", "dates"],
fieldProps: {
referenceNo: { label: "搜索计划" },
dates: { label: "出发日期" ,col:8},
},
}}
onSubmit={(err, formVal, filedsVal) => {
getPlanList(travelAgencyId, formVal.referenceNo, formVal.startdate, formVal.endtime);
}}
/>
</Col>
<Col md={4} lg={4} xxl={4}>
<Button icon={<AuditOutlined />} onClick={() => navigate(`/airticket/invoice`)}>
报账
</Button>
</Col>
</Row>
<Row>
<Col md={24} lg={24} xxl={24}>
<Table bordered={true} rowKey="id" columns={planListColumns} dataSource={planList} loading={loading} pagination={{ defaultPageSize: 20, showTotal: showTotal }} />

@ -0,0 +1,151 @@
import { useState, useEffect } from "react";
import { Grid, Divider, Layout, Spin, Input, Col, Row, Space, Checkbox, Table, Button } from "antd";
import { PhoneOutlined, CustomerServiceOutlined, AudioOutlined, AuditOutlined } from "@ant-design/icons";
import { useParams, useHref, useNavigate, NavLink } from "react-router-dom";
import { isEmpty, formatColonTime } from "@/utils/commons";
import dayjs from "dayjs";
import SearchForm from "@/components/SearchForm";
import BackBtn from "@/components/BackBtn";
import airTicketStore from "@/stores/Airticket";
import { usingStorage } from "@/hooks/usingStorage";
const Invoice = props => {
const navigate = useNavigate();
const { travelAgencyId } = usingStorage();
const [getVEIFlightBill, vEIFlightBill, loading] = airTicketStore(state => [state.getVEIFlightBill, state.vEIFlightBill, state.loading]);
const showTotal = total => `合计 ${total} `;
//
const totalFee = vEIFlightBill.reduce((acc, curr) => acc + curr.ServiceFee, 0);
const vEIFlightBillColumns = [
{
title: "团名",
key: "GRI_No",
dataIndex: "GRI_No",
render: (text, record) => `${record.GRI_No} ${record.WL}`,
},
{
title: "费用类型",
key: "CostType",
dataIndex: "CostType",
},
{
title: "手续费/费用",
children: [
{
title: totalFee,
dataIndex: "ServiceFee",
},
],
},
{
title: "出发日期",
key: "StartDate",
dataIndex: "StartDate",
sorter: (a, b) => {
const dateA = new Date(a.StartDate);
const dateB = new Date(b.StartDate);
return dateB.getTime() - dateA.getTime();
},
},
{
title: "城市",
key: "FromCity",
dataIndex: "FromCity",
render: (text, record) => (record.CostType == "出票" ? `${record.FromCity} - ${record.ToCity}` : "-"),
},
{
title: "航班",
key: "FlightNo",
dataIndex: "FlightNo",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "PNR",
key: "PNR",
dataIndex: "PNR",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "票号",
key: "TicketNo",
dataIndex: "TicketNo",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "机票类型",
key: "FlightType",
dataIndex: "FlightType",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "机票价格",
key: "Cost",
dataIndex: "Cost",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "折扣",
key: "Discount",
dataIndex: "Discount",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
title: "备注",
key: "Memo",
dataIndex: "Memo",
},
{
title: "审核状态",
children: [
{
title: <Button type="link">全选</Button>,
dataIndex: "CheckStatus", //2
render: (text, record) => (record.CheckStatus < 2 ? <Checkbox>待提交</Checkbox> : record.CheckStatusName),
},
],
sorter: (a, b) => {
return b.CheckStatus - a.CheckStatus;
},
},
];
useEffect(() => {}, []);
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Row>
<Col md={20} lg={20} xxl={20}>
<SearchForm
initialValue={{
dates: [dayjs().startOf("M"), dayjs().endOf("M")],
}}
fieldsConfig={{
shows: ["referenceNo", "dates", "invoiceStatus"],
fieldProps: {
referenceNo: { label: "搜索计划" },
dates: { label: "出发日期", col: 8 },
},
}}
onSubmit={(err, formVal, filedsVal) => {
getVEIFlightBill(travelAgencyId, formVal.referenceNo, formVal.invoiceStatus, formVal.startdate, formVal.endtime);
}}
/>
</Col>
<Col md={4} lg={4} xxl={4}>
<BackBtn to={"/airticket"} />
</Col>
</Row>
<Row>
<Col md={24} lg={24} xxl={24}>
<Table bordered={true} rowKey="id" columns={vEIFlightBillColumns} dataSource={vEIFlightBill} loading={loading} pagination={{ defaultPageSize: 100, showTotal: showTotal }} />
</Col>
<Col md={24} lg={24} xxl={24}></Col>
</Row>
</Space>
);
};
export default Invoice;

@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { Grid, Divider, Layout, Modal, Form, Input, Col, Row, Space, Collapse, Table, Button, Select, App, Typography } from "antd";
import { Grid, Divider, Layout, Modal, Form, Input, Col, Row, Space, Collapse, Table, Button, Select, App, Popconfirm } from "antd";
import { PhoneOutlined, FrownTwoTone, LikeTwoTone, ArrowUpOutlined, ArrowDownOutlined } from "@ant-design/icons";
import { useParams, useHref, useNavigate, NavLink } from "react-router-dom";
import { isEmpty, formatColonTime } from "@/utils/commons";
@ -7,106 +7,41 @@ import { OFFICEWEBVIEWERURL } from "@/config";
import airTicketStore from "@/stores/Airticket";
import { usingStorage } from "@/hooks/usingStorage";
import BackBtn from "@/components/BackBtn";
const AirticketPlan = props => {
const { coli_sn, gri_sn } = useParams();
const { travelAgencyId, loginToken } = usingStorage();
const [getPlanDetail, planDetail, getGuestList, guestList, loading, postFlightDetail, getFlightCostList, postFlightCost] = airTicketStore(state => [
const [getPlanDetail, planDetail, getGuestList, guestList, loading, postFlightDetail, postFlightCost, deleteFlightCost] = airTicketStore(state => [
state.getPlanDetail,
state.planDetail,
state.getGuestList,
state.guestList,
state.loading,
state.postFlightDetail,
state.getFlightCostList,
state.postFlightCost,
state.deleteFlightCost,
]);
const reservationUrl = `https://p9axztuwd7x8a7.mycht.cn/Service_BaseInfoWeb/FlightPlanDocx?GRI_SN=${gri_sn}&VEI_SN=${travelAgencyId}`;
const reservationUrl = `https://p9axztuwd7x8a7.mycht.cn/Service_BaseInfoWeb/FlightPlanDocx?GRI_SN=${gri_sn}&VEI_SN=${travelAgencyId}&token=${loginToken}`;
const reservationPreviewUrl = OFFICEWEBVIEWERURL + encodeURIComponent(reservationUrl);
const [form] = Form.useForm();
const { notification } = App.useApp();
//console.log(reservationPreviewUrl);
//
const guestListColumns = [
{
title: "姓名",
key: "MEI_Name",
dataIndex: "MEI_Name",
},
{
title: "证件类型",
key: "MEI_PassportType",
dataIndex: "MEI_PassportType",
},
{
title: "证件号",
key: "MEI_PassportNo",
dataIndex: "MEI_PassportNo",
},
{
title: "证件有效期",
key: "MEI_PassportValidDate",
dataIndex: "MEI_PassportValidDate",
},
{
title: "性别",
key: "MEI_Gender",
dataIndex: "MEI_Gender",
},
{
title: "年龄",
key: "MEI_age",
dataIndex: "MEI_age",
},
{
title: "国籍",
key: "MEI_Country",
dataIndex: "MEI_Country",
},
{
title: "票号",
key: "MEI_SN",
dataIndex: "MEI_SN",
},
{
title: "PNR",
key: "MEI_SN",
dataIndex: "MEI_SN",
},
{
title: "机票费用RMB含基建和税",
key: "MEI_SN",
dataIndex: "MEI_SN",
},
{
title: "折扣",
key: "MEI_SN",
dataIndex: "MEI_SN",
},
{
title: "手续费",
key: "MEI_SN",
dataIndex: "MEI_SN",
},
{
title: "机票类型(成人/儿童/婴儿)",
key: "MEI_SN",
dataIndex: "MEI_SN",
},
{
title: "操作",
key: "MEI_SN",
dataIndex: "MEI_SN",
render: (_, record) => {
return (
<span>
<Typography.Link onClick={() => showModal(record)}>编辑</Typography.Link> | <Typography.Link onClick={() => console.log("del")}>删除</Typography.Link>
</span>
);
},
},
];
const guestList_select = () => {
return (
guestList &&
guestList.map(item => {
return { label: `${item.MEI_Name} , ${item.MEI_PassportNo}`, value: `${item.MEI_Name} , ${item.MEI_PassportNo} , ${item.MEI_Country} , ${item.MEI_Gender} , ${item.MEI_age} , ${item.MEI_Birthday}` };
})
);
};
const guestList_OnChange = value => {
console.log(value);
ticket_form.setFieldsValue({ Memo: `${value}` });
};
//
const costListColumns = [
@ -117,8 +52,8 @@ const AirticketPlan = props => {
},
{
title: "手续费/费用",
key: "Cost",
dataIndex: "Cost",
key: "ServiceFee",
dataIndex: "ServiceFee",
},
{
title: "PNR",
@ -128,8 +63,8 @@ const AirticketPlan = props => {
},
{
title: "票号",
key: "FlightCost",
dataIndex: "FlightCost",
key: "TicketNo",
dataIndex: "TicketNo",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
@ -140,8 +75,8 @@ const AirticketPlan = props => {
},
{
title: "机票价格",
key: "FlightCost",
dataIndex: "FlightCost",
key: "Cost",
dataIndex: "Cost",
render: (text, record) => (record.CostType == "出票" ? text : "-"),
},
{
@ -157,9 +92,20 @@ const AirticketPlan = props => {
},
{
title: "编辑",
key: "CLF_SN",
dataIndex: "CLF_SN",
render: (text, record) => <a onClick={() => showModal(record)}>编辑</a>,
key: "CLC_SN",
dataIndex: "CLC_SN",
render: (text, record) => (
<>
<Space>
<a onClick={() => showModal(record)}>编辑</a>
<Popconfirm title="删除" description="请确认是否删除?" onConfirm={() => handleDelete(record.CLC_SN)} okText="是" cancelText="否">
<Button danger type="link">
删除
</Button>
</Popconfirm>
</Space>
</>
),
},
];
@ -228,7 +174,7 @@ const AirticketPlan = props => {
</Form.Item>
<Form.Item label="出发">
<Space>
<Form.Item name="FromAirportCode" noStyle>
<Form.Item name="FromAirport" noStyle>
<Input placeholder="机场" />
</Form.Item>
<Form.Item name="FromTerminal" noStyle>
@ -241,7 +187,7 @@ const AirticketPlan = props => {
</Form.Item>
<Form.Item label="抵达">
<Space>
<Form.Item name="ToAirportCode" noStyle>
<Form.Item name="ToAirport" noStyle>
<Input placeholder="机场" />
</Form.Item>
<Form.Item name="ToTerminal" noStyle>
@ -300,7 +246,7 @@ const AirticketPlan = props => {
? planDetail.map(item => {
return {
key: item.id,
label: `${item.StartDate}`,
label: `${item.StartDate} ${item.FlightNo}(${item.FromAirport}${item.FlightStart}-${item.ToAirport}${item.FlightEnd})(${item.FlightCabin})`,
children: <Airticket_form airInfo={item} />,
};
})
@ -314,7 +260,8 @@ const AirticketPlan = props => {
const showModal = ticket => {
setIsModalOpen(true);
ticket.CostType == "出票" ? setisTicketType(true) : setisTicketType(false);//
if (isEmpty(ticket.CostType)) ticket.CostType = "出票";
ticket.CostType == "出票" ? setisTicketType(true) : setisTicketType(false); //
ticket_form.setFieldsValue(ticket);
};
@ -357,6 +304,29 @@ const AirticketPlan = props => {
setIsModalOpen(false);
};
const handleDelete = CLC_SN => {
deleteFlightCost(CLC_SN)
.then(() => {
notification.success({
message: `成功`,
description: "删除成功!",
placement: "top",
duration: 4,
icon: <LikeTwoTone />,
});
getPlanDetail(travelAgencyId, gri_sn);
})
.catch(() => {
notification.error({
message: `错误`,
description: "删除失败",
placement: "top",
duration: 4,
icon: <FrownTwoTone />,
});
});
};
const onChangeType = value => {
if (value == "出票") {
setisTicketType(true);
@ -413,7 +383,7 @@ const AirticketPlan = props => {
<Form.Item label="PNR" name="PNR">
<Input />
</Form.Item>
<Form.Item label="票号" name="Cost">
<Form.Item label="票号" name="TicketNo">
<Input />
</Form.Item>
<Form.Item label="机票类型" name="FlightType">
@ -437,8 +407,7 @@ const AirticketPlan = props => {
]}
/>
</Form.Item>
<Form.Item label="机票价格" name="FlightCost">
<Form.Item label="机票价格" name="Cost">
<Input
placeholder="含基建和税"
prefix="¥"
@ -454,32 +423,8 @@ const AirticketPlan = props => {
}}
/>
</Form.Item>
<Form.Item label="选择客人" name="MEI_Name66">
<Select
style={{
width: 160,
}}
onChange={() => console.log("dsads")}
options={[
{
value: "",
label: "",
},
{
value: "jack",
label: "成人",
},
{
value: "lucy",
label: "儿童",
},
{
value: "Yiminghe",
label: "婴儿",
},
]}
/>
<Select onChange={value => guestList_OnChange(value)} options={guestList_select()} />
</Form.Item>
</>
)}
@ -508,14 +453,20 @@ const AirticketPlan = props => {
useEffect(() => {
getPlanDetail(travelAgencyId, gri_sn);
getGuestList(travelAgencyId, coli_sn);
getGuestList(coli_sn);
}, []);
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Row>
<Col md={20} lg={20} xxl={20}></Col>
<Col md={4} lg={4} xxl={4}>
<BackBtn to={"/airticket"} />
</Col>
</Row>
<Row>
<Col md={24} lg={24} xxl={24} style={{ height: "100%" }}>
<iframe id="msdoc-iframe-reservation" title="msdoc-iframe-reservation" src={reservationPreviewUrl} style={{ width: "100%", height: "600px" }}></iframe>
{/* <iframe id="msdoc-iframe-reservation" title="msdoc-iframe-reservation" src={reservationPreviewUrl} style={{ width: "100%", height: "600px" }}></iframe> */}
<Button type="link" target="_blank" href={reservationUrl}>
下载
</Button>

Loading…
Cancel
Save