Merge branch 'main' of github.com:hainatravel/GHHub

perf/export-docx
黄文强@HWQ-PC 1 year ago
commit 944c607c77

@ -1,7 +1,7 @@
{ {
"name": "global-highlights-hub", "name": "global-highlights-hub",
"private": true, "private": true,
"version": "2.0.0-alpha.0", "version": "2.0.0-alpha.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

@ -29,6 +29,7 @@
"sureCancel": "Are you sure to cancel?", "sureCancel": "Are you sure to cancel?",
"sureDelete":"Are you sure to delete?", "sureDelete":"Are you sure to delete?",
"Yes": "Yes", "Yes": "Yes",
"No": "No",
"Success": "Success", "Success": "Success",
"Failed": "Failed", "Failed": "Failed",

@ -98,6 +98,7 @@
"CopyFormMsg": { "CopyFormMsg": {
"Source": "Source ", "Source": "Source ",
"target": "Target ", "target": "Target ",
"withQuote": "Whether to copy the quotation",
"requiredVendor": "Please pick a target vendor", "requiredVendor": "Please pick a target vendor",
"requiredTypes": "Please select product types", "requiredTypes": "Please select product types",
"requiredDept": "Please pick a owner department" "requiredDept": "Please pick a owner department"

@ -29,6 +29,7 @@
"sureCancel": "确定取消?", "sureCancel": "确定取消?",
"sureDelete":"确定删除?", "sureDelete":"确定删除?",
"Yes": "是", "Yes": "是",
"No": "否",
"Success": "成功", "Success": "成功",
"Failed": "失败", "Failed": "失败",

@ -98,6 +98,7 @@
"CopyFormMsg": { "CopyFormMsg": {
"Source": "源", "Source": "源",
"target": "目标", "target": "目标",
"withQuote": "是否复制报价",
"requiredVendor": "请选择目标供应商", "requiredVendor": "请选择目标供应商",
"requiredTypes": "请选择产品类型", "requiredTypes": "请选择产品类型",
"requiredDept": "请选择所属小组" "requiredDept": "请选择所属小组"

@ -9,7 +9,7 @@ const BackBtn = ({to, ...props}) => {
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<> <>
{isNotEmpty(to) ? <Link to={to}>{t('Back')}</Link> : <Button type='link' onClick={() => navigate(-1)}>{t('Back')}</Button>} {isNotEmpty(to) ? <Link to={to} className='px-4'>{t('Back')}</Link> : <Button type='link' onClick={() => navigate(-1)}>{t('Back')}</Button>}
</> </>
); );
}; };

@ -2,15 +2,6 @@ import { Select } from 'antd';
import { useProductsTypes } from '@/hooks/useProductsSets'; import { useProductsTypes } from '@/hooks/useProductsSets';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { fetchJSON } from '@/utils/request';
import { HT_HOST } from '@/config';
//
export const fetchVendorList = async (q) => {
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/VendorList`, { q })
return errcode !== 0 ? [] : result
}
const ProductsTypesSelector = ({...props}) => { const ProductsTypesSelector = ({...props}) => {
const productsTypes = useProductsTypes(); const productsTypes = useProductsTypes();
const { t } = useTranslation(); const { t } = useTranslation();

@ -9,8 +9,9 @@ import { useTranslation } from 'react-i18next';
import SearchInput from './SearchInput'; import SearchInput from './SearchInput';
import AuditStateSelector from './AuditStateSelector'; import AuditStateSelector from './AuditStateSelector';
import DeptSelector from './DeptSelector'; import DeptSelector from './DeptSelector';
import ProductsTypesSelector, { fetchVendorList } from './ProductsTypesSelector'; import ProductsTypesSelector from './ProductsTypesSelector';
import CitySelector from '@/components/CitySelector'; import CitySelector from '@/components/CitySelector';
import VendorSelector from '@/components/VendorSelector';
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
@ -248,14 +249,7 @@ function getFields(props) {
'agency', 'agency',
99, 99,
<Form.Item name='agency' label={t('products:Vendor')} {...fieldProps.agency} initialValue={at(props, 'initialValue.agency')[0]}> <Form.Item name='agency' label={t('products:Vendor')} {...fieldProps.agency} initialValue={at(props, 'initialValue.agency')[0]}>
<SearchInput <VendorSelector {...fieldComProps.agency} />
placeholder={t('products:Vendor')}
mode={'multiple'}
maxTagCount={0}
{...fieldComProps.agency}
fetchOptions={fetchVendorList}
map={{ travel_agency_name: 'label', travel_agency_id: 'value' }}
/>
</Form.Item>, </Form.Item>,
fieldProps?.agency?.col || 6 fieldProps?.agency?.col || 6
), ),

@ -3,7 +3,7 @@ import { Layout, Flex, theme, Spin, Divider } from 'antd';
import BackBtn from './BackBtn'; import BackBtn from './BackBtn';
const { Content, Header } = Layout; const { Content, Header } = Layout;
const HeaderWrapper = ({ children, header, loading, ...props }) => { const HeaderWrapper = ({ children, header, loading, backTo, ...props }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const { const {
token: { colorBgContainer }, token: { colorBgContainer },
@ -16,7 +16,7 @@ const HeaderWrapper = ({ children, header, loading, ...props }) => {
<Flex justify={'space-between'} align={'center'} className='h-full'> <Flex justify={'space-between'} align={'center'} className='h-full'>
{/* {header} */} {/* {header} */}
<div className='grow h-full'>{header}</div> <div className='grow h-full'>{header}</div>
<BackBtn /> <BackBtn to={backTo} />
</Flex> </Flex>
</Header> </Header>
<Divider className='my-2' /> <Divider className='my-2' />

@ -0,0 +1,29 @@
import { createContext, useEffect, useState } from 'react';
import {} from 'antd';
import SearchInput from './SearchInput';
import { fetchJSON } from '@/utils/request';
import { HT_HOST } from '@/config';
import { useTranslation } from 'react-i18next';
//
export const fetchVendorList = async (q) => {
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/VendorList`, { q });
return errcode !== 0 ? [] : result;
};
const VendorSelector = ({ ...props }) => {
const { t } = useTranslation();
return (
<>
<SearchInput
placeholder={t('products:Vendor')}
mode={'multiple'}
maxTagCount={0}
{...props}
fetchOptions={fetchVendorList}
map={{ travel_agency_name: 'label', travel_agency_id: 'value' }}
/>
</>
);
};
export default VendorSelector;

@ -52,7 +52,7 @@ export const useProductsTypes = (showAll = false) => {
{ label: t('products:type.UltraService'), value: 'B', key: 'B' }, { label: t('products:type.UltraService'), value: 'B', key: 'B' },
{ label: t('products:type.Car'), value: 'J', key: 'J' }, { label: t('products:type.Car'), value: 'J', key: 'J' },
{ label: t('products:type.Guide'), value: 'Q', key: 'Q' }, { label: t('products:type.Guide'), value: 'Q', key: 'Q' },
{ label: t('products:type.Attractions'), value: '7', key: '7' }, { label: t('products:type.Attractions'), value: '7', key: '7' }, // landscape
{ label: t('products:type.Meals'), value: 'R', key: 'R' }, { label: t('products:type.Meals'), value: 'R', key: 'R' },
{ label: t('products:type.Extras'), value: '8', key: '8' }, { label: t('products:type.Extras'), value: '8', key: '8' },
{ label: t('products:type.Package'), value: 'D', key: 'D' }, { label: t('products:type.Package'), value: 'D', key: 'D' },

@ -30,8 +30,8 @@ const airTicketStore = create((set, get) => ({
async getPlanDetail(vei_sn, gri_sn) { async getPlanDetail(vei_sn, gri_sn) {
const { setPlanDetail } = get(); const { setPlanDetail } = get();
const searchParams = { const searchParams = {
vei_sn: 6376, //vei_sn, vei_sn: 4272, //vei_sn,
gri_sn: 369040, //gri_sn gri_sn: 372928, //gri_sn
}; };
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/GetFlightPlanDetail`, searchParams); const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/GetFlightPlanDetail`, searchParams);
const _result = errcode !== 0 ? [] : result; const _result = errcode !== 0 ? [] : result;

@ -30,7 +30,7 @@ export const copyAgencyDataAction = async (postbody) => {
}; };
export const getAgencyProductsAction = async (param) => { export const getAgencyProductsAction = async (param) => {
const _param = { ...param, use_year: (param.use_year || '').replace('all', ''), audit_state: (param.audit_state || '').replace('all', '') }; const _param = { ...param, use_year: String(param.use_year || '').replace('all', ''), audit_state: (param.audit_state || '').replace('all', '') };
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/travel_agency_products`, _param); const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/travel_agency_products`, _param);
return errcode !== 0 ? { agency: {}, products: [] } : result; return errcode !== 0 ? { agency: {}, products: [] } : result;
}; };
@ -39,6 +39,7 @@ export const getAgencyProductsAction = async (param) => {
* *
*/ */
export const addProductExtraAction = async (body) => { export const addProductExtraAction = async (body) => {
console.log('addProductExtraAction', body);
return true; // test: 先不更新到HT return true; // test: 先不更新到HT
const { errcode, result } = await postJSON(`${HT_HOST}/Service_BaseInfoWeb/products_extras_add`, body); const { errcode, result } = await postJSON(`${HT_HOST}/Service_BaseInfoWeb/products_extras_add`, body);
return errcode === 0 ? true : false; return errcode === 0 ? true : false;

@ -128,6 +128,7 @@ function Management() {
const onAccountFinish = (values) => { const onAccountFinish = (values) => {
saveOrUpdateAccount(values) saveOrUpdateAccount(values)
.then(() => { .then(() => {
setAccountModalOpen(false)
handelAccountSearch() handelAccountSearch()
}) })
.catch(ex => { .catch(ex => {
@ -224,7 +225,7 @@ function Management() {
htmlType: 'submit', htmlType: 'submit',
}} }}
title={t('account:detail')} title={t('account:detail')}
open={isAccountModalOpen} onOk={() => setAccountModalOpen(false)} onCancel={() => setAccountModalOpen(false)} open={isAccountModalOpen} onCancel={() => setAccountModalOpen(false)}
destroyOnClose destroyOnClose
forceRender forceRender
modalRender={(dom) => ( modalRender={(dom) => (

@ -132,6 +132,7 @@ function RoleList() {
const onRoleFinish = (values) => { const onRoleFinish = (values) => {
saveOrUpdateRole(values) saveOrUpdateRole(values)
.then(() => { .then(() => {
setRoleModalOpen(false)
fetchRoleList() fetchRoleList()
.then(r => { .then(r => {
setRoleAllList(r) setRoleAllList(r)
@ -148,6 +149,7 @@ function RoleList() {
} }
const onRoleFailed = (error) => { const onRoleFailed = (error) => {
console.log('Failed:', error)
// form.resetFields() // form.resetFields()
} }
@ -160,7 +162,7 @@ function RoleList() {
htmlType: 'submit', htmlType: 'submit',
}} }}
title={t('account:detail')} title={t('account:detail')}
open={isRoleModalOpen} onOk={() => setRoleModalOpen(false)} onCancel={() => setRoleModalOpen(false)} open={isRoleModalOpen} onCancel={() => setRoleModalOpen(false)}
destroyOnClose destroyOnClose
forceRender forceRender
modalRender={(dom) => ( modalRender={(dom) => (

@ -1,5 +1,5 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Grid, Divider, Layout, Form, Input, Col, Row, Space, Collapse, Table, Button } from "antd"; import { Grid, Divider, Layout, Modal, Form, Input, Col, Row, Space, Collapse, Table, Button, Select, InputNumber, Typography } from "antd";
import { PhoneOutlined, CustomerServiceOutlined, AudioOutlined, ArrowUpOutlined, ArrowDownOutlined } from "@ant-design/icons"; import { PhoneOutlined, CustomerServiceOutlined, AudioOutlined, ArrowUpOutlined, ArrowDownOutlined } from "@ant-design/icons";
import { useParams, useHref, useNavigate, NavLink } from "react-router-dom"; import { useParams, useHref, useNavigate, NavLink } from "react-router-dom";
import { isEmpty, formatColonTime } from "@/utils/commons"; import { isEmpty, formatColonTime } from "@/utils/commons";
@ -15,14 +15,96 @@ const AirticketPlan = props => {
const reservationUrl = `https://p9axztuwd7x8a7.mycht.cn/Service_BaseInfoWeb/FlightPlanDocx?GRI_SN=${coli_sn}&VEI_SN=${travelAgencyId}`; const reservationUrl = `https://p9axztuwd7x8a7.mycht.cn/Service_BaseInfoWeb/FlightPlanDocx?GRI_SN=${coli_sn}&VEI_SN=${travelAgencyId}`;
const reservationPreviewUrl = OFFICEWEBVIEWERURL + encodeURIComponent(reservationUrl); const reservationPreviewUrl = OFFICEWEBVIEWERURL + encodeURIComponent(reservationUrl);
console.log(reservationPreviewUrl); // 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 Airticket_form = props => { const Airticket_form = props => {
const aitInfo = props.airInfo; const aitInfo = props.airInfo;
return ( return (
<> <>
<Form <Form
name="basic" component={false}
name={"ticket_form_" + aitInfo.id}
labelCol={{ labelCol={{
span: 8, span: 8,
}} }}
@ -38,18 +120,15 @@ const AirticketPlan = props => {
// onFinish={onFinish} // onFinish={onFinish}
// onFinishFailed={onFinishFailed} // onFinishFailed={onFinishFailed}
autoComplete="off"> autoComplete="off">
<Form.Item label="日期" name="username" rules={[{ required: true }]}>
<Input value={aitInfo.StartDate} />
</Form.Item>
<Form.Item label="城市" name="password" rules={[{ required: true }]}> <Form.Item label="城市" name="password" rules={[{ required: true }]}>
<Space.Compact> <Space.Compact>
<Input placeholder="出发" prefix={<ArrowUpOutlined />} value={aitInfo.FromCity} /> <Input placeholder="出发" prefix={<ArrowUpOutlined />} value={aitInfo.FromCity} />
<Input placeholder="抵达" prefix={<ArrowDownOutlined />} value={aitInfo.ToCity} /> <Input placeholder="抵达" prefix={<ArrowDownOutlined />} value={aitInfo.ToCity} />
</Space.Compact> </Space.Compact>
</Form.Item> </Form.Item>
<Form.Item label="航班" name="password" rules={[{ required: true }]}> <Form.Item label="日期和航班" name="username" rules={[{ required: true }]}>
<Space.Compact> <Space.Compact>
<Input value={aitInfo.StartDate} />
<Input placeholder="航空公司" /> <Input placeholder="航空公司" />
<Input placeholder="航班号" value={aitInfo.FlightNo} /> <Input placeholder="航班号" value={aitInfo.FlightNo} />
</Space.Compact> </Space.Compact>
@ -74,18 +153,24 @@ const AirticketPlan = props => {
<Input placeholder="行李" /> <Input placeholder="行李" />
</Space.Compact> </Space.Compact>
</Form.Item> </Form.Item>
{/* <Form.Item <Form.Item
wrapperCol={{ wrapperCol={{
offset: 8, offset: 14,
span: 16, span: 16,
}}> }}>
<Space>
<Button type="primary" htmlType="submit"> <Button type="primary" htmlType="submit">
Submit 保存机票信息
</Button> </Button>
</Form.Item> */} <Button type="dashed" htmlType="submit">
</Form> 添加出票信息和费用
<Divider /> </Button>
</Space>
</Form.Item>
<Divider orientation="left">费用列表</Divider>
<Table bordered={true} rowKey="id" columns={guestListColumns} dataSource={guestList} loading={loading} pagination={false} /> <Table bordered={true} rowKey="id" columns={guestListColumns} dataSource={guestList} loading={loading} pagination={false} />
</Form>
</> </>
); );
}; };
@ -102,73 +187,146 @@ const AirticketPlan = props => {
: []; : [];
}; };
const guestListColumns = [ // begin
{ const [isModalOpen, setIsModalOpen] = useState(false);
title: "姓名", const [ticket_form] = Form.useForm();
key: "MEI_Name",
dataIndex: "MEI_Name", const showModal = ticket => {
}, setIsModalOpen(true);
{ ticket_form.setFieldsValue(ticket);
title: "证件类型", };
key: "MEI_PassportType",
dataIndex: "MEI_PassportType", const handleOk = () => {
}, ticket_form
.validateFields()
.then(values => {
//
console.log("Received values of form: ", values);
ticket_form.resetFields();
setIsModalOpen(false);
})
.catch(info => {
console.log("Validate Failed:", info);
});
};
const handleCancel = () => {
ticket_form.resetFields();
setIsModalOpen(false);
};
const onChangeType = value => {
console.log(value);
};
const TicketModal = () => {
return (
<>
<Modal title="Basic Modal" open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
<Form
form={ticket_form}
labelCol={{
span: 5,
}}>
<Form.Item label="费用类型" name="MEI_Name2222">
<Select
defaultValue="jack"
style={{
width: 160,
}}
onChange={onChangeType}
options={[
{ {
title: "证件号", value: "jack",
key: "MEI_PassportNo", label: "出票",
dataIndex: "MEI_PassportNo",
}, },
{ {
title: "证件有效期", value: "lucy",
key: "MEI_PassportValidDate", label: "改签",
dataIndex: "MEI_PassportValidDate",
}, },
{ {
title: "性别", value: "Yiminghe",
key: "MEI_Gender", label: "退票",
dataIndex: "MEI_Gender",
}, },
{ {
title: "年龄", value: "Yimingh2e",
key: "MEI_age", label: "其它",
dataIndex: "MEI_age",
},
{
title: "国籍",
key: "MEI_Country",
dataIndex: "MEI_Country",
}, },
]}
/>
</Form.Item>
<Form.Item label="机票类型" name="MEI_Name2222">
<Select
style={{
width: 160,
}}
onChange={() => console.log("dsads")}
options={[
{ {
title: "票号", value: "jack",
key: "MEI_SN", label: "成人",
dataIndex: "MEI_SN",
}, },
{ {
title: "PNR", value: "lucy",
key: "MEI_SN", label: "儿童",
dataIndex: "MEI_SN",
}, },
{ {
title: "机票费用RMB含基建和税", value: "Yiminghe",
key: "MEI_SN", label: "婴儿",
dataIndex: "MEI_SN",
}, },
]}
/>
</Form.Item>
<Form.Item label="票号">
<Input />
</Form.Item>
<Form.Item label="机票价格">
<Input placeholder="含基建和税" prefix="¥" />
</Form.Item>
<Form.Item label="折扣" name="MEI_Name33">
<Input />
</Form.Item>
<Form.Item label="PNR">
<Input />
</Form.Item>
<Form.Item label="手续费/费用" name="MEI_Name66" rules={[{ required: true }]}>
<Input prefix="¥" />
</Form.Item>
<Form.Item label="选择客人" name="MEI_Name66">
<Select
style={{
width: 160,
}}
onChange={() => console.log("dsads")}
options={[
{ {
title: "折扣", value: "jack",
key: "MEI_SN", label: "成人",
dataIndex: "MEI_SN",
}, },
{ {
title: "手续费", value: "lucy",
key: "MEI_SN", label: "儿童",
dataIndex: "MEI_SN",
}, },
{ {
title: "机票类型(成人/儿童/婴儿)", value: "Yiminghe",
key: "MEI_SN", label: "婴儿",
dataIndex: "MEI_SN",
}, },
]; ]}
/>
</Form.Item>
<Form.Item label="备注">
<Input.TextArea rows={4} />
</Form.Item>
</Form>
</Modal>
</>
);
};
// end
useEffect(() => { useEffect(() => {
getPlanDetail(travelAgencyId, coli_sn); getPlanDetail(travelAgencyId, coli_sn);
@ -190,7 +348,9 @@ const AirticketPlan = props => {
<Collapse items={detail_items()} /> <Collapse items={detail_items()} />
</Col> </Col>
</Row> </Row>
<TicketModal />
</Space> </Space>
); );
}; };
export default AirticketPlan; export default AirticketPlan;

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useParams, Link } from 'react-router-dom'; import { useParams, Link, Navigate, useNavigate } from 'react-router-dom';
import { App, Button, Collapse, Table, Space, Divider } from 'antd'; import { App, Empty, Button, Collapse, Table, Space, Divider, Select } from 'antd';
import { useProductsTypes, useProductsAuditStatesMapVal } from '@/hooks/useProductsSets'; import { useProductsTypes, useProductsAuditStatesMapVal } from '@/hooks/useProductsSets';
import SecondHeaderWrapper from '@/components/SecondHeaderWrapper'; import SecondHeaderWrapper from '@/components/SecondHeaderWrapper';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -10,19 +10,49 @@ import useAuthStore from '@/stores/Auth';
import RequireAuth from '@/components/RequireAuth'; import RequireAuth from '@/components/RequireAuth';
// import PrintContractPDF from './PrintContractPDF'; // import PrintContractPDF from './PrintContractPDF';
import { PERM_PRODUCTS_OFFER_AUDIT, PERM_PRODUCTS_OFFER_PUT } from '@/config'; import { PERM_PRODUCTS_OFFER_AUDIT, PERM_PRODUCTS_OFFER_PUT } from '@/config';
import dayjs from 'dayjs';
const Header = ({ title, agency, refresh, ...props }) => { import VendorSelector from '@/components/VendorSelector';
const Header = ({ refresh, ...props }) => {
const { travel_agency_id, use_year, audit_state } = useParams(); const { travel_agency_id, use_year, audit_state } = useParams();
const { t } = useTranslation(); const { t } = useTranslation();
const [activeAgency] = useProductsStore((state) => [state.activeAgency]); const isPermitted = useAuthStore(state => state.isPermitted);
const [activeAgency, setActiveAgency] = useProductsStore((state) => [state.activeAgency, state.setActiveAgency]);
const { message, notification } = App.useApp(); const { message, notification } = App.useApp();
const navigate = useNavigate();
const yearOptions = [];
const currentYear = dayjs().year();
const baseYear = Number(use_year === 'all' ? currentYear : use_year);
for (let i = baseYear - 3; i <= baseYear + 3; i++) {
yearOptions.push({ label: i, value: i, });
}
const [param, setParam] = useState({ pick_year: baseYear, pick_agency: travel_agency_id, });
const [pickYear, setPickYear] = useState(baseYear);
useEffect(() => {
refresh(param);
navigate(`/products/${activeAgency.travel_agency_id}/${pickYear}/${audit_state}/audit`);
return () => {};
}, [param]);
const handleYearChange = (value) => {
setPickYear(value);
setParam((pre) => ({ ...pre, ...{ pick_year: value } }));
};
const handleAgencyChange = ({ label, value }) => {
setActiveAgency({ travel_agency_id: value, travel_agency_name: label });
setParam((pre) => ({ ...pre, ...{ pick_agency: value } }));
};
const handleAuditItem = (state, row) => { const handleAuditItem = (state, row) => {
postProductsQuoteAuditAction(state, { id: row.id, travel_agency_id: activeAgency.travel_agency_id }) postProductsQuoteAuditAction(state, { id: row.id, travel_agency_id: activeAgency.travel_agency_id })
.then((json) => { .then((json) => {
if (json.errcode === 0) { if (json.errcode === 0) {
message.success(json.errmsg); message.success(json.errmsg);
if (typeof refresh === 'function') { if (typeof refresh === 'function') {
refresh(); refresh(param);
} }
} }
}) })
@ -39,20 +69,33 @@ const Header = ({ title, agency, refresh, ...props }) => {
<div className='flex justify-end items-center gap-4 h-full'> <div className='flex justify-end items-center gap-4 h-full'>
<div className='grow'> <div className='grow'>
<h2 className='m-0 leading-tight'> <h2 className='m-0 leading-tight'>
{title} {isPermitted(PERM_PRODUCTS_OFFER_AUDIT) ? (
<VendorSelector
value={{ label: activeAgency.travel_agency_name, value: activeAgency.travel_agency_id }}
onChange={handleAgencyChange}
mode={null}
className='w-72'
size='large'
variant={'borderless'}
/>
) : (
activeAgency.travel_agency_name
)}
<Divider type={'vertical'} /> <Divider type={'vertical'} />
{(use_year || '').replace('all', '')} <Select options={yearOptions} variant={'borderless'} className='w-24' size='large' value={pickYear} onChange={handleYearChange} />
{/* <Divider type={'vertical'} />
{(use_year || '').replace('all', '')} */}
</h2> </h2>
</div> </div>
{/* <Button size='small'>{t('Copy')}</Button> */} {/* <Button size='small'>{t('Copy')}</Button> */}
{/* <Button size='small'>{t('Import')}</Button> */} {/* <Button size='small'>{t('Import')}</Button> */}
<RequireAuth subject={PERM_PRODUCTS_OFFER_PUT}> <RequireAuth subject={PERM_PRODUCTS_OFFER_PUT}>
<Link className='px-2' to={`/products/${travel_agency_id}/${use_year}/${audit_state}/edit`}> <Link className='px-2' to={`/products/${activeAgency.travel_agency_id}/${pickYear}/${audit_state}/edit`}>
{t('Edit')} {t('Edit')}
</Link> </Link>
</RequireAuth> </RequireAuth>
<RequireAuth subject={PERM_PRODUCTS_OFFER_AUDIT}> <RequireAuth subject={PERM_PRODUCTS_OFFER_AUDIT}>
<Button size='small' type={'primary'} onClick={() => handleAuditItem('2', agency)}> <Button size='small' type={'primary'} onClick={() => handleAuditItem('2', activeAgency)}>
{t('products:auditStateAction.Published')} {t('products:auditStateAction.Published')}
</Button> </Button>
</RequireAuth> </RequireAuth>
@ -60,7 +103,7 @@ const Header = ({ title, agency, refresh, ...props }) => {
{t('products:auditStateAction.Approved')} {t('products:auditStateAction.Approved')}
</Button> */} </Button> */}
<RequireAuth subject={PERM_PRODUCTS_OFFER_AUDIT}> <RequireAuth subject={PERM_PRODUCTS_OFFER_AUDIT}>
<Button size='small' type={'primary'} danger ghost onClick={() => handleAuditItem('3', agency)}> <Button size='small' type={'primary'} danger ghost onClick={() => handleAuditItem('3', activeAgency)}>
{t('products:auditStateAction.Rejected')} {t('products:auditStateAction.Rejected')}
</Button> </Button>
</RequireAuth> </RequireAuth>
@ -216,27 +259,22 @@ const TypesPanels = (props) => {
const onCollapseChange = (_activeKey) => { const onCollapseChange = (_activeKey) => {
setActiveKey(_activeKey); setActiveKey(_activeKey);
}; };
return <Collapse items={showTypes} activeKey={activeKey} onChange={onCollapseChange} />; return isEmpty(agencyProducts) ? <Empty /> : <Collapse items={showTypes} activeKey={activeKey} onChange={onCollapseChange} />;
}; };
const Audit = ({ ...props }) => { const Audit = ({ ...props }) => {
const { travel_agency_id, use_year, audit_state } = useParams(); const { travel_agency_id, use_year, audit_state } = useParams();
const [loading, activeAgency, getAgencyProducts] = useProductsStore((state) => [state.loading, state.activeAgency, state.getAgencyProducts]); const [loading, activeAgency, getAgencyProducts] = useProductsStore((state) => [state.loading, state.activeAgency, state.getAgencyProducts]);
const handleGetAgencyProducts = () => { const handleGetAgencyProducts = ({pick_year, pick_agency}={}) => {
getAgencyProducts({ travel_agency_id, use_year, audit_state }); const year = pick_year || use_year;
const agency = pick_agency || travel_agency_id;
getAgencyProducts({ travel_agency_id: agency, use_year: year, audit_state });
}; };
useEffect(() => {
handleGetAgencyProducts();
return () => {};
}, [travel_agency_id]);
return ( return (
<> <>
<SecondHeaderWrapper header={<Header title={activeAgency.travel_agency_name} agency={activeAgency} refresh={handleGetAgencyProducts} />} loading={loading} > <SecondHeaderWrapper header={<Header title={activeAgency.travel_agency_name} refresh={handleGetAgencyProducts} />} loading={loading} backTo={`/products`}>
{/* debug: 0 */}
{/* <PrintContractPDF /> */} {/* <PrintContractPDF /> */}
<TypesPanels refresh={handleGetAgencyProducts} /> <TypesPanels refresh={handleGetAgencyProducts} />

@ -1,11 +1,12 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { App, Form, Modal, DatePicker, Divider } from 'antd'; import { App, Form, Modal, DatePicker, Divider, Switch } from 'antd';
import { isEmpty, objectMapper } from '@/utils/commons'; import { isEmpty, objectMapper } from '@/utils/commons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import SearchInput from '@/components/SearchInput'; import SearchInput from '@/components/SearchInput';
import DeptSelector from '@/components/DeptSelector'; import DeptSelector from '@/components/DeptSelector';
import ProductsTypesSelector, { fetchVendorList } from '@/components/ProductsTypesSelector'; import ProductsTypesSelector from '@/components/ProductsTypesSelector';
import { fetchVendorList } from '@/components/VendorSelector';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import arraySupport from 'dayjs/plugin/arraySupport'; import arraySupport from 'dayjs/plugin/arraySupport';
import { copyAgencyDataAction } from '@/stores/Products/Index'; import { copyAgencyDataAction } from '@/stores/Products/Index';
@ -28,7 +29,7 @@ export const CopyProductsForm = ({ action, initialValues, onFormInstanceReady, s
const onValuesChange = (changeValues, allValues) => {}; const onValuesChange = (changeValues, allValues) => {};
return ( return (
<Form layout='horizontal' form={form} name='form_in_modal' initialValues={initialValues} onValuesChange={onValuesChange}> <Form layout='horizontal' form={form} name='form_in_modal' initialValues={initialValues} onValuesChange={onValuesChange} >
{action === '#' && <Form.Item name='agency' label={`${t('products:CopyFormMsg.target')}${t('products:Vendor')}`} rules={[{ required: true, message: t('products:CopyFormMsg.requiredVendor') }]}> {action === '#' && <Form.Item name='agency' label={`${t('products:CopyFormMsg.target')}${t('products:Vendor')}`} rules={[{ required: true, message: t('products:CopyFormMsg.requiredVendor') }]}>
<SearchInput <SearchInput
placeholder={t('products:Vendor')} placeholder={t('products:Vendor')}
@ -38,7 +39,7 @@ export const CopyProductsForm = ({ action, initialValues, onFormInstanceReady, s
map={{ travel_agency_name: 'label', travel_agency_id: 'value' }} map={{ travel_agency_name: 'label', travel_agency_id: 'value' }}
/> />
</Form.Item>} </Form.Item>}
<Form.Item name={`products_types`} label={t('products:ProductType')}> <Form.Item name={`products_types`} label={t('products:ProductType')} >
<ProductsTypesSelector maxTagCount={1} mode={'multiple'} placeholder={t('All')} /> <ProductsTypesSelector maxTagCount={1} mode={'multiple'} placeholder={t('All')} />
</Form.Item> </Form.Item>
{action === '#' && <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}> {action === '#' && <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>
@ -46,11 +47,15 @@ export const CopyProductsForm = ({ action, initialValues, onFormInstanceReady, s
<DeptSelector isLeaf={true} /> <DeptSelector isLeaf={true} />
</Form.Item> </Form.Item>
</RequireAuth>} </RequireAuth>}
<Form.Item name={'source_use_year'} label={`${t('products:CopyFormMsg.Source')}${t('products:UseYear')}`} initialValue={dayjs([source.sourceYear, 1, 1])}> <Form.Item name={'source_use_year'} label={`${t('products:CopyFormMsg.Source')}${t('products:UseYear')}`} initialValue={dayjs([source.sourceYear, 1, 1])} rules={[{ required: true,}]}>
<DatePicker picker='year' allowClear /> <DatePicker picker='year' allowClear />
</Form.Item> </Form.Item>
<Form.Item name={'target_use_year'} label={`${t('products:CopyFormMsg.target')}${t('products:UseYear')}`}> <Form.Item name={'target_use_year'} label={`${t('products:CopyFormMsg.target')}${t('products:UseYear')}`} rules={[{ required: true,}]}>
<DatePicker picker='year' allowClear disabledDate={(current) => current <= dayjs([source.sourceYear, 12, 31])} /> <DatePicker picker='year' allowClear />
{/* disabledDate={(current) => current <= dayjs([source.sourceYear, 12, 31])} */}
</Form.Item>
<Form.Item name={'with_quote'} label={`${t('products:CopyFormMsg.withQuote')}`}>
<Switch checkedChildren={t('Yes')} unCheckedChildren={t('No')} />
</Form.Item> </Form.Item>
</Form> </Form>
); );
@ -68,7 +73,7 @@ const formValuesMapper = (values) => {
'products_types': { 'products_types': {
key: 'products_types', key: 'products_types',
transform: (value) => { transform: (value) => {
return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.value : ''; return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.value : '-1';
}, },
}, },
'dept': { 'dept': {
@ -77,6 +82,7 @@ const formValuesMapper = (values) => {
return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.value : ''; return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.value : '';
}, },
}, },
'with_quote': { key: 'with_quote', transform: (value) => (value ? 1 : 0) },
}; };
let dest = {}; let dest = {};
const { agency, year, ...omittedValue } = values; const { agency, year, ...omittedValue } = values;

@ -6,9 +6,12 @@ import dayjs from 'dayjs';
import arraySupport from 'dayjs/plugin/arraySupport'; import arraySupport from 'dayjs/plugin/arraySupport';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import useProductsStore, { copyAgencyDataAction } from '@/stores/Products/Index'; import useProductsStore, { copyAgencyDataAction } from '@/stores/Products/Index';
import { useProductsTypes, useProductsAuditStatesMapVal } from '@/hooks/useProductsSets';
import useFormStore from '@/stores/Form'; import useFormStore from '@/stores/Form';
import { objectMapper } from '@/utils/commons'; import { objectMapper } from '@/utils/commons';
import CopyProductsFormModal from './Detail/CopyProducts'; import CopyProductsFormModal from './Detail/CopyProducts';
import useAuthStore from '@/stores/Auth';
import RequireAuth from '@/components/RequireAuth';
dayjs.extend(arraySupport); dayjs.extend(arraySupport);
@ -20,6 +23,8 @@ function Index() {
const [searchValues, setSearchValues] = useProductsStore((state) => [state.searchValues, state.setSearchValues]); const [searchValues, setSearchValues] = useProductsStore((state) => [state.searchValues, state.setSearchValues]);
const formValuesToSub = useFormStore(state => state.formValuesToSub); const formValuesToSub = useFormStore(state => state.formValuesToSub);
const stateMapVal = useProductsAuditStatesMapVal();
const useYear = formValuesToSub.year; const useYear = formValuesToSub.year;
const handleSearchAgency = (formVal = undefined) => { const handleSearchAgency = (formVal = undefined) => {
@ -60,7 +65,15 @@ function Index() {
{ title: t('products:Vendor'), key: 'vendor', dataIndex: 'travel_agency_name' }, { title: t('products:Vendor'), key: 'vendor', dataIndex: 'travel_agency_name' },
{ title: t('products:CreatedBy'), key: 'poster_by', dataIndex: 'poster_name' }, { title: t('products:CreatedBy'), key: 'poster_by', dataIndex: 'poster_name' },
{ title: t('products:CreateDate'), key: 'poster_date', dataIndex: 'poster_date' }, { title: t('products:CreateDate'), key: 'poster_date', dataIndex: 'poster_date' },
{ title: t('products:AuState'), key: 'audit_state', dataIndex: 'audit_state' }, {
title: t('products:AuState'),
key: 'audit_state',
dataIndex: 'audit_state',
render: (_, r) => {
const stateCls = ` text-${stateMapVal[`${r.audit_state_id}`]?.color} `;
return <span className={stateCls}>{stateMapVal[`${r.audit_state_id}`]?.label}</span>;
},
},
{ title: t('products:AuditedBy'), key: 'audited_by', dataIndex: 'audited_by_name' }, { title: t('products:AuditedBy'), key: 'audited_by', dataIndex: 'audited_by_name' },
{ title: t('products:AuditDate'), key: 'audit_date', dataIndex: 'audit_date' }, { title: t('products:AuditDate'), key: 'audit_date', dataIndex: 'audit_date' },
{ {
@ -69,9 +82,15 @@ function Index() {
render: (_, r) => ( render: (_, r) => (
<Space size={'large'}> <Space size={'large'}>
<Link to={`/products/${r.travel_agency_id}/${searchValues.use_year || 'all'}/${searchValues.audit_state || 'all'}/edit`}>{t('Edit')}</Link> <Link to={`/products/${r.travel_agency_id}/${searchValues.use_year || 'all'}/${searchValues.audit_state || 'all'}/edit`}>{t('Edit')}</Link>
{r.audit_state_id >= 0 ? (
<Link to={`/products/${r.travel_agency_id}/${searchValues.use_year || 'all'}/${searchValues.audit_state || 'all'}/audit`}>{t('Audit')}</Link> <Link to={`/products/${r.travel_agency_id}/${searchValues.use_year || 'all'}/${searchValues.audit_state || 'all'}/audit`}>{t('Audit')}</Link>
<Button type='link' size={'small'} onClick={() => openCopyModal(r, '#')}>{t('Copy')+'-'+t('products:#')}</Button> ) : (
<Button type='link' size={'small'} onClick={() => openCopyModal(r, 'o')}>{t('Copy')+'-'+t('products:Offer')}</Button> <span className='text-muted'>{t('Audit')}</span>
)}
<Button type='link' size={'small'} onClick={() => openCopyModal(r, '#')}>
{t('Copy') + '-' + t('products:#')}
</Button>
{/* <Button type='link' size={'small'} onClick={() => openCopyModal(r, 'o')}>{t('Copy')+'-'+t('products:Offer')}</Button> */}
</Space> </Space>
), ),
}, },
@ -102,6 +121,7 @@ function Index() {
{/* 复制弹窗 */} {/* 复制弹窗 */}
<CopyProductsFormModal <CopyProductsFormModal
initialValues={{agency: {label: sourceAgency.travel_agency_name, value: sourceAgency.travel_agency_id}}}
open={copyModalVisible} open={copyModalVisible}
action={copyAction} action={copyAction}
source={{ sourceAgency, sourceYear: useYear }} source={{ sourceAgency, sourceYear: useYear }}

@ -2,6 +2,11 @@
import colors from 'tailwindcss/colors' import colors from 'tailwindcss/colors'
export default { export default {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
safelist: [
'text-primary',
'text-danger',
'text-muted',
],
darkMode: 'media', darkMode: 'media',
theme: { theme: {
colors: { colors: {

Loading…
Cancel
Save