diff --git a/doc/价格管理平台.bmpr b/doc/价格管理平台.bmpr index 2e3bd02..eb1a8d6 100644 Binary files a/doc/价格管理平台.bmpr and b/doc/价格管理平台.bmpr differ diff --git a/package.json b/package.json index 7b26815..dd39be7 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,6 @@ "i18next": "^23.11.5", "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^2.5.2", - "mobx": "^6.9.0", - "mobx-react": "^7.6.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-i18next": "^14.1.2", diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 0810454..b113807 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -21,6 +21,13 @@ "preview": "Preview", "Total": "Total", "Action": "Action", + "Import": "Import", + "Export": "Export", + "Copy": "Copy", + + "Table": { + "Total": "Total {{total}} items" + }, "Login": "Login", "Username": "Username", diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 6e50c59..0ccfecf 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -7,15 +7,26 @@ "Attractions": "Attractions", "Meals": "Meals", "Extras": "Extras", + "Overtravel": "超公里", "Special": "Special" }, "auditState": { "New": "New", "Pending": "Pending", - "Approve": "Approve", + "Approved": "Approved", "Rejected": "Rejected", "Published": "Published" }, + "auditStateAction": { + "New": "New", + "Pending": "Pending", + "Approved": "Approve", + "Rejected": "Reject", + "Published": "Publish" + }, + "Status": "Status", + "State": "State", + "Title": "Title", "Vendor": "Vendor", "AuState": "Audit State", @@ -24,7 +35,6 @@ "AuditedBy": "Audited By", "AuditDate": "Audit Date", - "productProject": "Product project", "Code": "Code", "City": "City", diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 4ef0694..e3239a1 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -21,6 +21,13 @@ "preview": "预览", "Total": "总数", "Action": "操作", + "Import": "导入", + "Export": "导出", + "Copy": "复制", + + "Table": { + "Total": "共 {{total}} 条" + }, "Login": "登录", "Username": "账号", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 87b9914..3abea44 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -6,21 +6,53 @@ "Package": "包价线路", "Attractions": "景点", "Meals": "餐费", - "Extras": "附加", + "Extras": "附加项目", + "Overtravel": "超公里", "Special": "特殊项目" }, "auditState": { "New": "新增", "Pending": "待审核", - "Approve": "已通过", + "Approved": "已通过", "Rejected": "已拒绝", "Published": "已发布" }, + "auditStateAction": { + "New": "新增", + "Pending": "待审核", + "Approved": "审核通过", + "Rejected": "审核拒绝", + "Published": "审核发布" + }, + "Status": "状态", + "State": "状态", + "Title": "名称", "Vendor": "供应商", "AuState": "审核状态", "CreatedBy": "提交人员", "CreateDate": "提交时间", + + + "AuditedBy": "审核人员", + "AuditDate": "审核时间", + + "Quotation": "报价", + "Offer": "报价", + "Unit": "单位", + "GroupSize": "人等", + "UseDates": "使用日期", + "Weekdays": "有效日/周X", + + "UseYear": "年份", + + "AgeType": { + "Type": "人群", + "Adult": "成人", + "Child": "儿童" + }, + + "#": "#" "Auditors": "审核人员", "AuditDate": "审核时间", diff --git a/src/components/AuditStateSelector.jsx b/src/components/AuditStateSelector.jsx new file mode 100644 index 0000000..db4c77d --- /dev/null +++ b/src/components/AuditStateSelector.jsx @@ -0,0 +1,12 @@ +import { Select } from 'antd'; +import { useProductsAuditStates } from '@/hooks/useProductsSets'; + +const AuditStateSelector = ({ ...props }) => { + const states = useProductsAuditStates(); + return ( + <> + + , fieldProps?.referenceNo?.col || 6 ), item( 'PNR', 99, - - + + , fieldProps?.PNR?.col || 4 ), @@ -202,37 +208,56 @@ function getFields(props) { , fieldProps?.dates?.col || midCol ), + item( + 'username', + 3, + + + , + fieldProps?.username?.col || 4 + ), + item( + 'realname', + 4, + + + , + fieldProps?.realname?.col || 4 + ), /** * */ + item( + 'year', + 99, + + + , + fieldProps?.year?.col || 3 + ), item( 'agency', 99, - + , fieldProps?.agency?.col || 6 ), item( - 'auditState', + 'audit_state', 99, - - - - - - - - - - - - - - - {/* Role Edit */} - + )} > -
+ + + - {t('account:management.newRole')} - - - - - - - + +
+ + + + + + + + + + + +
{t('account:management.tile')} { + onSubmit={(err, formValues, filedsVal) => { + console.info(formValues) setDataLoading(true) - fetchReservationList(formVal) + searchAccountByCriteria(formValues) .catch(ex => { notification.error({ message: 'Notification', @@ -388,7 +265,6 @@ function Management() { - @@ -397,6 +273,7 @@ function Management() { 1) { + return ( + + ) + } + } + + const onPermissionChange = (newValue) => { + console.log('onChange ', newValue) + setPermissionValue(newValue) + } + + useEffect (() => { + setDataLoading(true) + fetchRoleList() + .then(r => { + setRoleAllList(r) + }) + .finally(() => { + setDataLoading(false) + }) + }, []) + + const [permissionValue, setPermissionValue] = useState(['0-0-0']) + const [isRoleModalOpen, setRoleModalOpen] = useState(false) + const [dataLoading, setDataLoading] = useState(false) + const [roleAllList, setRoleAllList] = useState([]) + + const [roleForm] = Form.useForm() + const [saveOrUpdateRole, newRole] = + useAccountStore((state) => + [state.saveOrUpdateRole, state.newRole]) + + const { notification, modal } = App.useApp() + + const onRoleSeleted = (role) => { + roleForm.setFieldsValue(role) + setRoleModalOpen(true) + } + + const onNewRole = () => { + const role = newRole() + roleForm.setFieldsValue(role) + setRoleModalOpen(true) + } + + const onRoleFinish = (values) => { + console.log(values) + saveOrUpdateRole(values) + .catch(ex => { + console.info(ex.message) + notification.error({ + message: 'Notification', + description: ex.message, + placement: 'top', + duration: 4, + }) + }) + } + + const onRoleFailed = (error) => { + console.log('Failed:', error) + // form.resetFields() + } + + return ( + <> + setRoleModalOpen(false)} onCancel={() => setRoleModalOpen(false)} + destroyOnClose={true} + clearOnDestroy={true} + modalRender={(dom) => ( +
+ {dom} + + )} + > + + + + + + + +
+ + {t('account:management.roleList')} + + + + + + + + + + + +
{ return t('Total') + `:${total}` } + }} + onChange={(pagination) => { onSearchClick(pagination.current) }} + columns={roleListColumns} dataSource={roleAllList} + /> + + + + + ) +} + +export default RoleList diff --git a/src/views/airticket/Index.jsx b/src/views/airticket/Index.jsx index 06cbb02..32a84a5 100644 --- a/src/views/airticket/Index.jsx +++ b/src/views/airticket/Index.jsx @@ -1,8 +1,8 @@ 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 { useParams, useHref, useNavigate } from "react-router-dom"; -import { isEmpty } from "@/utils/commons"; +import { useParams, useHref, useNavigate, NavLink } from "react-router-dom"; +import { isEmpty, formatColonTime } from "@/utils/commons"; import dayjs from "dayjs"; import SearchForm from "@/components/SearchForm"; @@ -29,6 +29,11 @@ const planListColumns = [ 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: "出发城市", @@ -49,16 +54,25 @@ const planListColumns = [ title: "起飞时间", key: "FlightTimeStart", dataIndex: "FlightTimeStart", + render: text => formatColonTime(text), }, { title: "落地时间", key: "FlightTimeEnd", dataIndex: "FlightTimeEnd", + render: text => formatColonTime(text), }, { - title: "PNR 暂时用FlightInfo", + title: "是否出票", + key: "COLD_PlanVEI_SN", + dataIndex: "COLD_PlanVEI_SN", + render: (text, record) => "否", + }, + { + title: "操作", key: "FlightInfo", dataIndex: "FlightInfo", + render: (text, record) => {"编辑"}, }, ]; @@ -66,7 +80,7 @@ const Airticket = props => { const href = useHref(); const navigate = useNavigate(); const { phonenumber } = useParams(); - const {travelAgencyId, } = usingStorage(); + 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} `; @@ -82,14 +96,14 @@ const Airticket = props => { dates: [dayjs().startOf("M"), dayjs().endOf("M")], }} fieldsConfig={{ - shows: ["referenceNo", "PNR", "dates"], + shows: ["referenceNo", "dates"], fieldProps: { referenceNo: { label: "搜索计划" }, dates: { label: "出发日期" }, }, }} onSubmit={(err, formVal, filedsVal) => { - getPlanList(travelAgencyId, formVal.referenceNo, formVal.PNR, formVal.startdate, formVal.endtime); + getPlanList(travelAgencyId, formVal.referenceNo, formVal.startdate, formVal.endtime); }} /> diff --git a/src/views/airticket/Plan.jsx b/src/views/airticket/Plan.jsx new file mode 100644 index 0000000..29ba956 --- /dev/null +++ b/src/views/airticket/Plan.jsx @@ -0,0 +1,196 @@ +import { useState, useEffect } from "react"; +import { Grid, Divider, Layout, Form, Input, Col, Row, Space, Collapse, Table, Button } from "antd"; +import { PhoneOutlined, CustomerServiceOutlined, AudioOutlined, ArrowUpOutlined, ArrowDownOutlined } from "@ant-design/icons"; +import { useParams, useHref, useNavigate, NavLink } from "react-router-dom"; +import { isEmpty, formatColonTime } from "@/utils/commons"; +import { OFFICEWEBVIEWERURL } from "@/config"; + +import airTicketStore from "@/stores/Airticket"; +import { usingStorage } from "@/hooks/usingStorage"; + +const AirticketPlan = props => { + const { coli_sn } = useParams(); + const { travelAgencyId, loginToken } = usingStorage(); + const [getPlanDetail, planDetail, getGuestList, guestList, loading] = airTicketStore(state => [state.getPlanDetail, state.planDetail, state.getGuestList, state.guestList, state.loading]); + const reservationUrl = `https://p9axztuwd7x8a7.mycht.cn/Service_BaseInfoWeb/FlightPlanDocx?GRI_SN=${coli_sn}&VEI_SN=${travelAgencyId}`; + const reservationPreviewUrl = OFFICEWEBVIEWERURL + encodeURIComponent(reservationUrl); + + console.log(reservationPreviewUrl); + + const Airticket_form = props => { + const aitInfo = props.airInfo; + return ( + <> +
+ + + + + + + } value={aitInfo.FromCity} /> + } value={aitInfo.ToCity} /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* + + */} + + +
+ + ); + }; + + const detail_items = () => { + return planDetail + ? planDetail.map(item => { + return { + key: item.id, + label: `${item.StartDate} 计划: ${item.FlightInfo}`, + children: , + }; + }) + : []; + }; + + 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", + }, + ]; + + useEffect(() => { + getPlanDetail(travelAgencyId, coli_sn); + getGuestList(travelAgencyId, coli_sn); + console.log(detail_items()); + }, []); + + return ( + + + + {/* */} + + + 出票信息 + + + + + + ); +}; +export default AirticketPlan; diff --git a/src/views/invoice/Index.jsx b/src/views/invoice/Index.jsx index 17a9603..559a718 100644 --- a/src/views/invoice/Index.jsx +++ b/src/views/invoice/Index.jsx @@ -1,6 +1,4 @@ import { NavLink, useNavigate } from "react-router-dom"; -import { useState } from "react"; -import { toJS } from "mobx"; import { Row, Col, Space, Button, Table, App, Steps } from "antd"; import { formatDate, isNotEmpty } from "@/utils/commons"; import { AuditOutlined, SmileOutlined, SolutionOutlined, EditOutlined } from "@ant-design/icons"; @@ -103,7 +101,7 @@ function Index() { -
+
diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index f4187a2..e055cc3 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -1,30 +1,182 @@ -import { createContext, useContext, useEffect, useState } from 'react'; -import { Button, Table, Tabs } from 'antd'; -import { useProductsTypes } from '@/hooks/useProductsSets'; +import { useEffect, useState } from 'react'; +import { useParams, } from 'react-router-dom'; +import { App, Button, Collapse, Table, Space, } from 'antd'; +import { useProductsTypes, useProductsAuditStatesMapVal } from '@/hooks/useProductsSets'; import SecondHeaderWrapper from '@/components/SecondHeaderWrapper'; +import { useTranslation } from 'react-i18next'; +import useProductsStore, { postProductsQuoteAudit } from '@/stores/Products/Index'; +import { isEmpty } from '@/utils/commons'; -const Header = () => { +const Header = ({ title, agency, refresh, ...props}) => { + const { t } = useTranslation(); + const [activeAgency, ] = useProductsStore((state) => [state.activeAgency, ]); + const { message, notification } = App.useApp(); + const handleAuditItem = (state, row) => { + postProductsQuoteAudit(state, {id: row.id, travel_agency_id: activeAgency.travel_agency_id}) + .then((json) => { + if (json.errcode === 0) { + message.success(json.errmsg); + if (typeof refresh === 'function') { + refresh(); + } + } + }) + .catch((ex) => { + notification.error({ + message: 'Notification', + description: ex.message, + placement: 'top', + duration: 4, + }); + }); + }; return ( -
-
- - +
+
+

{title}

+
+ {/* */} + {/* */} + + {/* */} + + {/* todo: export */} +
); }; -const TypesTabs = () => { +const PriceTable = ({dataSource,refresh}) => { + const { t } = useTranslation('products'); + const [loading, activeAgency, ] = useProductsStore((state) => [state.loading, state.activeAgency, ]); + const { message, notification } = App.useApp(); + const stateMapVal = useProductsAuditStatesMapVal(); + + const handleAuditPriceItem = (state, row) => { + postProductsQuoteAudit(state, {id: row.id, travel_agency_id: activeAgency.travel_agency_id}) + .then((json) => { + if (json.errcode === 0) { + message.success(json.errmsg); + if (typeof refresh === 'function') { + refresh(); + } + } + }) + .catch((ex) => { + notification.error({ + message: 'Notification', + description: ex.message, + placement: 'top', + duration: 4, + }); + }); + }; + + const columns = [ + { key: 'title', dataIndex: ['info', 'title'], title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan }) }, + { key: 'adult', title: t('AgeType.Adult'), render: (_, { value, currency, unit_name }) => `${value} ${currency} / ${unit_name}` }, + { key: 'child', title: t('AgeType.Child'), render: (_, { value, currency, unit_name }) => `${value} ${currency} / ${unit_name}` }, + // {key: 'price', title: t('Currency'), }, + // {key: 'currency', title: t('Currency'), }, + // {key: 'unit', title: t('Unit'), }, + { + key: 'groupSize', + dataIndex: ['group_size_min'], + title: t('GroupSize'), + render: (_, { group_size_min, group_size_max }) => `${group_size_min} - ${group_size_max}`, + }, + { + key: 'useDates', + dataIndex: ['use_dates_start'], + title: t('UseDates'), + render: (_, { use_dates_start, use_dates_end }) => `${use_dates_start} ~ ${use_dates_end}`, + }, + { key: 'weekdays', dataIndex: ['weekdays'], title: t('Weekdays') }, + { + key: 'state', + title: t('State'), + render: (_, r) => { + return stateMapVal[`${r.audit_state_id}`]?.label; + }, + }, + { + title: '价格审核', + key: 'action', + render: (_, r) => r.audit_state_id <= 0 ?( + + + + + ) : null, + }, + ]; + return
r.id} />; +} + +/** + * + */ +const TypesPanels = (props) => { + const [loading, agencyProducts, ] = useProductsStore((state) => [state.loading, state.agencyProducts]); + // console.log(agencyProducts); const productsTypes = useProductsTypes(); + const [activeKey, setActiveKey] = useState([]); + const [showTypes, setShowTypes] = useState([]); + useEffect(() => { + // 只显示有产品的类型; 展开产品的价格表, 合并名称列 + const hasDataTypes = Object.keys(agencyProducts); + const _show = productsTypes + .filter((kk) => hasDataTypes.includes(kk.value)) + .map((ele) => ({ + ...ele, + children: ( + r.concat(c.quotation.map((q, i) => ({ ...q, info: c.info, rowSpan: i === 0 ? c.quotation.length : 0 }))), [])} + refresh={props.refresh} + /> + ), + })); + setShowTypes(_show); + + setActiveKey(isEmpty(_show) ? [] : [_show[0].key]); + + return () => {}; + }, [productsTypes, agencyProducts]); + + const onCollapseChange = (_activeKey) => { + setActiveKey(_activeKey) + } return ( - + ) } const Audit = ({ ...props }) => { + const { travel_agency_id, use_year, audit_state } = useParams(); + const [activeAgency, getAgencyProducts] = useProductsStore((state) => [state.activeAgency, state.getAgencyProducts]); + + const handleGetAgencyProducts = () => { + getAgencyProducts({ travel_agency_id, use_year, audit_state }); + } + + useEffect(() => { + handleGetAgencyProducts(); + + return () => {}; + }, [travel_agency_id]) + return ( <> - }> - + }> +
+
); diff --git a/src/views/products/Index.jsx b/src/views/products/Index.jsx index b793b2f..d1b1495 100644 --- a/src/views/products/Index.jsx +++ b/src/views/products/Index.jsx @@ -1,22 +1,32 @@ -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { Link } from 'react-router-dom'; -import { Row, Col, Space, Typography, Table, Button } from 'antd'; -import useProductsStore from '@/stores/Products/Index'; -import { usingStorage } from '@/hooks/usingStorage'; +import { Row, Col, Space, Table } from 'antd'; import SearchForm from '@/components/SearchForm'; import dayjs from 'dayjs'; import { useTranslation } from 'react-i18next'; -import { useProductsTypes } from '@/hooks/useProductsSets'; +import useProductsStore from '@/stores/Products/Index'; +import useFormStore from '@/stores/Form'; +import { objectMapper } from '@/utils/commons'; function Index() { const { t } = useTranslation(); - const { userId } = usingStorage(); - const [loading, productsList] = useProductsStore((state) => [state.loading, state.productsList]); + const [loading, agencyList, searchAgency] = useProductsStore((state) => [state.loading, state.agencyList, state.searchAgency]); + const [searchValues, setSearchValues] = useProductsStore((state) => [state.searchValues, state.setSearchValues]); + const formValuesToSub = useFormStore(state => state.formValuesToSub); + + const handleSearchAgency = (formVal = undefined) => { + const { starttime, endtime, ...param } = formVal || formValuesToSub; + const searchParam = objectMapper(param, { agency: 'travel_agency_ids', startdate: 'edit_date1', enddate: 'edit_date2' }); + setSearchValues(searchParam); + searchAgency(searchParam); + } - const [noticeList, setNoticeList] = useState([]); - useEffect(() => {}, []); + useEffect(() => { + // handleSearchAgency(); + }, []); + + const showTotal = (total) => t('Table.Total', { total }); - const showTotal = (total) => `Total ${total} items`; const columns = [ { title: t('products:Vendor'), key: 'vendor', dataIndex: 'travel_agency_name' }, { title: t('products:CreatedBy'), key: 'created_by', dataIndex: 'created_by' }, @@ -29,8 +39,8 @@ function Index() { key: 'action', render: (_, r) => ( - {t('Edit')} - {t('Audit')} + {t('Edit')} + {t('Audit')} ), }, @@ -38,23 +48,37 @@ function Index() { return ( { + handleSearchAgency(formVal); }} />
-
+