diff --git a/doc/RBAC 权限.sql b/doc/RBAC 权限.sql index 489d8f4..5f6befb 100644 --- a/doc/RBAC 权限.sql +++ b/doc/RBAC 权限.sql @@ -59,6 +59,19 @@ VALUES ('管理角色', '/account/role-new', 'system') INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) VALUES ('所有海外功能', '/oversea/all', 'oversea') + +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('团预订', '/reservation/all', 'oversea') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('团预订(客服)', '/reservation/most', 'oversea') + +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('账单', '/invoice/all', 'oversea') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('反馈表', '/feedback/all', 'oversea') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('质量评分', '/report/all', 'oversea') + INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) VALUES ('所有国内功能', '/domestic/all', 'domestic') INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) diff --git a/src/components/SearchInput.jsx b/src/components/SearchInput.jsx index 134c301..19724a5 100644 --- a/src/components/SearchInput.jsx +++ b/src/components/SearchInput.jsx @@ -31,6 +31,7 @@ function DebounceSelect({ fetchOptions, debounceTimeout = 800, ...props }) { showSearch allowClear maxTagCount={1} + loading={fetching} dropdownStyle={{width: '20rem'}} {...props} onSearch={debounceFetcher} diff --git a/src/config.js b/src/config.js index 0d4e825..848b932 100644 --- a/src/config.js +++ b/src/config.js @@ -29,7 +29,12 @@ export const PERM_ROLE_NEW = '/account/role-new' // 海外供应商 // category: oversea -export const PERM_OVERSEA = '/oversea/all' +export const PERM_OVERSEA = '/oversea/all' // @Deprecated 准备作废... +export const PERM_RESERVATION_ALL = '/reservation/all' // 供应商使用,只能搜索自己的数据 +export const PERM_RESERVATION_MOST = '/reservation/most' // 客服使用,可是选择其他供应商搜索 +export const PERM_INVOICE_ALL = '/invoice/all' +export const PERM_FEEDBACK_ALL = '/feedback/all' +export const PERM_REPORT_ALL = '/report/all' // 国内供应商 // category: domestic diff --git a/src/main.jsx b/src/main.jsx index d1fee90..af56f23 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -49,7 +49,10 @@ import ImageViewer from '@/views/ImageViewer'; import CustomerImageViewer from '@/views/CustomerImageViewer'; import PickYear from './views/products/PickYear' -import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA,PERM_TRAIN_TICKET, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT, PERM_PRODUCTS_OFFER_PUT } from '@/config' +import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, + PERM_TRAIN_TICKET, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT, PERM_PRODUCTS_OFFER_PUT, + PERM_RESERVATION_ALL, PERM_FEEDBACK_ALL, PERM_INVOICE_ALL, PERM_REPORT_ALL +} from '@/config' import './i18n' @@ -66,23 +69,24 @@ const initRouter = async () => { { path: 'account/management', element: }, { path: 'account/role-list', element: }, // - { path: 'reservation/newest', element: }, - { path: 'reservation/:reservationId', element: }, + { path: 'reservation/newest', element: }, + { path: 'reservation/:reservationId', element: }, // - { path: 'feedback', element: }, - { path: 'feedback/:GRI_SN/:CII_SN/:RefNo', element: }, - { path: 'feedback/:GRI_SN/:RefNo', element: }, + { path: 'feedback', element: }, + { path: 'feedback/:GRI_SN/:CII_SN/:RefNo', element: }, + { path: 'feedback/:GRI_SN/:RefNo', element: }, // - { path: 'report', element: }, + { path: 'report', element: }, // { path: 'notice', element: }, { path: 'notice/:CCP_BLID', element: }, // - { path: 'invoice',element:}, - { path: 'invoice/detail/:GMDSN/:GSN',element:}, - { path: 'invoice/history/:GMDSN/:GSN',element:}, - { path: 'invoice/paid',element:}, - { path: 'invoice/paid/detail/:flid', element: }, + { path: 'invoice',element:}, + { path: 'invoice/detail/:GMDSN/:GSN',element:}, + { path: 'invoice/history/:GMDSN/:GSN',element:}, + { path: 'invoice/paid',element:}, + { path: 'invoice/paid/detail/:flid', element: }, + { path: 'airticket',element: }, { path: 'airticket/plan/:coli_sn/:gri_sn',element:}, { path: 'airticket/invoice',element:}, diff --git a/src/stores/Reservation.js b/src/stores/Reservation.js index e28ea15..670fe18 100644 --- a/src/stores/Reservation.js +++ b/src/stores/Reservation.js @@ -38,7 +38,7 @@ export const fetchAttachList = async (reservationId) => { const useReservationStore = create(devtools((set, get) => ({ cityList: [], - + selectedAgencyId: -1, selectedReservation: null, selectedConfirmation: null, arrivalDateRange: [], @@ -60,12 +60,12 @@ const useReservationStore = create(devtools((set, get) => ({ ], getCityListByReservationId: async (reservationId) => { - const { travelAgencyId } = usingStorage() + const { selectedAgencyId } = get() set(() => ({ cityList: [] })) - const cityListJson = await fetchCityList(travelAgencyId, reservationId) + const cityListJson = await fetchCityList(selectedAgencyId, reservationId) const mapCityList = cityListJson.map((data) => { return { key: data.CII_SN, @@ -93,12 +93,14 @@ const useReservationStore = create(devtools((set, get) => ({ }, fetchReservationList: (formValues, current=1) => { - const { travelAgencyId } = usingStorage() + set(() => ({ + selectedAgencyId: formValues.selectedAgencyId + })) const { reservationPage } = get() // 设置为 0,后端会重新计算总数,当跳转第 X 页时可用原来的总数。 const totalNum = current == 1 ? 0 : reservationPage.total const fetchUrl = prepareUrl(HT_HOST + '/service-cusservice/GetPlanSearchList') - .append('VEI_SN', travelAgencyId) + .append('VEI_SN', formValues.selectedAgencyId) .append('GroupNo', formValues.referenceNo) .append('DateStart', formValues.startdate) .append('DateEnd', formValues.enddate) @@ -138,8 +140,7 @@ const useReservationStore = create(devtools((set, get) => ({ }) }, - fetchAllGuideList: () => { - const { travelAgencyId } = usingStorage() + fetchAgencyGuideList: (travelAgencyId) => { const fetchUrl = prepareUrl(HT_HOST + '/service-cusservice/PTGetGuideList') .append('VEI_SN', travelAgencyId) .build() @@ -161,8 +162,7 @@ const useReservationStore = create(devtools((set, get) => ({ }) }, - getReservationDetail: async (reservationId) => { - const { travelAgencyId } = usingStorage() + getReservationDetail: async (travelAgencyId, reservationId) => { const { planDetail, planChangeList } = await fetchPlanDetail(travelAgencyId, reservationId) const attachListJson = await fetchAttachList(reservationId) diff --git a/src/views/App.jsx b/src/views/App.jsx index 2ac855e..567fc5c 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -22,7 +22,9 @@ import { useDefaultLgc } from '@/i18n/LanguageSwitcher' import { appendRequestParams } from '@/utils/request' import LogUploader from '@/components/LogUploader' -import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT,PERM_TRAIN_TICKET } from '@/config' +import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT,PERM_TRAIN_TICKET, + PERM_RESERVATION_ALL, PERM_FEEDBACK_ALL, PERM_INVOICE_ALL, PERM_REPORT_ALL + } from '@/config' const { Header, Content, Footer } = Layout @@ -98,10 +100,10 @@ function App() { mode='horizontal' selectedKeys={[defaultPath]} items={[ - isPermitted(PERM_OVERSEA) ? { key: 'reservation', label: {t('menu.Reservation')} } : null, - isPermitted(PERM_OVERSEA) ? { key: 'invoice', label: {t('menu.Invoice')} } : null, - isPermitted(PERM_OVERSEA) ? { key: 'feedback', label: {t('menu.Feedback')} } : null, - isPermitted(PERM_OVERSEA) ? { key: 'report', label: {t('menu.Report')} } : null, + isPermitted(PERM_RESERVATION_ALL) ? { key: 'reservation', label: {t('menu.Reservation')} } : null, + isPermitted(PERM_INVOICE_ALL) ? { key: 'invoice', label: {t('menu.Invoice')} } : null, + isPermitted(PERM_FEEDBACK_ALL) ? { key: 'feedback', label: {t('menu.Feedback')} } : null, + isPermitted(PERM_REPORT_ALL) ? { key: 'report', label: {t('menu.Report')} } : null, isPermitted(PERM_AIR_TICKET) ? { key: 'airticket', label: {t('menu.Airticket')} } : null, isPermitted(PERM_TRAIN_TICKET) ? { key: 'trainticket', label: {t('menu.Trainticket')} } : null, isProductPermitted ? { key: 'products', label: {t('menu.Products')} } : null, diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index e972632..8c9196f 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -3,7 +3,7 @@ import useAccountStore, { fetchRoleList, fetchTravelAgencyByName, genRandomPassw import useFormStore from '@/stores/Form' import { isEmpty, debounce } from '@/utils/commons' import { ExclamationCircleFilled } from '@ant-design/icons' -import { App, Button, Col, Form, Input, Modal, Row, Select, Space, Table, Typography, Switch } from 'antd' +import { App, Button, Col, Form, Input, Modal, Row, Select, Space, Table, Typography, Switch, Spin } from 'antd' import dayjs from 'dayjs' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -306,7 +306,7 @@ function Management() { showSearch filterOption={false} onSearch={debounce(handleTravelAgencySearch, 800)} - notFoundContent={null} + notFoundContent={dataLoading ? : null} > diff --git a/src/views/reservation/Detail.jsx b/src/views/reservation/Detail.jsx index 52f7f1c..697241c 100644 --- a/src/views/reservation/Detail.jsx +++ b/src/views/reservation/Detail.jsx @@ -52,7 +52,7 @@ function Detail() { } function attachmentRender(_, confirm) { - const attachmentKey = `GHH/${travelAgencyId}/${reservationId}/PCISN${confirm.key}`; + const attachmentKey = `GHH/${selectedAgencyId}/${reservationId}/PCISN${confirm.key}`; return ( <> @@ -77,19 +77,19 @@ function Detail() { const { notification } = App.useApp(); const { reservationId } = useParams(); - const { travelAgencyId, loginToken } = usingStorage() + const { loginToken } = usingStorage() - const [getReservationDetail, reservationDetail, confirmationList, selectConfirmation, submitConfirmation] = + const [getReservationDetail, reservationDetail, confirmationList, selectConfirmation, submitConfirmation, selectedAgencyId] = useReservationStore((state) => - [state.getReservationDetail, state.reservationDetail, state.confirmationList, state.selectConfirmation, state.submitConfirmation]) + [state.getReservationDetail, state.reservationDetail, state.confirmationList, state.selectConfirmation, state.submitConfirmation, state.selectedAgencyId]) const randomString = new Date().getTime() const officeWebViewerUrl = 'https://view.officeapps.live.com/op/embed.aspx?wdPrint=1&wdHideGridlines=0&wdHideComments=1&wdEmbedCode=0&src='; // 测试文档:https://www.chinahighlights.com/public/reservationW220420009.doc const reservationUrl = - `https://p9axztuwd7x8a7.mycht.cn/service-fileServer/DownloadPlanDoc?GRI_SN=${reservationId}&VEI_SN=${travelAgencyId}&token=${loginToken}&FileType=1&v=${randomString}` + `https://p9axztuwd7x8a7.mycht.cn/service-fileServer/DownloadPlanDoc?GRI_SN=${reservationId}&VEI_SN=${selectedAgencyId}&token=${loginToken}&FileType=1&v=${randomString}` const nameCardUrl = - `https://p9axztuwd7x8a7.mycht.cn/service-fileServer/DownloadPlanDoc?GRI_SN=${reservationId}&VEI_SN=${travelAgencyId}&token=${loginToken}&FileType=2&v=${randomString}` + `https://p9axztuwd7x8a7.mycht.cn/service-fileServer/DownloadPlanDoc?GRI_SN=${reservationId}&VEI_SN=${selectedAgencyId}&token=${loginToken}&FileType=2&v=${randomString}` const showConfirmModal = (confirm) => { setIsModalOpen(true); @@ -117,7 +117,7 @@ function Detail() { setReservationPreviewUrl(officeWebViewerUrl + encodeURIComponent(reservationUrl)) setNameCardPreviewUrl(officeWebViewerUrl + encodeURIComponent(nameCardUrl)) - getReservationDetail(reservationId) + getReservationDetail(selectedAgencyId, reservationId) .catch(ex => { notification.error({ message: `Notification`, diff --git a/src/views/reservation/Newest.jsx b/src/views/reservation/Newest.jsx index 32b2630..5579e22 100644 --- a/src/views/reservation/Newest.jsx +++ b/src/views/reservation/Newest.jsx @@ -6,7 +6,10 @@ import { isEmpty } from '@/utils/commons' import { useTranslation } from 'react-i18next' import useFormStore from '@/stores/Form' import useReservationStore from '@/stores/Reservation' +import useAuthStore from '@/stores/Auth' import SearchForm from '@/components/SearchForm' +import { usingStorage } from '@/hooks/usingStorage' +import { PERM_RESERVATION_MOST } from '@/config'; const { Title } = Typography @@ -94,25 +97,18 @@ function Newest() { const [dataLoading, setDataLoading] = useState(false) const [guideSelectOptions, setGuideSelectOptions] = useState([]) + const { travelAgencyId } = usingStorage() + const isPermitted = useAuthStore((state) => state.isPermitted) const formValuesToSub = useFormStore((state) => state.formValuesToSub) - const [fetchAllGuideList, fetchReservationList, reservationList, reservationPage, cityList, selectReservation, getCityListByReservationId, setupCityGuide, updateReservationGuide] = + const [fetchAgencyGuideList, fetchReservationList, reservationList, reservationPage, cityList, selectReservation, getCityListByReservationId, setupCityGuide, updateReservationGuide] = useReservationStore((state) => - [state.fetchAllGuideList, state.fetchReservationList, state.reservationList, state.reservationPage, state.cityList, state.selectReservation, state.getCityListByReservationId, state.setupCityGuide, state.updateReservationGuide]) + [state.fetchAgencyGuideList, state.fetchReservationList, state.reservationList, state.reservationPage, state.cityList, state.selectReservation, state.getCityListByReservationId, state.setupCityGuide, state.updateReservationGuide]) const { notification } = App.useApp() useEffect (() => { - fetchAllGuideList() - .then((guideList) => { - const selectOptions = guideList.map((data) => { - return { - value: data.guideId, - label: data.guideName - } - }) - setGuideSelectOptions(selectOptions) - }) - }, [fetchAllGuideList]) + initAgencyGuideList(travelAgencyId) + }, []) const showCityGuideModal = (reservation) => { setDataLoading(true) @@ -145,10 +141,46 @@ function Newest() { setDataLoading(false); } + const initAgencyGuideList = (agencyId) => { + fetchAgencyGuideList(agencyId) + .then((guideList) => { + const selectOptions = guideList.map((data) => { + return { + value: data.guideId, + label: data.guideName + } + }) + setGuideSelectOptions(selectOptions) + }) + } + // 默认重新搜索第一页,所有状态的计划 const searchReservation = (submitValues, current=1) => { setDataLoading(true) - fetchReservationList(submitValues, current) + + const getSelectedAgencyId = () => { + + if (isPermitted(PERM_RESERVATION_MOST)) { + return isEmpty(submitValues.agency) ? travelAgencyId : parseInt(submitValues.agency, 10) + } else { + return travelAgencyId + } + } + const selectedAgencyId = getSelectedAgencyId() + + initAgencyGuideList(selectedAgencyId) + + if (isEmpty(selectedAgencyId)) { + notification.error({ + message: `Notification`, + description: 'Agency is required', + placement: 'top', + duration: 4, + }); + return + } + const formValues = {...submitValues, ...{ selectedAgencyId }}; + fetchReservationList(formValues, current) .catch(ex => { notification.error({ message: `Notification`, @@ -199,10 +231,14 @@ function Newest() { { searchReservation(initialValue)