From 1142a1a893f89d3112512ee3415134b280e180ef Mon Sep 17 00:00:00 2001 From: Lei OT Date: Mon, 17 Jun 2024 11:52:10 +0800 Subject: [PATCH 001/120] =?UTF-8?q?=E4=BE=9B=E5=BA=94=E5=95=86=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E4=BA=A7=E5=93=81=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/common.json | 3 +- public/locales/en/products.json | 26 +++++++++++++ public/locales/zh/common.json | 3 +- public/locales/zh/products.json | 26 +++++++++++++ src/components/SearchForm.jsx | 66 ++++++++++++++++++++++++++++++--- src/components/SearchInput.jsx | 48 ++++++++++++++++++++++++ src/hooks/useProductsSets.js | 46 +++++++++++++++++++++++ src/i18n/index.js | 2 +- src/main.jsx | 2 + src/stores/Products/Index.js | 24 ++++++++++++ src/views/App.jsx | 2 + src/views/invoice/Index.jsx | 2 +- src/views/products/Index.jsx | 55 +++++++++++++++++++++++++++ 13 files changed, 296 insertions(+), 9 deletions(-) create mode 100644 public/locales/en/products.json create mode 100644 public/locales/zh/products.json create mode 100644 src/components/SearchInput.jsx create mode 100644 src/hooks/useProductsSets.js create mode 100644 src/stores/Products/Index.js create mode 100644 src/views/products/Index.jsx diff --git a/public/locales/en/common.json b/public/locales/en/common.json index a513e2a..4575ad3 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -51,7 +51,8 @@ "Feedback": "Feedback", "Notice": "Notice", "Report": "Report", - "Airticket": "AirTicket" + "Airticket": "AirTicket", + "Products": "Products" }, "Validation": { "Title": "Notification", diff --git a/public/locales/en/products.json b/public/locales/en/products.json new file mode 100644 index 0000000..4813a31 --- /dev/null +++ b/public/locales/en/products.json @@ -0,0 +1,26 @@ +{ + "type": { + "Experience": "Experience", + "Car": "Transport Services", + "Guide": "Guide Services", + "Package": "Package", + "Attractions": "Attractions", + "Meals": "Meals", + "Extras": "Extras", + "Special": "Special" + }, + "auditState": { + "New": "New", + "Pending": "Pending", + "Approve": "Approve", + "Rejected": "Rejected", + "Published": "Published" + }, + "Title": "Title", + "Vendor": "Vendor", + "AuState": "Audit State", + "CreatedBy": "Created By", + "CreateDate": "Create Date", + "Auditors": "Auditors", + "AuditDate": "Audit Date" +} diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 59c417e..d689719 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -51,7 +51,8 @@ "Feedback": "反馈表", "Notice": "通知", "Report": "质量评分", - "Airticket": "机票订票" + "Airticket": "机票订票", + "Products": "产品管理" }, "Validation": { "Title": "温馨提示", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json new file mode 100644 index 0000000..326bd78 --- /dev/null +++ b/public/locales/zh/products.json @@ -0,0 +1,26 @@ +{ + "type": { + "Experience": "综费", + "Car": "车费", + "Guide": "导服", + "Package": "包价线路", + "Attractions": "景点", + "Meals": "餐费", + "Extras": "附加", + "Special": "特殊项目" + }, + "auditState": { + "New": "新增", + "Pending": "待审核", + "Approve": "已通过", + "Rejected": "已拒绝", + "Published": "已发布" + }, + "Title": "名称", + "Vendor": "供应商", + "AuState": "审核状态", + "CreatedBy": "提交人员", + "CreateDate": "提交时间", + "Auditors": "审核人员", + "AuditDate": "审核时间" +} diff --git a/src/components/SearchForm.jsx b/src/components/SearchForm.jsx index 0e33c82..6ccbeae 100644 --- a/src/components/SearchForm.jsx +++ b/src/components/SearchForm.jsx @@ -5,6 +5,16 @@ import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from '@/config'; import useFormStore from '@/stores/Form'; import usePresets from '@/hooks/usePresets'; import { useTranslation } from 'react-i18next'; +import SearchInput from './SearchInput'; +import { fetchJSON } from '@/utils/request'; +import { HT_HOST } from '@/config'; + + +//供应商列表 +export const fetchVendorList = async () => { + const { errcode, Result } = await fetchJSON(`${HT_HOST}/service-cusservice/PTGetHWVendorList`) + return errcode !== 0 ? [] : Result +} const { RangePicker } = DatePicker; @@ -28,6 +38,7 @@ const SearchForm = ({ initialValue, onSubmit, onReset, ...props }) => { const formValuesMapper = (values) => { const destinationObject = { + 'keyword': { key: 'keyword', transform: (value) => value || '' }, 'referenceNo': { key: 'referenceNo', transform: (value) => value || '' }, 'dates': [ { key: 'startdate', transform: (arrVal) => (arrVal ? arrVal[0].format(DATE_FORMAT) : '') }, @@ -36,6 +47,13 @@ const SearchForm = ({ initialValue, onSubmit, onReset, ...props }) => { { key: 'endtime', transform: (arrVal) => (arrVal ? arrVal[1].format(SMALL_DATETIME_FORMAT) : '') }, ], 'invoiceStatus': { key: 'invoiceStatus', transform: (value) => value?.value || value?.key || '', default: '' }, + 'auditStatus': { key: 'auditStatus', 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 : ''; + }, + }, }; let dest = {}; const { dates, ...omittedValue } = values; @@ -57,9 +75,9 @@ const SearchForm = ({ initialValue, onSubmit, onReset, ...props }) => { }, []); const onFinish = (values) => { - console.log('Received values of form, origin form value: ', values); + console.log('Received values of form, origin form value: \n', values); const dest = formValuesMapper(values); - console.log('form value send to onSubmit:', dest); + console.log('form value send to onSubmit:\n', dest); const str = new URLSearchParams(dest).toString(); setFormValues(values); setFormValuesToSub(dest); @@ -132,17 +150,25 @@ function getFields(props) { }; let baseChildren = []; baseChildren = [ + item( + 'keyword', + 99, + + + , + fieldProps?.keyword?.col || 6 + ), item( 'referenceNo', - 1, + 99, , - fieldProps?.referenceNo?.col || 4 + fieldProps?.referenceNo?.col || 6 ), item( 'PNR', - 2, + 99, , @@ -176,6 +202,36 @@ function getFields(props) { , fieldProps?.dates?.col || midCol ), + /** + * + */ + item( + 'agency', + 99, + + + , + fieldProps?.agency?.col || 6 + ), + item( + 'auditState', + 99, + + : null} + optionFilterProp='label' + > + {options.map((d) => ( + + {d.label} + + ))} + + ); +} + +export default DebounceSelect; diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js new file mode 100644 index 0000000..0834d52 --- /dev/null +++ b/src/hooks/useProductsSets.js @@ -0,0 +1,46 @@ +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +/** + * 产品管理 相关的预设数据 + */ + +export const useProductsTypes = () => { + const [types, setTypes] = useState([]); + const { t, i18n } = useTranslation(); + + useEffect(() => { + const newData = [ + { label: t('products:type.Experience'), value: t('products:type.Experience') }, + {label: t("products:type.Car"), value: t("products:type.Car"),}, + {label: t("products:type.Guide"), value: t("products:type.Guide"),}, + {label: t("products:type.Package"), value: t("products:type.Package"),}, + {label: t("products:type.Attractions"), value: t("products:type.Attractions"),}, + {label: t("products:type.Meals"), value: t("products:type.Meals"),}, + {label: t("products:type.Extras"), value: t("products:type.Extras"),}, + {label: t("products:type.Special"), value: t("products:type.Special")}, + ]; + setTypes(newData); + }, [i18n.language]); + + return types; +}; + + +export const useProductsAuditStatus = () => { + const [types, setTypes] = useState([]); + const { t, i18n } = useTranslation(); + + useEffect(() => { + const newData = [ + { value: '0', label: 'New' }, + { value: '1', label: 'Pending' }, + { value: '2', label: 'Approve' }, + { value: '3', label: 'Rejected' }, + { value: '4', label: 'Published' }, + ]; + setTypes(newData); + }, [i18n.language]); + + return types; +}; diff --git a/src/i18n/index.js b/src/i18n/index.js index 9efd3bd..edd3f75 100644 --- a/src/i18n/index.js +++ b/src/i18n/index.js @@ -17,7 +17,7 @@ i18n backend: { loadPath: '/locales/{{lng}}/{{ns}}.json', }, - ns: ['common', 'group', 'vendor', 'account'], + ns: ['common', 'group', 'vendor', 'account', 'products'], defaultNS: 'common', detection: { // convertDetectedLanguage: 'Iso15897', diff --git a/src/main.jsx b/src/main.jsx index ff392ac..0f7c4fc 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -30,6 +30,7 @@ import InvoiceDetail from "@/views/invoice/Detail"; import InvoicePaid from "@/views/invoice/Paid"; import InvoicePaidDetail from "@/views/invoice/PaidDetail"; import Airticket from "@/views/airticket/Index"; +import ProductsIndex from '@/views/products/Index'; import './i18n'; @@ -65,6 +66,7 @@ const router = createBrowserRouter([ { path: "invoice/paid",element:}, { path: "invoice/paid/detail/:flid",element:}, { path: "airticket",element:}, + { path: "products",element:}, ] }, { diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js new file mode 100644 index 0000000..87066a9 --- /dev/null +++ b/src/stores/Products/Index.js @@ -0,0 +1,24 @@ +import { create } from 'zustand'; +import { devtools } from 'zustand/middleware'; + +import { fetchJSON } from '@/utils/request'; +import { HT_HOST } from '@/config'; + +const initialState = { + loading: false, + productsList: [], +}; +export const useProductsStore = create( + devtools((set, get) => ({ + // 初始化状态 + ...initialState, + + // state actions + setProductsList: (productsList) => set({ productsList }), + + reset: () => set(initialState), + + // side effects + })) +); +export default useProductsStore; diff --git a/src/views/App.jsx b/src/views/App.jsx index dd2dec6..cbb2949 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -125,6 +125,7 @@ function App() { { key: 'feedback', label: {t('menu.Feedback')} }, { key: 'report', label: {t('menu.Report')} }, { key: 'airticket', label: {t('menu.Airticket')} }, + { key: 'products', label: {t('menu.Products')} }, { key: 'notice', label: ( @@ -151,6 +152,7 @@ function App() { { label: {t('account:management.tile')}, key: '3' }, { type: 'divider' }, { label: {t('Logout')}, key: '4' }, + { label: {t('ChangeVendor')}, key: 'change-vendor' }, ], { type: 'divider' }, { label: <>v{BUILD_VERSION}, key: 'BUILD_VERSION' }, diff --git a/src/views/invoice/Index.jsx b/src/views/invoice/Index.jsx index 02033ed..17a9603 100644 --- a/src/views/invoice/Index.jsx +++ b/src/views/invoice/Index.jsx @@ -82,7 +82,7 @@ function Index() { fieldsConfig={{ shows: ['referenceNo', 'invoiceStatus', 'dates'], fieldProps: { - referenceNo: { col: 5 }, + referenceNo: { col: 7 }, invoiceStatus: { col: 4}, dates: { col: 10 }, }, diff --git a/src/views/products/Index.jsx b/src/views/products/Index.jsx new file mode 100644 index 0000000..e75ac50 --- /dev/null +++ b/src/views/products/Index.jsx @@ -0,0 +1,55 @@ +import { useEffect, useState } from 'react'; +import { Row, Col, Space, Typography, Table, Button } from 'antd'; +import useProductsStore from '@/stores/Products/Index'; +import { usingStorage } from '@/hooks/usingStorage'; +import SearchForm from '@/components/SearchForm'; +import dayjs from 'dayjs'; +import { useTranslation } from 'react-i18next'; +import { useProductsTypes } from '@/hooks/useProductsSets'; + +function Index() { + const { t } = useTranslation(); + const { userId } = usingStorage(); + const [loading, productsList] = useProductsStore((state) => state.productsList); + + const [noticeList, setNoticeList] = useState([]); + useEffect(() => {}, []); + + const showTotal = (total) => `Total ${total} items`; + const columns = [ + { title: t('products:Vendor'), key: '', dataIndex: '' }, + { title: t('products:AuState'), key: '', dataIndex: '' }, + { title: t('products:CreatedBy'), key: '', dataIndex: '' }, + { title: t('products:CreateDate'), key: '', dataIndex: '' }, + { title: t('products:Auditors'), key: '', dataIndex: '' }, + { title: t('products:AuditDate'), key: '', dataIndex: '' }, + { title: '', key: 'action', render: () => }, + ]; + return ( + + { + }} + /> + + + + + + + + ); +} + +export default Index; From fa0550dfdb324b583fd941c4c16eb2c3ba4cc655 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Mon, 17 Jun 2024 17:00:53 +0800 Subject: [PATCH 002/120] =?UTF-8?q?=E5=AE=A2=E6=9C=8D=E5=AE=A1=E6=A0=B8?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/common.json | 2 ++ public/locales/en/products.json | 2 +- public/locales/zh/common.json | 2 ++ src/components/SearchForm.jsx | 4 ++-- src/components/SecondHeaderWrapper.jsx | 28 ++++++++++++++++++++++ src/hooks/useProductsSets.js | 16 ++++++------- src/main.jsx | 4 ++++ src/stores/Products/Index.js | 13 ++++++++++- src/views/products/Audit.jsx | 32 ++++++++++++++++++++++++++ src/views/products/Detail.jsx | 10 ++++++++ src/views/products/Index.jsx | 26 ++++++++++++++------- 11 files changed, 119 insertions(+), 20 deletions(-) create mode 100644 src/components/SecondHeaderWrapper.jsx create mode 100644 src/views/products/Audit.jsx create mode 100644 src/views/products/Detail.jsx diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 4575ad3..0810454 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -11,6 +11,7 @@ "Close": "Close", "Save": "Save", "Edit": "Edit", + "Audit": "Audit", "Delete": "Delete", "Add": "Add", "View": "View", @@ -19,6 +20,7 @@ "Upload": "Upload", "preview": "Preview", "Total": "Total", + "Action": "Action", "Login": "Login", "Username": "Username", diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 4813a31..2076875 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -21,6 +21,6 @@ "AuState": "Audit State", "CreatedBy": "Created By", "CreateDate": "Create Date", - "Auditors": "Auditors", + "AuditedBy": "Audited By", "AuditDate": "Audit Date" } diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index d689719..4ef0694 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -11,6 +11,7 @@ "Close": "关闭", "Save": "保存", "Edit": "编辑", + "Audit": "审核", "Delete": "删除", "Add": "添加", "View": "查看", @@ -19,6 +20,7 @@ "Upload": "上传", "preview": "预览", "Total": "总数", + "Action": "操作", "Login": "登录", "Username": "账号", diff --git a/src/components/SearchForm.jsx b/src/components/SearchForm.jsx index 6ccbeae..97ca443 100644 --- a/src/components/SearchForm.jsx +++ b/src/components/SearchForm.jsx @@ -47,7 +47,7 @@ const SearchForm = ({ initialValue, onSubmit, onReset, ...props }) => { { key: 'endtime', transform: (arrVal) => (arrVal ? arrVal[1].format(SMALL_DATETIME_FORMAT) : '') }, ], 'invoiceStatus': { key: 'invoiceStatus', transform: (value) => value?.value || value?.key || '', default: '' }, - 'auditStatus': { key: 'auditStatus', transform: (value) => value?.value || value?.key || '', default: '' }, + 'auditState': { key: 'auditState', transform: (value) => value?.value || value?.key || '', default: '' }, 'agency': { key: 'agency', transform: (value) => { @@ -225,7 +225,7 @@ function getFields(props) { { value: '0', label: 'New' }, { value: '1', label: 'Pending' }, { value: '2', label: 'Approve' }, - { value: '3', label: 'Rejected' }, + // { value: '3', label: 'Rejected' }, { value: '4', label: 'Published' }, ]} /> diff --git a/src/components/SecondHeaderWrapper.jsx b/src/components/SecondHeaderWrapper.jsx new file mode 100644 index 0000000..2b2ffca --- /dev/null +++ b/src/components/SecondHeaderWrapper.jsx @@ -0,0 +1,28 @@ +import { Outlet, useNavigate } from 'react-router-dom'; +import { Layout, Flex, theme } from 'antd'; +import BackBtn from './BackBtn'; + +const { Content, Header } = Layout; +const HeaderWrapper = ({ children, header, ...props }) => { + const navigate = useNavigate(); + const { + token: { colorBgContainer }, + } = theme.useToken(); + return ( + <> + +
+ + {/* {header} */} +
{header}
+ +
+
+ + {children || } + +
+ + ); +}; +export default HeaderWrapper; diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index 0834d52..4545d91 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -11,14 +11,14 @@ export const useProductsTypes = () => { useEffect(() => { const newData = [ - { label: t('products:type.Experience'), value: t('products:type.Experience') }, - {label: t("products:type.Car"), value: t("products:type.Car"),}, - {label: t("products:type.Guide"), value: t("products:type.Guide"),}, - {label: t("products:type.Package"), value: t("products:type.Package"),}, - {label: t("products:type.Attractions"), value: t("products:type.Attractions"),}, - {label: t("products:type.Meals"), value: t("products:type.Meals"),}, - {label: t("products:type.Extras"), value: t("products:type.Extras"),}, - {label: t("products:type.Special"), value: t("products:type.Special")}, + { label: t('products:type.Experience'), value: 'Experience', key: 'Experience' }, + { label: t('products:type.Car'), value: 'Car', key: 'Car' }, + { label: t('products:type.Guide'), value: 'Guide', key: 'Guide' }, + { label: t('products:type.Package'), value: 'Package', key: 'Package' }, + { label: t('products:type.Attractions'), value: 'Attractions', key: 'Attractions' }, + { label: t('products:type.Meals'), value: 'Meals', key: 'Meals' }, + { label: t('products:type.Extras'), value: 'Extras', key: 'Extras' }, + { label: t('products:type.Special'), value: 'Special', key: 'Special' }, ]; setTypes(newData); }, [i18n.language]); diff --git a/src/main.jsx b/src/main.jsx index 0f7c4fc..3733462 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -31,6 +31,8 @@ import InvoicePaid from "@/views/invoice/Paid"; import InvoicePaidDetail from "@/views/invoice/PaidDetail"; import Airticket from "@/views/airticket/Index"; import ProductsIndex from '@/views/products/Index'; +import ProductsDetail from '@/views/products/Detail'; +import ProductsAudit from '@/views/products/Audit'; import './i18n'; @@ -67,6 +69,8 @@ const router = createBrowserRouter([ { path: "invoice/paid/detail/:flid",element:}, { path: "airticket",element:}, { path: "products",element:}, + { path: "products/:travel_agency_id/audit",element:}, + { path: "products/:travel_agency_id",element:}, ] }, { diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js index 87066a9..872bec9 100644 --- a/src/stores/Products/Index.js +++ b/src/stores/Products/Index.js @@ -6,7 +6,18 @@ import { HT_HOST } from '@/config'; const initialState = { loading: false, - productsList: [], + productsList: [ + { + 'audit_date': '2001-03-03', + 'travel_agency_name': '新油低外', + 'travel_agency_id': '650000200301029585', + 'created_by': '冯丽', + 'create_date': '1989-06-20', + 'lastedit_memo': 'nostrud ad eu', + 'audited_by': '黎静', + 'audit_state': '1', + }, + ], }; export const useProductsStore = create( devtools((set, get) => ({ diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx new file mode 100644 index 0000000..f4187a2 --- /dev/null +++ b/src/views/products/Audit.jsx @@ -0,0 +1,32 @@ +import { createContext, useContext, useEffect, useState } from 'react'; +import { Button, Table, Tabs } from 'antd'; +import { useProductsTypes } from '@/hooks/useProductsSets'; +import SecondHeaderWrapper from '@/components/SecondHeaderWrapper'; + +const Header = () => { + return ( +
+
+ + +
+ ); +}; + +const TypesTabs = () => { + const productsTypes = useProductsTypes(); + return ( + + ) +} + +const Audit = ({ ...props }) => { + return ( + <> + }> + + + + ); +}; +export default Audit; diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx new file mode 100644 index 0000000..807e0e9 --- /dev/null +++ b/src/views/products/Detail.jsx @@ -0,0 +1,10 @@ +import { createContext, useContext, useEffect, useState } from 'react'; +import { Table } from 'antd'; + +const Detail = ((props) => { + return ( + <> + + ); +}); +export default Detail; diff --git a/src/views/products/Index.jsx b/src/views/products/Index.jsx index e75ac50..b793b2f 100644 --- a/src/views/products/Index.jsx +++ b/src/views/products/Index.jsx @@ -1,4 +1,5 @@ import { useEffect, useState } 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'; @@ -10,20 +11,29 @@ import { useProductsTypes } from '@/hooks/useProductsSets'; function Index() { const { t } = useTranslation(); const { userId } = usingStorage(); - const [loading, productsList] = useProductsStore((state) => state.productsList); + const [loading, productsList] = useProductsStore((state) => [state.loading, state.productsList]); const [noticeList, setNoticeList] = useState([]); useEffect(() => {}, []); const showTotal = (total) => `Total ${total} items`; const columns = [ - { title: t('products:Vendor'), key: '', dataIndex: '' }, - { title: t('products:AuState'), key: '', dataIndex: '' }, - { title: t('products:CreatedBy'), key: '', dataIndex: '' }, - { title: t('products:CreateDate'), key: '', dataIndex: '' }, - { title: t('products:Auditors'), key: '', dataIndex: '' }, - { title: t('products:AuditDate'), key: '', dataIndex: '' }, - { title: '', key: 'action', render: () => }, + { title: t('products:Vendor'), key: 'vendor', dataIndex: 'travel_agency_name' }, + { title: t('products:CreatedBy'), key: 'created_by', dataIndex: 'created_by' }, + { title: t('products:CreateDate'), key: 'create_date', dataIndex: 'create_date' }, + { title: t('products:AuState'), key: 'audit_state', dataIndex: 'audit_state' }, + { title: t('products:AuditedBy'), key: 'audited_by', dataIndex: 'audited_by' }, + { title: t('products:AuditDate'), key: 'audit_date', dataIndex: 'audit_date' }, + { + title: '', + key: 'action', + render: (_, r) => ( + + {t('Edit')} + {t('Audit')} + + ), + }, ]; return ( From f323f0b511e858f253afec08ab85973e62a16f59 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Tue, 18 Jun 2024 13:57:55 +0800 Subject: [PATCH 003/120] =?UTF-8?q?=E5=AE=A1=E6=A0=B8=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/common.json | 3 +++ public/locales/en/products.json | 18 +++++++++++++++++- public/locales/zh/common.json | 3 +++ public/locales/zh/products.json | 20 ++++++++++++++++++-- src/components/AuditStateSelector.jsx | 12 ++++++++++++ src/components/SearchForm.jsx | 20 ++++++-------------- src/components/SecondHeaderWrapper.jsx | 2 +- src/hooks/useProductsSets.js | 12 ++++++------ src/views/products/Index.jsx | 10 ++++++---- 9 files changed, 72 insertions(+), 28 deletions(-) create mode 100644 src/components/AuditStateSelector.jsx diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 0810454..09471cf 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -21,6 +21,9 @@ "preview": "Preview", "Total": "Total", "Action": "Action", + "Import": "Import", + "Export": "Export", + "Copy": "Copy", "Login": "Login", "Username": "Username", diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 2076875..637b8f8 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -16,11 +16,27 @@ "Rejected": "Rejected", "Published": "Published" }, + "Status": "Status", + "Title": "Title", "Vendor": "Vendor", "AuState": "Audit State", "CreatedBy": "Created By", "CreateDate": "Create Date", "AuditedBy": "Audited By", - "AuditDate": "Audit Date" + "AuditDate": "Audit Date", + + "Quotation": "Quotation", + "Unit": "Unit", + "GroupSize": "Group Size", + "UseDates": "Use Dates", + "Weekdays": "Weekdays", + + "AgeType": { + "Type": "Age Type", + "Adult": "Adult", + "Child": "Child" + }, + + "#": "#" } diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 4ef0694..2c54705 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -21,6 +21,9 @@ "preview": "预览", "Total": "总数", "Action": "操作", + "Import": "导入", + "Export": "导出", + "Copy": "复制", "Login": "登录", "Username": "账号", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 326bd78..8d5c4e9 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -16,11 +16,27 @@ "Rejected": "已拒绝", "Published": "已发布" }, + "Status": "状态", + "Title": "名称", "Vendor": "供应商", "AuState": "审核状态", "CreatedBy": "提交人员", "CreateDate": "提交时间", - "Auditors": "审核人员", - "AuditDate": "审核时间" + "AuditedBy": "审核人员", + "AuditDate": "审核时间", + + "Quotation": "报价", + "Unit": "单位", + "GroupSize": "人等", + "UseDates": "使用日期", + "Weekdays": "有效日", + + "AgeType": { + "Type": "人群", + "Adult": "成人", + "Child": "儿童" + }, + + "#": "#" } 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?.auditState?.col || 3 ), diff --git a/src/components/SecondHeaderWrapper.jsx b/src/components/SecondHeaderWrapper.jsx index 2b2ffca..097b0d4 100644 --- a/src/components/SecondHeaderWrapper.jsx +++ b/src/components/SecondHeaderWrapper.jsx @@ -14,7 +14,7 @@ const HeaderWrapper = ({ children, header, ...props }) => {
{/* {header} */} -
{header}
+
{header}
diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index 4545d91..ee33d47 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -27,17 +27,17 @@ export const useProductsTypes = () => { }; -export const useProductsAuditStatus = () => { +export const useProductsAuditStates = () => { const [types, setTypes] = useState([]); const { t, i18n } = useTranslation(); useEffect(() => { const newData = [ - { value: '0', label: 'New' }, - { value: '1', label: 'Pending' }, - { value: '2', label: 'Approve' }, - { value: '3', label: 'Rejected' }, - { value: '4', label: 'Published' }, + { value: '0', label: t('products:auditState.New') }, + { value: '1', label: t('products:auditState.Pending') }, + { value: '2', label: t('products:auditState.Approve') }, + { value: '3', label: t('products:auditState.Rejected') }, + { value: '4', label: t('products:auditState.Published') }, ]; setTypes(newData); }, [i18n.language]); diff --git a/src/views/products/Index.jsx b/src/views/products/Index.jsx index b793b2f..df1b189 100644 --- a/src/views/products/Index.jsx +++ b/src/views/products/Index.jsx @@ -17,6 +17,7 @@ function Index() { useEffect(() => {}, []); 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' }, @@ -38,9 +39,6 @@ function Index() { return ( { }} />
-
+
From 232a8f85c875a1d1b0f0fd8b4f86b58dbcce668d Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 19 Jun 2024 10:19:43 +0800 Subject: [PATCH 004/120] =?UTF-8?q?=E5=AE=A1=E6=A0=B8=E4=BA=A7=E5=93=81?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2:=20=E8=A1=A8=E6=A0=BC,=20=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/zh/products.json | 2 +- src/hooks/useProductsSets.js | 10 +- src/stores/Products/Index.js | 246 +++++++++++++++++++++++++++++++- src/views/App.jsx | 1 - src/views/products/Audit.jsx | 106 ++++++++++++-- src/views/products/Index.jsx | 6 +- 6 files changed, 351 insertions(+), 20 deletions(-) diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 8d5c4e9..932fc24 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -30,7 +30,7 @@ "Unit": "单位", "GroupSize": "人等", "UseDates": "使用日期", - "Weekdays": "有效日", + "Weekdays": "有效日/周X", "AgeType": { "Type": "人群", diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index ee33d47..888368d 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -33,11 +33,11 @@ export const useProductsAuditStates = () => { useEffect(() => { const newData = [ - { value: '0', label: t('products:auditState.New') }, - { value: '1', label: t('products:auditState.Pending') }, - { value: '2', label: t('products:auditState.Approve') }, - { value: '3', label: t('products:auditState.Rejected') }, - { value: '4', label: t('products:auditState.Published') }, + { key: '0', value: '0', label: t('products:auditState.New') }, + { key: '1', value: '1', label: t('products:auditState.Pending') }, + { key: '2', value: '2', label: t('products:auditState.Approve') }, + { key: '3', value: '3', label: t('products:auditState.Rejected') }, + { key: '4', value: '4', label: t('products:auditState.Published') }, ]; setTypes(newData); }, [i18n.language]); diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js index 872bec9..1747757 100644 --- a/src/stores/Products/Index.js +++ b/src/stores/Products/Index.js @@ -3,10 +3,19 @@ import { devtools } from 'zustand/middleware'; import { fetchJSON } from '@/utils/request'; import { HT_HOST } from '@/config'; +import { groupBy } from '@/utils/commons'; + +const searchAgency = async (param) => { + const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/products_search`, param); + +}; +const getAgencyProducts = async (param) => { + const { errcode, Result, Result1 } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/products_search`, param); +}; const initialState = { loading: false, - productsList: [ + agencyList: [ { 'audit_date': '2001-03-03', 'travel_agency_name': '新油低外', @@ -18,6 +27,237 @@ const initialState = { 'audit_state': '1', }, ], + activeAgency: {}, + agencyProducts: groupBy([ + { + "info": { + "id": "640000198509289851", + "title": "如拉下完公", + "code": "grlkt", + "type": "Guide", + "audit_state": "1", + "create_date": "2022-01-13", + "created_by": "郝涛", + "travel_agency_id": "710000200712195349", + "travel_agency_name": "国得气验", + "lastedit_memo": "划百引程级门会需代领主属快。", + "remarks": "及决对金利低集小理电和按常如门。", + "duration": 2, + "duration_unit": "m", + "open_weekdays": "6", + "recommends_rate": 3, + "dept": 1, + "display_to_c": "2", + "km": 27, + "city_id": 77, + "city_name": "称命" + }, + "quotation": [ + { + "id": "21000020030611324X", + "value": 70, + "currency": "CNY", + "unit": "团", + "age_type": "儿童", + "group_size_min": 4, + "group_size_max": 4, + "use_dates_start": "2004-01-19", + "use_dates_end": "1990-03-10", + "weekdays": "4", + "audit_state": "ea pariatur", + "lastedit_memo": "sunt" + }, + { + "id": "610000197306240177", + "value": 86, + "currency": "CNY", + "unit": "人", + "age_type": "儿童", + "group_size_min": 6, + "group_size_max": 8, + "use_dates_start": "1996-12-16", + "use_dates_end": "1974-11-19", + "weekdays": "4", + "audit_state": "aliqua aute quis ipsum", + "lastedit_memo": "commodo adipisicing ea ipsum" + } + ], + "lgc_details": [ + { + "lgc": "mollit", + "title": "林运但", + "description": "学克信图走法因心委周说步将且文手越。", + "id": "35" + }, + { + "lgc": "et laborum", + "title": "备上引深量知量", + "description": "到听少文话包由北层中争二调原务越明在。", + "id": "74" + }, + { + "lgc": "minim velit", + "title": "安都始新", + "description": "取影压前手府要青白支大而。", + "id": "23" + } + ] + }, + { + "info": { + "id": "41000019901227754X", + "title": "据划京少国取", + "code": "ore", + "type": "Guide", + "audit_state": "2", + "create_date": "1979-01-31", + "created_by": "陆芳", + "travel_agency_id": "110000198612200137", + "travel_agency_name": "少平酸型", + "lastedit_memo": "八想军也装运知长示各院步济水。", + "remarks": "千改原统实专回列参目党却是样与后收。", + "duration": 3, + "duration_unit": "d", + "open_weekdays": "5", + "recommends_rate": 5, + "dept": 1, + "display_to_c": "2", + "km": 30, + "city_id": 62, + "city_name": "业入" + }, + "quotation": [ + { + "id": "37000019760525515X", + "value": 93, + "currency": "CNY", + "unit": "团", + "age_type": "成人", + "group_size_min": 7, + "group_size_max": 11, + "use_dates_start": "1992-11-22", + "use_dates_end": "1997-07-16", + "weekdays": "7", + "audit_state": "id nulla irure cupidatat", + "lastedit_memo": "quis aute reprehenderit consectetur" + }, + { + "id": "150000199506023175", + "value": 90, + "currency": "CNY", + "unit": "人", + "age_type": "儿童", + "group_size_min": 9, + "group_size_max": 10, + "use_dates_start": "2007-09-11", + "use_dates_end": "2013-07-27", + "weekdays": "5", + "audit_state": "commodo ad ut", + "lastedit_memo": "id anim incididunt" + } + ], + "lgc_details": [ + { + "lgc": "adipisicing elit Excepteur in", + "title": "很很结龙认", + "description": "事起复京长立然将采共层列工。", + "id": "43" + }, + { + "lgc": "dolore fugiat", + "title": "专中小", + "description": "示史想当集认点离反而原化精满并计前。", + "id": "28" + }, + { + "lgc": "sunt consectetur ea cillum", + "title": "他率带没", + "description": "节经厂面际是统表王活基书色活至是干验。", + "id": "83" + }, + { + "lgc": "incididunt labore fugiat", + "title": "精话西改", + "description": "须事金性别民学少拉个且须专需断连。", + "id": "97" + }, + { + "lgc": "dolore id", + "title": "文技话", + "description": "上任成条到则查支外很素给务府三。", + "id": "99" + } + ] + }, + { + "info": { + "id": "44000019990112280X", + "title": "节到和", + "code": "ixlmndtmz", + "type": "Meals", + "audit_state": "1", + "create_date": "2006-12-30", + "created_by": "易敏", + "travel_agency_id": "640000197111288408", + "travel_agency_name": "术备带走", + "lastedit_memo": "认队什教调问传改万消然声地全。", + "remarks": "属须厂几问总识看部群该克员方。", + "duration": 2, + "duration_unit": "m", + "open_weekdays": "6", + "recommends_rate": 3, + "dept": 2, + "display_to_c": "1", + "km": 13, + "city_id": 55, + "city_name": "铁以" + }, + "quotation": [ + { + "id": "13000019860219219X", + "value": 88, + "currency": "CNY", + "unit": "团", + "age_type": "儿童", + "group_size_min": 2, + "group_size_max": 4, + "use_dates_start": "1991-03-19", + "use_dates_end": "1974-03-13", + "weekdays": "3", + "audit_state": "officia voluptate ad adipisicing dolore", + "lastedit_memo": "Duis amet veniam enim" + }, + { + "id": "420000201706118123", + "value": 61, + "currency": "CNY", + "unit": "人", + "age_type": "儿童", + "group_size_min": 4, + "group_size_max": 10, + "use_dates_start": "1992-04-23", + "use_dates_end": "1970-07-19", + "weekdays": "5", + "audit_state": "commodo labore", + "lastedit_memo": "ullamco anim culpa do in" + } + ], + "lgc_details": [ + { + "lgc": "ut minim", + "title": "回等这意", + "description": "农满界个整千书得被写况空派会想头无。", + "id": "40" + }, + { + "lgc": "laborum id elit irure commodo", + "title": "增正数白养土子", + "description": "么划才共别程以元于族完难变。", + "id": "84" + } + ] + } +], row => row.info.type), }; export const useProductsStore = create( devtools((set, get) => ({ @@ -25,7 +265,9 @@ export const useProductsStore = create( ...initialState, // state actions - setProductsList: (productsList) => set({ productsList }), + setAgencyList: (agencyList) => set({ agencyList }), + setActiveAgency: activeAgency => set({ activeAgency }), + setAgencyProducts: agencyProducts => set({ agencyProducts }), reset: () => set(initialState), diff --git a/src/views/App.jsx b/src/views/App.jsx index cbb2949..0edd221 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -152,7 +152,6 @@ function App() { { label: {t('account:management.tile')}, key: '3' }, { type: 'divider' }, { label: {t('Logout')}, key: '4' }, - { label: {t('ChangeVendor')}, key: 'change-vendor' }, ], { type: 'divider' }, { label: <>v{BUILD_VERSION}, key: 'BUILD_VERSION' }, diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index f4187a2..cc5b79f 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -1,14 +1,22 @@ import { createContext, useContext, useEffect, useState } from 'react'; -import { Button, Table, Tabs } from 'antd'; +import { Link, useLocation, } from 'react-router-dom'; +import { Button, Collapse, Table, Tabs, Typography, Space, } from 'antd'; import { useProductsTypes } from '@/hooks/useProductsSets'; import SecondHeaderWrapper from '@/components/SecondHeaderWrapper'; +import { useTranslation } from 'react-i18next'; +import useProductsStore from '@/stores/Products/Index'; +import { isEmpty } from '@/utils/commons'; -const Header = () => { +const Header = ({title, ...props}) => { + const { t } = useTranslation(); return ( -
-
- - +
+
+

{title}

+
+ {/* */} + {/* */} +
); }; @@ -20,11 +28,93 @@ const TypesTabs = () => { ) } +const subjectComponents = { + // 'sum_profit': ProfitTable, + // 'in_order_count': Count, + // 'confirm_order_count': Count, + // 'depart_order_count': Count, + // 'confirm_rates': Rates, + // 'praise_rates': Rates, + // 'sum_person_num': Count, +}; + +const PriceTable = ({dataSource}) => { + const { t } = useTranslation('products'); + // console.log(dataSource, ); + const columns = [ + { key: 'title', dataIndex: ['info', 'title'], title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan }) }, + { key: 'value', title: t('Quotation'), render: (_, { value, currency, unit }) => `${value} ${currency} / ${unit}` }, + // {key: 'price', title: t('Currency'), }, + // {key: 'currency', title: t('Currency'), }, + // {key: 'unit', title: t('Unit'), }, + { key: 'ageType', dataIndex: ['age_type'], title: t('AgeType.Type') }, + { + 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: 'status', title: t('Status'), render: () => '已通过'}, + { + title: '', + key: 'action', + render: () => ( + + + + + ), + }, + ]; + return
r.id} />; +} + +/** + * + */ +const TypesPanels = () => { + 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 }))), [])} /> })); + setShowTypes(_show); + + setActiveKey(isEmpty(_show) ? [] : [_show[0].key]); + + return () => {}; + }, [productsTypes, agencyProducts]); + + const onCollapseChange = (_activeKey) => { + setActiveKey(_activeKey) + } + return ( + + ) +} + const Audit = ({ ...props }) => { + // const [loading, agencyProducts] = useProductsStore((state) => [state.loading, state.agencyProducts]); + return ( <> - }> - + }> +
+ + {/* */}
); diff --git a/src/views/products/Index.jsx b/src/views/products/Index.jsx index df1b189..0562519 100644 --- a/src/views/products/Index.jsx +++ b/src/views/products/Index.jsx @@ -1,17 +1,17 @@ import { useEffect, useState } 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 SearchForm from '@/components/SearchForm'; import dayjs from 'dayjs'; import { useTranslation } from 'react-i18next'; +import useProductsStore from '@/stores/Products/Index'; import { useProductsTypes } from '@/hooks/useProductsSets'; function Index() { const { t } = useTranslation(); const { userId } = usingStorage(); - const [loading, productsList] = useProductsStore((state) => [state.loading, state.productsList]); + const [loading, agencyList, ] = useProductsStore((state) => [state.loading, state.agencyList, ]); const [noticeList, setNoticeList] = useState([]); useEffect(() => {}, []); @@ -56,7 +56,7 @@ function Index() { />
-
+
From a579ee4ee5f3686e287408296da17c8d59c39725 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 19 Jun 2024 10:20:21 +0800 Subject: [PATCH 005/120] =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=8F=82=E6=95=B0:?= =?UTF-8?q?=20lgc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/i18n/LanguageSwitcher.jsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/i18n/LanguageSwitcher.jsx b/src/i18n/LanguageSwitcher.jsx index ac1f009..af87005 100644 --- a/src/i18n/LanguageSwitcher.jsx +++ b/src/i18n/LanguageSwitcher.jsx @@ -1,6 +1,12 @@ -import React, { useState } from 'react'; -import { Dropdown, Menu } from 'antd'; +import { useState, useEffect } from 'react'; +import { Dropdown } from 'antd'; import { useTranslation } from 'react-i18next'; +import { appendRequestParams } from '@/utils/request'; + +const i18n_to_htcode = { + 'zh': 2, + 'en': 1, +}; /** * 语言选择组件 @@ -8,6 +14,13 @@ import { useTranslation } from 'react-i18next'; const Language = () => { const { t, i18n } = useTranslation(); const [selectedKeys, setSelectedKeys] = useState([i18n.language]); + + useEffect(() => { + appendRequestParams('lgc', i18n_to_htcode[i18n.language]); + + return () => {}; + }, [i18n.language]); + // 切换语言事件 const handleChangeLanguage = ({ key }) => { setSelectedKeys([key]); From d24c34d4acf93dacae1fa35e427a37ade7cda7d3 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 19 Jun 2024 13:45:11 +0800 Subject: [PATCH 006/120] =?UTF-8?q?feat:=20=E4=BA=A7=E5=93=81=E7=AE=A1?= =?UTF-8?q?=E7=90=86:=20=E5=AE=A2=E6=9C=8D=E9=A6=96=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/common.json | 4 +++ public/locales/en/products.json | 5 +++- public/locales/zh/common.json | 4 +++ public/locales/zh/products.json | 4 ++- src/components/SearchForm.jsx | 8 +++--- src/hooks/useProductsSets.js | 16 ++++++++---- src/stores/Products/Index.js | 12 +++++++-- src/views/products/Audit.jsx | 44 +++++++++++++-------------------- src/views/products/Index.jsx | 35 +++++++++++++++++--------- 9 files changed, 80 insertions(+), 52 deletions(-) diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 09471cf..b113807 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -25,6 +25,10 @@ "Export": "Export", "Copy": "Copy", + "Table": { + "Total": "Total {{total}} items" + }, + "Login": "Login", "Username": "Username", "Password": "Password", diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 637b8f8..35c6d67 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -12,11 +12,12 @@ "auditState": { "New": "New", "Pending": "Pending", - "Approve": "Approve", + "Approved": "Approved", "Rejected": "Rejected", "Published": "Published" }, "Status": "Status", + "State": "State", "Title": "Title", "Vendor": "Vendor", @@ -27,6 +28,8 @@ "AuditDate": "Audit Date", "Quotation": "Quotation", + "Offer": "Offer", + "Unit": "Unit", "GroupSize": "Group Size", "UseDates": "Use Dates", diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 2c54705..e3239a1 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -25,6 +25,10 @@ "Export": "导出", "Copy": "复制", + "Table": { + "Total": "共 {{total}} 条" + }, + "Login": "登录", "Username": "账号", "Password": "密码", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 932fc24..d4d640d 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -12,11 +12,12 @@ "auditState": { "New": "新增", "Pending": "待审核", - "Approve": "已通过", + "Approved": "已通过", "Rejected": "已拒绝", "Published": "已发布" }, "Status": "状态", + "State": "状态", "Title": "名称", "Vendor": "供应商", @@ -27,6 +28,7 @@ "AuditDate": "审核时间", "Quotation": "报价", + "Offer": "报价", "Unit": "单位", "GroupSize": "人等", "UseDates": "使用日期", diff --git a/src/components/SearchForm.jsx b/src/components/SearchForm.jsx index ab5626b..9f55352 100644 --- a/src/components/SearchForm.jsx +++ b/src/components/SearchForm.jsx @@ -50,7 +50,7 @@ const SearchForm = ({ initialValue, onSubmit, onReset, ...props }) => { { key: 'endtime', transform: (arrVal) => (arrVal ? arrVal[1].format(SMALL_DATETIME_FORMAT) : '') }, ], 'invoiceStatus': { key: 'invoiceStatus', transform: (value) => value?.value || value?.key || '', default: '' }, - 'auditState': { key: 'auditState', transform: (value) => value?.value || value?.key || '', default: '' }, + 'audit_state': { key: 'audit_state', transform: (value) => value?.value || value?.key || '', default: '' }, 'agency': { key: 'agency', transform: (value) => { @@ -217,12 +217,12 @@ function getFields(props) { fieldProps?.agency?.col || 6 ), item( - 'auditState', + 'audit_state', 99, - + , - fieldProps?.auditState?.col || 3 + fieldProps?.audit_state?.col || 3 ), ]; diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index 888368d..f002991 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -33,14 +33,20 @@ export const useProductsAuditStates = () => { useEffect(() => { const newData = [ - { key: '0', value: '0', label: t('products:auditState.New') }, - { key: '1', value: '1', label: t('products:auditState.Pending') }, - { key: '2', value: '2', label: t('products:auditState.Approve') }, - { key: '3', value: '3', label: t('products:auditState.Rejected') }, - { key: '4', value: '4', label: t('products:auditState.Published') }, + { key: '-1', value: '-1', label: t('products:auditState.New') }, + { key: '0', value: '0', label: t('products:auditState.Pending') }, + { key: '2', value: '2', label: t('products:auditState.Approved') }, + // { key: '3', value: '3', label: t('products:auditState.Rejected') }, + { key: '1', value: '1', label: t('products:auditState.Published') }, ]; setTypes(newData); }, [i18n.language]); return types; }; + +export const useProductsAuditStatesMapVal = (value) => { + const stateSets = useProductsAuditStates(); + const stateMapVal = stateSets.reduce((r, c) => ({ ...r, [`${c.value}`]: c }), {}); + return stateMapVal; +}; diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js index 1747757..ecd1fe1 100644 --- a/src/stores/Products/Index.js +++ b/src/stores/Products/Index.js @@ -5,9 +5,9 @@ import { fetchJSON } from '@/utils/request'; import { HT_HOST } from '@/config'; import { groupBy } from '@/utils/commons'; -const searchAgency = async (param) => { +const searchAgencyAction = async (param) => { const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/products_search`, param); - + return errcode !== 0 ? [] : result; }; const getAgencyProducts = async (param) => { const { errcode, Result, Result1 } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/products_search`, param); @@ -265,6 +265,7 @@ export const useProductsStore = create( ...initialState, // state actions + setLoading: loading => set({ loading }), setAgencyList: (agencyList) => set({ agencyList }), setActiveAgency: activeAgency => set({ activeAgency }), setAgencyProducts: agencyProducts => set({ agencyProducts }), @@ -272,6 +273,13 @@ export const useProductsStore = create( reset: () => set(initialState), // side effects + searchAgency: async (param) => { + const { setLoading, setAgencyList } = get(); + setLoading(true); + const res = await searchAgencyAction(param); + setAgencyList(res); + setLoading(false); + }, })) ); export default useProductsStore; diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index cc5b79f..cbc8fdb 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -1,7 +1,7 @@ import { createContext, useContext, useEffect, useState } from 'react'; import { Link, useLocation, } from 'react-router-dom'; import { Button, Collapse, Table, Tabs, Typography, Space, } from 'antd'; -import { useProductsTypes } from '@/hooks/useProductsSets'; +import { useProductsTypes, useProductsAuditStatesMapVal, useProductsAuditStates } from '@/hooks/useProductsSets'; import SecondHeaderWrapper from '@/components/SecondHeaderWrapper'; import { useTranslation } from 'react-i18next'; import useProductsStore from '@/stores/Products/Index'; @@ -21,26 +21,10 @@ const Header = ({title, ...props}) => { ); }; -const TypesTabs = () => { - const productsTypes = useProductsTypes(); - return ( - - ) -} - -const subjectComponents = { - // 'sum_profit': ProfitTable, - // 'in_order_count': Count, - // 'confirm_order_count': Count, - // 'depart_order_count': Count, - // 'confirm_rates': Rates, - // 'praise_rates': Rates, - // 'sum_person_num': Count, -}; - -const PriceTable = ({dataSource}) => { +const PriceTable = ({dataSource, loading}) => { const { t } = useTranslation('products'); - // console.log(dataSource, ); + const stateMapVal = useProductsAuditStatesMapVal(); + const columns = [ { key: 'title', dataIndex: ['info', 'title'], title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan }) }, { key: 'value', title: t('Quotation'), render: (_, { value, currency, unit }) => `${value} ${currency} / ${unit}` }, @@ -52,23 +36,29 @@ const PriceTable = ({dataSource}) => { key: 'groupSize', dataIndex: ['group_size_min'], title: t('GroupSize'), - render: (_, { group_size_min, group_size_max} ) => `${group_size_min} - ${group_size_max}`, + 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}`, + 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.info.audit_state}`]?.label; + }, }, - { key: 'weekdays', dataIndex: ['weekdays'], title: t('Weekdays'), }, - {key: 'status', title: t('Status'), render: () => '已通过'}, { title: '', key: 'action', render: () => ( - - + + ), }, @@ -90,7 +80,7 @@ const TypesPanels = () => { 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 }))), [])} /> })); + .map((ele) => ({ ...ele, children: r.concat(c.quotation.map((q, i) => ({ ...q, info: c.info, rowSpan: i=== 0 ? c.quotation.length : 0 }))), [])} /> })); setShowTypes(_show); setActiveKey(isEmpty(_show) ? [] : [_show[0].key]); diff --git a/src/views/products/Index.jsx b/src/views/products/Index.jsx index 0562519..02d57ec 100644 --- a/src/views/products/Index.jsx +++ b/src/views/products/Index.jsx @@ -1,22 +1,29 @@ -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 { 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 useProductsStore from '@/stores/Products/Index'; -import { useProductsTypes } from '@/hooks/useProductsSets'; +import useFormStore from '@/stores/Form'; +import { objectMapper } from '@/utils/commons'; function Index() { const { t } = useTranslation(); - const { userId } = usingStorage(); - const [loading, agencyList, ] = useProductsStore((state) => [state.loading, state.agencyList, ]); + const [loading, agencyList, searchAgency] = useProductsStore((state) => [state.loading, state.agencyList, state.searchAgency]); + const formValuesToSub = useFormStore(state => state.formValuesToSub); - const [noticeList, setNoticeList] = useState([]); - useEffect(() => {}, []); + const handleSearchAgency = (formVal = undefined) => { + const { starttime, endtime, ...param } = formVal || formValuesToSub; + const searchParam = objectMapper(param, { agency: 'travel_agency_id', startdate: 'edit_date1', enddate: 'edit_date2' }); + searchAgency(searchParam); + } - const showTotal = (total) => `Total ${total} items`; + useEffect(() => { + handleSearchAgency(); + }, []); + + const showTotal = (total) => t('Table.Total', { total }); const columns = [ { title: t('products:Vendor'), key: 'vendor', dataIndex: 'travel_agency_name' }, @@ -40,18 +47,22 @@ function Index() { { + handleSearchAgency(formVal); }} /> From f8ffaf899d77dd7d106a31a8328da09e3dcf82e3 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 19 Jun 2024 15:55:06 +0800 Subject: [PATCH 007/120] =?UTF-8?q?feat:=20=E4=BA=A7=E5=93=81=E7=AE=A1?= =?UTF-8?q?=E7=90=86:=20=E5=AE=A2=E6=9C=8D=E5=AE=A1=E6=A0=B8=E4=BA=A7?= =?UTF-8?q?=E5=93=81,=20=E4=BB=B7=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/products.json | 7 +++ public/locales/zh/products.json | 7 +++ src/stores/Products/Index.js | 64 ++++++++++++++++++------ src/views/products/Audit.jsx | 87 +++++++++++++++++++++++++++------ 4 files changed, 134 insertions(+), 31 deletions(-) diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 35c6d67..6033e89 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -16,6 +16,13 @@ "Rejected": "Rejected", "Published": "Published" }, + "auditStateAction": { + "New": "New", + "Pending": "Pending", + "Approved": "Approve", + "Rejected": "Rejecte", + "Published": "Publish" + }, "Status": "Status", "State": "State", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index d4d640d..01f7836 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -16,6 +16,13 @@ "Rejected": "已拒绝", "Published": "已发布" }, + "auditStateAction": { + "New": "新增", + "Pending": "待审核", + "Approved": "审核通过", + "Rejected": "审核拒绝", + "Published": "发布上线" + }, "Status": "状态", "State": "状态", diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js index ecd1fe1..544c478 100644 --- a/src/stores/Products/Index.js +++ b/src/stores/Products/Index.js @@ -1,32 +1,53 @@ import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; -import { fetchJSON } from '@/utils/request'; +import { fetchJSON, postForm } from '@/utils/request'; import { HT_HOST } from '@/config'; import { groupBy } from '@/utils/commons'; -const searchAgencyAction = async (param) => { +export const searchAgencyAction = async (param) => { const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/products_search`, param); return errcode !== 0 ? [] : result; }; -const getAgencyProducts = async (param) => { - const { errcode, Result, Result1 } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/products_search`, param); +export const getAgencyProductsAction = async (param) => { + const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/travel-agency-products`, param); + return errcode !== 0 ? [] : result; +}; + +export const postProductsQuoteAudit = async (auditState, quoteRow) => { + const postbody = { + audit_state: auditState, + id: quoteRow.id, + travel_agency_id: quoteRow.info.travel_agency_id, + }; + const formData = new FormData(); + Object.keys(postbody).forEach((key) => { + formData.append(key, postbody[key]); + }); + const json = await postForm(`${HT_HOST}/Service_BaseInfoWeb/travel-agency-products-quote-audit`, formData); + return json; + // return errcode !== 0 ? {} : result; +}; + +export const postProductsAudit = async (auditState, infoRow) => { + const postbody = { + audit_state: auditState, + id: infoRow.id, + travel_agency_id: infoRow.travel_agency_id, + }; + const formData = new FormData(); + Object.keys(postbody).forEach((key) => { + formData.append(key, postbody[key]); + }); + const json = await postForm(`${HT_HOST}/Service_BaseInfoWeb/travel-agency-products-audit`, formData); + return json; + // const { errcode, result } = json; + // return errcode !== 0 ? {} : result; }; const initialState = { loading: false, - agencyList: [ - { - 'audit_date': '2001-03-03', - 'travel_agency_name': '新油低外', - 'travel_agency_id': '650000200301029585', - 'created_by': '冯丽', - 'create_date': '1989-06-20', - 'lastedit_memo': 'nostrud ad eu', - 'audited_by': '黎静', - 'audit_state': '1', - }, - ], + agencyList: [], activeAgency: {}, agencyProducts: groupBy([ { @@ -280,6 +301,17 @@ export const useProductsStore = create( setAgencyList(res); setLoading(false); }, + + getAgencyProducts: async (param) => { + const { setLoading, setActiveAgency, setAgencyProducts } = get(); + setLoading(true); + const res = await getAgencyProductsAction(param); + const productsData = groupBy(res, row => row.info.type); + setAgencyProducts(productsData); + setActiveAgency(res[0].info); + setLoading(false); + }, + })) ); export default useProductsStore; diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index cbc8fdb..3a4f3e6 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -1,14 +1,31 @@ -import { createContext, useContext, useEffect, useState } from 'react'; -import { Link, useLocation, } from 'react-router-dom'; -import { Button, Collapse, Table, Tabs, Typography, Space, } from 'antd'; -import { useProductsTypes, useProductsAuditStatesMapVal, useProductsAuditStates } 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 from '@/stores/Products/Index'; +import useProductsStore, { postProductsQuoteAudit } from '@/stores/Products/Index'; import { isEmpty } from '@/utils/commons'; -const Header = ({title, ...props}) => { +const Header = ({ title, agency, ...props}) => { const { t } = useTranslation(); + const { message, notification } = App.useApp(); + const handleAuditItem = (state, row) => { + postProductsQuoteAudit(state, row) + .then((json) => { + if (json.errcode === 0) { + message.success('✔'); + } + }) + .catch((ex) => { + notification.error({ + message: 'Notification', + description: ex.message, + placement: 'top', + duration: 4, + }); + }); + }; return (
@@ -16,15 +33,37 @@ const Header = ({title, ...props}) => {
{/* */} {/* */} - + + {/* todo: export */} +
); }; const PriceTable = ({dataSource, loading}) => { const { t } = useTranslation('products'); + const { message, notification } = App.useApp(); const stateMapVal = useProductsAuditStatesMapVal(); + const handleAuditPriceItem = (state, row) => { + postProductsQuoteAudit(state, row) + .then((json) => { + if (json.errcode === 0) { + message.success('✔'); + } + }) + .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: 'value', title: t('Quotation'), render: (_, { value, currency, unit }) => `${value} ${currency} / ${unit}` }, @@ -55,10 +94,10 @@ const PriceTable = ({dataSource, loading}) => { { title: '', key: 'action', - render: () => ( + render: (_, r) => ( - - + + ), }, @@ -76,11 +115,19 @@ const TypesPanels = () => { 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 }))), [])} /> })); + .map((ele) => ({ + ...ele, + children: ( + r.concat(c.quotation.map((q, i) => ({ ...q, info: c.info, rowSpan: i === 0 ? c.quotation.length : 0 }))), [])} + /> + ), + })); setShowTypes(_show); setActiveKey(isEmpty(_show) ? [] : [_show[0].key]); @@ -97,14 +144,24 @@ const TypesPanels = () => { } const Audit = ({ ...props }) => { - // const [loading, agencyProducts] = useProductsStore((state) => [state.loading, state.agencyProducts]); + const { agencyId: travel_agency_id } = useParams(); + const [activeAgency, getAgencyProducts] = useProductsStore((state) => [state.activeAgency, state.getAgencyProducts]); + + const handleGetAgencyProducts = () => { + getAgencyProducts({ travel_agency_id }); + } + + useEffect(() => { + handleGetAgencyProducts(); + + return () => {}; + }, [travel_agency_id]) return ( <> - }> + }>
- {/* */}
); From a7c87170a0154d68314944ec7f35965214f54c09 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Thu, 20 Jun 2024 14:04:22 +0800 Subject: [PATCH 008/120] =?UTF-8?q?feat:=20=E4=BA=A7=E5=93=81=E7=AE=A1?= =?UTF-8?q?=E7=90=86:=20=E5=AE=A2=E6=9C=8D=E9=A6=96=E9=A1=B5,=20=E5=AE=A1?= =?UTF-8?q?=E6=A0=B8=E4=BA=A7=E5=93=81:=20=E6=A0=B8=E5=AF=B9=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/SearchForm.jsx | 6 +++--- src/hooks/useProductsSets.js | 25 ++++++++++++++++++++----- src/stores/Products/Index.js | 12 ++++++------ src/views/products/Audit.jsx | 12 +++++++----- 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/components/SearchForm.jsx b/src/components/SearchForm.jsx index 9f55352..6a53cac 100644 --- a/src/components/SearchForm.jsx +++ b/src/components/SearchForm.jsx @@ -15,8 +15,8 @@ import AuditStateSelector from './AuditStateSelector'; //供应商列表 export const fetchVendorList = async () => { - const { errcode, Result } = await fetchJSON(`${HT_HOST}/service-cusservice/PTGetHWVendorList`) - return errcode !== 0 ? [] : Result + const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/VendorList`) + return errcode !== 0 ? [] : result } const { RangePicker } = DatePicker; @@ -212,7 +212,7 @@ function getFields(props) { 'agency', 99, - + , fieldProps?.agency?.col || 6 ), diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index f002991..93feaac 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -3,6 +3,21 @@ import { useTranslation } from 'react-i18next'; /** * 产品管理 相关的预设数据 + * 项目类型 +1 酒店预定 +2 火车 +3 飞机票务 +4 游船 +5 快巴 +6 旅行社(综费) +7 景点 +8 特殊项目 +9 其他 +A 酒店 +B 超公里 +C 餐费 +D 包价包 +X 站 */ export const useProductsTypes = () => { @@ -11,13 +26,13 @@ export const useProductsTypes = () => { useEffect(() => { const newData = [ - { label: t('products:type.Experience'), value: 'Experience', key: 'Experience' }, + { label: t('products:type.Experience'), value: '6', key: '6' }, { label: t('products:type.Car'), value: 'Car', key: 'Car' }, { label: t('products:type.Guide'), value: 'Guide', key: 'Guide' }, - { label: t('products:type.Package'), value: 'Package', key: 'Package' }, - { label: t('products:type.Attractions'), value: 'Attractions', key: 'Attractions' }, - { label: t('products:type.Meals'), value: 'Meals', key: 'Meals' }, - { label: t('products:type.Extras'), value: 'Extras', key: 'Extras' }, + { label: t('products:type.Package'), value: 'D', key: 'D' }, + { label: t('products:type.Attractions'), value: '7', key: '7' }, + { label: t('products:type.Meals'), value: 'C', key: 'C' }, + { label: t('products:type.Extras'), value: '8', key: '8' }, { label: t('products:type.Special'), value: 'Special', key: 'Special' }, ]; setTypes(newData); diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js index 544c478..51d3010 100644 --- a/src/stores/Products/Index.js +++ b/src/stores/Products/Index.js @@ -10,21 +10,21 @@ export const searchAgencyAction = async (param) => { return errcode !== 0 ? [] : result; }; export const getAgencyProductsAction = async (param) => { - const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/travel-agency-products`, param); - return errcode !== 0 ? [] : result; + const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/travel_agency_products`, param); + return errcode !== 0 ? { agency: {}, products: [] } : result; }; export const postProductsQuoteAudit = async (auditState, quoteRow) => { const postbody = { audit_state: auditState, id: quoteRow.id, - travel_agency_id: quoteRow.info.travel_agency_id, + travel_agency_id: quoteRow.travel_agency_id, }; const formData = new FormData(); Object.keys(postbody).forEach((key) => { formData.append(key, postbody[key]); }); - const json = await postForm(`${HT_HOST}/Service_BaseInfoWeb/travel-agency-products-quote-audit`, formData); + const json = await postForm(`${HT_HOST}/Service_BaseInfoWeb/quotation_audit`, formData); return json; // return errcode !== 0 ? {} : result; }; @@ -306,9 +306,9 @@ export const useProductsStore = create( const { setLoading, setActiveAgency, setAgencyProducts } = get(); setLoading(true); const res = await getAgencyProductsAction(param); - const productsData = groupBy(res, row => row.info.type); + const productsData = groupBy(res.products, row => row.info.product_type_id); setAgencyProducts(productsData); - setActiveAgency(res[0].info); + setActiveAgency(res.agency); setLoading(false); }, diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index 3a4f3e6..16cefc9 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -9,9 +9,10 @@ import { isEmpty } from '@/utils/commons'; const Header = ({ title, agency, ...props}) => { const { t } = useTranslation(); + const [activeAgency, ] = useProductsStore((state) => [state.activeAgency]); const { message, notification } = App.useApp(); const handleAuditItem = (state, row) => { - postProductsQuoteAudit(state, row) + postProductsQuoteAudit(state, {id: row.id, travel_agency_id: activeAgency.travel_agency_id}) .then((json) => { if (json.errcode === 0) { message.success('✔'); @@ -44,11 +45,12 @@ const Header = ({ title, agency, ...props}) => { const PriceTable = ({dataSource, loading}) => { const { t } = useTranslation('products'); + const [activeAgency, ] = useProductsStore((state) => [state.activeAgency]); const { message, notification } = App.useApp(); const stateMapVal = useProductsAuditStatesMapVal(); const handleAuditPriceItem = (state, row) => { - postProductsQuoteAudit(state, row) + postProductsQuoteAudit(state, {id: row.id, travel_agency_id: activeAgency.travel_agency_id}) .then((json) => { if (json.errcode === 0) { message.success('✔'); @@ -66,7 +68,7 @@ const PriceTable = ({dataSource, loading}) => { const columns = [ { key: 'title', dataIndex: ['info', 'title'], title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan }) }, - { key: 'value', title: t('Quotation'), render: (_, { value, currency, unit }) => `${value} ${currency} / ${unit}` }, + { key: 'value', title: t('Quotation'), render: (_, { value, currency, unit_name }) => `${value} ${currency} / ${unit_name}` }, // {key: 'price', title: t('Currency'), }, // {key: 'currency', title: t('Currency'), }, // {key: 'unit', title: t('Unit'), }, @@ -88,7 +90,7 @@ const PriceTable = ({dataSource, loading}) => { key: 'state', title: t('State'), render: (_, r) => { - return stateMapVal[`${r.info.audit_state}`]?.label; + return stateMapVal[`${r.audit_state_id}`]?.label; }, }, { @@ -144,7 +146,7 @@ const TypesPanels = () => { } const Audit = ({ ...props }) => { - const { agencyId: travel_agency_id } = useParams(); + const { travel_agency_id } = useParams(); const [activeAgency, getAgencyProducts] = useProductsStore((state) => [state.activeAgency, state.getAgencyProducts]); const handleGetAgencyProducts = () => { From a61ef8882baabc51b483b1b0555644a0bbb39daa Mon Sep 17 00:00:00 2001 From: Lei OT Date: Fri, 21 Jun 2024 09:38:35 +0800 Subject: [PATCH 009/120] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=8F=82=E6=95=B0:=20wu=5Fid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useProductsSets.js | 14 +++++++------- src/stores/Auth.js | 1 + src/views/App.jsx | 3 ++- src/views/products/Audit.jsx | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index 93feaac..0994b3f 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -9,13 +9,13 @@ import { useTranslation } from 'react-i18next'; 3 飞机票务 4 游船 5 快巴 -6 旅行社(综费) -7 景点 -8 特殊项目 +6 旅行社(综费) - +7 景点 - +8 特殊项目 - 9 其他 A 酒店 -B 超公里 -C 餐费 +B 超公里 - +C 餐费 - D 包价包 X 站 */ @@ -33,7 +33,7 @@ export const useProductsTypes = () => { { label: t('products:type.Attractions'), value: '7', key: '7' }, { label: t('products:type.Meals'), value: 'C', key: 'C' }, { label: t('products:type.Extras'), value: '8', key: '8' }, - { label: t('products:type.Special'), value: 'Special', key: 'Special' }, + // { label: t('products:type.Special'), value: 'Special', key: 'Special' }, ]; setTypes(newData); }, [i18n.language]); @@ -51,7 +51,7 @@ export const useProductsAuditStates = () => { { key: '-1', value: '-1', label: t('products:auditState.New') }, { key: '0', value: '0', label: t('products:auditState.Pending') }, { key: '2', value: '2', label: t('products:auditState.Approved') }, - // { key: '3', value: '3', label: t('products:auditState.Rejected') }, + { key: '3', value: '3', label: t('products:auditState.Rejected') }, { key: '1', value: '1', label: t('products:auditState.Published') }, ]; setTypes(newData); diff --git a/src/stores/Auth.js b/src/stores/Auth.js index f7f31f3..5511e6b 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -88,6 +88,7 @@ const useAuthStore = create((set, get) => ({ setStorage(KEY_TRAVEL_AGENCY_ID, userDetail.LMI_VEI_SN) setStorage(KEY_USER_DETAIL, {username: userDetail.LoginName, travelAgencyName: userDetail.VName}) appendRequestParams('token', loginToken) + appendRequestParams('wu_id', userDetail.LMI_SN) // loadPageSpy(`${json.Result.VName}-${json.Result.LoginName}`) startTokenInterval() }, diff --git a/src/views/App.jsx b/src/views/App.jsx index 0edd221..b61c1ce 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -28,7 +28,7 @@ function App() { const [validateUserPassword, tokenTimeout] = useAuthStore( (state) => [state.validateUserPassword, state.tokenTimeout]) - const { loginToken, userDetail } = usingStorage() + const { loginToken, userDetail, userId } = usingStorage() const noticeUnRead = useNoticeStore((state) => state.noticeUnRead) const href = useHref() @@ -40,6 +40,7 @@ function App() { if (!needToLogin) { appendRequestParams('token', loginToken) + appendRequestParams('wu_id', userId) } useEffect(() => { diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index 16cefc9..c548de2 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -96,12 +96,12 @@ const PriceTable = ({dataSource, loading}) => { { title: '', key: 'action', - render: (_, r) => ( + render: (_, r) => r.audit_state_id <= 0 ?( - ), + ) : null, }, ]; return
r.id} />; From 7b2c836e9134c3af90aad7f4f95c6db86a233421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=96=87=E5=BC=BA=40HWQ-PC?= Date: Fri, 21 Jun 2024 15:29:54 +0800 Subject: [PATCH 010/120] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BE=9B=E5=BA=94?= =?UTF-8?q?=E5=95=86=E4=BB=B7=E6=A0=BC=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/zh/common.json | 3 +- src/main.jsx | 3 +- src/views/App.jsx | 1 + src/views/gysgl/Index.jsx | 581 ++++++++++++++++++++++++++++ src/views/gysgl/components/date.jsx | 86 ++++ 5 files changed, 672 insertions(+), 2 deletions(-) create mode 100644 src/views/gysgl/Index.jsx create mode 100644 src/views/gysgl/components/date.jsx diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index d689719..f99dd71 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -52,7 +52,8 @@ "Notice": "通知", "Report": "质量评分", "Airticket": "机票订票", - "Products": "产品管理" + "Products": "产品管理", + "gysgl": "供应商价格管理" }, "Validation": { "Title": "温馨提示", diff --git a/src/main.jsx b/src/main.jsx index 0f7c4fc..d0b8310 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -31,7 +31,7 @@ import InvoicePaid from "@/views/invoice/Paid"; import InvoicePaidDetail from "@/views/invoice/PaidDetail"; import Airticket from "@/views/airticket/Index"; import ProductsIndex from '@/views/products/Index'; - +import Gysgl from "@/views/gysgl/Index" import './i18n'; configure({ @@ -67,6 +67,7 @@ const router = createBrowserRouter([ { path: "invoice/paid/detail/:flid",element:}, { path: "airticket",element:}, { path: "products",element:}, + { path: "gysgl",element:}, ] }, { diff --git a/src/views/App.jsx b/src/views/App.jsx index cbb2949..3678318 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -135,6 +135,7 @@ function App() { ), }, + {key: 'gysgl', label:{t('menu.gysgl')}} ]} /> diff --git a/src/views/gysgl/Index.jsx b/src/views/gysgl/Index.jsx new file mode 100644 index 0000000..46fb28a --- /dev/null +++ b/src/views/gysgl/Index.jsx @@ -0,0 +1,581 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { Button, Card, Col, Row, Breadcrumb, Table, Popconfirm, Form, Input, InputNumber, Tag, Modal, Select, Tree } from 'antd'; +import { Link } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import DateComponent from './components/date'; +import { fetchJSON } from "@/utils/request"; + +const cpxm = [ + { code: "code", name: "简码" }, + { code: "city_name", name: "城市" }, + { code: "remarks", name: "备注" }, + { code: "open_hours", name: "游览时间" }, + { code: "recommends_rate", name: "推荐指数" } +]; + +const initialData = [ + { key: '1', crj: '15', etj: "美元", bz: 'aa', lx: 'as', rq: 'dfae', rd: 'dawd', yxq: 'dawda' }, + { key: '2', crj: '15', etj: "美元", bz: 'aa', lx: 'as', rq: 'dfae', rd: 'dawd', yxq: 'dawda' }, + { key: '3', crj: '15', etj: "美元", bz: 'aa', lx: 'as', rq: 'dfae', rd: 'dawd', yxq: 'dawda' }, + { key: '4', crj: '15', etj: "美元", bz: 'aa', lx: 'as', rq: 'dfae', rd: 'dawd', yxq: 'dawda' }, +]; + +const EditableCell = ({ editing, dataIndex, title, inputType, record, children, handleDateSelect, ...restProps }) => { + let inputNode = inputType === 'number' ? : ; + if (dataIndex === 'yxq' && editing) { + return ( + + ); + } + + return ( + + ); +}; + +function Index() { + const { t, i18n } = useTranslation(); + const [form] = Form.useForm(); + const [quotation, setQuotation] = useState(initialData); + const [editingKey, setEditingKey] = useState(''); + const [tags, setTags] = useState(['中文', 'English']); + const [isModalVisible, setIsModalVisible] = useState(false); + const [selectedTag, setSelectedTag] = useState('中文'); + const [saveData, setSaveData] = useState(null); + const [datePickerVisible, setDatePickerVisible] = useState(false); + const [currentKey, setCurrentKey] = useState(null); + const [languageStatus, setLanguageStatus] = useState([{ "中文": { title: "", description: "" } }, { "English": { title: "", description: "" } }]); + const [formState, setFormState] = useState({}); + const [selectedNodeKey, setSelectedNodeKey] = useState(null); + + const isEditing = (record) => record.key === editingKey; + + const edit = (record) => { + form.setFieldsValue({ ...record }); + setEditingKey(record.key); + }; + + const cancel = () => { + setEditingKey(''); + }; + + const handleSave = async (key) => { + try { + const row = await form.validateFields(); + const newData = [...quotation]; + + const index = newData.findIndex((item) => key === item.key); + if (index > -1) { + const item = newData[index]; + newData.splice(index, 1, { ...item, ...row }); + setQuotation(newData); + setEditingKey(''); + } else { + newData.push(row); + setQuotation(newData); + setEditingKey(''); + } + } catch (errInfo) { + console.log('Validate Failed:', errInfo); + } + }; + + const handleDelete = (key) => { + const newData = [...quotation]; + const index = newData.findIndex((item) => key === item.key); + newData.splice(index, 1); + setQuotation(newData); + }; + + const handleAdd = () => { + const newData = { + key: `${quotation.length + 1}`, + jg: '', + bz: '', + lx: '', + zm: '', + rq: '', + rd: '', + yxq: '', + }; + setQuotation([...quotation, newData]); + }; + + const handleDateSelect = (key) => { + setCurrentKey(key); + setDatePickerVisible(true); + }; + + const handleDateChange = (date) => { + if (currentKey) { + const newData = [...quotation]; + const index = newData.findIndex((item) => currentKey === item.key); + if (index > -1) { + newData[index].yxq = date; + setQuotation(newData); + setCurrentKey(null); + setDatePickerVisible(false); + } + } + }; + + const columns = [ + { title: '成人价', dataIndex: 'crj', width: '10%', editable: true }, + { title: '儿童价', dataIndex: 'etj', width: '10%', editable: true }, + { title: '币种', dataIndex: 'bz', width: '10%', editable: true }, + { title: '类型', dataIndex: 'lx', width: '10%', editable: true }, + { title: '人群', dataIndex: 'rq', width: '10%', editable: true }, + { title: '人等', dataIndex: 'rd', width: '10%', editable: true }, + { title: '有效期', dataIndex: 'yxq', width: '30%', editable: true }, + { + title: '操作', + dataIndex: 'operation', + render: (_, record) => { + const editable = isEditing(record); + return editable ? ( + + handleSave(record.key)} style={{ marginRight: 8 }}>保存 + 取消 + + ) : ( + + edit(record)} style={{ marginRight: 8 }}>编辑 + handleDelete(record.key)}> + 删除 + + + ); + }, + }, + ]; + + + const mergedColumns = columns.map((col) => { + if (!col.editable) { + return col; + } + return { + ...col, + onCell: (record) => ({ + record, + inputType: col.dataIndex === 'age' ? 'number' : 'text', + dataIndex: col.dataIndex, + title: col.title, + editing: isEditing(record), + handleDateSelect: handleDateSelect, + }), + }; + }); + + const handleTagClick = (tag) => { + setSelectedTag(tag); + + }; + + const showModal = () => setIsModalVisible(true); + + const handleOk = () => setIsModalVisible(false); + + const handleCancel = () => setIsModalVisible(false); + + + const handleTagChange = (value) => { + if (!tags.includes(value)) { + setTags([...tags, value]); + setLanguageStatus([...languageStatus, { [value]: { title: "", description: "" } }]); + } + setSelectedTag(value); + setIsModalVisible(false); + }; + + const handleChange = (field, value) => { + const updatedLanguageStatus = languageStatus.map(lang => { + if (lang[selectedTag]) { + return { ...lang, [selectedTag]: { ...lang[selectedTag], [field]: value } }; + } + return lang; + }); + setLanguageStatus(updatedLanguageStatus); + }; + + const findLanguageDetails = (tag) => { + const lang = languageStatus.find(lang => lang[tag]); + return lang ? lang[tag] : { title: "", description: "" }; + }; + + + //树组件方法 + const handleNodeSelect = async (_, { node }) => { + // 保存当前表单数据 + if (selectedNodeKey) { + await handleSaveForm(); + } + + // 更新选中的节点 + setSelectedNodeKey(node.key); + + // 加载新节点的表单数据 + if (formState[node.key]) { + form.setFieldsValue(formState[node.key]); + setQuotation(formState[node.key].quotation || []); + setLanguageStatus(formState[node.key].lgc_details || languageStatus); + } else { + form.resetFields(); + setQuotation(initialData); + setLanguageStatus([{ "中文": { title: "", description: "" } }, { "English": { title: "", description: "" } }]); + } + }; + + const handleSaveForm = async () => { + try { + const values = await form.validateFields(); + const newFormState = { + ...formState, + [selectedNodeKey]: { + ...values, + quotation, + lgc_details: languageStatus, + }, + }; + setFormState(newFormState); + } catch (error) { + console.error('Validation failed:', error); + } + }; + + + const onSave = (values) => { + const tempData = values; + tempData['quotation'] = quotation; + tempData['extras'] = quotationBdcp; + tempData['lgc_details'] = languageStatus; + setSaveData(tempData); + console.log("保存的数据",tempData) + }; + + + //绑定产品 + const initialDataBdcp = [ + { key: '1', mc: '15', jg: "美元", lx: 'aa' }, + { key: '2', mc: '15', jg: "美元", lx: 'aa' }, + { key: '3', mc: '15', jg: "美元", lx: 'aa' }, + { key: '4', mc: '15', jg: "美元", lx: 'aa' }, + ] + // const [form] = Form.useForm(); + const [quotationBdcp, setQuotationBdcp] = useState(initialDataBdcp); + const isEditingBdcp = (record) => record.key === editingKeyBdcp; + const [editingKeyBdcp, setEditingKeyBdcp] = useState(''); + + const editBdcp = (record) => { + form.setFieldsValue({ ...record }); + setEditingKeyBdcp(record.key); + }; + const cancelBdcp = () => { + setEditingKeyBdcp(''); + }; + const EditableCellBdcp = ({ + editing, + dataIndex, + title, + inputType, + record, + index, + children, + ...restProps + }) => { + const inputNode = inputType === 'number' ? : ; + return ( + + ); + }; + const bdcpColums = [ + { title: '名称', dataIndex: 'mc', width: '15%', editable: true }, + { title: '价格', dataIndex: 'jg', width: '15%', editable: true }, + { title: '类型', dataIndex: 'lx', width: '40%', editable: true }, + { + title: '操作', + dataIndex: 'operation', + render: (_, record) => { + const editable = isEditingBdcp(record); + return editable ? ( + + handleSaveBdcp(record.key)} style={{ marginRight: 8 }}>保存 + 取消 + + ) : ( + + editBdcp(record)} style={{ marginRight: 8 }}>编辑 + handleDeleteBdcp(record.key)}> + 删除 + + + ); + }, + }, + ].map(col => { + if (!col.editable) { + return col; + } + return { + ...col, + onCell: record => ({ + record, + inputType: col.dataIndex === 'jg' ? 'number' : 'text', + dataIndex: col.dataIndex, + title: col.title, + editing: isEditingBdcp(record), + }), + }; + }); + + + + const handleAddBdcp = () => { + const newData = { + key: `${quotationBdcp.length + 1}`, + mc: '', + jg: '', + lx: '', + }; + setQuotationBdcp([...quotationBdcp, newData]); + setEditingKeyBdcp(''); // 添加这一行 + }; + + const handleSaveBdcp = async (key) => { + try { + const row = await form.validateFields(); + const newData = [...quotationBdcp]; + + const index = newData.findIndex((item) => key === item.key); + if (index > -1) { + const item = newData[index]; + newData.splice(index, 1, { ...item, ...row }); + setQuotationBdcp(newData); + setEditingKeyBdcp(''); + } else { + newData.push(row); + setQuotationBdcp(newData); + setEditingKeyBdcp(''); + } + } catch (errInfo) { + console.log('Validate Failed:', errInfo); + } + }; + + + const handleDeleteBdcp = (key) => { + const newData = [...quotationBdcp]; + const index = newData.findIndex((item) => key === item.key); + newData.splice(index, 1); + setQuotationBdcp(newData); + }; + + const componentsBdcp = { + body: { + cell: EditableCellBdcp, + }, + }; + + + //Effect + useEffect(() => { + if (saveData) { + } + }, [saveData]); + + useEffect(() => { + // const { errcode, result } = fetchJSON('http://127.0.0.1:4523/m1/2602949-1933890-default/Service_BaseInfoWeb/travel_agency_products'); + console.log("get请求") + }, []) + + useEffect(() => { + if (selectedNodeKey) { + handleSaveForm(); + } + }, [selectedNodeKey]); + + + return ( +
+ +
+ + + + + + + + 供应商 }, + { title: 综费 }, + { title: '文章列表' } + ]} /> + } + > +

产品项目

+ + {cpxm.map((item, index) => ( +
+ + + + + ))} + + + + {tags.map(tag => ( + handleTagClick(tag)} + color={tag === selectedTag ? 'blue' : undefined} + style={{ cursor: 'pointer' }} + > + {tag} + + ))} + + + + }> + + + + + + handleChange('title', e.target.value)} + /> + + + handleChange('description', e.target.value)} + /> + + + + + +

供应商报价

+ +
+ {children} + + + {editing ? ( + + {inputNode} + + ) : ( + children + )} + + {editing ? ( + + {inputNode} + + ) : ( + children + )} +
+ + + + + +

绑定产品

+ +
+ + + + + + + + + + + {datePickerVisible && ( + setDatePickerVisible(false)} + onCancel={() => setDatePickerVisible(false)} + > + + + )} + + ); +} +export default Index; + + diff --git a/src/views/gysgl/components/date.jsx b/src/views/gysgl/components/date.jsx new file mode 100644 index 0000000..51bc9ca --- /dev/null +++ b/src/views/gysgl/components/date.jsx @@ -0,0 +1,86 @@ +import React, { useState } from 'react'; +import { DatePicker, Input, Button } from 'antd'; +import dayjs from 'dayjs'; + +const DateComponent = ({ onDateChange }) => { + const dateFormat = 'YYYY/MM/DD'; + const { RangePicker } = DatePicker; + + const [selectedMonths, setSelectedMonths] = useState([]); + const [selectedDays, setSelectedDays] = useState([]); + + const handleChange = (date, dateString) => { + const dateFw = dateString[0] + "-" + dateString[1]; + onDateChange(dateFw); + }; + + const months = [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ]; + + const days = [ + 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' + ]; + + const handleMonthClick = (month) => { + setSelectedMonths((prevSelectedMonths) => { + if (prevSelectedMonths.includes(month)) { + return prevSelectedMonths.filter((m) => m !== month); + } else { + return [...prevSelectedMonths, month]; + } + }); + }; + + const handleDayClick = (day) => { + setSelectedDays((prevSelectedDays) => { + if (prevSelectedDays.includes(day)) { + return prevSelectedDays.filter((d) => d !== day); + } else { + return [...prevSelectedDays, day]; + } + }); + }; + + return ( +
+

Title

+ +

Data

+ +

Months

+
+ {months.map((month, index) => ( + + ))} +
+

Weekdays

+
+ {days.map((day, index) => ( + + ))} +
+
+ ); +}; + +export default DateComponent; From e9b7aec5219c997bc6acce03d232c99d83237ecb Mon Sep 17 00:00:00 2001 From: Lei OT Date: Fri, 21 Jun 2024 14:20:06 +0800 Subject: [PATCH 011/120] =?UTF-8?q?feat:=20=E4=BA=A7=E5=93=81=E7=AE=A1?= =?UTF-8?q?=E7=90=86:=20=E6=90=9C=E7=B4=A2+=E5=B9=B4=E4=BB=BD,=E7=8A=B6?= =?UTF-8?q?=E6=80=81;=20=E4=BA=A7=E5=93=81=E7=B1=BB=E5=9E=8B=E6=8E=92?= =?UTF-8?q?=E5=BA=8F;=20=E5=AE=A1=E6=A0=B8=E9=A1=B5:=20=E6=88=90=E4=BA=BA,?= =?UTF-8?q?=E5=84=BF=E7=AB=A5=E4=BB=B7;=20=E8=AF=AD=E7=A7=8D=E5=88=97?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/products.json | 5 +- public/locales/zh/products.json | 7 +- src/components/SearchForm.jsx | 35 ++- src/components/SearchInput.jsx | 1 + .../{usePresets.js => useDatePresets.js} | 4 +- src/hooks/useLanguageSets.js | 14 ++ src/hooks/useProductsSets.js | 1 + src/main.jsx | 4 +- src/stores/Products/Index.js | 236 +----------------- src/views/products/Audit.jsx | 51 ++-- src/views/products/Index.jsx | 27 +- 11 files changed, 111 insertions(+), 274 deletions(-) rename src/hooks/{usePresets.js => useDatePresets.js} (91%) create mode 100644 src/hooks/useLanguageSets.js diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 6033e89..b5e7694 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -7,6 +7,7 @@ "Attractions": "Attractions", "Meals": "Meals", "Extras": "Extras", + "Overtravel": "超公里", "Special": "Special" }, "auditState": { @@ -20,7 +21,7 @@ "New": "New", "Pending": "Pending", "Approved": "Approve", - "Rejected": "Rejecte", + "Rejected": "Reject", "Published": "Publish" }, "Status": "Status", @@ -42,6 +43,8 @@ "UseDates": "Use Dates", "Weekdays": "Weekdays", + "UseYear": "Use Year", + "AgeType": { "Type": "Age Type", "Adult": "Adult", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 01f7836..9f1f8b6 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -6,7 +6,8 @@ "Package": "包价线路", "Attractions": "景点", "Meals": "餐费", - "Extras": "附加", + "Extras": "附加项目", + "Overtravel": "超公里", "Special": "特殊项目" }, "auditState": { @@ -21,7 +22,7 @@ "Pending": "待审核", "Approved": "审核通过", "Rejected": "审核拒绝", - "Published": "发布上线" + "Published": "审核发布" }, "Status": "状态", "State": "状态", @@ -41,6 +42,8 @@ "UseDates": "使用日期", "Weekdays": "有效日/周X", + "UseYear": "年份", + "AgeType": { "Type": "人群", "Adult": "成人", diff --git a/src/components/SearchForm.jsx b/src/components/SearchForm.jsx index 6a53cac..a669294 100644 --- a/src/components/SearchForm.jsx +++ b/src/components/SearchForm.jsx @@ -3,7 +3,7 @@ import { Form, Input, Row, Col, Select, DatePicker, Space, Button } from 'antd'; import { objectMapper, at } from '@/utils/commons'; import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from '@/config'; import useFormStore from '@/stores/Form'; -import usePresets from '@/hooks/usePresets'; +import useDatePresets from '@/hooks/useDatePresets'; import { useTranslation } from 'react-i18next'; import { fetchJSON } from '@/utils/request'; @@ -14,8 +14,8 @@ import AuditStateSelector from './AuditStateSelector'; //供应商列表 -export const fetchVendorList = async () => { - const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/VendorList`) +export const fetchVendorList = async (q) => { + const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/VendorList`, { q }) return errcode !== 0 ? [] : result } @@ -23,7 +23,7 @@ const { RangePicker } = DatePicker; const SearchForm = ({ initialValue, onSubmit, onReset, ...props }) => { const { t } = useTranslation(); - const presets = usePresets(); + 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(); @@ -57,6 +57,9 @@ const SearchForm = ({ initialValue, onSubmit, onReset, ...props }) => { return Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.value : ''; }, }, + 'year': [ + { key: 'year', transform: (arrVal) => (arrVal ? arrVal.format('YYYY') : '') }, + ], }; let dest = {}; const { dates, ...omittedValue } = values; @@ -165,15 +168,15 @@ function getFields(props) { 'referenceNo', 99, - + , fieldProps?.referenceNo?.col || 6 ), item( 'PNR', 99, - - + + , fieldProps?.PNR?.col || 4 ), @@ -208,11 +211,26 @@ function getFields(props) { /** * */ + item( + 'year', + 99, + + + , + fieldProps?.year?.col || 3 + ), item( 'agency', 99, - + , fieldProps?.agency?.col || 6 ), @@ -224,7 +242,6 @@ function getFields(props) { , fieldProps?.audit_state?.col || 3 ), - ]; baseChildren = baseChildren .map((x) => { diff --git a/src/components/SearchInput.jsx b/src/components/SearchInput.jsx index d816647..dcf2657 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} + dropdownStyle={{width: '16rem'}} {...props} onSearch={debounceFetcher} notFoundContent={fetching ? : null} diff --git a/src/hooks/usePresets.js b/src/hooks/useDatePresets.js similarity index 91% rename from src/hooks/usePresets.js rename to src/hooks/useDatePresets.js index aa046e6..87397b9 100644 --- a/src/hooks/usePresets.js +++ b/src/hooks/useDatePresets.js @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import dayjs from "dayjs"; import { useTranslation } from 'react-i18next'; -const usePresets = () => { +const useDatePresets = () => { const [presets, setPresets] = useState([]); const { t, i18n } = useTranslation(); @@ -39,4 +39,4 @@ const usePresets = () => { return presets; } -export default usePresets; +export default useDatePresets; diff --git a/src/hooks/useLanguageSets.js b/src/hooks/useLanguageSets.js new file mode 100644 index 0000000..1dab4b1 --- /dev/null +++ b/src/hooks/useLanguageSets.js @@ -0,0 +1,14 @@ +export const useLanguageSets = () => { + const newData = [ + { key: '1', value: '1', label: 'English' }, + { key: '2', value: '2', label: 'Chinese (中文)' }, + { key: '3', value: '3', label: 'Japanese (日本語)' }, + { key: '4', value: '4', label: 'German (Deutsch)' }, + { key: '5', value: '5', label: 'French (Français)' }, + { key: '6', value: '6', label: 'Spanish (Español)' }, + { key: '7', value: '7', label: 'Russian (Русский)' }, + { key: '8', value: '8', label: 'Italian (Italiano)' }, + ]; + + return newData; +}; diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index 0994b3f..5f3a85b 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -27,6 +27,7 @@ export const useProductsTypes = () => { useEffect(() => { const newData = [ { label: t('products:type.Experience'), value: '6', key: '6' }, + { label: t('products:type.Overtravel'), value: 'B', key: 'B' }, { label: t('products:type.Car'), value: 'Car', key: 'Car' }, { label: t('products:type.Guide'), value: 'Guide', key: 'Guide' }, { label: t('products:type.Package'), value: 'D', key: 'D' }, diff --git a/src/main.jsx b/src/main.jsx index 3733462..a4556b5 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -69,8 +69,8 @@ const router = createBrowserRouter([ { path: "invoice/paid/detail/:flid",element:}, { path: "airticket",element:}, { path: "products",element:}, - { path: "products/:travel_agency_id/audit",element:}, - { path: "products/:travel_agency_id",element:}, + { path: "products/:travel_agency_id/:use_year/:audit_state/audit",element:}, + { path: "products/:travel_agency_id/:use_year/:audit_state/edit",element:}, ] }, { diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js index 51d3010..a18f098 100644 --- a/src/stores/Products/Index.js +++ b/src/stores/Products/Index.js @@ -10,7 +10,8 @@ export const searchAgencyAction = async (param) => { return errcode !== 0 ? [] : result; }; export const getAgencyProductsAction = async (param) => { - const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/travel_agency_products`, param); + const _param = { ...param, use_year: (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); return errcode !== 0 ? { agency: {}, products: [] } : result; }; @@ -47,238 +48,10 @@ export const postProductsAudit = async (auditState, infoRow) => { const initialState = { loading: false, + searchValues: {}, agencyList: [], activeAgency: {}, - agencyProducts: groupBy([ - { - "info": { - "id": "640000198509289851", - "title": "如拉下完公", - "code": "grlkt", - "type": "Guide", - "audit_state": "1", - "create_date": "2022-01-13", - "created_by": "郝涛", - "travel_agency_id": "710000200712195349", - "travel_agency_name": "国得气验", - "lastedit_memo": "划百引程级门会需代领主属快。", - "remarks": "及决对金利低集小理电和按常如门。", - "duration": 2, - "duration_unit": "m", - "open_weekdays": "6", - "recommends_rate": 3, - "dept": 1, - "display_to_c": "2", - "km": 27, - "city_id": 77, - "city_name": "称命" - }, - "quotation": [ - { - "id": "21000020030611324X", - "value": 70, - "currency": "CNY", - "unit": "团", - "age_type": "儿童", - "group_size_min": 4, - "group_size_max": 4, - "use_dates_start": "2004-01-19", - "use_dates_end": "1990-03-10", - "weekdays": "4", - "audit_state": "ea pariatur", - "lastedit_memo": "sunt" - }, - { - "id": "610000197306240177", - "value": 86, - "currency": "CNY", - "unit": "人", - "age_type": "儿童", - "group_size_min": 6, - "group_size_max": 8, - "use_dates_start": "1996-12-16", - "use_dates_end": "1974-11-19", - "weekdays": "4", - "audit_state": "aliqua aute quis ipsum", - "lastedit_memo": "commodo adipisicing ea ipsum" - } - ], - "lgc_details": [ - { - "lgc": "mollit", - "title": "林运但", - "description": "学克信图走法因心委周说步将且文手越。", - "id": "35" - }, - { - "lgc": "et laborum", - "title": "备上引深量知量", - "description": "到听少文话包由北层中争二调原务越明在。", - "id": "74" - }, - { - "lgc": "minim velit", - "title": "安都始新", - "description": "取影压前手府要青白支大而。", - "id": "23" - } - ] - }, - { - "info": { - "id": "41000019901227754X", - "title": "据划京少国取", - "code": "ore", - "type": "Guide", - "audit_state": "2", - "create_date": "1979-01-31", - "created_by": "陆芳", - "travel_agency_id": "110000198612200137", - "travel_agency_name": "少平酸型", - "lastedit_memo": "八想军也装运知长示各院步济水。", - "remarks": "千改原统实专回列参目党却是样与后收。", - "duration": 3, - "duration_unit": "d", - "open_weekdays": "5", - "recommends_rate": 5, - "dept": 1, - "display_to_c": "2", - "km": 30, - "city_id": 62, - "city_name": "业入" - }, - "quotation": [ - { - "id": "37000019760525515X", - "value": 93, - "currency": "CNY", - "unit": "团", - "age_type": "成人", - "group_size_min": 7, - "group_size_max": 11, - "use_dates_start": "1992-11-22", - "use_dates_end": "1997-07-16", - "weekdays": "7", - "audit_state": "id nulla irure cupidatat", - "lastedit_memo": "quis aute reprehenderit consectetur" - }, - { - "id": "150000199506023175", - "value": 90, - "currency": "CNY", - "unit": "人", - "age_type": "儿童", - "group_size_min": 9, - "group_size_max": 10, - "use_dates_start": "2007-09-11", - "use_dates_end": "2013-07-27", - "weekdays": "5", - "audit_state": "commodo ad ut", - "lastedit_memo": "id anim incididunt" - } - ], - "lgc_details": [ - { - "lgc": "adipisicing elit Excepteur in", - "title": "很很结龙认", - "description": "事起复京长立然将采共层列工。", - "id": "43" - }, - { - "lgc": "dolore fugiat", - "title": "专中小", - "description": "示史想当集认点离反而原化精满并计前。", - "id": "28" - }, - { - "lgc": "sunt consectetur ea cillum", - "title": "他率带没", - "description": "节经厂面际是统表王活基书色活至是干验。", - "id": "83" - }, - { - "lgc": "incididunt labore fugiat", - "title": "精话西改", - "description": "须事金性别民学少拉个且须专需断连。", - "id": "97" - }, - { - "lgc": "dolore id", - "title": "文技话", - "description": "上任成条到则查支外很素给务府三。", - "id": "99" - } - ] - }, - { - "info": { - "id": "44000019990112280X", - "title": "节到和", - "code": "ixlmndtmz", - "type": "Meals", - "audit_state": "1", - "create_date": "2006-12-30", - "created_by": "易敏", - "travel_agency_id": "640000197111288408", - "travel_agency_name": "术备带走", - "lastedit_memo": "认队什教调问传改万消然声地全。", - "remarks": "属须厂几问总识看部群该克员方。", - "duration": 2, - "duration_unit": "m", - "open_weekdays": "6", - "recommends_rate": 3, - "dept": 2, - "display_to_c": "1", - "km": 13, - "city_id": 55, - "city_name": "铁以" - }, - "quotation": [ - { - "id": "13000019860219219X", - "value": 88, - "currency": "CNY", - "unit": "团", - "age_type": "儿童", - "group_size_min": 2, - "group_size_max": 4, - "use_dates_start": "1991-03-19", - "use_dates_end": "1974-03-13", - "weekdays": "3", - "audit_state": "officia voluptate ad adipisicing dolore", - "lastedit_memo": "Duis amet veniam enim" - }, - { - "id": "420000201706118123", - "value": 61, - "currency": "CNY", - "unit": "人", - "age_type": "儿童", - "group_size_min": 4, - "group_size_max": 10, - "use_dates_start": "1992-04-23", - "use_dates_end": "1970-07-19", - "weekdays": "5", - "audit_state": "commodo labore", - "lastedit_memo": "ullamco anim culpa do in" - } - ], - "lgc_details": [ - { - "lgc": "ut minim", - "title": "回等这意", - "description": "农满界个整千书得被写况空派会想头无。", - "id": "40" - }, - { - "lgc": "laborum id elit irure commodo", - "title": "增正数白养土子", - "description": "么划才共别程以元于族完难变。", - "id": "84" - } - ] - } -], row => row.info.type), + agencyProducts: {}, }; export const useProductsStore = create( devtools((set, get) => ({ @@ -287,6 +60,7 @@ export const useProductsStore = create( // state actions setLoading: loading => set({ loading }), + setSearchValues: searchValues => set({ searchValues }), setAgencyList: (agencyList) => set({ agencyList }), setActiveAgency: activeAgency => set({ activeAgency }), setAgencyProducts: agencyProducts => set({ agencyProducts }), diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index c548de2..e055cc3 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -7,15 +7,18 @@ import { useTranslation } from 'react-i18next'; import useProductsStore, { postProductsQuoteAudit } from '@/stores/Products/Index'; import { isEmpty } from '@/utils/commons'; -const Header = ({ title, agency, ...props}) => { +const Header = ({ title, agency, refresh, ...props}) => { const { t } = useTranslation(); - const [activeAgency, ] = useProductsStore((state) => [state.activeAgency]); + 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('✔'); + message.success(json.errmsg); + if (typeof refresh === 'function') { + refresh(); + } } }) .catch((ex) => { @@ -34,8 +37,14 @@ const Header = ({ title, agency, ...props}) => { {/* */} {/* */} - + {/* */} + {/* todo: export */} @@ -43,9 +52,9 @@ const Header = ({ title, agency, ...props}) => { ); }; -const PriceTable = ({dataSource, loading}) => { +const PriceTable = ({dataSource,refresh}) => { const { t } = useTranslation('products'); - const [activeAgency, ] = useProductsStore((state) => [state.activeAgency]); + const [loading, activeAgency, ] = useProductsStore((state) => [state.loading, state.activeAgency, ]); const { message, notification } = App.useApp(); const stateMapVal = useProductsAuditStatesMapVal(); @@ -53,7 +62,10 @@ const PriceTable = ({dataSource, loading}) => { postProductsQuoteAudit(state, {id: row.id, travel_agency_id: activeAgency.travel_agency_id}) .then((json) => { if (json.errcode === 0) { - message.success('✔'); + message.success(json.errmsg); + if (typeof refresh === 'function') { + refresh(); + } } }) .catch((ex) => { @@ -68,11 +80,11 @@ const PriceTable = ({dataSource, loading}) => { const columns = [ { key: 'title', dataIndex: ['info', 'title'], title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan }) }, - { key: 'value', title: t('Quotation'), render: (_, { value, currency, unit_name }) => `${value} ${currency} / ${unit_name}` }, + { 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: 'ageType', dataIndex: ['age_type'], title: t('AgeType.Type') }, { key: 'groupSize', dataIndex: ['group_size_min'], @@ -94,24 +106,24 @@ const PriceTable = ({dataSource, loading}) => { }, }, { - title: '', + title: '价格审核', key: 'action', render: (_, r) => r.audit_state_id <= 0 ?( - + ) : null, }, ]; - return
r.id} />; + return
r.id} />; } /** * */ -const TypesPanels = () => { - const [loading, agencyProducts] = useProductsStore((state) => [state.loading, state.agencyProducts]); +const TypesPanels = (props) => { + const [loading, agencyProducts, ] = useProductsStore((state) => [state.loading, state.agencyProducts]); // console.log(agencyProducts); const productsTypes = useProductsTypes(); const [activeKey, setActiveKey] = useState([]); @@ -125,8 +137,9 @@ const TypesPanels = () => { ...ele, children: ( r.concat(c.quotation.map((q, i) => ({ ...q, info: c.info, rowSpan: i === 0 ? c.quotation.length : 0 }))), [])} + refresh={props.refresh} /> ), })); @@ -146,11 +159,11 @@ const TypesPanels = () => { } const Audit = ({ ...props }) => { - const { travel_agency_id } = useParams(); + const { travel_agency_id, use_year, audit_state } = useParams(); const [activeAgency, getAgencyProducts] = useProductsStore((state) => [state.activeAgency, state.getAgencyProducts]); const handleGetAgencyProducts = () => { - getAgencyProducts({ travel_agency_id }); + getAgencyProducts({ travel_agency_id, use_year, audit_state }); } useEffect(() => { @@ -161,9 +174,9 @@ const Audit = ({ ...props }) => { return ( <> - }> + }>
- +
); diff --git a/src/views/products/Index.jsx b/src/views/products/Index.jsx index 02d57ec..5a6385c 100644 --- a/src/views/products/Index.jsx +++ b/src/views/products/Index.jsx @@ -11,16 +11,18 @@ import { objectMapper } from '@/utils/commons'; function Index() { const { t } = useTranslation(); 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_id', startdate: 'edit_date1', enddate: 'edit_date2' }); + setSearchValues(searchParam); searchAgency(searchParam); } useEffect(() => { - handleSearchAgency(); + // handleSearchAgency(); }, []); const showTotal = (total) => t('Table.Total', { total }); @@ -37,8 +39,8 @@ function Index() { key: 'action', render: (_, r) => ( - {t('Edit')} - {t('Audit')} + {t('Edit')} + {t('Audit')} ), }, @@ -47,19 +49,21 @@ function Index() { { handleSearchAgency(formVal); @@ -67,7 +71,14 @@ function Index() { />
-
+
From cacc4832c5ce355b0bf179cfd6675b5a6c867544 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Fri, 21 Jun 2024 15:58:33 +0800 Subject: [PATCH 012/120] =?UTF-8?q?feat:=20=E4=BA=A7=E5=93=81=E7=AE=A1?= =?UTF-8?q?=E7=90=86:=20=E6=90=9C=E7=B4=A2:=20=E5=A4=9A=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/products/Index.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/views/products/Index.jsx b/src/views/products/Index.jsx index 5a6385c..d1b1495 100644 --- a/src/views/products/Index.jsx +++ b/src/views/products/Index.jsx @@ -16,7 +16,7 @@ function Index() { const handleSearchAgency = (formVal = undefined) => { const { starttime, endtime, ...param } = formVal || formValuesToSub; - const searchParam = objectMapper(param, { agency: 'travel_agency_id', startdate: 'edit_date1', enddate: 'edit_date2' }); + const searchParam = objectMapper(param, { agency: 'travel_agency_ids', startdate: 'edit_date1', enddate: 'edit_date2' }); setSearchValues(searchParam); searchAgency(searchParam); } @@ -49,14 +49,14 @@ function Index() { Date: Fri, 21 Jun 2024 16:55:22 +0800 Subject: [PATCH 013/120] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E4=BB=B7?= =?UTF-8?q?=E6=A0=BC=E7=BC=96=E8=BE=91=E7=95=8C=E9=9D=A2=EF=BC=9B=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E7=BC=96=E8=BE=91=E8=A7=92=E8=89=B2=EF=BC=9B=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E8=A7=92=E8=89=B2=EF=BC=9B=E9=87=8D=E7=BD=AE=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E5=AF=86=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/价格管理平台.bmpr | Bin 272384 -> 276480 bytes src/stores/Account.js | 45 ++++++++---- src/views/account/Management.jsx | 122 ++++--------------------------- src/views/account/RoleList.jsx | 46 ++++++------ 4 files changed, 68 insertions(+), 145 deletions(-) diff --git a/doc/价格管理平台.bmpr b/doc/价格管理平台.bmpr index e1065f737e4b4c4b23473125fbab526ac344148e..eb1a8d6ac17c14b1cafbeb9b654b686874de0f72 100644 GIT binary patch delta 57794 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 delta 55638 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 f+CV$1F}qMY4E__gRyzGwB%XWnrT_R=!>#`p { return errcode !== 0 ? {} : result } +export const postAccountPassword = async (formData) => { + + const { errcode, result } = await postForm( + `${HT_HOST}/service-CooperateSOA/reset_account_password`, formData) + return errcode !== 0 ? {} : result +} + export const fetchAccountList = async (params) => { const { errcode, result } = await fetchJSON( @@ -42,25 +49,35 @@ const useAccountStore = create((set, get) => ({ accountList: [], - selectedAccount: null, - - selectAccount: (account) => { - set(() => ({ - selectedAccount: account - })) - }, - disableAccount: async (accountId) => { const formData = new FormData() formData.append('wu_id', accountId) - formData.append('account_status', 'enable') + // enable disable + formData.append('account_status', 'disable') const result = await postAccountStatus(formData) console.info(result) }, + resetAccountPassword: async (accountId, password) => { + + const formData = new FormData() + formData.append('wu_id', accountId) + formData.append('newPassword', password) + + return postAccountPassword(formData) + }, + + newRole: () => { + return { + role_id: null, + role_name: '', + role_ids: '' + } + }, + saveOrUpdateRole: async (formValues) => { const formData = new FormData() formData.append('role_id', formValues.role_id) @@ -71,12 +88,11 @@ const useAccountStore = create((set, get) => ({ }, saveOrUpdateAccount: async (formValues) => { - const { selectedAccount } = get() const { userId } = usingStorage() const formData = new FormData() - formData.append('wu_id', selectedAccount.userId) - formData.append('lmi_sn', selectedAccount.lmi_sn) - formData.append('lmi2_sn', selectedAccount.lmi2_sn) + formData.append('wu_id', formValues.userId) + formData.append('lmi_sn', formValues.lmi_sn) + formData.append('lmi2_sn', formValues.lmi2_sn) formData.append('user_name', formValues.username) formData.append('real_name', formValues.realname) formData.append('email', formValues.email) @@ -110,7 +126,8 @@ const useAccountStore = create((set, get) => ({ lastLogin: r.wu_lastlogindate, travelAgency: r.travel_agency_name, travelAgencyId: r.travel_agency_id, - roleId: r.roles, + // 数据库支持逗号分隔多角色(5,6,7),目前界面只需单个。 + roleId: parseInt(r.roles), role: r.roles_name, } }) diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index f0e7b9f..6c2c323 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -12,92 +12,6 @@ import { PERM_ROLE_NEW } from '@/config' const { Title } = Typography -const permissionData = [ - { - title: '海外供应商', - value: 'oversea-0', - key: 'oversea-0', - children: [ - { - title: '所有海外功能', - value: 'oversea-0-0', - key: 'oversea-0-0', - }, - ], - }, - { - title: '机票管理', - value: '0-0', - key: '0-0', - children: [ - { - title: '录入机票价格', - value: '0-0-0', - key: '0-0-0', - }, - ], - }, - { - title: '产品管理', - value: '0-1', - key: '0-1', - children: [ - { - title: '搜索供应商产品', - value: 'B-1-0', - key: 'B-1-0', - }, - { - title: '录入产品价格', - value: '0-1-0', - key: '0-1-0', - }, - { - title: '新增产品描述', - value: '0-1-1', - key: '0-1-1', - }, - { - title: '复制供应商产品信息', - value: '0-1-2', - key: '0-1-2', - }, - ], - }, - { - title: '账号管理', - value: '2-1', - key: '2-1', - children: [ - { - title: '搜索账号', - value: '2-1-01', - key: '2-1-01', - }, - { - title: '新增账号', - value: '2-1-11', - key: '2-1-11', - }, - { - title: '禁用账号', - value: '2-1-21', - key: '2-1-21', - }, - { - title: '重置账号密码', - value: '2-1-31', - key: '2-1-31', - }, - { - title: '新增角色', - value: '2-1-41', - key: '2-1-41', - }, - ], - }, -] - function Management() { const { t } = useTranslation() @@ -121,8 +35,7 @@ function Management() { }, { title: t('account:role'), - dataIndex: 'role', - render: roleRender + dataIndex: 'role' }, { title: t('account:lastLogin'), @@ -141,12 +54,6 @@ function Management() { ) } - function roleRender(text) { - return ( - - ) - } - function actionRender(text, account) { return ( @@ -156,28 +63,19 @@ function Management() { ) } - const onPermissionChange = (newValue) => { - console.log('onChange ', newValue) - setPermissionValue(newValue) - } - - const [permissionValue, setPermissionValue] = useState(['0-0-0']) const [isAccountModalOpen, setAccountModalOpen] = useState(false) - const [isRoleModalOpen, setRoleModalOpen] = useState(false) const [dataLoading, setDataLoading] = useState(false) const [roleAllList, setRoleAllList] = useState([]) const [accountForm] = Form.useForm() - const [searchAccountByCriteria, accountList, disableAccount, selectedAccount, saveOrUpdateAccount, selectAccount] = + const [searchAccountByCriteria, accountList, disableAccount, saveOrUpdateAccount, resetAccountPassword] = useAccountStore((state) => - [state.searchAccountByCriteria, state.accountList, state.disableAccount, state.selectedAccount, state.saveOrUpdateAccount, state.selectAccount]) + [state.searchAccountByCriteria, state.accountList, state.disableAccount, state.saveOrUpdateAccount, state.resetAccountPassword]) const { notification, modal } = App.useApp() const onAccountSeleted = async (account) => { accountForm.setFieldsValue(account) - selectAccount(account) - console.info(account) const roleList = await fetchRoleList() setRoleAllList(roleList.map(r => { return { @@ -193,7 +91,6 @@ function Management() { console.log(values) saveOrUpdateAccount(values) .catch(ex => { - console.info(ex.message) notification.error({ message: 'Notification', description: ex.message, @@ -222,11 +119,21 @@ function Management() { } const showResetPasswordConfirm = (account) => { + const randomPassword = account.username + (Math.floor(Math.random() * 900) + 100) modal.confirm({ title: 'Do you want to reset password?', icon: , content: `Username: ${account.username}, Realname: ${account.realname}`, onOk() { + resetAccountPassword(account.userId, randomPassword) + .then(() => { + notification.info({ + message: '新密码:' + randomPassword, + description: `请复制密码给 [${account.realname}]`, + placement: 'top', + duration: 60, + }) + }) console.log('ResetPassword') }, onCancel() { @@ -263,6 +170,9 @@ function Management() { )} > + + + onRoleSeleted(role)}>{text} - ) - } - - function actionRender(text, account) { - return ( - - - - - ) + function actionRender(text, role) { + if (role.role_id > 1) { + return ( + + ) + } } const onPermissionChange = (newValue) => { @@ -139,10 +131,14 @@ function RoleList() { } useEffect (() => { + setDataLoading(true) fetchRoleList() .then(r => { setRoleAllList(r) }) + .finally(() => { + setDataLoading(false) + }) }, []) const [permissionValue, setPermissionValue] = useState(['0-0-0']) @@ -151,16 +147,20 @@ function RoleList() { const [roleAllList, setRoleAllList] = useState([]) const [roleForm] = Form.useForm() - const [saveOrUpdateRole] = + const [saveOrUpdateRole, newRole] = useAccountStore((state) => - [state.saveOrUpdateRole]) + [state.saveOrUpdateRole, state.newRole]) const { notification, modal } = App.useApp() - const onRoleSeleted = async (role) => { + const onRoleSeleted = (role) => { + roleForm.setFieldsValue(role) + setRoleModalOpen(true) + } + + const onNewRole = () => { + const role = newRole() roleForm.setFieldsValue(role) - // selectAccount(account) - // console.info(account) setRoleModalOpen(true) } @@ -212,11 +212,7 @@ function RoleList() { )} > - - - + - + From ce345c73472cbf41d9118ce11ae75b9913da030e Mon Sep 17 00:00:00 2001 From: Lei OT Date: Mon, 24 Jun 2024 09:25:47 +0800 Subject: [PATCH 014/120] =?UTF-8?q?HT=E8=AF=AD=E7=A7=8D;=20=E5=BD=93?= =?UTF-8?q?=E5=89=8D=E8=AF=AD=E7=A7=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...seLanguageSets.js => useHTLanguageSets.js} | 2 +- src/hooks/useProductsSets.js | 67 ++++++++++++++----- src/i18n/LanguageSwitcher.jsx | 5 ++ 3 files changed, 56 insertions(+), 18 deletions(-) rename src/hooks/{useLanguageSets.js => useHTLanguageSets.js} (90%) diff --git a/src/hooks/useLanguageSets.js b/src/hooks/useHTLanguageSets.js similarity index 90% rename from src/hooks/useLanguageSets.js rename to src/hooks/useHTLanguageSets.js index 1dab4b1..73b827e 100644 --- a/src/hooks/useLanguageSets.js +++ b/src/hooks/useHTLanguageSets.js @@ -1,4 +1,4 @@ -export const useLanguageSets = () => { +export const useHTLanguageSets = () => { const newData = [ { key: '1', value: '1', label: 'English' }, { key: '2', value: '2', label: 'Chinese (中文)' }, diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index 5f3a85b..35cd3f5 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -4,20 +4,39 @@ import { useTranslation } from 'react-i18next'; /** * 产品管理 相关的预设数据 * 项目类型 -1 酒店预定 -2 火车 -3 飞机票务 -4 游船 -5 快巴 -6 旅行社(综费) - -7 景点 - -8 特殊项目 - -9 其他 -A 酒店 -B 超公里 - -C 餐费 - -D 包价包 -X 站 +酒店预定 1 +火车 2 +飞机票务 3 +游船 4 +快巴 5 +旅行社(综费) 6 +景点 7 +特殊项目 8 +其他 9 +酒店 A +超公里 B +餐费 C +小包价 D +站 X +购物 S +餐 R +娱乐 E +精华线路 T +客人testimonial F +线路订单 O +省 P +信息 I +国家 G +城市 K +图片 H +地图 M +包价线路 L +节日节庆 V +火车站 N +手机租赁 Z + * * todo: webht 类型 +P 导游 +T 车费 */ export const useProductsTypes = () => { @@ -28,8 +47,8 @@ export const useProductsTypes = () => { const newData = [ { label: t('products:type.Experience'), value: '6', key: '6' }, { label: t('products:type.Overtravel'), value: 'B', key: 'B' }, - { label: t('products:type.Car'), value: 'Car', key: 'Car' }, - { label: t('products:type.Guide'), value: 'Guide', key: 'Guide' }, + { label: t('products:type.Car'), value: 'T', key: 'T' }, + { label: t('products:type.Guide'), value: 'P', key: 'P' }, { label: t('products:type.Package'), value: 'D', key: 'D' }, { label: t('products:type.Attractions'), value: '7', key: '7' }, { label: t('products:type.Meals'), value: 'C', key: 'C' }, @@ -42,7 +61,6 @@ export const useProductsTypes = () => { return types; }; - export const useProductsAuditStates = () => { const [types, setTypes] = useState([]); const { t, i18n } = useTranslation(); @@ -66,3 +84,18 @@ export const useProductsAuditStatesMapVal = (value) => { const stateMapVal = stateSets.reduce((r, c) => ({ ...r, [`${c.value}`]: c }), {}); return stateMapVal; }; + +export const useProductsTypesFieldsets = (type, role) => { + const infoDefault = ['code', 'title']; + const infoAdmin = ['remarks', 'dept']; + const infoTypesMap = { + '6': [], + 'B': ['city_id', 'km'], + '车费': ['description', 'city_id', 'recommends_rate', 'duration', 'display_to_c'], // todo: 包价类型?? + '导游': ['description', 'city_id', 'duration', ], + 'D': [], //todo: 木有图 + '7': ['description', 'city_id', 'recommends_rate', 'duration', 'display_to_c', 'open_weekdays'], // todo: 怎么是2个图 + 'C': ['description', 'city_id',], + '8': [], // todo: ? + }; +}; diff --git a/src/i18n/LanguageSwitcher.jsx b/src/i18n/LanguageSwitcher.jsx index af87005..dd1a743 100644 --- a/src/i18n/LanguageSwitcher.jsx +++ b/src/i18n/LanguageSwitcher.jsx @@ -8,6 +8,11 @@ const i18n_to_htcode = { 'en': 1, }; +export const useLanguage = () => { + const { i18n } = useTranslation(); + return { language: i18n.language, }; +}; + /** * 语言选择组件 */ From 219372c06e78eb50ce11aeb78de3206c96d8d12f Mon Sep 17 00:00:00 2001 From: Lei OT Date: Mon, 24 Jun 2024 10:58:14 +0800 Subject: [PATCH 015/120] =?UTF-8?q?=E6=96=B0=E5=A2=9EHT=E4=BA=A7=E5=93=81?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B:=20J=20=E8=BD=A6=E8=B4=B9,=20Q=20=E5=AF=BC?= =?UTF-8?q?=E6=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useProductsSets.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index 35cd3f5..6749ede 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -34,9 +34,9 @@ import { useTranslation } from 'react-i18next'; 节日节庆 V 火车站 N 手机租赁 Z - * * todo: webht 类型 -P 导游 -T 车费 + * * webht 类型, 20240624 新增HT类型 +Q 导游 +J 车费 */ export const useProductsTypes = () => { @@ -47,8 +47,8 @@ export const useProductsTypes = () => { const newData = [ { label: t('products:type.Experience'), value: '6', key: '6' }, { label: t('products:type.Overtravel'), value: 'B', key: 'B' }, - { label: t('products:type.Car'), value: 'T', key: 'T' }, - { label: t('products:type.Guide'), value: 'P', key: 'P' }, + { label: t('products:type.Car'), value: 'J', key: 'J' }, + { label: t('products:type.Guide'), value: 'Q', key: 'Q' }, { label: t('products:type.Package'), value: 'D', key: 'D' }, { label: t('products:type.Attractions'), value: '7', key: '7' }, { label: t('products:type.Meals'), value: 'C', key: 'C' }, @@ -72,6 +72,7 @@ export const useProductsAuditStates = () => { { key: '2', value: '2', label: t('products:auditState.Approved') }, { key: '3', value: '3', label: t('products:auditState.Rejected') }, { key: '1', value: '1', label: t('products:auditState.Published') }, + // ELSE 未知 ]; setTypes(newData); }, [i18n.language]); @@ -85,15 +86,18 @@ export const useProductsAuditStatesMapVal = (value) => { return stateMapVal; }; +/** + * @ignore + */ export const useProductsTypesFieldsets = (type, role) => { const infoDefault = ['code', 'title']; - const infoAdmin = ['remarks', 'dept']; + const infoAdmin = ['remarks', 'dept', 'display_to_c']; const infoTypesMap = { '6': [], 'B': ['city_id', 'km'], - '车费': ['description', 'city_id', 'recommends_rate', 'duration', 'display_to_c'], // todo: 包价类型?? - '导游': ['description', 'city_id', 'duration', ], - 'D': [], //todo: 木有图 + 'J': ['description', 'city_id', 'recommends_rate', 'duration', 'display_to_c'], + 'Q': ['description', 'city_id', 'duration', ], + 'D': ['description', 'city_id', 'recommends_rate','duration',], '7': ['description', 'city_id', 'recommends_rate', 'duration', 'display_to_c', 'open_weekdays'], // todo: 怎么是2个图 'C': ['description', 'city_id',], '8': [], // todo: ? From 86242addab2776bbf2ec44555ccc07c6d435d610 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Mon, 24 Jun 2024 13:45:29 +0800 Subject: [PATCH 016/120] =?UTF-8?q?=E5=88=A0=E9=99=A4=20Mobx=20=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E7=9A=84=E5=BC=95=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 10 - src/stores/Feedback.js | 1 - src/stores/Invoice.js | 368 ------------------------------------ src/views/invoice/Index.jsx | 4 +- 4 files changed, 1 insertion(+), 382 deletions(-) diff --git a/src/main.jsx b/src/main.jsx index d78954e..bd73f53 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,5 +1,4 @@ import React from "react"; -import { configure } from "mobx"; import ReactDOM from "react-dom/client"; import { createBrowserRouter, @@ -41,15 +40,6 @@ import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } import './i18n'; -configure({ - useProxies: "ifavailable", - enforceActions: "observed", - computedRequiresReaction: true, - observableRequiresReaction: false, - reactionRequiresObservable: true, - disableErrorBoundaries: process.env.NODE_ENV == "production" -}); - const router = createBrowserRouter([ { path: "/", diff --git a/src/stores/Feedback.js b/src/stores/Feedback.js index dd3feb1..9b2356e 100644 --- a/src/stores/Feedback.js +++ b/src/stores/Feedback.js @@ -1,4 +1,3 @@ -import { makeAutoObservable, runInAction } from 'mobx'; import { fetchJSON, postForm } from '@/utils/request'; import { groupBy } from '@/utils/commons'; import * as config from '@/config'; diff --git a/src/stores/Invoice.js b/src/stores/Invoice.js index 814e69f..09ac0f2 100644 --- a/src/stores/Invoice.js +++ b/src/stores/Invoice.js @@ -1,10 +1,5 @@ -import { makeAutoObservable, runInAction } from "mobx"; import { fetchJSON, postForm } from "@/utils/request"; -import { prepareUrl, isNotEmpty, objectMapper } from "@/utils/commons"; import { HT_HOST } from "@/config"; -import { json } from "react-router-dom"; -import * as config from "@/config"; -import dayjs from "dayjs"; import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; @@ -138,366 +133,3 @@ const useInvoiceStore = create( ); export default useInvoiceStore; - -export class Invoice { - constructor(root) { - makeAutoObservable(this, { rootStore: false }); - this.root = root; - } - - invoiceList = []; //账单列表 - invoicekImages = []; //图片列表 - invoiceGroupInfo = {}; //账单详细 - invoiceProductList = []; //账单细项 - invoiceZDDetail = []; //报账信息 - invoiceCurrencyList = []; //币种 - invoicePicList = []; //多账单图片列表数组 - invoiceFormData = { info_money: 0, info_Currency: "", info_date: "" }; //存储form数据 - - invoicePaid = [] ; //支付账单列表 - invoicePaidDetail = []; //每期账单详细 - - loading = false; - search_date_start = dayjs().subtract(2, "M").startOf("M"); - search_date_end = dayjs().endOf("M"); - - onDateRangeChange = dates => { - console.log(dates); - this.search_date_start = dates==null? null: dates[0]; - this.search_date_end = dates==null? null: dates[1]; - }; - - fetchInvoiceList(VEI_SN, GroupNo, DateStart, DateEnd,OrderType) { - this.loading = true; - const fetchUrl = prepareUrl(HT_HOST + "/service-cusservice/PTSearchGMBPageList") - .append("VEI_SN", VEI_SN) - .append("OrderType", 0) - .append("GroupNo", GroupNo.trim()) - .append("DateStart", DateStart) - .append("DateEnd", DateEnd) - .append("Orderbytype", 1) - .append("TimeType", 0) - .append("limitmarket", "") - .append("mddgroup", "") - .append("SecuryGroup", "") - .append("TotalNum", 0) - .append("PageSize", 2000) - .append("PageIndex", 1) - .append("PayState",OrderType) - .append("token",this.root.authStore.login.token) - .build(); - - return fetchJSON(fetchUrl).then(json => { - runInAction(() => { - this.loading = false; - if (json.errcode == 0) { - if (isNotEmpty(json.Result)) { - this.invoiceList = json.Result.map((data, index) => { - return { - key: data.GMDSN, - gmd_gri_sn: data.GMD_GRI_SN, - gmd_vei_sn: data.GMD_VEI_SN, - GetGDate: data.GetGDate, - GMD_FillWorkers_SN: data.GMD_FillWorkers_SN, - GMD_FWks_LastEditTime: data.GMD_FWks_LastEditTime, - GMD_VerifyUser_SN: data.GMD_VerifyUser_SN, - GMD_Dealed: data.GMD_Dealed, - GMD_VRequestVerify: data.GMD_VRequestVerify, - LeftGDate: data.LeftGDate, - GMD_FillWorkers_Name: data.GMD_FillWorkers_Name, - GroupName: data.GroupName, - AllMoney: data.AllMoney, - PersonNum: data.PersonNum, - GMD_Currency: data.GMD_Currency, - VName: data.VName, - FKState: data.FKState, - }; - }); - } else { - this.invoiceList = []; - } - } else { - throw new Error(json.errmsg + ": " + json.errcode); - } - }); - }); - } - - fetchInvoiceDetail(GMDSN, GSN) { - const fetchUrl = prepareUrl(HT_HOST + "/service-cusservice/PTGetZDDetail") - .append("VEI_SN", this.root.authStore.login.travelAgencyId) - .append("GRI_SN", GSN) - .append("GMD_SN", GMDSN) - .append("LGC", 1) - .append("Bill", 1) - .append("token",this.root.authStore.login.token) - .build(); - - return fetchJSON(fetchUrl).then(json => { - runInAction(() => { - if (json.errcode == 0) { - this.invoiceGroupInfo = json.GroupInfo[0]; - this.invoiceProductList = json.ProductList; - this.invoiceCurrencyList = json.CurrencyList; - this.invoiceZDDetail = json.ZDDetail; - } else { - throw new Error(json.errmsg + ": " + json.errcode); - } - }); - return json; - }); - } - - //获取供应商提交的图片 - getInvoicekImages(VEI_SN, GRI_SN) { - let url = `/service-fileServer/ListFile`; - url += `?GRI_SN=${GRI_SN}&VEI_SN=${VEI_SN}&FilePathName=invoice`; - url += `&token=${this.root.authStore.login.token}`; - fetch(config.HT_HOST + url) - .then(response => response.json()) - .then(json => { - console.log(json); - runInAction(() => { - this.invoicekImages = json.result.map((data, index) => { - return { - uid: -index, //用负数,防止添加删除的时候错误 - name: data.file_name, - status: "done", - url: data.file_url, - }; - }); - }); - }) - .catch(error => { - console.log("fetch data failed", error); - }); - } - - //从数据库获取图片列表 - getInvoicekImages_fromData(jsonData) { - let arrLen = jsonData.length; - let arrPicList = jsonData.map((data, index) => { - const GMD_Pic = data.GMD_Pic; - let picList = []; - if (isNotEmpty(GMD_Pic)) { - let js_Pic = JSON.parse(GMD_Pic); - picList = js_Pic.map((picData, pic_Index) => { - return { - uid: -pic_Index, //用负数,防止添加删除的时候错误 - name: "", - status: "done", - url: picData.url, - }; - }); - } - if (data.GMD_Dealed == false && arrLen == index + 1) { - this.invoicekImages = picList; - } - return picList; - }); - - runInAction(() => { - this.invoicePicList = arrPicList; - }); - } - - //获取数据库的表单默认数据回填。 - getFormData(jsonData) { - let arrLen = jsonData.length; - return jsonData.map((data, index) => { - if (data.GMD_Dealed == false && arrLen == index + 1) { - //只有最后一条账单未审核通过才显示 - runInAction(() => { - this.invoiceFormData = { info_money: data.GMD_Cost, info_Currency: data.GMD_Currency, info_date: isNotEmpty(data.GMD_PayDate) ? dayjs(data.GMD_PayDate) : "" }; - }); - } - }); - } - - removeFeedbackImages(fileurl) { - let url = `/service-fileServer/FileDelete`; - url += `?fileurl=${fileurl}`; - url += `&token=${this.root.authStore.login.token}`; - return fetch(config.HT_HOST + url) - .then(response => response.json()) - .then(json => { - console.log(json); - return json.Result; - }) - .catch(error => { - console.log("fetch data failed", error); - }); - } - - postEditInvoiceDetail(GMD_SN, Currency, Cost, PayDate, Pic, Memo) { - let postUrl = HT_HOST + "/service-cusservice/EditSupplierFK"; - let formData = new FormData(); - formData.append("LMI_SN", this.root.authStore.login.userId); - formData.append("GMD_SN", GMD_SN); - formData.append("Currency", Currency); - formData.append("Cost", Cost); - formData.append("PayDate", isNotEmpty(PayDate) ? PayDate : ""); - formData.append("Pic", Pic); - formData.append("Memo", Memo); - formData.append("token",this.root.authStore.login.token); - - return postForm(postUrl, formData).then(json => { - console.info(json); - return json; - }); - } - - postAddInvoice(GRI_SN, Currency, Cost, PayDate, Pic, Memo) { - let postUrl = HT_HOST + "/service-cusservice/AddSupplierFK"; - let formData = new FormData(); - formData.append("LMI_SN", this.root.authStore.login.userId); - formData.append("VEI_SN", this.root.authStore.login.travelAgencyId); - formData.append("GRI_SN", GRI_SN); - formData.append("Currency", Currency); - formData.append("Cost", Cost); - formData.append("PayDate", isNotEmpty(PayDate) ? PayDate : ""); - formData.append("Pic", Pic); - formData.append("Memo", Memo); - formData.append("token",this.root.authStore.login.token); - return postForm(postUrl, formData).then(json => { - console.info(json); - return json; - }); - } - - //账单状态 - invoiceStatus(FKState) { - switch (FKState - 1) { - case 1: - return "Submitted"; - break; - case 2: - return "Travel Advisor"; - break; - case 3: - return "Finance Dept"; - break; - case 4: - return "Paid"; - break; - default: - return ""; - break; - } - } - - fetchInvoicePaid(VEI_SN, GroupNo, DateStart, DateEnd) { - this.loading = true; - const fetchUrl = prepareUrl(HT_HOST + "/service-Cooperate/Cooperate/GetInvoicePaid") - .append("VEI_SN", VEI_SN) - .append("GroupNo", GroupNo) - .append("DateStart", DateStart) - .append("DateEnd", DateEnd) - .append("token",this.root.authStore.login.token) - .build(); - - return fetchJSON(fetchUrl).then(json => { - runInAction(() => { - this.loading = false; - if (json.errcode == 0) { - if (isNotEmpty(json.Result)) { - this.invoicePaid = json.Result.map((data, index) => { - return { - key: data.fl_id, - fl_finaceNo: data.fl_finaceNo, - fl_vei_sn: data.fl_vei_sn, - fl_year: data.fl_year, - fl_month: data.fl_month, - fl_memo: data.fl_memo, - fl_adddate: data.fl_adddate, - fl_addUserSn: data.fl_addUserSn, - fl_updateUserSn: data.fl_updateUserSn, - fl_updatetime: data.fl_updatetime, - fl_state: data.fl_state, - fl_paid: data.fl_paid, - fl_pic: data.fl_pic, - fcount: data.fcount, - pSum: data.pSum, - }; - }); - } else { - this.invoicePaid = []; - } - } else { - throw new Error(json.errmsg + ": " + json.errcode); - } - }); - }); - - } - - - fetchInvoicePaidDetail(VEI_SN,FLID){ - this.loading = true; - const fetchUrl = prepareUrl(HT_HOST + "/service-Cooperate/Cooperate/GetInvoicePaidDetail") - .append("VEI_SN", VEI_SN) - .append("fl_id", FLID) - .append("token",this.root.authStore.login.token) - .build(); - - return fetchJSON(fetchUrl).then(json => { - runInAction(() => { - this.loading = false; - if (json.errcode == 0) { - if (isNotEmpty(json.Result)) { - this.invoicePaidDetail = json.Result.map((data, index) => { - return { - key: data.fl2_id, - fl2_fl_id: data.fl2_fl_id, - fl2_GroupName: data.fl2_GroupName, - fl2_gri_sn: data.fl2_gri_sn, - fl2_gmd_sn: data.fl2_gmd_sn, - fl2_wl: data.fl2_wl, - fl2_ArriveDate: data.fl2_ArriveDate, - fl2_price: data.fl2_price, - fl2_state: data.fl2_state, - fl2_updatetime: data.fl2_updatetime, - fl2_updateUserSn: data.fl2_updateUserSn, - fl2_memo: data.fl2_memo, - fl2_memo2: data.fl2_memo2, - fl2_paid: data.fl2_paid, - fl2_pic: data.fl2_pic, - }; - }); - } else { - this.invoicePaidDetail = []; - } - } else { - throw new Error(json.errmsg + ": " + json.errcode); - } - }); - }); - } - - /* 测试数据 */ - //账单列表范例数据 - testData = [ - { - GSMSN: 449865, - gmd_gri_sn: 334233, - gmd_vei_sn: 628, - GetDate: "2023-04-2 00:33:33", - GMD_FillWorkers_SN: 8617, - GMD_FWks_LastEditTime: "2023-04-26 12:33:33", - GMD_VerifyUser_SN: 8928, - GMD_Dealed: 1, - GMD_VRequestVerify: 1, - TotalCount: 22, - LeftGDate: "2023-03-30 00:00:00", - GMD_FillWorkers_Name: "", - GroupName: " 中华游230501-CA230402033", - AllMoney: 3539, - FKState: 1, - GMD_Currency: "", - PersonNum: "1大1小", - VName: "", - }, - ]; -} - -// export default Invoice; 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() { -
+
From 3ec9a438339eb0d7a8ae07ecbbd91c84bf3dd719 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Mon, 24 Jun 2024 14:13:27 +0800 Subject: [PATCH 017/120] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=9D=83=E9=99=90?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E3=80=81=E7=BC=96=E8=BE=91=E3=80=81=E5=88=86?= =?UTF-8?q?=E9=85=8D=E8=A7=92=E8=89=B2=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Account.js | 16 +++- src/views/account/Management.jsx | 26 +++-- src/views/account/RoleList.jsx | 158 ++++++++++++------------------- 3 files changed, 90 insertions(+), 110 deletions(-) diff --git a/src/stores/Account.js b/src/stores/Account.js index 6c6410e..f6cfa4f 100644 --- a/src/stores/Account.js +++ b/src/stores/Account.js @@ -45,6 +45,20 @@ export const fetchRoleList = async () => { return errcode !== 0 ? {} : result } +export const fetchPermissionList = async () => { + + const { errcode, result } = await fetchJSON( + `${HT_HOST}/service-CooperateSOA/get_all_permission_list`) + return errcode !== 0 ? {} : result +} + +export const fetchPermissionListByRoleId = async (params) => { + + const { errcode, result } = await fetchJSON( + `${HT_HOST}/service-CooperateSOA/get_role_permission_list`, params) + return errcode !== 0 ? {} : result +} + const useAccountStore = create((set, get) => ({ accountList: [], @@ -82,7 +96,7 @@ const useAccountStore = create((set, get) => ({ const formData = new FormData() formData.append('role_id', formValues.role_id) formData.append('role_name', formValues.role_name) - formData.append('res_ids', '2,3') + formData.append('res_ids', formValues.res_array.join(',')) return postRoleForm(formData) }, diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 6c2c323..e895ba4 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -1,9 +1,11 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { Row, Col, Space, Button, Table, Select, TreeSelect, Typography, Modal, App, Form, Input } from 'antd' import { ExclamationCircleFilled } from '@ant-design/icons' import { useTranslation } from 'react-i18next' import useFormStore from '@/stores/Form' import useAuthStore from '@/stores/Auth' +import dayjs from 'dayjs' +import { isEmpty } from '@/utils/commons' import useAccountStore from '@/stores/Account' import { fetchRoleList } from '@/stores/Account' import SearchForm from '@/components/SearchForm' @@ -40,6 +42,7 @@ function Management() { { title: t('account:lastLogin'), dataIndex: 'lastLogin', + render: (text) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD HH:mm:ss')) }, { title: t('account:action'), @@ -74,16 +77,21 @@ function Management() { const { notification, modal } = App.useApp() + useEffect (() => { + fetchRoleList() + .then((roleList) => { + setRoleAllList(roleList.map(r => { + return { + value: r.role_id, + label: r.role_name, + disabled: r.role_id === 1 + } + })) + }) + }, []) + const onAccountSeleted = async (account) => { accountForm.setFieldsValue(account) - const roleList = await fetchRoleList() - setRoleAllList(roleList.map(r => { - return { - value: r.role_id, - label: r.role_name, - disabled: r.role_id === 1 - } - })) setAccountModalOpen(true) } diff --git a/src/views/account/RoleList.jsx b/src/views/account/RoleList.jsx index 9686304..db2e6ae 100644 --- a/src/views/account/RoleList.jsx +++ b/src/views/account/RoleList.jsx @@ -1,103 +1,15 @@ import { useState, useEffect } from 'react' -import { Row, Col, Space, Button, Table, Select, TreeSelect, Typography, Modal, App, Form, Input } from 'antd' -import { ExclamationCircleFilled } from '@ant-design/icons' +import { Row, Col, Space, Button, Table, TreeSelect, Typography, Modal, App, Form, Input } from 'antd' import { useTranslation } from 'react-i18next' -import useFormStore from '@/stores/Form' -import useAuthStore from '@/stores/Auth' import useAccountStore from '@/stores/Account' -import { fetchRoleList } from '@/stores/Account' -import SearchForm from '@/components/SearchForm' +import { fetchRoleList, fetchPermissionList, fetchPermissionListByRoleId } from '@/stores/Account' +import dayjs from 'dayjs' +import { isEmpty } from '@/utils/commons' import RequireAuth from '@/components/RequireAuth' import { PERM_ROLE_NEW } from '@/config' const { Title } = Typography -const permissionData = [ - { - title: '海外供应商', - value: 'oversea-0', - key: 'oversea-0', - children: [ - { - title: '所有海外功能', - value: 'oversea-0-0', - key: 'oversea-0-0', - }, - ], - }, - { - title: '机票管理', - value: '0-0', - key: '0-0', - children: [ - { - title: '录入机票价格', - value: '0-0-0', - key: '0-0-0', - }, - ], - }, - { - title: '产品管理', - value: '0-1', - key: '0-1', - children: [ - { - title: '搜索供应商产品', - value: 'B-1-0', - key: 'B-1-0', - }, - { - title: '录入产品价格', - value: '0-1-0', - key: '0-1-0', - }, - { - title: '新增产品描述', - value: '0-1-1', - key: '0-1-1', - }, - { - title: '复制供应商产品信息', - value: '0-1-2', - key: '0-1-2', - }, - ], - }, - { - title: '账号管理', - value: '2-1', - key: '2-1', - children: [ - { - title: '搜索账号', - value: '2-1-01', - key: '2-1-01', - }, - { - title: '新增账号', - value: '2-1-11', - key: '2-1-11', - }, - { - title: '禁用账号', - value: '2-1-21', - key: '2-1-21', - }, - { - title: '重置账号密码', - value: '2-1-31', - key: '2-1-31', - }, - { - title: '新增角色', - value: '2-1-41', - key: '2-1-41', - }, - ], - }, -] - function RoleList() { const { t } = useTranslation() @@ -109,6 +21,7 @@ function RoleList() { { title: t('account:createdOn'), dataIndex: 'created_on', + render: (text) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD HH:mm:ss')) }, { title: t('account:action'), @@ -126,10 +39,16 @@ function RoleList() { } const onPermissionChange = (newValue) => { - console.log('onChange ', newValue) setPermissionValue(newValue) } + function groupByParam(array, param) { + return array.reduce((result, item) => { + (result[item[param]] = result[item[param]] || []).push(item) + return result + }, {}) + } + useEffect (() => { setDataLoading(true) fetchRoleList() @@ -139,9 +58,38 @@ function RoleList() { .finally(() => { setDataLoading(false) }) + + const permissionTree = [] + fetchPermissionList() + .then(r => { + + const groupPermissionData = groupByParam(r, 'res_category') + const categoryKeys = Object.keys(groupPermissionData) + + categoryKeys.forEach((categoryName) => { + const permissisonList = groupPermissionData[categoryName] + const categoryGroup = { + title: categoryName, + value: categoryName, + key: categoryName, + children: permissisonList.map(p => { + return { + disableCheckbox: p.res_id == 1, + title: p.res_name, + value: p.res_id, + key: p.res_id, + } + }) + } + permissionTree.push(categoryGroup) + }) + + setPermissionTreeData(permissionTree) + }) }, []) - const [permissionValue, setPermissionValue] = useState(['0-0-0']) + const [permissionValue, setPermissionValue] = useState(['4','5']) + const [permissionTreeData, setPermissionTreeData] = useState(['0-0-0']) const [isRoleModalOpen, setRoleModalOpen] = useState(false) const [dataLoading, setDataLoading] = useState(false) const [roleAllList, setRoleAllList] = useState([]) @@ -154,7 +102,11 @@ function RoleList() { const { notification, modal } = App.useApp() const onRoleSeleted = (role) => { - roleForm.setFieldsValue(role) + fetchPermissionListByRoleId({role_id: role.role_id}) + .then(result => { + role.res_array = result.map(r => r.res_id) + roleForm.setFieldsValue(role) + }) setRoleModalOpen(true) } @@ -165,10 +117,14 @@ function RoleList() { } const onRoleFinish = (values) => { - console.log(values) saveOrUpdateRole(values) + .then(() => { + fetchRoleList() + .then(r => { + setRoleAllList(r) + }) + }) .catch(ex => { - console.info(ex.message) notification.error({ message: 'Notification', description: ex.message, @@ -179,7 +135,6 @@ function RoleList() { } const onRoleFailed = (error) => { - console.log('Failed:', error) // form.resetFields() } @@ -225,8 +180,11 @@ function RoleList() { > - - + Date: Mon, 24 Jun 2024 14:31:13 +0800 Subject: [PATCH 018/120] =?UTF-8?q?=E5=AF=BC=E8=88=AA=E5=92=8C=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E5=A2=9E=E5=8A=A0=E6=9D=83=E9=99=90=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Auth.js | 3 +++ src/views/App.jsx | 20 +++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/stores/Auth.js b/src/stores/Auth.js index fe0900e..d2371d7 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -50,6 +50,9 @@ const useAuthStore = create((set, get) => ({ }, isPermitted: (perm) => { + // 测试权限使用: + // if (perm === '/account/management') return false + // if (perm === '/account/role/new') return false return true // 以上是 Hardcode 判断 // 以下是权限列表从数据库读取后使用的方法 diff --git a/src/views/App.jsx b/src/views/App.jsx index 779993c..607dfe3 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -17,6 +17,8 @@ import useNoticeStore from '@/stores/Notice'; import useAuthStore from '@/stores/Auth' import { usingStorage } from '@/hooks/usingStorage' +import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } from '@/config' + const { Header, Content, Footer } = Layout; const { Title } = Typography; @@ -25,8 +27,8 @@ function App() { const [password, setPassword] = useState('') - const [validateUserPassword, tokenTimeout] = useAuthStore( - (state) => [state.validateUserPassword, state.tokenTimeout]) + const [validateUserPassword, tokenTimeout, isPermitted] = useAuthStore( + (state) => [state.validateUserPassword, state.tokenTimeout, state.isPermitted]) const { loginToken, userDetail } = usingStorage() @@ -120,11 +122,11 @@ function App() { mode='horizontal' selectedKeys={[defaultPath]} items={[ - { key: 'reservation', label: {t('menu.Reservation')} }, - { key: 'invoice', label: {t('menu.Invoice')} }, - { key: 'feedback', label: {t('menu.Feedback')} }, - { key: 'report', label: {t('menu.Report')} }, - { key: 'airticket', label: {t('menu.Airticket')} }, + 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_AIR_TICKET) ? { key: 'airticket', label: {t('menu.Airticket')} } : null, { key: 'notice', label: ( @@ -148,8 +150,8 @@ function App() { items: [...[ { label: {t('ChangePassword')}, key: '0' }, { label: {t('Profile')}, key: '1' }, - { label: {t('account:management.tile')}, key: '3' }, - { label: {t('account:management.roleList')}, key: '4' }, + isPermitted(PERM_ACCOUNT_MANAGEMENT) ? { label: {t('account:management.tile')}, key: '3' } : null, + isPermitted(PERM_ROLE_NEW) ? { label: {t('account:management.roleList')}, key: '4' } : null, { type: 'divider' }, { label: {t('Logout')}, key: '99' }, ], From 697fa00be361584dcbe0ad3926b5828a0935e779 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Mon, 24 Jun 2024 14:58:14 +0800 Subject: [PATCH 019/120] =?UTF-8?q?=E4=BA=A7=E5=93=81=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E7=9A=84=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/config.js b/src/config.js index fc69f82..03d67fd 100644 --- a/src/config.js +++ b/src/config.js @@ -31,3 +31,10 @@ export const PERM_DOMESTIC = '/domestic/all' // 机票供应商 // category: air-ticket export const PERM_AIR_TICKET = '/air-ticket/all' + +// 价格管理 +export const PERM_PRODUCTS_MANAGEMENT = '/products/*'; // 管理 +export const PERM_PRODUCTS_INFO_AUDIT = '/products/info/audit'; // 信息.审核 +export const PERM_PRODUCTS_INFO_PUT = '/products/info/put'; // 信息.录入 +export const PERM_PRODUCTS_OFFER_AUDIT = '/products/offer/audit'; // 价格.审核 +export const PERM_PRODUCTS_OFFER_PUT = '/products/offer/put'; // 价格.录入 From 4afb1d4371082a5967d86c21f648480d513e6795 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Mon, 24 Jun 2024 15:11:57 +0800 Subject: [PATCH 020/120] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=9D=83=E9=99=90?= =?UTF-8?q?=E7=B1=BB=E5=88=AB=E5=90=8D=E7=A7=B0=EF=BC=8C=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E6=95=B0=E6=8D=AE=20SQL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/RBAC 权限.sql | 28 ++++++++++++++++++++++++---- src/config.js | 2 +- src/views/account/RoleList.jsx | 12 ++++++++++-- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/doc/RBAC 权限.sql b/doc/RBAC 权限.sql index 6e70db9..b463af5 100644 --- a/doc/RBAC 权限.sql +++ b/doc/RBAC 权限.sql @@ -45,14 +45,34 @@ VALUES ('技术研发部') INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) VALUES ('所有权限', '*', 'system') INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) -VALUES ('最新团计划', '/reservation/newest', 'oversea') +VALUES ('管理账号', '/account/management', 'system') INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) -VALUES ('账单', '/invoice', 'oversea') +VALUES ('新增账号', '/account/new', 'system') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('禁用账号', '/account/disable', 'system') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('重置密码', '/account/reset-password', 'system') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +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 ('所有国内功能', '/domestic/all', 'domestic') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('所有机票功能', '/air-ticket/all', 'air-ticket') +-- 价格管理 +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('管理产品', '/products/*', 'products') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('审核信息', '/products/info/audit', 'products') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('录入信息', '/products/info/put', 'products') INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) -VALUES ('账号权限管理', '/account/management', 'system') +VALUES ('审核价格', '/products/offer/audit', 'products') INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) -VALUES ('新增角色', '/account/new-role', 'system') +VALUES ('录入价格', '/products/offer/put', 'products') INSERT INTO [dbo].[auth_permission] ([role_id] ,[res_id]) VALUES (1, 1) diff --git a/src/config.js b/src/config.js index fc69f82..1d39e0e 100644 --- a/src/config.js +++ b/src/config.js @@ -18,7 +18,7 @@ export const PERM_ACCOUNT_MANAGEMENT = '/account/management' export const PERM_ACCOUNT_NEW = '/account/new' export const PERM_ACCOUNT_DISABLE = '/account/disable' export const PERM_ACCOUNT_RESET_PASSWORD = '/account/reset-password' -export const PERM_ROLE_NEW = '/account/role/new' +export const PERM_ROLE_NEW = '/account/role-new' // 海外供应商 // category: oversea diff --git a/src/views/account/RoleList.jsx b/src/views/account/RoleList.jsx index db2e6ae..feb2383 100644 --- a/src/views/account/RoleList.jsx +++ b/src/views/account/RoleList.jsx @@ -59,6 +59,14 @@ function RoleList() { setDataLoading(false) }) + const categoryMap = new Map([ + ['system', '系统管理'], + ['oversea', '海外供应商'], + ['domestic', '国内供应商'], + ['air-ticket', '机票供应商'], + ['products', '产品价格'], + ]); + const permissionTree = [] fetchPermissionList() .then(r => { @@ -69,7 +77,7 @@ function RoleList() { categoryKeys.forEach((categoryName) => { const permissisonList = groupPermissionData[categoryName] const categoryGroup = { - title: categoryName, + title: categoryMap.get(categoryName), value: categoryName, key: categoryName, children: permissisonList.map(p => { @@ -77,7 +85,7 @@ function RoleList() { disableCheckbox: p.res_id == 1, title: p.res_name, value: p.res_id, - key: p.res_id, + key: p.res_name, } }) } From efad9d8697963402549a91b6a9b319d74d98bcd3 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Tue, 25 Jun 2024 09:17:46 +0800 Subject: [PATCH 021/120] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E6=96=B0?= =?UTF-8?q?=E5=A2=9E/=E7=BC=96=E8=BE=91=E8=B4=A6=E5=8F=B7=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E4=BE=9B=E5=BA=94=E5=95=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Account.js | 9 +++- src/views/account/Management.jsx | 82 ++++++++++++++++++++++++-------- src/views/account/RoleList.jsx | 4 +- 3 files changed, 71 insertions(+), 24 deletions(-) diff --git a/src/stores/Account.js b/src/stores/Account.js index f6cfa4f..804299a 100644 --- a/src/stores/Account.js +++ b/src/stores/Account.js @@ -59,6 +59,13 @@ export const fetchPermissionListByRoleId = async (params) => { return errcode !== 0 ? {} : result } +export const fetchTravelAgencyByName = async (name) => { + + const { errcode, result } = await fetchJSON( + `${HT_HOST}/Service_BaseInfoWeb/VendorList`, {q: name}) + return errcode !== 0 ? {} : result +} + const useAccountStore = create((set, get) => ({ accountList: [], @@ -138,7 +145,7 @@ const useAccountStore = create((set, get) => ({ realname: r.real_name, email: r.email, lastLogin: r.wu_lastlogindate, - travelAgency: r.travel_agency_name, + travelAgencyName: r.travel_agency_name, travelAgencyId: r.travel_agency_id, // 数据库支持逗号分隔多角色(5,6,7),目前界面只需单个。 roleId: parseInt(r.roles), diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index e895ba4..d3d181c 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -2,15 +2,13 @@ import { useState, useEffect } from 'react' import { Row, Col, Space, Button, Table, Select, TreeSelect, Typography, Modal, App, Form, Input } from 'antd' import { ExclamationCircleFilled } from '@ant-design/icons' import { useTranslation } from 'react-i18next' -import useFormStore from '@/stores/Form' -import useAuthStore from '@/stores/Auth' +import { fetchTravelAgencyByName } from '@/stores/Account' import dayjs from 'dayjs' import { isEmpty } from '@/utils/commons' import useAccountStore from '@/stores/Account' +import useFormStore from '@/stores/Form' import { fetchRoleList } from '@/stores/Account' import SearchForm from '@/components/SearchForm' -import RequireAuth from '@/components/RequireAuth' -import { PERM_ROLE_NEW } from '@/config' const { Title } = Typography @@ -29,7 +27,7 @@ function Management() { }, { title: t('account:travelAgency'), - dataIndex: 'travelAgency', + dataIndex: 'travelAgencyName', }, { title: t('account:email'), @@ -69,12 +67,15 @@ function Management() { const [isAccountModalOpen, setAccountModalOpen] = useState(false) const [dataLoading, setDataLoading] = useState(false) const [roleAllList, setRoleAllList] = useState([]) + const [travelAgencyList, setTravelAgencyList] = useState([]) + const [currentTravelAgency, setCurrentTravelAgency] = useState(null) const [accountForm] = Form.useForm() const [searchAccountByCriteria, accountList, disableAccount, saveOrUpdateAccount, resetAccountPassword] = useAccountStore((state) => [state.searchAccountByCriteria, state.accountList, state.disableAccount, state.saveOrUpdateAccount, state.resetAccountPassword]) + const formValues = useFormStore(state => state.formValues) const { notification, modal } = App.useApp() useEffect (() => { @@ -90,14 +91,39 @@ function Management() { }) }, []) + const handelAccountSearch = () => { + setDataLoading(true) + searchAccountByCriteria(formValues) + .catch(ex => { + notification.error({ + message: 'Notification', + description: ex.message, + placement: 'top', + duration: 4, + }) + }) + .finally(() => { + setDataLoading(false) + }) + } + const onAccountSeleted = async (account) => { + console.info(account) + setTravelAgencyList([{ + label: account.travelAgencyName, + value: account.travelAgencyId + }]) accountForm.setFieldsValue(account) + setCurrentTravelAgency(account.travelAgencyId) setAccountModalOpen(true) } const onAccountFinish = (values) => { console.log(values) saveOrUpdateAccount(values) + .then(() => { + handelAccountSearch() + }) .catch(ex => { notification.error({ message: 'Notification', @@ -113,6 +139,24 @@ function Management() { // form.resetFields() } + // [{ value: 33032, label: 'test海外地接B' }] + const handleTravelAgencySearch = (newValue) => { + fetchTravelAgencyByName(newValue) + .then(result => { + setTravelAgencyList(result.map(r => { + return { + label: r.travel_agency_name, + value: r.travel_agency_id + } + })) + }) + } + + const handleTravelAgencyChange = (newValue) => { + console.info(newValue) + setCurrentTravelAgency(newValue) + } + const showDisableConfirm = (account) => { modal.confirm({ title: 'Do you want to disable this account?', @@ -227,7 +271,16 @@ function Management() { }, ]} > - + { - console.info(formValues) - setDataLoading(true) - searchAccountByCriteria(formValues) - .catch(ex => { - notification.error({ - message: 'Notification', - description: ex.message, - placement: 'top', - duration: 4, - }) - }) - .finally(() => { - setDataLoading(false) - }) + onSubmit={() => { + handelAccountSearch() }} /> diff --git a/src/views/account/RoleList.jsx b/src/views/account/RoleList.jsx index feb2383..3e57a52 100644 --- a/src/views/account/RoleList.jsx +++ b/src/views/account/RoleList.jsx @@ -96,8 +96,8 @@ function RoleList() { }) }, []) - const [permissionValue, setPermissionValue] = useState(['4','5']) - const [permissionTreeData, setPermissionTreeData] = useState(['0-0-0']) + const [permissionValue, setPermissionValue] = useState([]) + const [permissionTreeData, setPermissionTreeData] = useState([]) const [isRoleModalOpen, setRoleModalOpen] = useState(false) const [dataLoading, setDataLoading] = useState(false) const [roleAllList, setRoleAllList] = useState([]) From b0d4943f0986d220d0cf7856293d5c8b79f02c98 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Tue, 25 Jun 2024 10:18:08 +0800 Subject: [PATCH 022/120] =?UTF-8?q?=E6=9C=89=E6=95=88=E6=9C=9F;=20?= =?UTF-8?q?=E6=9C=89=E6=95=88=E7=9A=84=E5=91=A8X;=20style:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/common.json | 18 ++++++ public/locales/en/products.json | 3 + public/locales/zh/common.json | 18 ++++++ public/locales/zh/products.json | 3 + src/components/SecondHeaderWrapper.jsx | 6 +- src/hooks/useProductsSets.js | 12 ++-- src/views/products/Audit.jsx | 77 ++++++++++++++------------ src/views/products/Index.jsx | 6 +- 8 files changed, 97 insertions(+), 46 deletions(-) diff --git a/public/locales/en/common.json b/public/locales/en/common.json index b113807..dd10cb2 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -54,6 +54,24 @@ "lastThreeMonth": "Last Three Month", "thisYear": "This Year" }, + "weekdays": { + "1": "Monday", + "2": "Tuesday", + "3": "Wednesday", + "4": "Thursday", + "5": "Friday", + "6": "Saturday", + "7": "Sunday" + }, + "weekdaysShort": { + "1": "Mon", + "2": "Tue", + "3": "Wed", + "4": "Thu", + "5": "Fri", + "6": "Sat", + "7": "Sun" + }, "menu": { "Reservation": "Reservation", "Invoice": "Invoice", diff --git a/public/locales/en/products.json b/public/locales/en/products.json index b5e7694..7808b34 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -41,7 +41,10 @@ "Unit": "Unit", "GroupSize": "Group Size", "UseDates": "Use Dates", + "Weekdays": "Weekdays", + "OnWeekdays": "On Weekdays: ", + "Unlimited": "Unlimited", "UseYear": "Use Year", diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index a170faa..05a7e38 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -54,6 +54,24 @@ "lastThreeMonth": "前三个月", "thisYear": "今年" }, + "weekdays": { + "1": "一", + "2": "二", + "3": "三", + "4": "四", + "5": "五", + "6": "六", + "7": "日" + }, + "weekdaysShort": { + "1": "一", + "2": "二", + "3": "三", + "4": "四", + "5": "五", + "6": "六", + "7": "日" + }, "menu": { "Reservation": "团预订", "Invoice": "账单", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 9f1f8b6..7eb1c16 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -40,7 +40,10 @@ "Unit": "单位", "GroupSize": "人等", "UseDates": "使用日期", + "Weekdays": "有效日/周X", + "OnWeekdays": "周: ", + "Unlimited": "不限", "UseYear": "年份", diff --git a/src/components/SecondHeaderWrapper.jsx b/src/components/SecondHeaderWrapper.jsx index 097b0d4..41cec6a 100644 --- a/src/components/SecondHeaderWrapper.jsx +++ b/src/components/SecondHeaderWrapper.jsx @@ -1,9 +1,9 @@ import { Outlet, useNavigate } from 'react-router-dom'; -import { Layout, Flex, theme } from 'antd'; +import { Layout, Flex, theme, Spin } from 'antd'; import BackBtn from './BackBtn'; const { Content, Header } = Layout; -const HeaderWrapper = ({ children, header, ...props }) => { +const HeaderWrapper = ({ children, header, loading, ...props }) => { const navigate = useNavigate(); const { token: { colorBgContainer }, @@ -11,6 +11,7 @@ const HeaderWrapper = ({ children, header, ...props }) => { return ( <> +
{/* {header} */} @@ -21,6 +22,7 @@ const HeaderWrapper = ({ children, header, ...props }) => { {children || } + ); diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index 6749ede..0dd63f7 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -49,7 +49,7 @@ export const useProductsTypes = () => { { label: t('products:type.Overtravel'), value: 'B', key: 'B' }, { label: t('products:type.Car'), value: 'J', key: 'J' }, { label: t('products:type.Guide'), value: 'Q', key: 'Q' }, - { label: t('products:type.Package'), value: 'D', key: 'D' }, + { label: t('products:type.Package'), value: 'D', key: 'D' }, // 包价线路 { label: t('products:type.Attractions'), value: '7', key: '7' }, { label: t('products:type.Meals'), value: 'C', key: 'C' }, { label: t('products:type.Extras'), value: '8', key: '8' }, @@ -67,11 +67,11 @@ export const useProductsAuditStates = () => { useEffect(() => { const newData = [ - { key: '-1', value: '-1', label: t('products:auditState.New') }, - { key: '0', value: '0', label: t('products:auditState.Pending') }, - { key: '2', value: '2', label: t('products:auditState.Approved') }, - { key: '3', value: '3', label: t('products:auditState.Rejected') }, - { key: '1', value: '1', label: t('products:auditState.Published') }, + { key: '-1', value: '-1', label: t('products:auditState.New'), color: 'gray-500' }, + { key: '0', value: '0', label: t('products:auditState.Pending'), color: '' }, + { key: '2', value: '2', label: t('products:auditState.Approved'), color: 'primary' }, + { key: '3', value: '3', label: t('products:auditState.Rejected'), color: 'red-500' }, + { key: '1', value: '1', label: t('products:auditState.Published'), color: 'primary' }, // ELSE 未知 ]; setTypes(newData); diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index e055cc3..4e30330 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -1,18 +1,20 @@ import { useEffect, useState } from 'react'; -import { useParams, } from 'react-router-dom'; -import { App, Button, Collapse, Table, Space, } from 'antd'; +import { useParams } from 'react-router-dom'; +import { App, Button, Collapse, Table, Space, Divider } 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'; +// import PrintContractPDF from './PrintContractPDF'; -const Header = ({ title, agency, refresh, ...props}) => { +const Header = ({ title, agency, refresh, ...props }) => { + const { use_year, } = useParams(); const { t } = useTranslation(); - const [activeAgency, ] = useProductsStore((state) => [state.activeAgency, ]); + 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}) + postProductsQuoteAudit(state, { id: row.id, travel_agency_id: activeAgency.travel_agency_id }) .then((json) => { if (json.errcode === 0) { message.success(json.errmsg); @@ -33,7 +35,7 @@ const Header = ({ title, agency, refresh, ...props}) => { return (
-

{title}

+

{title} {use_year}

{/* */} {/* */} @@ -46,20 +48,21 @@ const Header = ({ title, agency, refresh, ...props}) => { - {/* todo: export */} - + {/* todo: export, 审核完成之后才能导出 */} + + {/* */}
); }; -const PriceTable = ({dataSource,refresh}) => { +const PriceTable = ({ dataSource, refresh }) => { const { t } = useTranslation('products'); - const [loading, activeAgency, ] = useProductsStore((state) => [state.loading, state.activeAgency, ]); + 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}) + postProductsQuoteAudit(state, { id: row.id, travel_agency_id: activeAgency.travel_agency_id }) .then((json) => { if (json.errcode === 0) { message.success(json.errmsg); @@ -79,7 +82,7 @@ const PriceTable = ({dataSource,refresh}) => { }; const columns = [ - { key: 'title', dataIndex: ['info', 'title'], title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan }) }, + { key: 'title', dataIndex: ['info', 'title'], width: '16rem', 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'), }, @@ -95,35 +98,37 @@ const PriceTable = ({dataSource,refresh}) => { key: 'useDates', dataIndex: ['use_dates_start'], title: t('UseDates'), - render: (_, { use_dates_start, use_dates_end }) => `${use_dates_start} ~ ${use_dates_end}`, + render: (_, { use_dates_start, use_dates_end, weekdays }) => `${use_dates_start} ~ ${use_dates_end}`, // + (weekdays ? `, ${t('OnWeekdays')}${weekdays}` : ''), }, - { key: 'weekdays', dataIndex: ['weekdays'], title: t('Weekdays') }, + { key: 'weekdays', dataIndex: ['weekdays'], title: t('Weekdays'), render: (text, r) => text || t('Unlimited') }, { key: 'state', title: t('State'), render: (_, r) => { - return stateMapVal[`${r.audit_state_id}`]?.label; + return {stateMapVal[`${r.audit_state_id}`]?.label}; }, }, { title: '价格审核', key: 'action', - render: (_, r) => r.audit_state_id <= 0 ?( - - - - - ) : null, + render: (_, r) => + r.audit_state_id <= 0 ? ( + + + + + ) : null, }, ]; - return
r.id} />; -} + return
r.id} />; +}; /** * */ const TypesPanels = (props) => { - const [loading, agencyProducts, ] = useProductsStore((state) => [state.loading, state.agencyProducts]); + const { t } = useTranslation(); + const [loading, agencyProducts] = useProductsStore((state) => [state.loading, state.agencyProducts]); // console.log(agencyProducts); const productsTypes = useProductsTypes(); const [activeKey, setActiveKey] = useState([]); @@ -138,7 +143,7 @@ const TypesPanels = (props) => { children: ( r.concat(c.quotation.map((q, i) => ({ ...q, info: c.info, rowSpan: i === 0 ? c.quotation.length : 0 }))), [])} + dataSource={agencyProducts[ele.value].reduce((r, c) => r.concat(c.quotation.map((q, i) => ({ ...q, weekdays: q.weekdays.split(',').filter(Boolean).map(w => t(`weekdaysShort.${w}`)).join(', '), info: c.info, rowSpan: i === 0 ? c.quotation.length : 0 }))), [])} refresh={props.refresh} /> ), @@ -151,31 +156,33 @@ const TypesPanels = (props) => { }, [productsTypes, agencyProducts]); const onCollapseChange = (_activeKey) => { - setActiveKey(_activeKey) - } - return ( - - ) -} + 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 [loading, activeAgency, getAgencyProducts] = useProductsStore((state) => [state.loading, state.activeAgency, state.getAgencyProducts]); const handleGetAgencyProducts = () => { getAgencyProducts({ travel_agency_id, use_year, audit_state }); - } + }; useEffect(() => { handleGetAgencyProducts(); return () => {}; - }, [travel_agency_id]) + }, [travel_agency_id]); return ( <> - }> + } loading={loading} >
+ {/* */} + {/* debug: 0 */} + {/* */} +
diff --git a/src/views/products/Index.jsx b/src/views/products/Index.jsx index d1b1495..400db96 100644 --- a/src/views/products/Index.jsx +++ b/src/views/products/Index.jsx @@ -16,7 +16,7 @@ function Index() { const handleSearchAgency = (formVal = undefined) => { const { starttime, endtime, ...param } = formVal || formValuesToSub; - const searchParam = objectMapper(param, { agency: 'travel_agency_ids', startdate: 'edit_date1', enddate: 'edit_date2' }); + const searchParam = objectMapper(param, { agency: 'travel_agency_ids', startdate: 'edit_date1', enddate: 'edit_date2', year: 'use_year' }); setSearchValues(searchParam); searchAgency(searchParam); } @@ -39,8 +39,8 @@ function Index() { key: 'action', render: (_, r) => ( - {t('Edit')} - {t('Audit')} + {t('Edit')} + {t('Audit')} ), }, From 079b74c3b4a58aae0045803f33905f90a0e81236 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Tue, 25 Jun 2024 10:54:14 +0800 Subject: [PATCH 023/120] =?UTF-8?q?=E4=BA=A7=E5=93=81=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 8 ++++---- src/views/products/{Index.jsx => Manage.jsx} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename src/views/products/{Index.jsx => Manage.jsx} (100%) diff --git a/src/main.jsx b/src/main.jsx index bd73f53..6df42c7 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -32,11 +32,11 @@ import Airticket from "@/views/airticket/Index"; import AirticketPlan from "@/views/airticket/Plan"; import { ThemeContext } from '@/stores/ThemeContext' -import ProductsIndex from '@/views/products/Index'; +import ProductsManage from '@/views/products/Manage'; import ProductsDetail from '@/views/products/Detail'; import ProductsAudit from '@/views/products/Audit'; import Gysgl from "@/views/gysgl/Index" -import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } from '@/config' +import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT } from '@/config' import './i18n'; @@ -65,8 +65,8 @@ const router = createBrowserRouter([ { path: "invoice/paid/detail/:flid", element: }, { path: "airticket",element: }, { path: "airticket/plan/:coli_sn",element:}, - { path: "products",element:}, - { path: "products/:travel_agency_id/:use_year/:audit_state/audit",element:}, + { path: "products",element: }, + { path: "products/:travel_agency_id/:use_year/:audit_state/audit",element:}, { path: "products/:travel_agency_id/:use_year/:audit_state/edit",element:}, { path: "gysgl",element:}, ] diff --git a/src/views/products/Index.jsx b/src/views/products/Manage.jsx similarity index 100% rename from src/views/products/Index.jsx rename to src/views/products/Manage.jsx From 7409ec1c16215b9d1d28d14e023caf904c90d78f Mon Sep 17 00:00:00 2001 From: Lei OT Date: Tue, 25 Jun 2024 11:21:16 +0800 Subject: [PATCH 024/120] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=E4=BA=A7=E5=93=81=E7=9A=84=E9=99=84=E5=8A=A0=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Products/Index.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js index a18f098..5ce07d3 100644 --- a/src/stores/Products/Index.js +++ b/src/stores/Products/Index.js @@ -15,6 +15,16 @@ export const getAgencyProductsAction = async (param) => { return errcode !== 0 ? { agency: {}, products: [] } : result; }; +/** + * 获取指定产品的附加项目 + * @param {object} param { id, travel_agency_id, use_year } + */ +export const getAgencyProductExtrasAction = async (param) => { + const _param = { ...param, use_year: (param.use_year || '').replace('all', '') }; + const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/products_extras`, _param); + return errcode !== 0 ? [] : result; +}; + export const postProductsQuoteAudit = async (auditState, quoteRow) => { const postbody = { audit_state: auditState, @@ -59,11 +69,11 @@ export const useProductsStore = create( ...initialState, // state actions - setLoading: loading => set({ loading }), - setSearchValues: searchValues => set({ searchValues }), + setLoading: (loading) => set({ loading }), + setSearchValues: (searchValues) => set({ searchValues }), setAgencyList: (agencyList) => set({ agencyList }), - setActiveAgency: activeAgency => set({ activeAgency }), - setAgencyProducts: agencyProducts => set({ agencyProducts }), + setActiveAgency: (activeAgency) => set({ activeAgency }), + setAgencyProducts: (agencyProducts) => set({ agencyProducts }), reset: () => set(initialState), @@ -80,12 +90,16 @@ export const useProductsStore = create( const { setLoading, setActiveAgency, setAgencyProducts } = get(); setLoading(true); const res = await getAgencyProductsAction(param); - const productsData = groupBy(res.products, row => row.info.product_type_id); + const productsData = groupBy(res.products, (row) => row.info.product_type_id); setAgencyProducts(productsData); setActiveAgency(res.agency); setLoading(false); }, + getAgencyProductExtras: async (param) => { + const res = await getAgencyProductExtrasAction(param); + // todo: + }, })) ); export default useProductsStore; From 261849fa5f2ebbfc25967c22bf3fe0261a773495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=96=87=E5=BC=BA=40HWQ-PC?= Date: Tue, 25 Jun 2024 11:41:44 +0800 Subject: [PATCH 025/120] =?UTF-8?q?1.=E4=BF=AE=E6=94=B9=E4=BE=9B=E5=BA=94?= =?UTF-8?q?=E5=95=86=E6=8A=A5=E4=BB=B7=E9=A1=B5=E9=9D=A2=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=202.=E5=A2=9E=E5=8A=A0=E6=97=A5=E6=9C=9F=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E5=88=B0components=203.=E5=9B=BD=E9=99=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/products.json | 33 +- public/locales/zh/common.json | 3 +- public/locales/zh/products.json | 31 +- src/components/date.jsx | 51 +++ src/main.jsx | 2 - src/views/App.jsx | 1 - src/views/gysgl/Index.jsx | 581 -------------------------- src/views/gysgl/components/date.jsx | 86 ---- src/views/products/Detail.jsx | 607 +++++++++++++++++++++++++++- 9 files changed, 714 insertions(+), 681 deletions(-) create mode 100644 src/components/date.jsx delete mode 100644 src/views/gysgl/Index.jsx delete mode 100644 src/views/gysgl/components/date.jsx diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 2076875..6e50c59 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -22,5 +22,36 @@ "CreatedBy": "Created By", "CreateDate": "Create Date", "AuditedBy": "Audited By", - "AuditDate": "Audit Date" + "AuditDate": "Audit Date", + + + "productProject": "Product project", + "Code": "Code", + "City": "City", + "Remarks": "Remarks", + "tourTime": "Tour time", + "recommendationRate": "Recommends rate", + "Name": "Name", + "Description":"Description", + "supplierQuotation": "Supplier quotation", + "addQuotation": "Add quotation", + "bindingProducts": "Binding products", + "addBinding": "Add binding", + + "adultPrice": "Adult price", + "childrenPrice": "Child price", + "currency": "Currency", + "Types": "Type", + "number": "Number", + "validityPeriod":"Validity period", + "operation": "Operation", + "price": "Price", + + + "save":"save", + "edit":"edit", + "delete":"delete", + "cancel":"cancel", + "sureCancel":"Sure you want to cancel?", + "sureDelete":"Sure you want to delete?" } diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 1a62dc1..4ef0694 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -54,8 +54,7 @@ "Notice": "通知", "Report": "质量评分", "Airticket": "机票订票", - "Products": "产品管理", - "gysgl": "供应商价格管理" + "Products": "产品管理" }, "Validation": { "Title": "温馨提示", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 326bd78..87b9914 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -22,5 +22,34 @@ "CreatedBy": "提交人员", "CreateDate": "提交时间", "Auditors": "审核人员", - "AuditDate": "审核时间" + "AuditDate": "审核时间", + + "productProject": "产品项目", + "Code": "代码", + "City": "城市", + "Remarks": "备注", + "tourTime": "游览时间", + "recommendationRate": "推荐指数", + "Name":"名称", + "Price":"价格", + "Description":"描述", + "supplierQuotation": "供应商报价", + "addQuotation": "添加报价", + "bindingProducts": "绑定产品", + "addBinding": "添加绑定", + + "adultPrice": "成人价", + "childrenPrice": "儿童价", + "currency": "币种", + "Types": "类型", + "number": "人等", + "validityPeriod":"有效期", + "operation": "操作", + "price": "价格", + "save":"保存", + "edit":"编辑", + "cancel":"取消", + "delete":"删除", + "sureCancel": "确定取消?", + "sureDelete":"确定删除?" } diff --git a/src/components/date.jsx b/src/components/date.jsx new file mode 100644 index 0000000..fec0402 --- /dev/null +++ b/src/components/date.jsx @@ -0,0 +1,51 @@ +import React, { useState } from 'react'; +import { DatePicker, Button } from 'antd'; + +const DateComponent = ({ onDateChange }) => { + const dateFormat = 'YYYY/MM/DD'; + const { RangePicker } = DatePicker; + const [dateRange, setDateRange] = useState(null); + const [selectedDays, setSelectedDays] = useState([]); + + const days = [ + 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' + ]; + + const handleChange = (date, dateString) => { + const range = dateString[0] + "-" + dateString[1]; + setDateRange(range); + onDateChange({ dateRange: range, selectedDays }); + }; + + const handleDayClick = (day) => { + setSelectedDays((prevSelectedDays) => { + const updatedDays = prevSelectedDays.includes(day) + ? prevSelectedDays.filter((d) => d !== day) + : [...prevSelectedDays, day]; + onDateChange({ dateRange, selectedDays: updatedDays }); + return updatedDays; + }); + }; + + return ( +
+

Data

+ +

Weekdays

+
+ {days.map((day, index) => ( + + ))} +
+
+ ); +}; + +export default DateComponent; diff --git a/src/main.jsx b/src/main.jsx index 6e78774..3ed3c89 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -33,7 +33,6 @@ import Airticket from "@/views/airticket/Index"; import ProductsIndex from '@/views/products/Index'; import ProductsDetail from '@/views/products/Detail'; import ProductsAudit from '@/views/products/Audit'; -import Gysgl from "@/views/gysgl/Index" import './i18n'; configure({ @@ -71,7 +70,6 @@ const router = createBrowserRouter([ { path: "products",element:}, { path: "products/:travel_agency_id/audit",element:}, { path: "products/:travel_agency_id",element:}, - { path: "gysgl",element:}, ] }, { diff --git a/src/views/App.jsx b/src/views/App.jsx index 3678318..cbb2949 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -135,7 +135,6 @@ function App() { ), }, - {key: 'gysgl', label:{t('menu.gysgl')}} ]} /> diff --git a/src/views/gysgl/Index.jsx b/src/views/gysgl/Index.jsx deleted file mode 100644 index d50713f..0000000 --- a/src/views/gysgl/Index.jsx +++ /dev/null @@ -1,581 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react'; -import { Button, Card, Col, Row, Breadcrumb, Table, Popconfirm, Form, Input, InputNumber, Tag, Modal, Select, Tree } from 'antd'; -import { Link } from 'react-router-dom'; -import { useTranslation } from 'react-i18next'; -import DateComponent from './components/date'; -import { fetchJSON } from "@/utils/request"; - -const cpxm = [ - { code: "code", name: "简码" }, - { code: "city_name", name: "城市" }, - { code: "remarks", name: "备注" }, - { code: "open_hours", name: "游览时间" }, - { code: "recommends_rate", name: "推荐指数" } -]; - -const initialData = [ - { key: '1', crj: '15', etj: "美元", bz: 'aa', lx: 'as', rq: 'dfae', rd: 'dawd', yxq: 'dawda' }, - { key: '2', crj: '15', etj: "美元", bz: 'aa', lx: 'as', rq: 'dfae', rd: 'dawd', yxq: 'dawda' }, - { key: '3', crj: '15', etj: "美元", bz: 'aa', lx: 'as', rq: 'dfae', rd: 'dawd', yxq: 'dawda' }, - { key: '4', crj: '15', etj: "美元", bz: 'aa', lx: 'as', rq: 'dfae', rd: 'dawd', yxq: 'dawda' }, -]; - -const EditableCell = ({ editing, dataIndex, title, inputType, record, children, handleDateSelect, ...restProps }) => { - let inputNode = inputType === 'number' ? : ; - if (dataIndex === 'yxq' && editing) { - return ( -
- ); - } - - return ( - - ); -}; - -function Index() { - const { t, i18n } = useTranslation(); - const [form] = Form.useForm(); - const [quotation, setQuotation] = useState(initialData); - const [editingKey, setEditingKey] = useState(''); - const [tags, setTags] = useState(['中文', 'English']); - const [isModalVisible, setIsModalVisible] = useState(false); - const [selectedTag, setSelectedTag] = useState('中文'); - const [saveData, setSaveData] = useState(null); - const [datePickerVisible, setDatePickerVisible] = useState(false); - const [currentKey, setCurrentKey] = useState(null); - const [languageStatus, setLanguageStatus] = useState([{ "中文": { title: "", description: "" } }, { "English": { title: "", description: "" } }]); - const [formState, setFormState] = useState({}); - const [selectedNodeKey, setSelectedNodeKey] = useState(null); - - const isEditing = (record) => record.key === editingKey; - - const edit = (record) => { - form.setFieldsValue({ ...record }); - setEditingKey(record.key); - }; - - const cancel = () => { - setEditingKey(''); - }; - - const handleSave = async (key) => { - try { - const row = await form.validateFields(); - const newData = [...quotation]; - - const index = newData.findIndex((item) => key === item.key); - if (index > -1) { - const item = newData[index]; - newData.splice(index, 1, { ...item, ...row }); - setQuotation(newData); - setEditingKey(''); - } else { - newData.push(row); - setQuotation(newData); - setEditingKey(''); - } - } catch (errInfo) { - console.log('Validate Failed:', errInfo); - } - }; - - const handleDelete = (key) => { - const newData = [...quotation]; - const index = newData.findIndex((item) => key === item.key); - newData.splice(index, 1); - setQuotation(newData); - }; - - const handleAdd = () => { - const newData = { - key: `${quotation.length + 1}`, - jg: '', - bz: '', - lx: '', - zm: '', - rq: '', - rd: '', - yxq: '', - }; - setQuotation([...quotation, newData]); - }; - - const handleDateSelect = (key) => { - setCurrentKey(key); - setDatePickerVisible(true); - }; - - const handleDateChange = (date) => { - if (currentKey) { - const newData = [...quotation]; - const index = newData.findIndex((item) => currentKey === item.key); - if (index > -1) { - newData[index].yxq = date; - setQuotation(newData); - setCurrentKey(null); - setDatePickerVisible(false); - } - } - }; - - const columns = [ - { title: '成人价', dataIndex: 'crj', width: '10%', editable: true }, - { title: '儿童价', dataIndex: 'etj', width: '10%', editable: true }, - { title: '币种', dataIndex: 'bz', width: '10%', editable: true }, - { title: '类型', dataIndex: 'lx', width: '10%', editable: true }, - { title: '人群', dataIndex: 'rq', width: '10%', editable: true }, - { title: '人等', dataIndex: 'rd', width: '10%', editable: true }, - { title: '有效期', dataIndex: 'yxq', width: '30%', editable: true }, - { - title: '操作', - dataIndex: 'operation', - render: (_, record) => { - const editable = isEditing(record); - return editable ? ( - - handleSave(record.key)} style={{ marginRight: 8 }}>保存 - 取消 - - ) : ( - - edit(record)} style={{ marginRight: 8 }}>编辑 - handleDelete(record.key)}> - 删除 - - - ); - }, - }, - ]; - - - const mergedColumns = columns.map((col) => { - if (!col.editable) { - return col; - } - return { - ...col, - onCell: (record) => ({ - record, - inputType: col.dataIndex === 'age' ? 'number' : 'text', - dataIndex: col.dataIndex, - title: col.title, - editing: isEditing(record), - handleDateSelect: handleDateSelect, - }), - }; - }); - - const handleTagClick = (tag) => { - setSelectedTag(tag); - - }; - - const showModal = () => setIsModalVisible(true); - - const handleOk = () => setIsModalVisible(false); - - const handleCancel = () => setIsModalVisible(false); - - - const handleTagChange = (value) => { - if (!tags.includes(value)) { - setTags([...tags, value]); - setLanguageStatus([...languageStatus, { [value]: { title: "", description: "" } }]); - } - setSelectedTag(value); - setIsModalVisible(false); - }; - - const handleChange = (field, value) => { - const updatedLanguageStatus = languageStatus.map(lang => { - if (lang[selectedTag]) { - return { ...lang, [selectedTag]: { ...lang[selectedTag], [field]: value } }; - } - return lang; - }); - setLanguageStatus(updatedLanguageStatus); - }; - - const findLanguageDetails = (tag) => { - const lang = languageStatus.find(lang => lang[tag]); - return lang ? lang[tag] : { title: "", description: "" }; - }; - - - //树组件方法 - const handleNodeSelect = async (_, { node }) => { - // 保存当前表单数据 - if (selectedNodeKey) { - await handleSaveForm(); - } - - // 更新选中的节点 - setSelectedNodeKey(node.key); - - // 加载新节点的表单数据 - if (formState[node.key]) { - form.setFieldsValue(formState[node.key]); - setQuotation(formState[node.key].quotation || []); - setLanguageStatus(formState[node.key].lgc_details || languageStatus); - } else { - form.resetFields(); - setQuotation(initialData); - setLanguageStatus([{ "中文": { title: "", description: "" } }, { "English": { title: "", description: "" } }]); - } - }; - - const handleSaveForm = async () => { - try { - const values = await form.validateFields(); - const newFormState = { - ...formState, - [selectedNodeKey]: { - ...values, - quotation, - lgc_details: languageStatus, - }, - }; - setFormState(newFormState); - } catch (error) { - console.error('Validation failed:', error); - } - }; - - - const onSave = (values) => { - const tempData = values; - tempData['quotation'] = quotation; - tempData['extras'] = quotationBdcp; - tempData['lgc_details'] = languageStatus; - setSaveData(tempData); - console.log("保存的数据",tempData) - }; - - - //绑定产品 - const initialDataBdcp = [ - { key: '1', mc: '15', jg: "美元", lx: 'aa' }, - { key: '2', mc: '15', jg: "美元", lx: 'aa' }, - { key: '3', mc: '15', jg: "美元", lx: 'aa' }, - { key: '4', mc: '15', jg: "美元", lx: 'aa' }, - ] - // const [form] = Form.useForm(); - const [quotationBdcp, setQuotationBdcp] = useState(initialDataBdcp); - const isEditingBdcp = (record) => record.key === editingKeyBdcp; - const [editingKeyBdcp, setEditingKeyBdcp] = useState(''); - - const editBdcp = (record) => { - form.setFieldsValue({ ...record }); - setEditingKeyBdcp(record.key); - }; - const cancelBdcp = () => { - setEditingKeyBdcp(''); - }; - const EditableCellBdcp = ({ - editing, - dataIndex, - title, - inputType, - record, - index, - children, - ...restProps - }) => { - const inputNode = inputType === 'number' ? : ; - return ( - - ); - }; - const bdcpColums = [ - { title: '名称', dataIndex: 'mc', width: '15%', editable: true }, - { title: '价格', dataIndex: 'jg', width: '15%', editable: true }, - { title: '类型', dataIndex: 'lx', width: '40%', editable: true }, - { - title: '操作', - dataIndex: 'operation', - render: (_, record) => { - const editable = isEditingBdcp(record); - return editable ? ( - - handleSaveBdcp(record.key)} style={{ marginRight: 8 }}>保存 - 取消 - - ) : ( - - editBdcp(record)} style={{ marginRight: 8 }}>编辑 - handleDeleteBdcp(record.key)}> - 删除 - - - ); - }, - }, - ].map(col => { - if (!col.editable) { - return col; - } - return { - ...col, - onCell: record => ({ - record, - inputType: col.dataIndex === 'jg' ? 'number' : 'text', - dataIndex: col.dataIndex, - title: col.title, - editing: isEditingBdcp(record), - }), - }; - }); - - - - const handleAddBdcp = () => { - const newData = { - key: `${quotationBdcp.length + 1}`, - mc: '', - jg: '', - lx: '', - }; - setQuotationBdcp([...quotationBdcp, newData]); - setEditingKeyBdcp(''); // 添加这一行 - }; - - const handleSaveBdcp = async (key) => { - try { - const row = await form.validateFields(); - const newData = [...quotationBdcp]; - - const index = newData.findIndex((item) => key === item.key); - if (index > -1) { - const item = newData[index]; - newData.splice(index, 1, { ...item, ...row }); - setQuotationBdcp(newData); - setEditingKeyBdcp(''); - } else { - newData.push(row); - setQuotationBdcp(newData); - setEditingKeyBdcp(''); - } - } catch (errInfo) { - console.log('Validate Failed:', errInfo); - } - }; - - - const handleDeleteBdcp = (key) => { - const newData = [...quotationBdcp]; - const index = newData.findIndex((item) => key === item.key); - newData.splice(index, 1); - setQuotationBdcp(newData); - }; - - const componentsBdcp = { - body: { - cell: EditableCellBdcp, - }, - }; - - - //Effect - useEffect(() => { - if (saveData) { - } - }, [saveData]); - - useEffect(() => { - // const { errcode, result } = fetchJSON('http://127.0.0.1:4523/m1/2602949-1933890-default/Service_BaseInfoWeb/travel_agency_products'); - console.log("get请求") - }, []) - - useEffect(() => { - if (selectedNodeKey) { - handleSaveForm(); - } - }, [selectedNodeKey]); - - - return ( -
- -
- - - - - - -
- 供应商 }, - { title: 综费 }, - { title: '文章列表' } - ]} /> - } - > -

产品项目

- - {cpxm.map((item, index) => ( -
- - - - - ))} - - - - {tags.map(tag => ( - handleTagClick(tag)} - color={tag === selectedTag ? 'blue' : undefined} - style={{ cursor: 'pointer' }} - > - {tag} - - ))} - + - - }> - - - - - - handleChange('title', e.target.value)} - /> - - - handleChange('description', e.target.value)} - /> - - - - - -

供应商报价

- -
- {children} - - - {editing ? ( - - {inputNode} - - ) : ( - children - )} - - {editing ? ( - - {inputNode} - - ) : ( - children - )} -
- - - - - -

绑定产品

- -
- - - - - - - - - - - {datePickerVisible && ( - setDatePickerVisible(false)} - onCancel={() => setDatePickerVisible(false)} - > - - - )} - - ); -} -export default Index; - - diff --git a/src/views/gysgl/components/date.jsx b/src/views/gysgl/components/date.jsx deleted file mode 100644 index 51bc9ca..0000000 --- a/src/views/gysgl/components/date.jsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { useState } from 'react'; -import { DatePicker, Input, Button } from 'antd'; -import dayjs from 'dayjs'; - -const DateComponent = ({ onDateChange }) => { - const dateFormat = 'YYYY/MM/DD'; - const { RangePicker } = DatePicker; - - const [selectedMonths, setSelectedMonths] = useState([]); - const [selectedDays, setSelectedDays] = useState([]); - - const handleChange = (date, dateString) => { - const dateFw = dateString[0] + "-" + dateString[1]; - onDateChange(dateFw); - }; - - const months = [ - 'January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December' - ]; - - const days = [ - 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' - ]; - - const handleMonthClick = (month) => { - setSelectedMonths((prevSelectedMonths) => { - if (prevSelectedMonths.includes(month)) { - return prevSelectedMonths.filter((m) => m !== month); - } else { - return [...prevSelectedMonths, month]; - } - }); - }; - - const handleDayClick = (day) => { - setSelectedDays((prevSelectedDays) => { - if (prevSelectedDays.includes(day)) { - return prevSelectedDays.filter((d) => d !== day); - } else { - return [...prevSelectedDays, day]; - } - }); - }; - - return ( -
-

Title

- -

Data

- -

Months

-
- {months.map((month, index) => ( - - ))} -
-

Weekdays

-
- {days.map((day, index) => ( - - ))} -
-
- ); -}; - -export default DateComponent; diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx index 807e0e9..7188431 100644 --- a/src/views/products/Detail.jsx +++ b/src/views/products/Detail.jsx @@ -1,10 +1,603 @@ -import { createContext, useContext, useEffect, useState } from 'react'; -import { Table } from 'antd'; +import React, { useState, useEffect, useRef } from 'react'; +import { Button, Card, Col, Row, Breadcrumb, Table, Popconfirm, Form, Input, InputNumber, Tag, Modal, Select, Tree } from 'antd'; +import { Link } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import DateComponent from '@/components/date'; +import { fetchJSON } from "@/utils/request"; -const Detail = ((props) => { + + +function Index() { + const { t } = useTranslation(); + const [form] = Form.useForm(); + const [editingKey, setEditingKey] = useState(''); + const [tags, setTags] = useState(['中文', 'English']); + const [isModalVisible, setIsModalVisible] = useState(false); + const [selectedTag, setSelectedTag] = useState('中文'); + const [saveData, setSaveData] = useState(null); + const [datePickerVisible, setDatePickerVisible] = useState(false); + const [currentKey, setCurrentKey] = useState(null); + const [languageStatus, setLanguageStatus] = useState([{ "中文": { title: "", description: "" } }, { "English": { title: "", description: "" } }]); + const [formState, setFormState] = useState({}); + const [selectedNodeKey, setSelectedNodeKey] = useState(null); + const [selectedDateData, setSelectedDateData] = useState({ dateRange: null, selectedDays: [] }); + + const productProject = [ + { code: "code", name: t('products:Code') }, + { code: "city_name", name: t('products:City') }, + { code: "remarks", name: t('products:Remarks') }, + { code: "open_hours", name: t('products:tourTime') }, + { code: "recommends_rate", name: t('products:recommendationRate') } + ]; + + const initialData = [ + { key: '1', adult_cost: '15', child_cost: "美元", currency: 'aa', age_type: 'as', group_size: 'dawd', validityPeriod: 'dawda' }, + { key: '2', adult_cost: '15', child_cost: "美元", currency: 'aa', age_type: 'as', group_size: 'dawd', validityPeriod: 'dawda' }, + { key: '3', adult_cost: '15', child_cost: "美元", currency: 'aa', age_type: 'as', group_size: 'dawd', validityPeriod: 'dawda' }, + { key: '4', adult_cost: '15', child_cost: "美元", currency: 'aa', age_type: 'as', group_size: 'dawd', validityPeriod: 'dawda' }, + ]; + const [quotation, setQuotation] = useState(initialData); + const isEditing = (record) => record.key === editingKey; + + const edit = (record) => { + form.setFieldsValue({ ...record }); + setEditingKey(record.key); + }; + + const cancel = () => { + setEditingKey(''); + }; + + const handleSave = async (key) => { + try { + const { info, ...restRow } = await form.validateFields(); + const newData = [...quotation]; + const index = newData.findIndex((item) => key === item.key); + if (index > -1) { + const item = newData[index]; + newData.splice(index, 1, { ...item, ...restRow }); + delete newData[index].quotation + delete newData[index].extras + setQuotation(newData); + setEditingKey(''); + } else { + newData.push(restRow); + setQuotation(newData); + setEditingKey(''); + } + } catch (errInfo) { + console.log('Validate Failed:', errInfo); + } + }; + + const handleDelete = (key) => { + const newData = [...quotation]; + const index = newData.findIndex((item) => key === item.key); + newData.splice(index, 1); + setQuotation(newData); + }; + + const handleAdd = () => { + const newData = { + key: `${quotation.length + 1}`, + value: '', + currency: '', + age_type: '', + weekdays: '', + group_size: '', + validityPeriod: '', + }; + setQuotation([...quotation, newData]); + }; + + const handleDateSelect = (key) => { + setCurrentKey(key); + setDatePickerVisible(true); + }; + + const handleDateChange = ({ dateRange, selectedDays }) => { + console.log('Date Range:', dateRange); + console.log('Selected Days:', selectedDays); + setSelectedDateData({ dateRange, selectedDays }) + }; + + const handleDateOk = () => { + const { dateRange } = selectedDateData; + + if (currentKey !== null) { + const newData = [...quotation]; + const index = newData.findIndex((item) => currentKey === item.key); + if (index > -1) { + newData[index].validityPeriod = dateRange; + + console.log() + setQuotation(newData); + setCurrentKey(null); + } + } + setDatePickerVisible(false); + } + + const EditableCell = ({ editing, dataIndex, title, inputType, record, children, handleDateSelect, ...restProps }) => { + let inputNode = inputType === 'number' ? : ; + if (dataIndex === 'validityPeriod' && editing) { + return ( +
+ ); + } + + return ( + + ); + }; + + const columns = [ + { title: t('products:adultPrice'), dataIndex: 'adult_cost', width: '10%', editable: true }, + { title: t('products:childrenPrice'), dataIndex: 'child_cost', width: '10%', editable: true }, + { title: t('products:currency'), dataIndex: 'currency', width: '10%', editable: true }, + { title: t('products:Types'), dataIndex: 'age_type', width: '10%', editable: true }, + { title: t('products:number'), dataIndex: 'group_size', width: '10%', editable: true }, + { title: t('products:validityPeriod'), dataIndex: 'validityPeriod', width: '30%', editable: true }, + { + title: t('products:operation'), + dataIndex: 'operation', + render: (_, record) => { + const editable = isEditing(record); + return editable ? ( + + handleSave(record.key)} style={{ marginRight: 8 }}>{t('products:save')} + {t('products:cancel')} + + ) : ( + + edit(record)} style={{ marginRight: 8 }}>{t('products:edit')} + handleDelete(record.key)}> + {t('products:delete')} + + + ); + }, + }, + ]; + + + const mergedColumns = columns.map((col) => { + if (!col.editable) { + return col; + } + return { + ...col, + onCell: (record) => ({ + record, + inputType: col.dataIndex === 'age' ? 'number' : 'text', + dataIndex: col.dataIndex, + title: col.title, + editing: isEditing(record), + handleDateSelect: handleDateSelect, + }), + }; + }); + + const handleTagClick = (tag) => { + setSelectedTag(tag); + + }; + + const showModal = () => setIsModalVisible(true); + + const handleOk = () => setIsModalVisible(false); + + const handleCancel = () => setIsModalVisible(false); + + + const handleTagChange = (value) => { + if (!tags.includes(value)) { + setTags([...tags, value]); + setLanguageStatus([...languageStatus, { [value]: { title: "", description: "" } }]); + } + setSelectedTag(value); + setIsModalVisible(false); + }; + + const handleChange = (field, value) => { + const updatedLanguageStatus = languageStatus.map(lang => { + if (lang[selectedTag]) { + return { ...lang, [selectedTag]: { ...lang[selectedTag], [field]: value } }; + } + return lang; + }); + setLanguageStatus(updatedLanguageStatus); + }; + + const findLanguageDetails = (tag) => { + const lang = languageStatus.find(lang => lang[tag]); + return lang ? lang[tag] : { title: "", description: "" }; + }; + + + //树组件方法 + const handleNodeSelect = async (_, { node }) => { + + // 如果点击的是同一个节点,不做任何操作 + if (selectedNodeKey === node.key) return; + + // 保存当前表单数据 + if (selectedNodeKey) { + await handleSaveForm(); + } + + // 更新选中的节点 + setSelectedNodeKey(node.key); + + // 加载新节点的表单数据 + if (formState[node.key]) { + form.setFieldsValue(formState[node.key]); + setQuotation(formState[node.key].quotation || []); + setLanguageStatus(formState[node.key].lgc_details || languageStatus); + } else { + form.resetFields(); + setQuotation(initialData); + setLanguageStatus([{ "中文": { title: "", description: "" } }, { "English": { title: "", description: "" } }]); + } + }; + + const handleSaveForm = async () => { + try { + const values = await form.validateFields(); + const newFormState = { + ...formState, + [selectedNodeKey]: { + ...values, + quotation, + lgc_details: languageStatus, + }, + }; + setFormState(newFormState); + } catch (error) { + console.error('Validation failed:', error); + } + }; + + + const onSave = (values) => { + const tempData = values; + tempData['quotation'] = quotation; + tempData['extras'] = bindingData; + tempData['lgc_details'] = languageStatus; + setSaveData(tempData); + console.log("保存的数据", tempData) + }; + + + //绑定产品 + const initBindingData = [ + { key: '1', title: '15', value: "美元", age_type: 'aa' }, + { key: '2', title: '15', value: "美元", age_type: 'aa' }, + { key: '3', title: '15', value: "美元", age_type: 'aa' }, + { key: '4', title: '15', value: "美元", age_type: 'aa' }, + ] + const [bindingData, setBindingData] = useState(initBindingData); + const isEditingBinding = (record) => record.key === editingKeyBinding; + const [editingKeyBinding, setEditingKeyBinding] = useState(''); + + const editBinding = (record) => { + form.setFieldsValue({ ...record }); + setEditingKeyBinding(record.key); + }; + const cancelBinding = () => { + setEditingKeyBinding(''); + }; + const EditableCellBinding = ({ + editing, + dataIndex, + title, + inputType, + record, + index, + children, + ...restProps + }) => { + const inputNode = inputType === 'number' ? : ; return ( - <> - + ); -}); -export default Detail; + }; + const bindingColums = [ + { title: t('products:Name'), dataIndex: 'title', width: '15%', editable: true }, + { title: t('products:price'), dataIndex: 'value', width: '15%', editable: true }, + { title: t('products:Types'), dataIndex: 'age_type', width: '40%', editable: true }, + { + title: t('products:operation'), + dataIndex: 'operation', + render: (_, record) => { + const editable = isEditingBinding(record); + return editable ? ( + + handleSaveBinding(record.key)} style={{ marginRight: 8 }}>{t('products:save')} + {t('products:cancel')} + + ) : ( + + editBinding(record)} style={{ marginRight: 8 }}>{t('products:edit')} + handleDeleteBinding(record.key)}> + {t('products:delete')} + + + ); + }, + }, + ].map(col => { + if (!col.editable) { + return col; + } + return { + ...col, + onCell: record => ({ + record, + inputType: col.dataIndex === 'value' ? 'number' : 'text', + dataIndex: col.dataIndex, + title: col.title, + editing: isEditingBinding(record), + }), + }; + }); + + + + const handleAddBinding = () => { + const newData = { + key: `${bindingData.length + 1}`, + title: '', + value: '', + age_type: '', + }; + setBindingData([...bindingData, newData]); + setEditingKeyBinding(''); // 添加这一行 + }; + + const handleSaveBinding = async (key) => { + try { + const row = await form.validateFields(); + const newData = [...bindingData]; + const { value, title, age_type } = row + const index = newData.findIndex((item) => key === item.key); + if (index > -1) { + const item = newData[index]; + newData.splice(index, 1, { ...item, value, title, age_type }); + + setBindingData(newData); + setEditingKeyBinding(''); + } else { + newData.push(row); + setBindingData(newData); + setEditingKeyBinding(''); + } + } catch (errInfo) { + console.log('Validate Failed:', errInfo); + } + }; + + + const handleDeleteBinding = (key) => { + const newData = [...bindingData]; + const index = newData.findIndex((item) => key === item.key); + newData.splice(index, 1); + setBindingData(newData); + }; + + const componentsBinding = { + body: { + cell: EditableCellBinding, + }, + }; + + const treeData = [ + { + title: '综费', + key: 'zf', + selectable: false, + children: [{ title: '北京怡然假日', key: 'bjyrjr' }] + }, + { + title: '车费', + key: 'cf', + selectable: false, + children: [ + { title: '北京', key: 'bj' }, + { title: '天津', key: 'tj' }, + { title: '北京-天津', key: 'bj-tj-3-5' } + ] + } + ] + + + + //Effect + useEffect(() => { + if (saveData) { + + } + }, [saveData]); + + useEffect(() => { + // const { errcode, result } = fetchJSON('http://127.0.0.1:4523/m1/2602949-1933890-default/Service_BaseInfoWeb/travel_agency_products'); + console.log("get请求") + + }, []) + + useEffect(() => { + if (selectedNodeKey) { + handleSaveForm(); + } + }, [selectedNodeKey]); + + + return ( +
+ +
+ + + + + + +
+ 供应商 }, + { title: 综费 }, + { title: '文章列表' } + ]} /> + } + > +

{t('products:productProject')}

+ + {productProject.map((item, index) => ( +
+ + + + + ))} + + + + {tags.map(tag => ( + handleTagClick(tag)} + color={tag === selectedTag ? 'blue' : undefined} + style={{ cursor: 'pointer' }} + > + {tag} + + ))} + + + + }> + + + + + + handleChange('title', e.target.value)} + /> + + + handleChange('description', e.target.value)} + /> + + + + + +

{t('products:supplierQuotation')}

+ +
+ {children} + + + {editing ? ( + + {inputNode} + + ) : ( + children + )} + + {editing ? ( + + {inputNode} + + ) : ( + children + )} +
+ + + + + +

{t('products:bindingProducts')}

+ +
+ + + + + + + + + + + {datePickerVisible && ( + setDatePickerVisible(false)} + > + + + )} + + ); +} +export default Index; + + From 3c533c96b41316a6e1f6a6ab9e0bb3de33bbb188 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Tue, 25 Jun 2024 13:46:31 +0800 Subject: [PATCH 026/120] style: --- src/components/SecondHeaderWrapper.jsx | 5 +++-- src/views/products/Audit.jsx | 4 +--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/SecondHeaderWrapper.jsx b/src/components/SecondHeaderWrapper.jsx index 41cec6a..9727e4a 100644 --- a/src/components/SecondHeaderWrapper.jsx +++ b/src/components/SecondHeaderWrapper.jsx @@ -1,5 +1,5 @@ import { Outlet, useNavigate } from 'react-router-dom'; -import { Layout, Flex, theme, Spin } from 'antd'; +import { Layout, Flex, theme, Spin, Divider } from 'antd'; import BackBtn from './BackBtn'; const { Content, Header } = Layout; @@ -10,7 +10,7 @@ const HeaderWrapper = ({ children, header, loading, ...props }) => { } = theme.useToken(); return ( <> - +
@@ -19,6 +19,7 @@ const HeaderWrapper = ({ children, header, loading, ...props }) => {
+
{children || } diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index 4e30330..9a8371b 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -35,7 +35,7 @@ const Header = ({ title, agency, refresh, ...props }) => { return (
-

{title} {use_year}

+

{title}{use_year}

{/* */} {/* */} @@ -178,8 +178,6 @@ const Audit = ({ ...props }) => { return ( <> } loading={loading} > -
- {/* */} {/* debug: 0 */} {/* */} From 1b97cb41ddf8058076a6d77c7a38e9371a016bac Mon Sep 17 00:00:00 2001 From: Lei OT Date: Tue, 25 Jun 2024 14:06:57 +0800 Subject: [PATCH 027/120] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=86=B2=E7=AA=81?= =?UTF-8?q?=E5=92=8C=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/common.json | 3 +++ public/locales/en/products.json | 17 ++++++------ public/locales/zh/common.json | 3 +++ public/locales/zh/products.json | 47 ++++++++++++++++----------------- 4 files changed, 37 insertions(+), 33 deletions(-) diff --git a/public/locales/en/common.json b/public/locales/en/common.json index dd10cb2..b8dc2bb 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -25,6 +25,9 @@ "Export": "Export", "Copy": "Copy", + "sureCancel": "Sure you want to cancel?", + "sureDelete":"Sure you want to delete?", + "Table": { "Total": "Total {{total}} items" }, diff --git a/public/locales/en/products.json b/public/locales/en/products.json index cca7397..51e8eb9 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -34,7 +34,7 @@ "CreateDate": "Create Date", "AuditedBy": "Audited By", "AuditDate": "Audit Date", - + "productProject": "Product project", "Code": "Code", "City": "City", @@ -57,14 +57,6 @@ "operation": "Operation", "price": "Price", - - "save":"save", - "edit":"edit", - "delete":"delete", - "cancel":"cancel", - "sureCancel":"Sure you want to cancel?", - "sureDelete":"Sure you want to delete?" - "Quotation": "Quotation", "Offer": "Offer", @@ -84,5 +76,12 @@ "Child": "Child" }, + "save":"save", + "edit":"edit", + "delete":"delete", + "cancel":"cancel", + "sureCancel":"Sure you want to cancel?", + "sureDelete":"Sure you want to delete?", + "#": "#" } diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 80ceafb..547bd74 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -25,6 +25,9 @@ "Export": "导出", "Copy": "复制", + "sureCancel": "确定取消?", + "sureDelete":"确定删除?", + "Table": { "Total": "共 {{total}} 条" }, diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 6c3b6b9..8a336cc 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -37,28 +37,6 @@ "AuditedBy": "审核人员", "AuditDate": "审核时间", - "Quotation": "报价", - "Offer": "报价", - "Unit": "单位", - "GroupSize": "人等", - "UseDates": "使用日期", - - "Weekdays": "有效日/周X", - "OnWeekdays": "周: ", - "Unlimited": "不限", - - "UseYear": "年份", - - "AgeType": { - "Type": "人群", - "Adult": "成人", - "Child": "儿童" - }, - - "#": "#" - "Auditors": "审核人员", - "AuditDate": "审核时间", - "productProject": "产品项目", "Code": "代码", "City": "城市", @@ -81,10 +59,31 @@ "validityPeriod":"有效期", "operation": "操作", "price": "价格", + + "Quotation": "报价", + "Offer": "报价", + "Unit": "单位", + "GroupSize": "人等", + "UseDates": "使用日期", + + "Weekdays": "有效日/周X", + "OnWeekdays": "周: ", + "Unlimited": "不限", + + "UseYear": "年份", + + "AgeType": { + "Type": "人群", + "Adult": "成人", + "Child": "儿童" + }, + "save":"保存", "edit":"编辑", - "cancel":"取消", "delete":"删除", + "cancel":"取消", "sureCancel": "确定取消?", - "sureDelete":"确定删除?" + "sureDelete":"确定删除?", + + "#": "#" } From 702d7876c07d76c86e317f3d54f48eb60790eef4 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Tue, 25 Jun 2024 15:15:52 +0800 Subject: [PATCH 028/120] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E6=9D=83=E9=99=90=E3=80=81=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E5=BC=80=E5=A7=8B=E5=88=A4=E6=96=AD=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 34 +++++++++++------ src/stores/Auth.js | 63 ++++++++++++++++---------------- src/views/App.jsx | 21 ++++++++--- src/views/account/Management.jsx | 6 ++- src/views/account/RoleList.jsx | 2 +- 5 files changed, 76 insertions(+), 50 deletions(-) diff --git a/src/main.jsx b/src/main.jsx index 9114460..b1eca60 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,5 +1,4 @@ import React from "react"; -import { configure } from "mobx"; import ReactDOM from "react-dom/client"; import { createBrowserRouter, @@ -32,19 +31,32 @@ import InvoicePaidDetail from "@/views/invoice/PaidDetail"; import Airticket from "@/views/airticket/Index"; import AirticketPlan from "@/views/airticket/Plan"; import { ThemeContext } from '@/stores/ThemeContext' +import { usingStorage } from '@/hooks/usingStorage' +import { isNotEmpty } from '@/utils/commons' +import { appendRequestParams } from '@/utils/request' +import useAuthStore from '@/stores/Auth' import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } from '@/config' import './i18n'; -configure({ - useProxies: "ifavailable", - enforceActions: "observed", - computedRequiresReaction: true, - observableRequiresReaction: false, - reactionRequiresObservable: true, - disableErrorBoundaries: process.env.NODE_ENV == "production" -}); +onInit(() => { + + const { loginToken, userId } = usingStorage() + + if (isNotEmpty(loginToken)) { + appendRequestParams('token', loginToken) + + console.info('loginToken') + } + + if (isNotEmpty(userId)) { + useAuthStore.getState().loadUserPermission(userId) + console.info('loadUserPermission') + } +}) + +onInit() const router = createBrowserRouter([ { @@ -80,7 +92,7 @@ const router = createBrowserRouter([ { path: "/logout", element: }, ] } -]); +]) ReactDOM.createRoot(document.getElementById("root")).render( @@ -92,4 +104,4 @@ ReactDOM.createRoot(document.getElementById("root")).render( /> // -); +) diff --git a/src/stores/Auth.js b/src/stores/Auth.js index d2371d7..e7fc4bb 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -9,6 +9,8 @@ const KEY_TRAVEL_AGENCY_ID = 'G-INT:TRAVEL_AGENCY_ID' const KEY_USER_ID = 'G-INT:USER_ID' const KEY_USER_DETAIL = 'G-JSON:USER_DETAIL' +const WILDCARD_TOKEN = '*' + export const fetchLoginToken = async (username, password) => { const formData = new FormData() @@ -28,6 +30,13 @@ export const fetchUserDetail = async (loginToken) => { return errcode !== 0 ? {} : Result } +export const fetchPermissionListByUserId = async (userId) => { + + const { errcode, result } = await fetchJSON( + `${HT_HOST}/service-CooperateSOA/get_account_permission_list`, { wu_id: userId}) + return errcode !== 0 ? {} : result +} + async function fetchLastRequet() { const { errcode, result } = await fetchJSON(`${HT_HOST}/service-CooperateSOA/GetLastReqDate`) return errcode !== 0 ? {} : result @@ -41,68 +50,60 @@ const useAuthStore = create((set, get) => ({ loginStatus: 0, - loginUser: { - token: '', - telephone: '', - emailAddress: '', - cityId: 0, - permissionList: [], - }, + permissionList: [], isPermitted: (perm) => { + const { permissionList } = get() // 测试权限使用: // if (perm === '/account/management') return false // if (perm === '/account/role/new') return false - return true + // return true // 以上是 Hardcode 判断 // 以下是权限列表从数据库读取后使用的方法 - // return this.permissionList.some((value, key, arry) => { - // if (value.indexOf(WILDCARD_TOKEN) > -1) { - // return true - // } - // if (value === perm) { - // return true - // } - // return false - // }) - + return permissionList.some((value, key, arry) => { + if (value.indexOf(WILDCARD_TOKEN) > -1) { + return true + } + if (value === perm) { + return true + } + return false + }) }, validateUserPassword: async (usr, pwd) => { - const { startTokenInterval } = get() + const { startTokenInterval, loadUserPermission } = get() const { setStorage } = usingStorage() - const { token: loginToken } = await fetchLoginToken(usr, pwd) - - const userDetail = await fetchUserDetail(loginToken) + const { token: loginToken, WU_ID: userId } = await fetchLoginToken(usr, pwd) + await loadUserPermission(userId) set(() => ({ - loginUser: { - telephone: userDetail.LkPhone, - emailAddress: userDetail.LMI_listmail, - cityId: userDetail.citysn, - }, tokenTimeout: false, loginStatus: 302 })) setStorage(KEY_LOGIN_TOKEN, loginToken) - setStorage(KEY_USER_ID, userDetail.LMI_SN) + setStorage(KEY_USER_ID, userId) setStorage(KEY_TRAVEL_AGENCY_ID, userDetail.LMI_VEI_SN) - setStorage(KEY_USER_DETAIL, {username: userDetail.LoginName, travelAgencyName: userDetail.VName}) appendRequestParams('token', loginToken) // loadPageSpy(`${json.Result.VName}-${json.Result.LoginName}`) startTokenInterval() }, + loadUserPermission: async(userId) => { + const permissionResult = await fetchPermissionListByUserId(userId) + set(() => ({ + permissionList: permissionResult.map(p => p.res_pattern) + })) + }, + logout: () => { const { tokenInterval } = get() const { clearStorage } = usingStorage() clearStorage() clearInterval(tokenInterval) set(() => ({ - loginUser: { - }, loginStatus: 0, tokenInterval: null, tokenTimeout: true diff --git a/src/views/App.jsx b/src/views/App.jsx index 607dfe3..1882319 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -5,7 +5,6 @@ import { DownOutlined } from '@ant-design/icons'; import 'antd/dist/reset.css'; import AppLogo from '@/assets/logo-gh.png'; import { isEmpty } from '@/utils/commons'; -import { appendRequestParams } from '@/utils/request' import Language from '../i18n/LanguageSwitcher'; import { useTranslation } from 'react-i18next'; import zhLocale from 'antd/locale/zh_CN'; @@ -15,6 +14,7 @@ import ErrorBoundary from '@/components/ErrorBoundary' import { BUILD_VERSION, } from '@/config'; import useNoticeStore from '@/stores/Notice'; import useAuthStore from '@/stores/Auth' +import { fetchUserDetail} from '@/stores/Auth' import { usingStorage } from '@/hooks/usingStorage' import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } from '@/config' @@ -23,14 +23,16 @@ const { Header, Content, Footer } = Layout; const { Title } = Typography; function App() { + const { t, i18n } = useTranslation(); const [password, setPassword] = useState('') + const [userDetail, setUserDetail] = useState({}) const [validateUserPassword, tokenTimeout, isPermitted] = useAuthStore( (state) => [state.validateUserPassword, state.tokenTimeout, state.isPermitted]) - const { loginToken, userDetail } = usingStorage() + const { loginToken } = usingStorage() const noticeUnRead = useNoticeStore((state) => state.noticeUnRead) const href = useHref() @@ -40,9 +42,16 @@ function App() { // 除了路由 /p...以外都需要登陆系统 const needToLogin = href !== '/login' && isEmpty(loginToken) - if (!needToLogin) { - appendRequestParams('token', loginToken) - } + useEffect(() => { + console.info('app.useEffect.loginToken: ' + loginToken) + fetchUserDetail(loginToken) + .then(u => { + setUserDetail({ + username: u.LoginName, + travelAgencyName: u.VName, + }) + }) + }, [loginToken]) useEffect(() => { if (needToLogin) { @@ -52,7 +61,7 @@ function App() { useEffect(() => { window.gtag('event', 'page_view', { page_location: window.location.href }); - }, [location]); + }, [location]) const onSubmit = () => { validateUserPassword(userDetail?.username, password) diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index d3d181c..517f03f 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -139,8 +139,8 @@ function Management() { // form.resetFields() } - // [{ value: 33032, label: 'test海外地接B' }] const handleTravelAgencySearch = (newValue) => { + setDataLoading(true) fetchTravelAgencyByName(newValue) .then(result => { setTravelAgencyList(result.map(r => { @@ -150,6 +150,9 @@ function Management() { } })) }) + .finally(() => { + setDataLoading(false) + }) } const handleTravelAgencyChange = (newValue) => { @@ -275,6 +278,7 @@ function Management() { options={travelAgencyList} value={currentTravelAgency} onChange={handleTravelAgencyChange} + loading={dataLoading} showSearch filterOption={false} onSearch={handleTravelAgencySearch} diff --git a/src/views/account/RoleList.jsx b/src/views/account/RoleList.jsx index 3e57a52..13a1501 100644 --- a/src/views/account/RoleList.jsx +++ b/src/views/account/RoleList.jsx @@ -85,7 +85,7 @@ function RoleList() { disableCheckbox: p.res_id == 1, title: p.res_name, value: p.res_id, - key: p.res_name, + key: p.res_id, } }) } From 752bd729eb3a613c8d876f3532adaa05319ced3c Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Tue, 25 Jun 2024 15:17:01 +0800 Subject: [PATCH 029/120] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/main.jsx b/src/main.jsx index b1eca60..5c6a620 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -40,23 +40,15 @@ import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } import './i18n'; -onInit(() => { +const { loginToken, userId } = usingStorage() - const { loginToken, userId } = usingStorage() +if (isNotEmpty(loginToken)) { + appendRequestParams('token', loginToken) +} - if (isNotEmpty(loginToken)) { - appendRequestParams('token', loginToken) - - console.info('loginToken') - } - - if (isNotEmpty(userId)) { - useAuthStore.getState().loadUserPermission(userId) - console.info('loadUserPermission') - } -}) - -onInit() +if (isNotEmpty(userId)) { + useAuthStore.getState().loadUserPermission(userId) +} const router = createBrowserRouter([ { From f2177a9e8e8ec3551173936c3a049835942a669e Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Tue, 25 Jun 2024 15:34:41 +0800 Subject: [PATCH 030/120] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BE=9B?= =?UTF-8?q?=E5=BA=94=E5=95=86ID=E9=94=99=E8=AF=AF=20feat:=20=E5=BA=95?= =?UTF-8?q?=E9=83=A8=E5=A2=9E=E5=8A=A0=E5=85=AC=E5=8F=B8=E5=90=8D=E5=AD=97?= =?UTF-8?q?=E5=92=8C=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Auth.js | 3 ++- src/views/App.jsx | 7 ++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/stores/Auth.js b/src/stores/Auth.js index e7fc4bb..55ab140 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -60,7 +60,7 @@ const useAuthStore = create((set, get) => ({ // return true // 以上是 Hardcode 判断 // 以下是权限列表从数据库读取后使用的方法 - return permissionList.some((value, key, arry) => { + return permissionList.some((value) => { if (value.indexOf(WILDCARD_TOKEN) > -1) { return true } @@ -76,6 +76,7 @@ const useAuthStore = create((set, get) => ({ const { setStorage } = usingStorage() const { token: loginToken, WU_ID: userId } = await fetchLoginToken(usr, pwd) + const userDetail = await fetchUserDetail(loginToken) await loadUserPermission(userId) set(() => ({ diff --git a/src/views/App.jsx b/src/views/App.jsx index 1882319..22d0737 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -43,7 +43,6 @@ function App() { const needToLogin = href !== '/login' && isEmpty(loginToken) useEffect(() => { - console.info('app.useEffect.loginToken: ' + loginToken) fetchUserDetail(loginToken) .then(u => { setUserDetail({ @@ -163,9 +162,7 @@ function App() { isPermitted(PERM_ROLE_NEW) ? { label: {t('account:management.roleList')}, key: '4' } : null, { type: 'divider' }, { label: {t('Logout')}, key: '99' }, - ], - { type: 'divider' }, - { label: <>v{BUILD_VERSION}, key: 'BUILD_VERSION' }, + ] ], }} trigger={['click']} @@ -192,7 +189,7 @@ function App() { }}> {needToLogin ? <>login... : } -
+
China Highlights International Travel Service Co., LTD, Version: {BUILD_VERSION}
From 2bdf22c1408381c5084de4559a81837adf92d28a Mon Sep 17 00:00:00 2001 From: Lei OT Date: Tue, 25 Jun 2024 16:48:38 +0800 Subject: [PATCH 031/120] =?UTF-8?q?feat:=20=E4=BA=A7=E5=93=81:=20=E9=99=84?= =?UTF-8?q?=E5=8A=A0=E9=A1=B9=E7=9B=AE:=20=E6=96=B0=E5=A2=9E;=20=E5=88=A0?= =?UTF-8?q?=E9=99=A4;=20=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/common.json | 4 + public/locales/en/products.json | 5 + public/locales/zh/common.json | 4 + public/locales/zh/products.json | 5 + src/stores/Products/Index.js | 18 ++- src/utils/request.js | 8 +- src/views/products/Detail.jsx | 6 +- src/views/products/Detail/Extras.jsx | 173 +++++++++++++++++++++++++++ 8 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 src/views/products/Detail/Extras.jsx diff --git a/public/locales/en/common.json b/public/locales/en/common.json index b8dc2bb..8ed1854 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -10,6 +10,7 @@ "Confirm": "Confirm", "Close": "Close", "Save": "Save", + "New": "New", "Edit": "Edit", "Audit": "Audit", "Delete": "Delete", @@ -28,6 +29,9 @@ "sureCancel": "Sure you want to cancel?", "sureDelete":"Sure you want to delete?", + "Success": "Success", + "Failed": "Failed", + "Table": { "Total": "Total {{total}} items" }, diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 51e8eb9..56eab4b 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -10,6 +10,11 @@ "Overtravel": "超公里", "Special": "Special" }, + "Components": { + "info": "Product Information", + "Quotation": "Quotation", + "Extras": "Add-on" + }, "auditState": { "New": "New", "Pending": "Pending", diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 547bd74..82b43f9 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -10,6 +10,7 @@ "Confirm": "确认", "Close": "关闭", "Save": "保存", + "New": "新增", "Edit": "编辑", "Audit": "审核", "Delete": "删除", @@ -28,6 +29,9 @@ "sureCancel": "确定取消?", "sureDelete":"确定删除?", + "Success": "成功", + "Failed": "失败", + "Table": { "Total": "共 {{total}} 条" }, diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 8a336cc..08f4872 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -10,6 +10,11 @@ "Overtravel": "超公里", "Special": "特殊项目" }, + "Components": { + "info": "产品信息", + "Quotation": "报价", + "Extras": "附加项目" + }, "auditState": { "New": "新增", "Pending": "待审核", diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js index 5ce07d3..e012aab 100644 --- a/src/stores/Products/Index.js +++ b/src/stores/Products/Index.js @@ -1,7 +1,7 @@ import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; -import { fetchJSON, postForm } from '@/utils/request'; +import { fetchJSON, postForm, postJSON } from '@/utils/request'; import { HT_HOST } from '@/config'; import { groupBy } from '@/utils/commons'; @@ -15,6 +15,22 @@ export const getAgencyProductsAction = async (param) => { return errcode !== 0 ? { agency: {}, products: [] } : result; }; +/** + * todo: + */ +export const addProductExtraAction = async (body) => { + const { errcode, result } = await postJSON(`${HT_HOST}/products/extras`, body); + return errcode === 0 ? true : false; +}; + +/** + * todo: + */ +export const delProductExtrasAction = async (body) => { + const { errcode, result } = await postJSON(`${HT_HOST}/products/extras/del`, body); + return errcode === 0 ? true : false; +}; + /** * 获取指定产品的附加项目 * @param {object} param { id, travel_agency_id, use_year } diff --git a/src/utils/request.js b/src/utils/request.js index ac97803..b99451d 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -113,8 +113,14 @@ export function postForm(url, data) { } export function postJSON(url, obj) { + const initParams = getRequestInitParams(); + const params4get = Object.assign({}, initParams); + const params = new URLSearchParams(params4get).toString(); + const ifp = url.includes('?') ? '&' : '?'; + const fUrl = params !== '' ? `${url}${ifp}${params}` : url; + const headerObj = getRequestHeader() - return fetch(url, { + return fetch(fUrl, { method: 'POST', body: JSON.stringify(obj), headers: { diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx index 7188431..f8335d8 100644 --- a/src/views/products/Detail.jsx +++ b/src/views/products/Detail.jsx @@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'; import DateComponent from '@/components/date'; import { fetchJSON } from "@/utils/request"; +import Extras from './Detail/Extras'; function Index() { @@ -561,7 +562,7 @@ function Index() { -

{t('products:bindingProducts')}

+ {/*

{t('products:bindingProducts')}

{t('products:addBinding')} - + */} + diff --git a/src/views/products/Detail/Extras.jsx b/src/views/products/Detail/Extras.jsx new file mode 100644 index 0000000..0aa1aa2 --- /dev/null +++ b/src/views/products/Detail/Extras.jsx @@ -0,0 +1,173 @@ +import { createContext, useContext, useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { App, Table, Button, Modal, Popconfirm } from 'antd'; +import useProductsStore, { postProductsQuoteAudit, getAgencyProductExtrasAction, getAgencyProductsAction, addProductExtraAction, delProductExtrasAction } from '@/stores/Products/Index'; +import { isEmpty, cloneDeep } from '@/utils/commons'; +import SearchForm from '@/components/SearchForm'; + +import RequireAuth from '@/components/RequireAuth'; +import { PERM_PRODUCTS_MANAGEMENT } from '@/config'; + +const NewAddonModal = ({ onPick, ...props }) => { + const { travel_agency_id, use_year } = useParams(); + const { t } = useTranslation(); + const { notification, message } = App.useApp(); + + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); // bind loading + const [searchLoading, setSearchLoading] = useState(false); + const [searchResult, setSearchResult] = useState([]); + + const onSearchProducts = async (values) => { + const copyObject = cloneDeep(values); + const { starttime, endtime, ...param } = copyObject; + setSearchLoading(true); + setSearchResult([]); + const result = await getAgencyProductsAction({ ...param, audit_state: '0', travel_agency_id, use_year }); + setSearchResult(result?.products || []); + setSearchLoading(false); + }; + const handleAddExtras = async (item) => { + // const success = await fetchBindOrder({ coli_sn, conversationid: currentConversationID }); + // success ? message.success('绑定成功') : message.error('绑定失败'); + // setOpen(false); + if (typeof onPick === 'function') { + onPick(item); + } + }; + + // todo: + const searchResultColumns = [ + { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('products:Title') }, + { + title: t('products:price'), + dataIndex: ['quotation', '0', 'value'], + width: '10rem', + render: (_, { quotation }) => `${quotation[0].value} ${quotation[0].currency} / ${quotation[0].unit_name}`, // todo: 成人 儿童 + }, + { + key: 'action', + title: '', + width: 150, + render: (_, record) => ( + + ), + }, + ]; + const paginationProps = { + showTotal: (total) => t('Table.Total', { total }), + }; + return ( + <> + + + setOpen(false)} destroyOnClose> + { + onSearchProducts(formVal); + }} + /> +
+ + + ); +}; + +/** + * + */ +const Extras = ({ productId, onChange, ...props }) => { + const { t } = useTranslation(); + const { notification, message } = App.useApp(); + + const { travel_agency_id, use_year } = useParams(); + + const [extrasData, setExtrasData] = useState([]); + + const handleGetAgencyProductExtras = async () => { + const data = await getAgencyProductExtrasAction({ id: productId, travel_agency_id, use_year }); + setExtrasData(data); + }; + + const handleNewAddOn = async (item) => { + setExtrasData(prev => [].concat(prev, [item])); + // todo: 提交后端; 重复绑定同一个 + const newSuccess = await addProductExtraAction({ travel_agency_id, id: productId, extras: [2] }); + newSuccess ? message.success(t('Success')) : message.error(t('Failed')); + await handleGetAgencyProductExtras(); + } + + const handleDelAddon = async (item) => { + // todo: 提交后端 + const delSuccess = await delProductExtrasAction({ travel_agency_id, id: productId, extras: [2] }); + delSuccess ? message.success(t('Success')) : message.error(t('Failed')); + await handleGetAgencyProductExtras(); + }; + + useEffect(() => { + handleGetAgencyProductExtras(); + + return () => {}; + }, []); + + const columns = [ + { title: t('products:Title'), dataIndex: ['info', 'title'], width: '16rem', }, + { + title: t('products:Offer'), + dataIndex: ['quotation', '0', 'value'], + width: '10rem', + + render: (_, { quotation }) => `${quotation[0].value} ${quotation[0].currency} / ${quotation[0].unit_name}`, // todo: 成人 儿童 + }, + // { title: t('products:Types'), dataIndex: 'age_type', width: '40%', }, + { + title: '', + dataIndex: 'operation', + width: '4rem', + render: (_, r) => ( + handleDelAddon(r)} > + + + ), + }, + ]; + + return ( + <> + +

{t('products:Components.Extras')}

+
r.info.id} /> + + + + ); +}; +export default Extras; From 056a0068473129fd2cf75ed87c07f35054e81c64 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Tue, 25 Jun 2024 17:13:45 +0800 Subject: [PATCH 032/120] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AE=A1=E6=A0=B8?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E8=B7=B3=E8=BD=AC`=E7=BC=96=E8=BE=91`;=20sty?= =?UTF-8?q?le:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/SecondHeaderWrapper.jsx | 30 +++++++++++++------------- src/views/products/Audit.jsx | 7 +++--- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/components/SecondHeaderWrapper.jsx b/src/components/SecondHeaderWrapper.jsx index 9727e4a..9a4f7bd 100644 --- a/src/components/SecondHeaderWrapper.jsx +++ b/src/components/SecondHeaderWrapper.jsx @@ -10,21 +10,21 @@ const HeaderWrapper = ({ children, header, loading, ...props }) => { } = theme.useToken(); return ( <> - - -
- - {/* {header} */} -
{header}
- -
-
-
- - {children || } - -
-
+ + +
+ + {/* {header} */} +
{header}
+ +
+
+ + + {children || } + +
+
); }; diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index 9a8371b..44fb12c 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { useParams } from 'react-router-dom'; +import { useParams, Link } from 'react-router-dom'; import { App, Button, Collapse, Table, Space, Divider } from 'antd'; import { useProductsTypes, useProductsAuditStatesMapVal } from '@/hooks/useProductsSets'; import SecondHeaderWrapper from '@/components/SecondHeaderWrapper'; @@ -9,7 +9,7 @@ import { isEmpty } from '@/utils/commons'; // import PrintContractPDF from './PrintContractPDF'; const Header = ({ title, agency, refresh, ...props }) => { - const { use_year, } = useParams(); + const { travel_agency_id, use_year, audit_state } = useParams(); const { t } = useTranslation(); const [activeAgency] = useProductsStore((state) => [state.activeAgency]); const { message, notification } = App.useApp(); @@ -35,10 +35,11 @@ const Header = ({ title, agency, refresh, ...props }) => { return (
-

{title}{use_year}

+

{title}{(use_year || '').replace('all', '')}

{/* */} {/* */} + {t('Edit')} From e4cc07eefea83d88b832719296e266d063a2a664 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 26 Jun 2024 09:14:35 +0800 Subject: [PATCH 033/120] =?UTF-8?q?feat:=20=E9=99=84=E5=8A=A0=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE:=20=E6=90=9C=E7=B4=A2=E4=BE=9B=E5=BA=94=E5=95=86?= =?UTF-8?q?=E7=9A=84=E4=BA=A7=E5=93=81,=20=E5=B7=B2=E5=AE=A1=E6=A0=B8?= =?UTF-8?q?=E5=8F=91=E5=B8=83=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/products/Detail/Extras.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/products/Detail/Extras.jsx b/src/views/products/Detail/Extras.jsx index 0aa1aa2..ebb6e2d 100644 --- a/src/views/products/Detail/Extras.jsx +++ b/src/views/products/Detail/Extras.jsx @@ -24,7 +24,7 @@ const NewAddonModal = ({ onPick, ...props }) => { const { starttime, endtime, ...param } = copyObject; setSearchLoading(true); setSearchResult([]); - const result = await getAgencyProductsAction({ ...param, audit_state: '0', travel_agency_id, use_year }); + const result = await getAgencyProductsAction({ ...param, audit_state: '1', travel_agency_id, use_year }); setSearchResult(result?.products || []); setSearchLoading(false); }; From 65064937f07d29d60ac795bab1664e33c63144cf Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 26 Jun 2024 10:39:52 +0800 Subject: [PATCH 034/120] =?UTF-8?q?feat:=20=E5=A4=8D=E5=88=B6=E4=BE=9B?= =?UTF-8?q?=E5=BA=94=E5=95=86=E4=BA=A7=E5=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/products.json | 4 +- public/locales/zh/products.json | 2 +- src/components/SearchForm.jsx | 7 ++-- src/stores/Products/Index.js | 15 ++++++- src/views/products/Audit.jsx | 6 +-- src/views/products/Detail/Extras.jsx | 4 +- src/views/products/Manage.jsx | 63 ++++++++++++++++++++-------- 7 files changed, 70 insertions(+), 31 deletions(-) diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 56eab4b..4997e31 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -3,14 +3,14 @@ "Experience": "Experience", "Car": "Transport Services", "Guide": "Guide Services", - "Package": "Package", + "Package": "Package Tour", "Attractions": "Attractions", "Meals": "Meals", "Extras": "Extras", "Overtravel": "超公里", "Special": "Special" }, - "Components": { + "EditComponents": { "info": "Product Information", "Quotation": "Quotation", "Extras": "Add-on" diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 08f4872..55005ce 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -10,7 +10,7 @@ "Overtravel": "超公里", "Special": "特殊项目" }, - "Components": { + "EditComponents": { "info": "产品信息", "Quotation": "报价", "Extras": "附加项目" diff --git a/src/components/SearchForm.jsx b/src/components/SearchForm.jsx index 979a6fc..f6bad2c 100644 --- a/src/components/SearchForm.jsx +++ b/src/components/SearchForm.jsx @@ -21,7 +21,7 @@ export const fetchVendorList = async (q) => { const { RangePicker } = DatePicker; -const SearchForm = ({ initialValue, onSubmit, onReset, ...props }) => { +const SearchForm = ({ initialValue, onSubmit, onReset, confirmText, formName, loading, ...props }) => { const { t } = useTranslation(); const presets = useDatePresets(); const [formValues, setFormValues] = useFormStore((state) => [state.formValues, state.setFormValues]); @@ -36,7 +36,6 @@ const SearchForm = ({ initialValue, onSubmit, onReset, ...props }) => { shows: [], ...props.fieldsConfig, }; - const { confirmText } = props; const readValues = { ...initialValue, ...formValues }; const formValuesMapper = (values) => { @@ -109,14 +108,14 @@ const SearchForm = ({ initialValue, onSubmit, onReset, ...props }) => { }; return ( <> -
+ {/* */} {getFields({ sort, initialValue: readValues, hides, shows, fieldProps, fieldComProps, form, presets, t })} {/* 'textAlign': 'right' */}
- {/*
r.info.id} /> diff --git a/src/views/products/Manage.jsx b/src/views/products/Manage.jsx index 400db96..e18b164 100644 --- a/src/views/products/Manage.jsx +++ b/src/views/products/Manage.jsx @@ -1,14 +1,16 @@ -import { useEffect } from 'react'; -import { Link } from 'react-router-dom'; -import { Row, Col, Space, Table } from 'antd'; +import { useEffect, useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { App, Space, Table, Button, Modal } from 'antd'; import SearchForm from '@/components/SearchForm'; import dayjs from 'dayjs'; import { useTranslation } from 'react-i18next'; -import useProductsStore from '@/stores/Products/Index'; +import useProductsStore, { copyAgencyDataAction } from '@/stores/Products/Index'; import useFormStore from '@/stores/Form'; import { objectMapper } from '@/utils/commons'; function Index() { + const { notification, message } = App.useApp(); + const navigate = useNavigate() const { t } = useTranslation(); const [loading, agencyList, searchAgency] = useProductsStore((state) => [state.loading, state.agencyList, state.searchAgency]); const [searchValues, setSearchValues] = useProductsStore((state) => [state.searchValues, state.setSearchValues]); @@ -21,6 +23,24 @@ function Index() { searchAgency(searchParam); } + const [copyModalVisible, setCopyModalVisible] = useState(false); + const [sourceAgency, setSourceAgency] = useState({}); + const [copyLoading, setCopyLoading] = useState(false); + const handleCopyAgency = async (toID) => { + setCopyLoading(true); + const success = await copyAgencyDataAction(sourceAgency.travel_agency_id, toID); + setCopyLoading(false); + success ? message.success('复制成功') : message.error('复制失败'); + + setCopyModalVisible(false); + navigate(`/products/${toID}/${searchValues.use_year || 'all'}/${searchValues.audit_state || 'all'}/edit`); + }; + + const openCopyModal = (from) => { + setSourceAgency(from); + setCopyModalVisible(true); + }; + useEffect(() => { // handleSearchAgency(); }, []); @@ -41,6 +61,7 @@ function Index() { {t('Edit')} {t('Audit')} + ), }, @@ -69,19 +90,27 @@ function Index() { handleSearchAgency(formVal); }} /> - - -
- - - +
+ + {/* 复制弹窗 */} + setCopyModalVisible(false)} destroyOnClose> +
复制源: {sourceAgency.travel_agency_name}
+ { + handleCopyAgency(formVal.agency); + }} + /> +
); } From cbdb737163cdce9402f86cbccd6835e67858cf08 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 26 Jun 2024 10:51:50 +0800 Subject: [PATCH 035/120] =?UTF-8?q?feat:=20=E5=AE=A2=E6=9C=8D=E9=A6=96?= =?UTF-8?q?=E9=A1=B5:=20=E6=90=9C=E7=B4=A2:=20=E4=BE=9B=E5=BA=94=E5=95=86?= =?UTF-8?q?=E5=A4=9A=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/SearchInput.jsx | 2 +- src/views/products/Manage.jsx | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/SearchInput.jsx b/src/components/SearchInput.jsx index dcf2657..134c301 100644 --- a/src/components/SearchInput.jsx +++ b/src/components/SearchInput.jsx @@ -31,7 +31,7 @@ function DebounceSelect({ fetchOptions, debounceTimeout = 800, ...props }) { showSearch allowClear maxTagCount={1} - dropdownStyle={{width: '16rem'}} + dropdownStyle={{width: '20rem'}} {...props} onSearch={debounceFetcher} notFoundContent={fetching ? : null} diff --git a/src/views/products/Manage.jsx b/src/views/products/Manage.jsx index e18b164..3e5d3b6 100644 --- a/src/views/products/Manage.jsx +++ b/src/views/products/Manage.jsx @@ -70,15 +70,12 @@ function Index() { Date: Wed, 26 Jun 2024 11:40:31 +0800 Subject: [PATCH 036/120] =?UTF-8?q?1.=E5=AE=8C=E5=96=84=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E6=95=88=E6=9E=9C=202.=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E8=A1=A8=E5=8D=95=E5=AF=B9=E5=BA=94=E7=9A=84=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/products.json | 2 +- public/locales/zh/products.json | 4 +- src/views/products/Detail.jsx | 281 +++++++++++++++++++++++--------- 3 files changed, 203 insertions(+), 84 deletions(-) diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 0ccfecf..b0363b7 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -56,7 +56,7 @@ "validityPeriod":"Validity period", "operation": "Operation", "price": "Price", - + "weekends":"Weekends", "save":"save", "edit":"edit", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 3abea44..17ec532 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -52,9 +52,7 @@ "Child": "儿童" }, - "#": "#" - "Auditors": "审核人员", - "AuditDate": "审核时间", + "#": "#", "productProject": "产品项目", "Code": "代码", diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx index 7188431..8ce7648 100644 --- a/src/views/products/Detail.jsx +++ b/src/views/products/Detail.jsx @@ -4,23 +4,25 @@ import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import DateComponent from '@/components/date'; import { fetchJSON } from "@/utils/request"; - - +import { type } from 'windicss/utils'; +import { searchAgencyAction, getAgencyProductsAction } from '@/stores/Products/Index'; function Index() { const { t } = useTranslation(); const [form] = Form.useForm(); - const [editingKey, setEditingKey] = useState(''); + const [editingid, setEditingid] = useState(''); const [tags, setTags] = useState(['中文', 'English']); const [isModalVisible, setIsModalVisible] = useState(false); const [selectedTag, setSelectedTag] = useState('中文'); const [saveData, setSaveData] = useState(null); const [datePickerVisible, setDatePickerVisible] = useState(false); - const [currentKey, setCurrentKey] = useState(null); + const [currentid, setCurrentid] = useState(null); const [languageStatus, setLanguageStatus] = useState([{ "中文": { title: "", description: "" } }, { "English": { title: "", description: "" } }]); const [formState, setFormState] = useState({}); - const [selectedNodeKey, setSelectedNodeKey] = useState(null); + const [selectedNodeid, setSelectedNodeid] = useState(null); const [selectedDateData, setSelectedDateData] = useState({ dateRange: null, selectedDays: [] }); + const [startValue,setStartValue] = useState(null); + const [endValue,setEndValue] = useState(null); const productProject = [ { code: "code", name: t('products:Code') }, @@ -31,88 +33,109 @@ function Index() { ]; const initialData = [ - { key: '1', adult_cost: '15', child_cost: "美元", currency: 'aa', age_type: 'as', group_size: 'dawd', validityPeriod: 'dawda' }, - { key: '2', adult_cost: '15', child_cost: "美元", currency: 'aa', age_type: 'as', group_size: 'dawd', validityPeriod: 'dawda' }, - { key: '3', adult_cost: '15', child_cost: "美元", currency: 'aa', age_type: 'as', group_size: 'dawd', validityPeriod: 'dawda' }, - { key: '4', adult_cost: '15', child_cost: "美元", currency: 'aa', age_type: 'as', group_size: 'dawd', validityPeriod: 'dawda' }, + { id: '1', adult_cost: '1000', child_cost: "800", currency: 'RMB', age_type: '每人', group_size_min: 4, group_size_max: 5, use_dates_start: '2024/06/12',use_dates_end: '2024/07/22', weekdays: '' }, + { id: '2', adult_cost: '1200', child_cost: "900", currency: 'RMB', age_type: '每人', group_size_min: 3, group_size_max: 5, use_dates_start: '2024/06/12',use_dates_end: '2024/07/22', weekdays: '' }, + { id: '3', adult_cost: '1500', child_cost: "1200", currency: 'RMB', age_type: '每人', group_size_min: 2, group_size_max: 5, use_dates_start: '2024/06/12',use_dates_end: '2024/07/22', weekdays: '' }, + { id: '4', adult_cost: '1100', child_cost: "700", currency: 'RMB', age_type: '每人', group_size_min: 1, group_size_max: 5, use_dates_start: '2024/06/12',use_dates_end: '2024/07/22', weekdays: '' }, ]; const [quotation, setQuotation] = useState(initialData); - const isEditing = (record) => record.key === editingKey; + const isEditing = (record) => record.id === editingid; const edit = (record) => { form.setFieldsValue({ ...record }); - setEditingKey(record.key); + setEditingid(record.id); }; const cancel = () => { - setEditingKey(''); + setEditingid(''); }; - const handleSave = async (key) => { + const handleSave = async (id) => { try { const { info, ...restRow } = await form.validateFields(); const newData = [...quotation]; - const index = newData.findIndex((item) => key === item.key); + const index = newData.findIndex((item) => id === item.id); if (index > -1) { const item = newData[index]; newData.splice(index, 1, { ...item, ...restRow }); delete newData[index].quotation delete newData[index].extras setQuotation(newData); - setEditingKey(''); + setEditingid(''); } else { newData.push(restRow); setQuotation(newData); - setEditingKey(''); + setEditingid(''); } } catch (errInfo) { console.log('Validate Failed:', errInfo); } }; - const handleDelete = (key) => { + const handleDelete = (id) => { const newData = [...quotation]; - const index = newData.findIndex((item) => key === item.key); + const index = newData.findIndex((item) => id === item.id); newData.splice(index, 1); setQuotation(newData); }; const handleAdd = () => { const newData = { - key: `${quotation.length + 1}`, + id: `${quotation.length + 1}`, value: '', currency: '', age_type: '', weekdays: '', - group_size: '', - validityPeriod: '', + use_dates_start: '', + use_dates_end: '', + group_size_min:'', + group_size_max:'' }; setQuotation([...quotation, newData]); }; - const handleDateSelect = (key) => { - setCurrentKey(key); + const handleDateSelect = (id) => { + setCurrentid(id); setDatePickerVisible(true); }; const handleDateChange = ({ dateRange, selectedDays }) => { - console.log('Date Range:', dateRange); - console.log('Selected Days:', selectedDays); + + // 计算周末 + const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday','Sunday']; + let weekDayCount = selectedDays.map(day => weekdays.indexOf(day) + 1).sort().join(','); + console.log('Weekday Count:', weekDayCount); + if (!weekDayCount || weekDayCount.length === 0) { + weekDayCount = "全年"; + } + const newData = [...quotation]; + const index = newData.findIndex((item) => currentid === item.id); + if (index > -1) { + newData[index].weekdays = weekDayCount; + setQuotation(newData); + } + setSelectedDateData({ dateRange, selectedDays }) }; const handleDateOk = () => { const { dateRange } = selectedDateData; + const dateRangeList = dateRange.split('-'); + console.log("dateRangeList",dateRangeList); + const use_dates_start = dateRangeList[0]; + const use_dates_end = dateRangeList[1]; - if (currentKey !== null) { + console.log("dateRange",dateRange) + + if (currentid !== null) { const newData = [...quotation]; - const index = newData.findIndex((item) => currentKey === item.key); + console.log("newData",newData) + const index = newData.findIndex((item) => currentid === item.id); if (index > -1) { - newData[index].validityPeriod = dateRange; - - console.log() + newData[index].use_dates_start = use_dates_start; + newData[index].use_dates_end = use_dates_end; setQuotation(newData); - setCurrentKey(null); + setCurrentid(null); } } setDatePickerVisible(false); @@ -124,11 +147,50 @@ function Index() { return (
); } + if (dataIndex === 'age_type' && editing) { + inputNode = ( + + ); + } + + if (dataIndex === 'currency' && editing) { + inputNode = ( + + ); + } + + if (dataIndex === 'group_size' && editing) { + const groupSizeValue = `${record.group_size_min}-${record.group_size_max}`; + return ( + + ); + } + return ( + @@ -498,7 +616,7 @@ function Index() {
{tags.map(tag => ( handleTagClick(tag)} color={tag === selectedTag ? 'blue' : undefined} style={{ cursor: 'pointer' }} @@ -581,6 +699,9 @@ function Index() { + From 37c3b5aa45a91a14013530f1efef7cad723cef11 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 26 Jun 2024 11:44:53 +0800 Subject: [PATCH 037/120] =?UTF-8?q?=E4=BD=BF=E7=94=A8Lifecycle=E5=9B=9E?= =?UTF-8?q?=E8=B0=83=E6=9D=83=E9=99=90=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 16 ++++++++++------ src/stores/Auth.js | 15 +++++++++++++-- src/utils/lifecycle.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 src/utils/lifecycle.js diff --git a/src/main.jsx b/src/main.jsx index 5c6a620..c6764aa 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -34,7 +34,7 @@ import { ThemeContext } from '@/stores/ThemeContext' import { usingStorage } from '@/hooks/usingStorage' import { isNotEmpty } from '@/utils/commons' import { appendRequestParams } from '@/utils/request' -import useAuthStore from '@/stores/Auth' +import { fireAuth } from "./utils/lifecycle" import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } from '@/config' @@ -42,14 +42,18 @@ import './i18n'; const { loginToken, userId } = usingStorage() -if (isNotEmpty(loginToken)) { - appendRequestParams('token', loginToken) -} +const initAppliction = async () => { + if (isNotEmpty(loginToken)) { + appendRequestParams('token', loginToken) + } -if (isNotEmpty(userId)) { - useAuthStore.getState().loadUserPermission(userId) + if (isNotEmpty(userId)) { + await fireAuth() + } } +await initAppliction() + const router = createBrowserRouter([ { path: "/", diff --git a/src/stores/Auth.js b/src/stores/Auth.js index 55ab140..b462a1d 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -3,6 +3,9 @@ import { appendRequestParams, fetchJSON, postForm } from '@/utils/request' import { HT_HOST } from "@/config" import { loadPageSpy } from '@/pageSpy' import { usingStorage } from '@/hooks/usingStorage' +import { devtools } from 'zustand/middleware' + +import { obervseLifecycle } from '@/utils/lifecycle' const KEY_LOGIN_TOKEN = 'G-STR:LOGIN_TOKEN' const KEY_TRAVEL_AGENCY_ID = 'G-INT:TRAVEL_AGENCY_ID' @@ -42,7 +45,14 @@ async function fetchLastRequet() { return errcode !== 0 ? {} : result } -const useAuthStore = create((set, get) => ({ +const useAuthStore = create(obervseLifecycle((set, get) => ({ + + onAuth: () => { + const { startTokenInterval, loadUserPermission } = get() + const { userId } = usingStorage() + loadUserPermission(userId) + startTokenInterval() + }, tokenInterval: null, @@ -157,6 +167,7 @@ const useAuthStore = create((set, get) => ({ } }); }, -})) + +}))) export default useAuthStore \ No newline at end of file diff --git a/src/utils/lifecycle.js b/src/utils/lifecycle.js new file mode 100644 index 0000000..a2de514 --- /dev/null +++ b/src/utils/lifecycle.js @@ -0,0 +1,42 @@ +const initListener = [] +const authListener = [] + +export const onInit = (fn) => { + initListener.push(fn) +} + +export const onAuth = (fn) => { + authListener.push(fn) +} + +export const fireInit = async () => { + initListener.forEach(async (fn) => { + await fn() + }) +} + +export const fireAuth = (obj) => { + authListener.forEach(fn => fn(obj)) +} + +// Zustand 中间件,用于订阅前端应用的生命周期,实验阶段 +export const obervseLifecycle = (fn) => (set, get, store) => { + + onInit(() => { + if (store.getState().hasOwnProperty('onInit')) { + store.getState().onInit() + } else { + console.info('store has no function: onInit.') + } + }) + + onAuth(() => { + if (store.getState().hasOwnProperty('onAuth')) { + store.getState().onAuth() + } else { + console.info('store has no function: onAuth.') + } + }) + + return fn(set, get, store) +} \ No newline at end of file From a3d02f4caf28999337aa4c430046b65374e9efda Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 26 Jun 2024 11:58:18 +0800 Subject: [PATCH 038/120] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 2 ++ src/views/App.jsx | 6 +----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main.jsx b/src/main.jsx index 6e84eb9..eb70c92 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -46,11 +46,13 @@ import './i18n'; const { loginToken, userId } = usingStorage() const initAppliction = async () => { + if (isNotEmpty(loginToken)) { appendRequestParams('token', loginToken) } if (isNotEmpty(userId)) { + appendRequestParams('wu_id', userId) await fireAuth() } } diff --git a/src/views/App.jsx b/src/views/App.jsx index f9a37ad..0ebcff4 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -32,7 +32,7 @@ function App() { const [validateUserPassword, tokenTimeout, isPermitted] = useAuthStore( (state) => [state.validateUserPassword, state.tokenTimeout, state.isPermitted]) - const { loginToken, userDetail, userId } = usingStorage() + const { loginToken } = usingStorage() const noticeUnRead = useNoticeStore((state) => state.noticeUnRead) const href = useHref() @@ -42,10 +42,6 @@ function App() { // 除了路由 /p...以外都需要登陆系统 const needToLogin = href !== '/login' && isEmpty(loginToken) - if (!needToLogin) { - appendRequestParams('token', loginToken) - appendRequestParams('wu_id', userId) - } useEffect(() => { fetchUserDetail(loginToken) .then(u => { From 1084db4b96f0eb50193aee8102576d271521cb83 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 26 Jun 2024 12:06:10 +0800 Subject: [PATCH 039/120] =?UTF-8?q?=E6=90=9C=E7=B4=A2=E8=A1=A8=E5=8D=95?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/SearchForm.jsx | 4 ++-- src/views/account/Management.jsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/SearchForm.jsx b/src/components/SearchForm.jsx index f6bad2c..9f985b9 100644 --- a/src/components/SearchForm.jsx +++ b/src/components/SearchForm.jsx @@ -209,7 +209,7 @@ function getFields(props) { ), item( 'username', - 3, + 99, , @@ -217,7 +217,7 @@ function getFields(props) { ), item( 'realname', - 4, + 99, , diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 6c2c323..737ff5a 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -243,6 +243,7 @@ function Management() { fieldProps: { dates: { label: t('group:ArrivalDate') }, }, + sort: { username: 1, realname: 2, dates: 3}, }} onSubmit={(err, formValues, filedsVal) => { console.info(formValues) From 6f446d852a4a5a6524654e37494564c3150cb332 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 26 Jun 2024 13:56:34 +0800 Subject: [PATCH 040/120] =?UTF-8?q?feat:=20=E7=99=BB=E9=99=86=E8=B6=85?= =?UTF-8?q?=E6=97=B6=E5=88=B7=E6=96=B0=E8=A6=81=E9=87=8D=E6=96=B0=E7=99=BB?= =?UTF-8?q?=E9=99=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Auth.js | 5 +++-- src/views/App.jsx | 16 +++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/stores/Auth.js b/src/stores/Auth.js index b462a1d..18dfc3b 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -130,7 +130,7 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ const now = new Date() const diffTime = now.getTime() - lastReqDate.getTime() const diffHours = diffTime/1000/60/60 - if (diffHours > 4) { + if (diffHours > 1) { loginTimeout() } } @@ -143,7 +143,8 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ loginTimeout: () => { const { tokenInterval } = get() - // TODO: 这里没有清理 token,刷新后可以正常使用系统 + const { clearStorage } = usingStorage() + clearStorage() clearInterval(tokenInterval) set(() => ({ tokenTimeout: true diff --git a/src/views/App.jsx b/src/views/App.jsx index 22d0737..2ac059d 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -4,7 +4,7 @@ import { Layout, Menu, ConfigProvider, theme, Dropdown, Space, Row, Col, Badge, import { DownOutlined } from '@ant-design/icons'; import 'antd/dist/reset.css'; import AppLogo from '@/assets/logo-gh.png'; -import { isEmpty } from '@/utils/commons'; +import { isEmpty, isNotEmpty } from '@/utils/commons'; import Language from '../i18n/LanguageSwitcher'; import { useTranslation } from 'react-i18next'; import zhLocale from 'antd/locale/zh_CN'; @@ -43,13 +43,15 @@ function App() { const needToLogin = href !== '/login' && isEmpty(loginToken) useEffect(() => { - fetchUserDetail(loginToken) - .then(u => { - setUserDetail({ - username: u.LoginName, - travelAgencyName: u.VName, + if (isNotEmpty(loginToken)) { + fetchUserDetail(loginToken) + .then(u => { + setUserDetail({ + username: u.LoginName, + travelAgencyName: u.VName, + }) }) - }) + } }, [loginToken]) useEffect(() => { From d0a4453ce6d6a5ff2f7d3807a75b71c342376f95 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 26 Jun 2024 14:02:07 +0800 Subject: [PATCH 041/120] =?UTF-8?q?=E4=BA=A7=E5=93=81=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E7=9A=84=20i8n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/common.json | 5 +++-- public/locales/en/products.json | 7 +++++++ public/locales/zh/common.json | 15 ++++++++------- public/locales/zh/products.json | 11 +++++++++-- src/components/SearchForm.jsx | 2 +- src/hooks/useDatePresets.js | 22 ++++++++++++++++++++-- src/views/products/Detail/Extras.jsx | 14 +++++++------- 7 files changed, 55 insertions(+), 21 deletions(-) diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 8ed1854..c87b7cd 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -26,8 +26,9 @@ "Export": "Export", "Copy": "Copy", - "sureCancel": "Sure you want to cancel?", - "sureDelete":"Sure you want to delete?", + "sureCancel": "Are you sure to cancel?", + "sureDelete":"Are you sure to delete?", + "Yes": "Yes", "Success": "Success", "Failed": "Failed", diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 4997e31..79bb594 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -39,6 +39,13 @@ "CreateDate": "Create Date", "AuditedBy": "Audited By", "AuditDate": "Audit Date", + "OpenHours": "Open Hours", + "Duration": "Duration", + "KM": "KM", + "RecommendsRate": "RecommendsRate", + "OpenWeekdays": "Open Weekdays", + "DisplayToC": "DisplayToC", + "Dept": "Dept", "productProject": "Product project", "Code": "Code", diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 82b43f9..459ff35 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -28,6 +28,7 @@ "sureCancel": "确定取消?", "sureDelete":"确定删除?", + "Yes": "是", "Success": "成功", "Failed": "失败", @@ -62,13 +63,13 @@ "thisYear": "今年" }, "weekdays": { - "1": "一", - "2": "二", - "3": "三", - "4": "四", - "5": "五", - "6": "六", - "7": "日" + "1": "周一", + "2": "周二", + "3": "周三", + "4": "周四", + "5": "周五", + "6": "周六", + "7": "周日" }, "weekdaysShort": { "1": "一", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 55005ce..65cb019 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -37,11 +37,18 @@ "AuState": "审核状态", "CreatedBy": "提交人员", "CreateDate": "提交时间", - - "AuditedBy": "审核人员", "AuditDate": "审核时间", + "OpenHours": "游览时间", + "Duration": "游览时长", + "KM": "公里数", + "RecommendsRate": "推荐指数", + "OpenWeekdays": "周开放日", + "DisplayToC": "报价信显示", + "Dept": "小组", + + "productProject": "产品项目", "Code": "代码", "City": "城市", diff --git a/src/components/SearchForm.jsx b/src/components/SearchForm.jsx index 9f985b9..f896792 100644 --- a/src/components/SearchForm.jsx +++ b/src/components/SearchForm.jsx @@ -3,7 +3,7 @@ import { Form, Input, Row, Col, Select, DatePicker, Space, Button } 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 {useDatePresets} from '@/hooks/useDatePresets'; import { useTranslation } from 'react-i18next'; import { fetchJSON } from '@/utils/request'; diff --git a/src/hooks/useDatePresets.js b/src/hooks/useDatePresets.js index 87397b9..2eb4c7d 100644 --- a/src/hooks/useDatePresets.js +++ b/src/hooks/useDatePresets.js @@ -1,8 +1,9 @@ import { useEffect, useState } from 'react'; import dayjs from "dayjs"; import { useTranslation } from 'react-i18next'; +import i18n from '@/i18n'; -const useDatePresets = () => { +export const useDatePresets = () => { const [presets, setPresets] = useState([]); const { t, i18n } = useTranslation(); @@ -39,4 +40,21 @@ const useDatePresets = () => { return presets; } -export default useDatePresets; +export const useWeekdays = () => { + const [data, setData] = useState([]); + const { t, i18n } = useTranslation(); + useEffect(() => { + const newData = [ + { value: '1', label: t('weekdays.1') }, + { value: '2', label: t('weekdays.2') }, + { value: '3', label: t('weekdays.3') }, + { value: '4', label: t('weekdays.4') }, + { value: '5', label: t('weekdays.5') }, + { value: '6', label: t('weekdays.6') }, + { value: '7', label: t('weekdays.7') }, + ]; + setData(newData); + return () => {}; + }, [i18n.language]); + return data; +}; diff --git a/src/views/products/Detail/Extras.jsx b/src/views/products/Detail/Extras.jsx index b5e0f1b..4caf981 100644 --- a/src/views/products/Detail/Extras.jsx +++ b/src/views/products/Detail/Extras.jsx @@ -1,9 +1,9 @@ -import { createContext, useContext, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { App, Table, Button, Modal, Popconfirm } from 'antd'; -import useProductsStore, { getAgencyProductExtrasAction, getAgencyProductsAction, addProductExtraAction, delProductExtrasAction } from '@/stores/Products/Index'; -import { isEmpty, cloneDeep } from '@/utils/commons'; +import { getAgencyProductExtrasAction, getAgencyProductsAction, addProductExtraAction, delProductExtrasAction } from '@/stores/Products/Index'; +import { cloneDeep } from '@/utils/commons'; import SearchForm from '@/components/SearchForm'; import RequireAuth from '@/components/RequireAuth'; @@ -63,7 +63,7 @@ const NewAddonModal = ({ onPick, ...props }) => { return ( <> setOpen(false)} destroyOnClose> @@ -119,14 +119,14 @@ const Extras = ({ productId, onChange, ...props }) => { setExtrasData(prev => [].concat(prev, [item])); // todo: 提交后端; 重复绑定同一个 const newSuccess = await addProductExtraAction({ travel_agency_id, id: productId, extras: [2] }); - newSuccess ? message.success(t('Success')) : message.error(t('Failed')); + newSuccess ? message.success(t('Action')+t('Success')) : message.error(t('Action')+t('Failed')); await handleGetAgencyProductExtras(); } const handleDelAddon = async (item) => { // todo: 提交后端 const delSuccess = await delProductExtrasAction({ travel_agency_id, id: productId, extras: [2] }); - delSuccess ? message.success(t('Success')) : message.error(t('Failed')); + delSuccess ? message.success(t('Action')+t('Success')) : message.error(t('Action')+t('Failed')); await handleGetAgencyProductExtras(); }; @@ -151,7 +151,7 @@ const Extras = ({ productId, onChange, ...props }) => { dataIndex: 'operation', width: '4rem', render: (_, r) => ( - handleDelAddon(r)} > + handleDelAddon(r)} okText={t('Yes')} > From 461263839bbf215a425fdf78b4ba1205580fa4ab Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 26 Jun 2024 14:50:51 +0800 Subject: [PATCH 042/120] =?UTF-8?q?=E4=BA=A7=E5=93=81=E7=AE=A1=E7=90=86:?= =?UTF-8?q?=20=E5=AE=A2=E6=9C=8D=E9=A6=96=E9=A1=B5:=20=E6=A0=B8=E5=AF=B9?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E7=BB=93=E6=9E=9C=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/products/Manage.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/products/Manage.jsx b/src/views/products/Manage.jsx index 3e5d3b6..65cfc6b 100644 --- a/src/views/products/Manage.jsx +++ b/src/views/products/Manage.jsx @@ -49,10 +49,10 @@ function Index() { const columns = [ { title: t('products:Vendor'), key: 'vendor', dataIndex: 'travel_agency_name' }, - { title: t('products:CreatedBy'), key: 'created_by', dataIndex: 'created_by' }, + { title: t('products:CreatedBy'), key: 'created_by', dataIndex: 'created_by_name' }, { title: t('products:CreateDate'), key: 'create_date', dataIndex: 'create_date' }, { title: t('products:AuState'), key: 'audit_state', dataIndex: 'audit_state' }, - { title: t('products:AuditedBy'), key: 'audited_by', dataIndex: 'audited_by' }, + { title: t('products:AuditedBy'), key: 'audited_by', dataIndex: 'audited_by_name' }, { title: t('products:AuditDate'), key: 'audit_date', dataIndex: 'audit_date' }, { title: '', From bebb8c0a093c0fec50805352ff15c160bd2a975b Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 26 Jun 2024 14:55:06 +0800 Subject: [PATCH 043/120] =?UTF-8?q?feat:=20=E4=BB=8E=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=E8=8E=B7=E5=8F=96=E9=BB=98=E8=AE=A4=E7=9A=84?= =?UTF-8?q?HT=E8=AF=AD=E7=A7=8D=E7=BC=96=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/i18n/LanguageSwitcher.jsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/i18n/LanguageSwitcher.jsx b/src/i18n/LanguageSwitcher.jsx index dd1a743..f09a07e 100644 --- a/src/i18n/LanguageSwitcher.jsx +++ b/src/i18n/LanguageSwitcher.jsx @@ -8,16 +8,15 @@ const i18n_to_htcode = { 'en': 1, }; -export const useLanguage = () => { +export const useDefaultLgc = () => { const { i18n } = useTranslation(); - return { language: i18n.language, }; + return { language: i18n_to_htcode[i18n.language], }; }; - /** * 语言选择组件 */ const Language = () => { - const { t, i18n } = useTranslation(); +const { t, i18n } = useTranslation(); const [selectedKeys, setSelectedKeys] = useState([i18n.language]); useEffect(() => { From fcef4c486ba4561455fa311cc1fe9b8635f2627b Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 26 Jun 2024 15:28:52 +0800 Subject: [PATCH 044/120] =?UTF-8?q?feat:=20=E8=B4=A6=E5=8F=B7=E8=A7=92?= =?UTF-8?q?=E8=89=B2=E7=AE=A1=E7=90=86=E5=A2=9E=E5=8A=A0=E4=B8=AD=E6=96=87?= =?UTF-8?q?=EF=BC=9B=E7=BB=9F=E4=B8=80=20userId=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/zh/account.json | 21 ++++- src/stores/Auth.js | 4 +- src/views/App.jsx | 5 +- src/views/account/Management.jsx | 80 +++++++++---------- src/views/account/RoleList.jsx | 129 ++++++++++++++++--------------- 5 files changed, 133 insertions(+), 106 deletions(-) diff --git a/public/locales/zh/account.json b/public/locales/zh/account.json index a7caf37..35c0dc0 100644 --- a/public/locales/zh/account.json +++ b/public/locales/zh/account.json @@ -8,5 +8,24 @@ "CurrentPassword": "请输入密码。", "NewPassword": "请输入新密码。", "ReenterPassword": "请重复输入密码。" - } + }, + "createdOn": "创建时间", + "action": "操作", + "action.edit": "编辑", + "action.disable": "禁用", + "action.resetPassword": "重置密码", + + "accountList": "管理账号", + "newAccount": "新增账号", + "detail": "详细信息", + "username": "用户名", + "realname": "姓名", + "travelAgencyName": "供应商名称", + "email": "邮箱地址", + "lastLogin": "最后登陆时间", + + "roleList": "管理角色", + "newRole": "新增角色", + "roleName": "角色名称", + "permission": "权限" } \ No newline at end of file diff --git a/src/stores/Auth.js b/src/stores/Auth.js index 18dfc3b..59c10c9 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -85,7 +85,7 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ const { startTokenInterval, loadUserPermission } = get() const { setStorage } = usingStorage() - const { token: loginToken, WU_ID: userId } = await fetchLoginToken(usr, pwd) + const { token: loginToken } = await fetchLoginToken(usr, pwd) const userDetail = await fetchUserDetail(loginToken) await loadUserPermission(userId) @@ -95,7 +95,7 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ })) setStorage(KEY_LOGIN_TOKEN, loginToken) - setStorage(KEY_USER_ID, userId) + setStorage(KEY_USER_ID, userDetail.LMI_SN) setStorage(KEY_TRAVEL_AGENCY_ID, userDetail.LMI_VEI_SN) appendRequestParams('token', loginToken) // loadPageSpy(`${json.Result.VName}-${json.Result.LoginName}`) diff --git a/src/views/App.jsx b/src/views/App.jsx index 2ac059d..7ac41a7 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -160,8 +160,9 @@ function App() { items: [...[ { label: {t('ChangePassword')}, key: '0' }, { label: {t('Profile')}, key: '1' }, - isPermitted(PERM_ACCOUNT_MANAGEMENT) ? { label: {t('account:management.tile')}, key: '3' } : null, - isPermitted(PERM_ROLE_NEW) ? { label: {t('account:management.roleList')}, key: '4' } : null, + { type: 'divider' }, + isPermitted(PERM_ACCOUNT_MANAGEMENT) ? { label: {t('account:accountList')}, key: '3' } : null, + isPermitted(PERM_ROLE_NEW) ? { label: {t('account:roleList')}, key: '4' } : null, { type: 'divider' }, { label: {t('Logout')}, key: '99' }, ] diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 517f03f..8a498dc 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -1,14 +1,12 @@ -import { useState, useEffect } from 'react' -import { Row, Col, Space, Button, Table, Select, TreeSelect, Typography, Modal, App, Form, Input } from 'antd' +import SearchForm from '@/components/SearchForm' +import useAccountStore, { fetchRoleList, fetchTravelAgencyByName } from '@/stores/Account' +import useFormStore from '@/stores/Form' +import { isEmpty } from '@/utils/commons' import { ExclamationCircleFilled } from '@ant-design/icons' -import { useTranslation } from 'react-i18next' -import { fetchTravelAgencyByName } from '@/stores/Account' +import { App, Button, Col, Form, Input, Modal, Row, Select, Space, Table, Typography } from 'antd' import dayjs from 'dayjs' -import { isEmpty } from '@/utils/commons' -import useAccountStore from '@/stores/Account' -import useFormStore from '@/stores/Form' -import { fetchRoleList } from '@/stores/Account' -import SearchForm from '@/components/SearchForm' +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' const { Title } = Typography @@ -26,7 +24,7 @@ function Management() { dataIndex: 'realname', }, { - title: t('account:travelAgency'), + title: t('account:travelAgencyName'), dataIndex: 'travelAgencyName', }, { @@ -34,7 +32,7 @@ function Management() { dataIndex: 'email', }, { - title: t('account:role'), + title: t('account:roleName'), dataIndex: 'role' }, { @@ -43,7 +41,7 @@ function Management() { render: (text) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD HH:mm:ss')) }, { - title: t('account:action'), + title: t('account:action.edit'), dataIndex: 'account:action', render: actionRender }, @@ -78,7 +76,7 @@ function Management() { const formValues = useFormStore(state => state.formValues) const { notification, modal } = App.useApp() - useEffect (() => { + useEffect(() => { fetchRoleList() .then((roleList) => { setRoleAllList(roleList.map(r => { @@ -189,7 +187,6 @@ function Management() { duration: 60, }) }) - console.log('ResetPassword') }, onCancel() { }, @@ -204,32 +201,32 @@ function Management() { autoFocus: true, htmlType: 'submit', }} - title={t('account:management.newAccount')} + title={t('account:detail')} open={isAccountModalOpen} onOk={() => setAccountModalOpen(false)} onCancel={() => setAccountModalOpen(false)} destroyOnClose={true} clearOnDestroy={true} modalRender={(dom) => (
+ name='AccountForm' + form={accountForm} + layout='vertical' + size='large' + style={{ + maxWidth: 600, + }} + onFinish={onAccountFinish} + onFinishFailed={onAccountFailed} + autoComplete='off' + > {dom} )} > - - - + + + -
- {t('account:management.tile')} + {t('account:accountList')} { @@ -316,7 +318,7 @@ function Management() {
- + diff --git a/src/views/account/RoleList.jsx b/src/views/account/RoleList.jsx index 13a1501..71909f1 100644 --- a/src/views/account/RoleList.jsx +++ b/src/views/account/RoleList.jsx @@ -1,12 +1,14 @@ -import { useState, useEffect } from 'react' -import { Row, Col, Space, Button, Table, TreeSelect, Typography, Modal, App, Form, Input } from 'antd' -import { useTranslation } from 'react-i18next' -import useAccountStore from '@/stores/Account' -import { fetchRoleList, fetchPermissionList, fetchPermissionListByRoleId } from '@/stores/Account' -import dayjs from 'dayjs' -import { isEmpty } from '@/utils/commons' import RequireAuth from '@/components/RequireAuth' import { PERM_ROLE_NEW } from '@/config' +import useAccountStore, { fetchPermissionList, fetchPermissionListByRoleId, fetchRoleList } from '@/stores/Account' +import { isEmpty } from '@/utils/commons' +import { + SyncOutlined, +} from '@ant-design/icons' +import { App, Button, Col, Form, Input, Modal, Row, Space, Table, Tag, TreeSelect, Typography } from 'antd' +import dayjs from 'dayjs' +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' const { Title } = Typography @@ -15,7 +17,7 @@ function RoleList() { const roleListColumns = [ { - title: t('account:rolename'), + title: t('account:roleName'), dataIndex: 'role_name', }, { @@ -30,8 +32,10 @@ function RoleList() { }, ] - function actionRender(text, role) { - if (role.role_id > 1) { + function actionRender(_, role) { + if (role.role_id == 1) { + return (} color='warning'>不能修改) + } else { return ( ) @@ -49,7 +53,7 @@ function RoleList() { }, {}) } - useEffect (() => { + useEffect(() => { setDataLoading(true) fetchRoleList() .then(r => { @@ -110,7 +114,7 @@ function RoleList() { const { notification, modal } = App.useApp() const onRoleSeleted = (role) => { - fetchPermissionListByRoleId({role_id: role.role_id}) + fetchPermissionListByRoleId({ role_id: role.role_id }) .then(result => { role.res_array = result.map(r => r.res_id) roleForm.setFieldsValue(role) @@ -154,71 +158,71 @@ function RoleList() { autoFocus: true, htmlType: 'submit', }} - title={t('account:management.newRole')} + title={t('account:detail')} open={isRoleModalOpen} onOk={() => setRoleModalOpen(false)} onCancel={() => setRoleModalOpen(false)} destroyOnClose={true} clearOnDestroy={true} modalRender={(dom) => (
+ name='RoleForm' + form={roleForm} + layout='vertical' + size='large' + style={{ + maxWidth: 600, + }} + onFinish={onRoleFinish} + onFinishFailed={onRoleFailed} + autoComplete='off' + > {dom} )} > - - - - - - - + + + + + + + - {t('account:management.roleList')} + {t('account:roleList')}
- + @@ -230,6 +234,7 @@ function RoleList() { loading={dataLoading} rowKey='role_id' pagination={{ + pageSize: 20, showQuickJumper: true, showLessItems: true, showSizeChanger: true, From d8b141cda8d672080c9cbbcb5ebaa4fe953bd44a Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 26 Jun 2024 15:50:02 +0800 Subject: [PATCH 045/120] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E4=BD=9C=E4=B8=BA=E9=BB=98=E8=AE=A4=E9=A6=96=E9=A1=B5?= =?UTF-8?q?=EF=BC=9B=E5=88=A0=E9=99=A4=20GA=20=E6=95=B0=E6=8D=AE=E6=94=B6?= =?UTF-8?q?=E9=9B=86=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 3 +-- src/stores/Auth.js | 4 ++-- src/views/App.jsx | 19 ++++++++----------- src/views/Index.jsx | 13 ------------- src/views/Login.jsx | 2 +- 5 files changed, 12 insertions(+), 29 deletions(-) delete mode 100644 src/views/Index.jsx diff --git a/src/main.jsx b/src/main.jsx index c6764aa..d3f498d 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -9,7 +9,6 @@ import App from "@/views/App"; import Standlone from "@/views/Standlone"; import Login from "@/views/Login"; import Logout from "@/views/Logout"; -import Index from "@/views/index"; import ErrorPage from "@/components/ErrorPage"; import RequireAuth from '@/components/RequireAuth' import ReservationNewest from "@/views/reservation/Newest"; @@ -60,7 +59,7 @@ const router = createBrowserRouter([ element: , errorElement: , children: [ - { index: true, element: }, + { index: true, element: }, { path: "account/change-password", element: }, { path: "account/profile", element: }, { path: "account/management", element: }, diff --git a/src/stores/Auth.js b/src/stores/Auth.js index 59c10c9..6e59ed9 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -85,7 +85,7 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ const { startTokenInterval, loadUserPermission } = get() const { setStorage } = usingStorage() - const { token: loginToken } = await fetchLoginToken(usr, pwd) + const { token: loginToken, WU_ID: userId } = await fetchLoginToken(usr, pwd) const userDetail = await fetchUserDetail(loginToken) await loadUserPermission(userId) @@ -95,7 +95,7 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ })) setStorage(KEY_LOGIN_TOKEN, loginToken) - setStorage(KEY_USER_ID, userDetail.LMI_SN) + setStorage(KEY_USER_ID, userId)//userDetail.LMI_SN) setStorage(KEY_TRAVEL_AGENCY_ID, userDetail.LMI_VEI_SN) appendRequestParams('token', loginToken) // loadPageSpy(`${json.Result.VName}-${json.Result.LoginName}`) diff --git a/src/views/App.jsx b/src/views/App.jsx index 7ac41a7..516c02d 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -60,10 +60,6 @@ function App() { } }, [href]) - useEffect(() => { - window.gtag('event', 'page_view', { page_location: window.location.href }); - }, [location]) - const onSubmit = () => { validateUserPassword(userDetail?.username, password) .catch(ex => { @@ -71,23 +67,24 @@ function App() { alert(t('Validation.LoginFailed')) }) setPassword('') - }; + } - const splitPath = href.split('/'); - let defaultPath = 'reservation'; + const splitPath = href.split('/') + let defaultPath = 'notice' if (splitPath.length > 1) { - defaultPath = splitPath[1]; + defaultPath = splitPath[1] } const { token: { colorBgContainer }, - } = theme.useToken(); + } = theme.useToken() const [antdLng, setAntdLng] = useState(enLocale); useEffect(() => { setAntdLng(i18n.language === 'en' ? enLocale : zhLocale); - }, [i18n.language]); + }, [i18n.language]) + return ( - ); + ) } export default App diff --git a/src/views/Index.jsx b/src/views/Index.jsx deleted file mode 100644 index 9034490..0000000 --- a/src/views/Index.jsx +++ /dev/null @@ -1,13 +0,0 @@ -export default function Index() { - return ( -

- Global Highlights Hub -
- Check out{" "} - - the docs at chinahighlights.com - - . -

- ); -} \ No newline at end of file diff --git a/src/views/Login.jsx b/src/views/Login.jsx index b973b96..f9c3008 100644 --- a/src/views/Login.jsx +++ b/src/views/Login.jsx @@ -17,7 +17,7 @@ function Login() { useEffect (() => { if (loginStatus === 302) { - navigate('/reservation/newest') + navigate('/') } }, [loginStatus]) From 4b77aa689e8a7c9396533fdaf3ffade85365ffc0 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 26 Jun 2024 15:57:36 +0800 Subject: [PATCH 046/120] =?UTF-8?q?=E4=BA=A7=E5=93=81=E7=BC=BA=E5=B0=91?= =?UTF-8?q?=E8=8B=B1=E6=96=87=E5=90=8D=E7=A7=B0=E6=97=B6,=20=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E8=AF=AD=E7=A7=8D=E8=A1=A8=E4=B8=AD=E6=96=87=E5=90=8D?= =?UTF-8?q?=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/products/Audit.jsx | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index 72a5742..2cbb8b1 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -62,6 +62,8 @@ const PriceTable = ({ dataSource, refresh }) => { const { message, notification } = App.useApp(); const stateMapVal = useProductsAuditStatesMapVal(); + // console.log(dataSource); + const handleAuditPriceItem = (state, row) => { postProductsQuoteAuditAction(state, { id: row.id, travel_agency_id: activeAgency.travel_agency_id }) .then((json) => { @@ -83,7 +85,7 @@ const PriceTable = ({ dataSource, refresh }) => { }; const columns = [ - { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan }) }, + { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan }), render: (text, r) => text || r.lgc_details?.['2']?.title || '' }, { 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'), }, @@ -135,16 +137,33 @@ const TypesPanels = (props) => { 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, + extra: t('Table.Total', { total: agencyProducts[ele.value].length }), children: ( r.concat(c.quotation.map((q, i) => ({ ...q, weekdays: q.weekdays.split(',').filter(Boolean).map(w => t(`weekdaysShort.${w}`)).join(', '), info: c.info, rowSpan: i === 0 ? c.quotation.length : 0 }))), [])} + dataSource={agencyProducts[ele.value].reduce( + (r, c) => + r.concat( + c.quotation.map((q, i) => ({ + ...q, + weekdays: q.weekdays + .split(',') + .filter(Boolean) + .map((w) => t(`weekdaysShort.${w}`)) + .join(', '), + info: c.info, + lgc_details: c.lgc_details.reduce((rlgc, clgc) => ({...r, [clgc.lgc]: clgc}), {}), + rowSpan: i === 0 ? c.quotation.length : 0, + })) + ), + [] + )} refresh={props.refresh} /> ), From 81f1c9cea9e27ae394a81a2a67327220cb952d91 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 26 Jun 2024 16:39:23 +0800 Subject: [PATCH 047/120] fix: wu_id -> lmi_sn --- src/main.jsx | 8 +++++--- src/stores/Account.js | 18 +++++++++--------- src/stores/Auth.js | 10 +++++----- src/views/account/Management.jsx | 4 +--- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main.jsx b/src/main.jsx index d3f498d..054b935 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -44,6 +44,8 @@ const { loginToken, userId } = usingStorage() const initAppliction = async () => { if (isNotEmpty(loginToken)) { appendRequestParams('token', loginToken) + appendRequestParams('lmi_sn', userId) + } if (isNotEmpty(userId)) { @@ -70,14 +72,14 @@ const router = createBrowserRouter([ { path: "feedback/:GRI_SN/:CII_SN/:RefNo", element: }, { path: "feedback/:GRI_SN/:RefNo", element: }, { path: "report", element: }, - { path: "notice", element: }, - { path: "notice/:CCP_BLID", element: }, + { path: "notice", element: }, + { path: "notice/:CCP_BLID", element: }, { path: "invoice",element:}, { path: "invoice/detail/:GMDSN/:GSN",element:}, { path: "invoice/paid",element:}, { path: "invoice/paid/detail/:flid", element: }, { path: "airticket",element: }, - { path: "airticket/plan/:coli_sn",element:}, + { path: "airticket/plan/:coli_sn",element:}, ] }, { diff --git a/src/stores/Account.js b/src/stores/Account.js index 804299a..01010d8 100644 --- a/src/stores/Account.js +++ b/src/stores/Account.js @@ -70,11 +70,11 @@ const useAccountStore = create((set, get) => ({ accountList: [], - disableAccount: async (accountId) => { + disableAccount: async (userId) => { const formData = new FormData() - formData.append('wu_id', accountId) - // enable disable + formData.append('lmi_sn', userId) + // enable | disable formData.append('account_status', 'disable') const result = await postAccountStatus(formData) @@ -82,10 +82,10 @@ const useAccountStore = create((set, get) => ({ console.info(result) }, - resetAccountPassword: async (accountId, password) => { + resetAccountPassword: async (userId, password) => { const formData = new FormData() - formData.append('wu_id', accountId) + formData.append('lmi_sn', userId) formData.append('newPassword', password) return postAccountPassword(formData) @@ -111,8 +111,8 @@ const useAccountStore = create((set, get) => ({ saveOrUpdateAccount: async (formValues) => { const { userId } = usingStorage() const formData = new FormData() - formData.append('wu_id', formValues.userId) - formData.append('lmi_sn', formValues.lmi_sn) + formData.append('wu_id', formValues.accountId) + formData.append('lmi_sn', formValues.userId) formData.append('lmi2_sn', formValues.lmi2_sn) formData.append('user_name', formValues.username) formData.append('real_name', formValues.realname) @@ -138,8 +138,8 @@ const useAccountStore = create((set, get) => ({ const mapAccoutList = resultArray.map((r) => { return { - userId: r.wu_id, - lmi_sn: r.lmi_sn, + accountId: r.wu_id, + userId: r.lmi_sn, lmi2_sn: r.lmi2_sn, username: r.user_name, realname: r.real_name, diff --git a/src/stores/Auth.js b/src/stores/Auth.js index 6e59ed9..8374151 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -36,7 +36,7 @@ export const fetchUserDetail = async (loginToken) => { export const fetchPermissionListByUserId = async (userId) => { const { errcode, result } = await fetchJSON( - `${HT_HOST}/service-CooperateSOA/get_account_permission_list`, { wu_id: userId}) + `${HT_HOST}/service-CooperateSOA/get_account_permission_list`, { lmi_sn: userId}) return errcode !== 0 ? {} : result } @@ -71,7 +71,7 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ // 以上是 Hardcode 判断 // 以下是权限列表从数据库读取后使用的方法 return permissionList.some((value) => { - if (value.indexOf(WILDCARD_TOKEN) > -1) { + if (value.indexOf(WILDCARD_TOKEN) == 0) { return true } if (value === perm) { @@ -85,9 +85,9 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ const { startTokenInterval, loadUserPermission } = get() const { setStorage } = usingStorage() - const { token: loginToken, WU_ID: userId } = await fetchLoginToken(usr, pwd) + const { token: loginToken } = await fetchLoginToken(usr, pwd) const userDetail = await fetchUserDetail(loginToken) - await loadUserPermission(userId) + await loadUserPermission(userDetail.LMI_SN) set(() => ({ tokenTimeout: false, @@ -95,7 +95,7 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ })) setStorage(KEY_LOGIN_TOKEN, loginToken) - setStorage(KEY_USER_ID, userId)//userDetail.LMI_SN) + setStorage(KEY_USER_ID, userDetail.LMI_SN) setStorage(KEY_TRAVEL_AGENCY_ID, userDetail.LMI_VEI_SN) appendRequestParams('token', loginToken) // loadPageSpy(`${json.Result.VName}-${json.Result.LoginName}`) diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 8a498dc..676ed9a 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -106,7 +106,6 @@ function Management() { } const onAccountSeleted = async (account) => { - console.info(account) setTravelAgencyList([{ label: account.travelAgencyName, value: account.travelAgencyId @@ -154,7 +153,6 @@ function Management() { } const handleTravelAgencyChange = (newValue) => { - console.info(newValue) setCurrentTravelAgency(newValue) } @@ -222,8 +220,8 @@ function Management() { )} > + - Date: Wed, 26 Jun 2024 17:02:18 +0800 Subject: [PATCH 048/120] =?UTF-8?q?debug:=20=E6=90=9C=E7=B4=A2=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E7=BB=91=E5=AE=9A=E7=9A=84=E9=99=84=E5=8A=A0=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE;=20=E4=BB=B7=E6=A0=BCvalue=E6=94=B9=E4=B8=BAadult=5Fc?= =?UTF-8?q?ost;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/products.json | 1 + public/locales/zh/products.json | 3 ++- src/hooks/useProductsSets.js | 33 ++++++++++++++++++---------- src/views/products/Audit.jsx | 4 ++-- src/views/products/Detail/Extras.jsx | 15 ++++++------- 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 6a62774..2a07ae5 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -1,4 +1,5 @@ { + "ProductType": "ProductType", "type": { "Experience": "Experience", "Car": "Transport Services", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 65cb019..1e1083b 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -1,4 +1,5 @@ { + "ProductType": "项目类型", "type": { "Experience": "综费", "Car": "车费", @@ -78,7 +79,7 @@ "GroupSize": "人等", "UseDates": "使用日期", - "Weekdays": "有效日/周X", + "Weekdays": "周末", "OnWeekdays": "周: ", "Unlimited": "不限", diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index 0dd63f7..5ee66d3 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -1,5 +1,7 @@ import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import useAuthStore from '@/stores/Auth'; +import { PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT } from '@/config'; /** * 产品管理 相关的预设数据 @@ -89,17 +91,26 @@ export const useProductsAuditStatesMapVal = (value) => { /** * @ignore */ -export const useProductsTypesFieldsets = (type, role) => { - const infoDefault = ['code', 'title']; +export const useProductsTypesFieldsets = (type) => { + const [isPermitted] = useAuthStore((state) => [state.isPermitted]); + const infoDefault = [['code'], ['title']]; const infoAdmin = ['remarks', 'dept', 'display_to_c']; const infoTypesMap = { - '6': [], - 'B': ['city_id', 'km'], - 'J': ['description', 'city_id', 'recommends_rate', 'duration', 'display_to_c'], - 'Q': ['description', 'city_id', 'duration', ], - 'D': ['description', 'city_id', 'recommends_rate','duration',], - '7': ['description', 'city_id', 'recommends_rate', 'duration', 'display_to_c', 'open_weekdays'], // todo: 怎么是2个图 - 'C': ['description', 'city_id',], - '8': [], // todo: ? + '6': [[],[]], + 'B': [['city_id', 'km'], []], + 'J': [['city_id', 'recommends_rate', 'duration', 'display_to_c'], ['description',]], + 'Q': [['city_id', 'duration', ], ['description',]], + 'D': [['city_id', 'recommends_rate','duration',], ['description',]], + '7': [['city_id', 'recommends_rate', 'duration', 'display_to_c', 'open_weekdays'], ['description',]], // todo: 怎么是2个图 + 'C': [['city_id',], ['description',]], + '8': [[],[]], // todo: ? }; -}; + const thisTypeFieldset = (_type) => { + const adminSet = isPermitted(PERM_PRODUCTS_MANAGEMENT) ? infoAdmin : []; + return [ + [...infoDefault[0], ...infoTypesMap[_type][0], ...adminSet], + [...infoDefault[1], ...infoTypesMap[_type][1]] + ]; + }; + return thisTypeFieldset(type); +} diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index 2cbb8b1..ca38cfd 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -86,8 +86,8 @@ const PriceTable = ({ dataSource, refresh }) => { const columns = [ { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan }), render: (text, r) => text || r.lgc_details?.['2']?.title || '' }, - { 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: 'adult', title: t('AgeType.Adult'), render: (_, { adult_cost, currency, unit_name }) => `${adult_cost} ${currency} / ${unit_name}` }, + { key: 'child', title: t('AgeType.Child'), render: (_, { child_cost, currency, unit_name }) => `${child_cost} ${currency} / ${unit_name}` }, // {key: 'price', title: t('Currency'), }, // {key: 'currency', title: t('Currency'), }, // {key: 'unit', title: t('Unit'), }, diff --git a/src/views/products/Detail/Extras.jsx b/src/views/products/Detail/Extras.jsx index 4caf981..c22a2fb 100644 --- a/src/views/products/Detail/Extras.jsx +++ b/src/views/products/Detail/Extras.jsx @@ -24,27 +24,26 @@ const NewAddonModal = ({ onPick, ...props }) => { const { starttime, endtime, ...param } = copyObject; setSearchLoading(true); setSearchResult([]); - const result = await getAgencyProductsAction({ ...param, audit_state: '1', travel_agency_id, use_year }); + // debug: audit_state: '1', + const result = await getAgencyProductsAction({ ...param, audit_state: '0', travel_agency_id, use_year }); setSearchResult(result?.products || []); setSearchLoading(false); }; const handleAddExtras = async (item) => { - // const success = await fetchBindOrder({ coli_sn, conversationid: currentConversationID }); - // success ? message.success('绑定成功') : message.error('绑定失败'); - // setOpen(false); if (typeof onPick === 'function') { onPick(item); } }; - // todo: + // todo: 如何显示价格表 const searchResultColumns = [ + { key: 'ptype', dataIndex: ['info', 'product_type_name'], width: '6rem', title: t('products:ProductType') }, { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('products:Title') }, { title: t('products:price'), - dataIndex: ['quotation', '0', 'value'], + dataIndex: ['quotation', '0', 'adult_cost'], width: '10rem', - render: (_, { quotation }) => `${quotation[0].value} ${quotation[0].currency} / ${quotation[0].unit_name}`, // todo: 成人 儿童 + render: (_, { quotation }) => `${quotation[0].adult_cost} ${quotation[0].currency} / ${quotation[0].unit_name}`, // todo: 成人 儿童 }, { key: 'action', @@ -143,7 +142,7 @@ const Extras = ({ productId, onChange, ...props }) => { dataIndex: ['quotation', '0', 'value'], width: '10rem', - render: (_, { quotation }) => `${quotation[0].value} ${quotation[0].currency} / ${quotation[0].unit_name}`, // todo: 成人 儿童 + render: (_, { quotation }) => `${quotation[0].adult_cost} ${quotation[0].currency} / ${quotation[0].unit_name}`, // todo: 成人 儿童 }, // { title: t('products:Types'), dataIndex: 'age_type', width: '40%', }, { From 09726a51bffe4f019a57d5c4af93c5b63bd91524 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Thu, 27 Jun 2024 10:02:02 +0800 Subject: [PATCH 049/120] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E7=8A=B6=E6=80=81=E5=92=8C=E5=90=AF=E7=94=A8=E5=92=8C?= =?UTF-8?q?=E7=A6=81=E7=94=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/zh/account.json | 3 +++ src/stores/Account.js | 12 +++++------ src/views/account/Management.jsx | 35 +++++++++++++++++++++++--------- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/public/locales/zh/account.json b/public/locales/zh/account.json index 35c0dc0..3849f07 100644 --- a/public/locales/zh/account.json +++ b/public/locales/zh/account.json @@ -12,7 +12,10 @@ "createdOn": "创建时间", "action": "操作", "action.edit": "编辑", + "action.enable": "启用", "action.disable": "禁用", + "action.enable.title": "确定启用该账号吗?", + "action.disable.title": "确定禁用该账号吗?", "action.resetPassword": "重置密码", "accountList": "管理账号", diff --git a/src/stores/Account.js b/src/stores/Account.js index 01010d8..ee3daca 100644 --- a/src/stores/Account.js +++ b/src/stores/Account.js @@ -70,16 +70,15 @@ const useAccountStore = create((set, get) => ({ accountList: [], - disableAccount: async (userId) => { + toggleAccountStatus: async (userId, status) => { + + const statusValue = status ? 'enable' : 'disable' const formData = new FormData() formData.append('lmi_sn', userId) - // enable | disable - formData.append('account_status', 'disable') - - const result = await postAccountStatus(formData) + formData.append('account_status', statusValue) - console.info(result) + return postAccountStatus(formData) }, resetAccountPassword: async (userId, password) => { @@ -147,6 +146,7 @@ const useAccountStore = create((set, get) => ({ lastLogin: r.wu_lastlogindate, travelAgencyName: r.travel_agency_name, travelAgencyId: r.travel_agency_id, + disabled: r.wu_limitsign, // 数据库支持逗号分隔多角色(5,6,7),目前界面只需单个。 roleId: parseInt(r.roles), role: r.roles_name, diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 676ed9a..5496997 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -3,7 +3,7 @@ import useAccountStore, { fetchRoleList, fetchTravelAgencyByName } from '@/store import useFormStore from '@/stores/Form' import { isEmpty } from '@/utils/commons' import { ExclamationCircleFilled } from '@ant-design/icons' -import { App, Button, Col, Form, Input, Modal, Row, Select, Space, Table, Typography } from 'antd' +import { App, Button, Col, Form, Input, Modal, Row, Select, Space, Table, Typography, Switch } from 'antd' import dayjs from 'dayjs' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -53,10 +53,12 @@ function Management() { ) } - function actionRender(text, account) { + function actionRender(_, account) { return ( - + { + showDisableConfirm(account, checked) + }} /> ) @@ -69,9 +71,9 @@ function Management() { const [currentTravelAgency, setCurrentTravelAgency] = useState(null) const [accountForm] = Form.useForm() - const [searchAccountByCriteria, accountList, disableAccount, saveOrUpdateAccount, resetAccountPassword] = + const [searchAccountByCriteria, accountList, toggleAccountStatus, saveOrUpdateAccount, resetAccountPassword] = useAccountStore((state) => - [state.searchAccountByCriteria, state.accountList, state.disableAccount, state.saveOrUpdateAccount, state.resetAccountPassword]) + [state.searchAccountByCriteria, state.accountList, state.toggleAccountStatus, state.saveOrUpdateAccount, state.resetAccountPassword]) const formValues = useFormStore(state => state.formValues) const { notification, modal } = App.useApp() @@ -116,7 +118,6 @@ function Management() { } const onAccountFinish = (values) => { - console.log(values) saveOrUpdateAccount(values) .then(() => { handelAccountSearch() @@ -156,13 +157,27 @@ function Management() { setCurrentTravelAgency(newValue) } - const showDisableConfirm = (account) => { + const showDisableConfirm = (account, status) => { + + const confirmTitle = status ? t('account:action.enable.title') : t('account:action.disable.title') + modal.confirm({ - title: 'Do you want to disable this account?', + title: confirmTitle, icon: , - content: `Username: ${account.username}, Realname: ${account.realname}`, + content: t('account:username') + ': ' + account.username + ', ' + t('account:realname') + ': ' + account.realname, onOk() { - disableAccount(account.userId) + toggleAccountStatus(account.userId, status) + .then(() => { + handelAccountSearch() + }) + .catch(ex => { + notification.error({ + message: 'Notification', + description: ex.message, + placement: 'top', + duration: 4, + }) + }) }, onCancel() { }, From cc2bf23bf5d4c0344afa6a3b765e1d158356bd7c Mon Sep 17 00:00:00 2001 From: Lei OT Date: Thu, 27 Jun 2024 11:14:52 +0800 Subject: [PATCH 050/120] =?UTF-8?q?feat:=20=E4=BA=A7=E5=93=81=E7=AE=A1?= =?UTF-8?q?=E7=90=86:=20`=E9=A4=90`=20=E7=B1=BB=E5=9E=8B=E4=BD=BF=E7=94=A8?= =?UTF-8?q?`R`=20;=20=E4=BC=98=E5=8C=96=E5=AE=A1=E6=A0=B8=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E4=BB=B7=E6=A0=BC=E8=A1=A8;=20=E9=99=84=E5=8A=A0?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E8=A1=A8=E6=98=BE=E7=A4=BA=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?;=20=E8=B6=85=E5=85=AC=E9=87=8CUltra=20Service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/products.json | 10 ++-- public/locales/zh/products.json | 10 ++-- src/hooks/useProductsSets.js | 78 +++++++++++++++------------- src/views/products/Audit.jsx | 8 ++- src/views/products/Detail/Extras.jsx | 5 +- 5 files changed, 62 insertions(+), 49 deletions(-) diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 2a07ae5..0e90423 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -1,5 +1,5 @@ { - "ProductType": "ProductType", + "ProductType": "Product Type", "type": { "Experience": "Experience", "Car": "Transport Services", @@ -8,8 +8,7 @@ "Attractions": "Attractions", "Meals": "Meals", "Extras": "Extras", - "Overtravel": "超公里", - "Special": "Special" + "UltraService": "Ultra Service" }, "EditComponents": { "info": "Product Information", @@ -30,6 +29,11 @@ "Rejected": "Reject", "Published": "Publish" }, + "PriceUnit": { + "0": "Person", + "1": "Group", + "title": "Price Unit" + }, "Status": "Status", "State": "State", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 1e1083b..8c592fb 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -3,13 +3,12 @@ "type": { "Experience": "综费", "Car": "车费", - "Guide": "导服", + "Guide": "导游", "Package": "包价线路", "Attractions": "景点", "Meals": "餐费", "Extras": "附加项目", - "Overtravel": "超公里", - "Special": "特殊项目" + "UltraService": "超公里" }, "EditComponents": { "info": "产品信息", @@ -30,6 +29,11 @@ "Rejected": "审核拒绝", "Published": "审核发布" }, + "PriceUnit": { + "0": "每人", + "1": "每团", + "title": "报价单位" + }, "Status": "状态", "State": "状态", diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index 5ee66d3..015fa8a 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -6,39 +6,39 @@ import { PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT } from '@/confi /** * 产品管理 相关的预设数据 * 项目类型 -酒店预定 1 -火车 2 -飞机票务 3 -游船 4 -快巴 5 -旅行社(综费) 6 -景点 7 -特殊项目 8 -其他 9 -酒店 A -超公里 B -餐费 C -小包价 D -站 X -购物 S -餐 R -娱乐 E -精华线路 T -客人testimonial F -线路订单 O -省 P -信息 I -国家 G -城市 K -图片 H -地图 M -包价线路 L -节日节庆 V -火车站 N -手机租赁 Z - * * webht 类型, 20240624 新增HT类型 -Q 导游 -J 车费 + * * 酒店预定 1 + * * 火车 2 + * * 飞机票务 3 + * * 游船 4 + * * 快巴 5 + * * 旅行社(综费) 6 + * * 景点 7 + * * 特殊项目 8 + * * 其他 9 + * * 酒店 A + * * 超公里 B + * * 餐费 C + * * 小包价 D + * * 站 X + * * 购物 S + * * 餐 R (餐厅) + * * 娱乐 E + * * 精华线路 T + * * 客人testimonial F + * * 线路订单 O + * * 省 P + * * 信息 I + * * 国家 G + * * 城市 K + * * 图片 H + * * 地图 M + * * 包价线路 L (已废弃) + * * 节日节庆 V + * * 火车站 N + * * 手机租赁 Z + * * ---- webht 类型, 20240624 新增HT类型 ---- + * * 导游 Q + * * 车费 J */ export const useProductsTypes = () => { @@ -48,20 +48,24 @@ export const useProductsTypes = () => { useEffect(() => { const newData = [ { label: t('products:type.Experience'), value: '6', key: '6' }, - { label: t('products:type.Overtravel'), 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.Guide'), value: 'Q', key: 'Q' }, { label: t('products:type.Package'), value: 'D', key: 'D' }, // 包价线路 { label: t('products:type.Attractions'), value: '7', key: '7' }, - { label: t('products:type.Meals'), value: 'C', key: 'C' }, + { label: t('products:type.Meals'), value: 'R', key: 'R' }, { label: t('products:type.Extras'), value: '8', key: '8' }, - // { label: t('products:type.Special'), value: 'Special', key: 'Special' }, ]; setTypes(newData); }, [i18n.language]); return types; }; +export const useProductsTypesMapVal = (value) => { + const stateSets = useProductsTypes(); + const stateMapVal = stateSets.reduce((r, c) => ({ ...r, [`${c.value}`]: c }), {}); + return stateMapVal; +}; export const useProductsAuditStates = () => { const [types, setTypes] = useState([]); @@ -102,7 +106,7 @@ export const useProductsTypesFieldsets = (type) => { 'Q': [['city_id', 'duration', ], ['description',]], 'D': [['city_id', 'recommends_rate','duration',], ['description',]], '7': [['city_id', 'recommends_rate', 'duration', 'display_to_c', 'open_weekdays'], ['description',]], // todo: 怎么是2个图 - 'C': [['city_id',], ['description',]], + 'R': [['city_id',], ['description',]], '8': [[],[]], // todo: ? }; const thisTypeFieldset = (_type) => { diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index ca38cfd..9a23e0d 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -85,11 +85,9 @@ const PriceTable = ({ dataSource, refresh }) => { }; const columns = [ - { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan }), render: (text, r) => text || r.lgc_details?.['2']?.title || '' }, - { key: 'adult', title: t('AgeType.Adult'), render: (_, { adult_cost, currency, unit_name }) => `${adult_cost} ${currency} / ${unit_name}` }, - { key: 'child', title: t('AgeType.Child'), render: (_, { child_cost, currency, unit_name }) => `${child_cost} ${currency} / ${unit_name}` }, - // {key: 'price', title: t('Currency'), }, - // {key: 'currency', title: t('Currency'), }, + { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan }), render: (text, r) => text || r.lgc_details?.['2']?.title || r.lgc_details?.['1']?.title || '' }, + { key: 'adult', title: t('AgeType.Adult'), render: (_, { adult_cost, currency, unit_id, unit_name }) => `${adult_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}` }, + { key: 'child', title: t('AgeType.Child'), render: (_, { child_cost, currency, unit_id, unit_name }) => `${child_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}` }, // {key: 'unit', title: t('Unit'), }, { key: 'groupSize', diff --git a/src/views/products/Detail/Extras.jsx b/src/views/products/Detail/Extras.jsx index c22a2fb..2ee8587 100644 --- a/src/views/products/Detail/Extras.jsx +++ b/src/views/products/Detail/Extras.jsx @@ -8,12 +8,15 @@ import SearchForm from '@/components/SearchForm'; import RequireAuth from '@/components/RequireAuth'; import { PERM_PRODUCTS_MANAGEMENT } from '@/config'; +import { useProductsTypesMapVal } from '@/hooks/useProductsSets'; const NewAddonModal = ({ onPick, ...props }) => { const { travel_agency_id, use_year } = useParams(); const { t } = useTranslation(); const { notification, message } = App.useApp(); + const productsTypesMapVal = useProductsTypesMapVal(); + const [open, setOpen] = useState(false); const [loading, setLoading] = useState(false); // bind loading const [searchLoading, setSearchLoading] = useState(false); @@ -37,7 +40,7 @@ const NewAddonModal = ({ onPick, ...props }) => { // todo: 如何显示价格表 const searchResultColumns = [ - { key: 'ptype', dataIndex: ['info', 'product_type_name'], width: '6rem', title: t('products:ProductType') }, + { key: 'ptype', dataIndex: ['info', 'product_type_id'], width: '6rem', title: t('products:ProductType'), render: (text, r) => productsTypesMapVal[text].label }, { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('products:Title') }, { title: t('products:price'), From ff6071cd2fbf602d1fdcc9f078ce8eddb7cbc3c6 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Thu, 27 Jun 2024 11:20:02 +0800 Subject: [PATCH 051/120] =?UTF-8?q?wu=5Fid=20=E6=94=B9=20lmi=5Fsn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 2 +- src/stores/Auth.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.jsx b/src/main.jsx index f7a0b42..02f2244 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -53,7 +53,7 @@ const initAppliction = async () => { } if (isNotEmpty(userId)) { - appendRequestParams('wu_id', userId) + appendRequestParams('lmi_sn', userId) await fireAuth() } } diff --git a/src/stores/Auth.js b/src/stores/Auth.js index 421d4cb..344e147 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -98,7 +98,7 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ setStorage(KEY_USER_ID, userDetail.LMI_SN) setStorage(KEY_TRAVEL_AGENCY_ID, userDetail.LMI_VEI_SN) appendRequestParams('token', loginToken) - appendRequestParams('wu_id', userDetail.LMI_SN) + appendRequestParams('lmi_sn', userDetail.LMI_SN) // loadPageSpy(`${json.Result.VName}-${json.Result.LoginName}`) startTokenInterval() }, @@ -172,4 +172,4 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ }))) -export default useAuthStore \ No newline at end of file +export default useAuthStore From e3a28ebf252b309f2c27c1c3597845d7dde0b8f9 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Thu, 27 Jun 2024 13:49:25 +0800 Subject: [PATCH 052/120] =?UTF-8?q?=E6=9D=83=E9=99=90=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E8=A7=92=E8=89=B2=E4=B8=8D=E5=90=8C=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E9=A6=96=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/RBAC 权限.sql | 10 ++++++++++ src/stores/Auth.js | 15 +++++++++++++++ src/views/Login.jsx | 7 +++---- src/views/account/Management.jsx | 6 +++--- src/views/account/RoleList.jsx | 1 + 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/doc/RBAC 权限.sql b/doc/RBAC 权限.sql index b463af5..3118103 100644 --- a/doc/RBAC 权限.sql +++ b/doc/RBAC 权限.sql @@ -74,6 +74,16 @@ VALUES ('审核价格', '/products/offer/audit', 'products') INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) VALUES ('录入价格', '/products/offer/put', 'products') +-- 默认页面 +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('最新计划', 'route=/reservation/newest', 'page') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('机票订票', 'route=/airticket', 'page') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('产品管理(客服)', 'route=/products', 'page') +INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) +VALUES ('产品管理(供应商)', 'route=/products?from', 'page') + INSERT INTO [dbo].[auth_permission] ([role_id] ,[res_id]) VALUES (1, 1) INSERT INTO [dbo].[auth_permission] ([role_id] ,[res_id]) diff --git a/src/stores/Auth.js b/src/stores/Auth.js index 8374151..41b8899 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -60,6 +60,8 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ loginStatus: 0, + defaltRoute: '', + permissionList: [], isPermitted: (perm) => { @@ -98,13 +100,26 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ setStorage(KEY_USER_ID, userDetail.LMI_SN) setStorage(KEY_TRAVEL_AGENCY_ID, userDetail.LMI_VEI_SN) appendRequestParams('token', loginToken) + appendRequestParams('lmi_sn', userDetail.LMI_SN) // loadPageSpy(`${json.Result.VName}-${json.Result.LoginName}`) startTokenInterval() }, loadUserPermission: async(userId) => { + let deaultPage = '/' const permissionResult = await fetchPermissionListByUserId(userId) + const pageList = permissionResult.filter(p => { + return p.res_category === 'page' + }) + if (pageList.length > 0) { + const resPattern = pageList[0].res_pattern + const splitResult = resPattern.split('=') + if (splitResult.length > 1) + deaultPage = splitResult[1] + } + set(() => ({ + defaultRoute: deaultPage, permissionList: permissionResult.map(p => p.res_pattern) })) }, diff --git a/src/views/Login.jsx b/src/views/Login.jsx index f9c3008..a994d2d 100644 --- a/src/views/Login.jsx +++ b/src/views/Login.jsx @@ -6,9 +6,8 @@ import useAuthStore from '@/stores/Auth' import useNoticeStore from '@/stores/Notice' function Login() { - const [validateUserPassword, loginStatus] = - useAuthStore((state) => [state.validateUserPassword, state.loginStatus]) - const getBulletinUnReadCount = useNoticeStore((state) => state.getBulletinUnReadCount) + const [validateUserPassword, loginStatus, defaultRoute] = + useAuthStore((state) => [state.validateUserPassword, state.loginStatus, state.defaultRoute]) const { t, i18n } = useTranslation() const { notification } = App.useApp() @@ -17,7 +16,7 @@ function Login() { useEffect (() => { if (loginStatus === 302) { - navigate('/') + navigate(defaultRoute) } }, [loginStatus]) diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 5496997..9fd988c 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -185,7 +185,7 @@ function Management() { } const showResetPasswordConfirm = (account) => { - const randomPassword = account.username + (Math.floor(Math.random() * 900) + 100) + const randomPassword = account.username + '@' + (Math.floor(Math.random() * 900) + 100) modal.confirm({ title: 'Do you want to reset password?', icon: , @@ -194,8 +194,8 @@ function Management() { resetAccountPassword(account.userId, randomPassword) .then(() => { notification.info({ - message: '新密码:' + randomPassword, - description: `请复制密码给 [${account.realname}]`, + message: `请复制新密码给 [${account.realname}]`, + description: '新密码:' + randomPassword, placement: 'top', duration: 60, }) diff --git a/src/views/account/RoleList.jsx b/src/views/account/RoleList.jsx index 71909f1..463ba60 100644 --- a/src/views/account/RoleList.jsx +++ b/src/views/account/RoleList.jsx @@ -69,6 +69,7 @@ function RoleList() { ['domestic', '国内供应商'], ['air-ticket', '机票供应商'], ['products', '产品价格'], + ['page', '默认页面'], ]); const permissionTree = [] From 2ad323edbbba48902f4837a3971af366799bb247 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Thu, 27 Jun 2024 15:01:46 +0800 Subject: [PATCH 053/120] =?UTF-8?q?=E5=AE=A2=E6=9C=8D=E5=AE=A1=E6=A0=B8?= =?UTF-8?q?=E4=BB=B7=E6=A0=BC:=20=E8=B6=85=E5=85=AC=E9=87=8C+=E5=85=AC?= =?UTF-8?q?=E9=87=8C=E6=95=B0=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/products/Audit.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index 9a23e0d..803507f 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -56,7 +56,7 @@ const Header = ({ title, agency, refresh, ...props }) => { ); }; -const PriceTable = ({ dataSource, refresh }) => { +const PriceTable = ({ productType, dataSource, refresh }) => { const { t } = useTranslation('products'); const [loading, activeAgency] = useProductsStore((state) => [state.loading, state.activeAgency]); const { message, notification } = App.useApp(); @@ -86,6 +86,7 @@ const PriceTable = ({ dataSource, refresh }) => { const columns = [ { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan }), render: (text, r) => text || r.lgc_details?.['2']?.title || r.lgc_details?.['1']?.title || '' }, + ...(productType === 'B' ? [{ key: 'km', dataIndex: ['info', 'km'], title: t('KM')}] : []), { key: 'adult', title: t('AgeType.Adult'), render: (_, { adult_cost, currency, unit_id, unit_name }) => `${adult_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}` }, { key: 'child', title: t('AgeType.Child'), render: (_, { child_cost, currency, unit_id, unit_name }) => `${child_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}` }, // {key: 'unit', title: t('Unit'), }, @@ -145,6 +146,7 @@ const TypesPanels = (props) => { children: ( r.concat( From 07b196fc6685383112a60698889cc46e7f75c880 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Thu, 27 Jun 2024 15:03:10 +0800 Subject: [PATCH 054/120] =?UTF-8?q?test:=20=E8=B0=83=E6=95=B4antd=E9=97=B4?= =?UTF-8?q?=E8=B7=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/App.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/views/App.jsx b/src/views/App.jsx index 3541d9b..b4fb2e2 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -90,6 +90,8 @@ function App() { theme={{ token: { colorPrimary: '#00b96b', + // "sizeStep": 3, + // "sizeUnit": 3, }, algorithm: theme.defaultAlgorithm, }}> From ce60237b3da40cc3e602dbc515880b2f14189faa Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Thu, 27 Jun 2024 15:10:58 +0800 Subject: [PATCH 055/120] =?UTF-8?q?feat:=20=E7=BB=9F=E4=B8=80=E7=99=BB?= =?UTF-8?q?=E9=99=86=E5=92=8C=E5=88=B7=E6=96=B0=E9=A1=B5=E9=9D=A2=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/价格管理平台.bmpr | Bin 276480 -> 278528 bytes src/main.jsx | 8 +-- src/stores/Auth.js | 89 +++++++++++++++---------------- src/utils/lifecycle.js | 4 +- src/views/App.jsx | 6 +-- src/views/Login.jsx | 6 +-- src/views/account/Management.jsx | 2 +- 7 files changed, 55 insertions(+), 60 deletions(-) diff --git a/doc/价格管理平台.bmpr b/doc/价格管理平台.bmpr index eb1a8d6ac17c14b1cafbeb9b654b686874de0f72..de2695d7d545f1645b1e5f1eacb453cdb73240fa 100644 GIT binary patch delta 15938 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 E0F~~jWdHyG delta 437 zcmW-cODL>i7=Yhf%`D6`q!Y0~7OtJ?c|N;{7e2?|HxX^7Ve%Y z26~|nVs>^yKXgDl#J_I|i;|GGT*jha%O&C0cQp+u>+7@rxH)093?!Y?Knq5TkAdbJ z+;bySxT@=aXpxI_=Tu;PCI6)PCtR4t1C?6-WX!2l`QB8i^zEobd~d0g`QBCunw2LB zMMW%5+vX55c8t8SZEP8iv1#Ot1LM%RG_H(m!#nThTQ2Y%1##aU8K;nVA>& zeS#`9F-#9s;D0oPS1R-V?6t}ynLU+dv^PBZ{E4p@ZOn7{;?_qg?S@;pPXBK3@pOSb K66F$q4156+&U7*W diff --git a/src/main.jsx b/src/main.jsx index 054b935..7946e02 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -39,16 +39,12 @@ import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } import './i18n'; -const { loginToken, userId } = usingStorage() const initAppliction = async () => { - if (isNotEmpty(loginToken)) { - appendRequestParams('token', loginToken) - appendRequestParams('lmi_sn', userId) - } + const { loginToken, userId } = usingStorage() - if (isNotEmpty(userId)) { + if (isNotEmpty(userId) && isNotEmpty(loginToken)) { await fireAuth() } } diff --git a/src/stores/Auth.js b/src/stores/Auth.js index 41b8899..a2b9a42 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -3,14 +3,12 @@ import { appendRequestParams, fetchJSON, postForm } from '@/utils/request' import { HT_HOST } from "@/config" import { loadPageSpy } from '@/pageSpy' import { usingStorage } from '@/hooks/usingStorage' -import { devtools } from 'zustand/middleware' import { obervseLifecycle } from '@/utils/lifecycle' const KEY_LOGIN_TOKEN = 'G-STR:LOGIN_TOKEN' const KEY_TRAVEL_AGENCY_ID = 'G-INT:TRAVEL_AGENCY_ID' const KEY_USER_ID = 'G-INT:USER_ID' -const KEY_USER_DETAIL = 'G-JSON:USER_DETAIL' const WILDCARD_TOKEN = '*' @@ -47,62 +45,33 @@ async function fetchLastRequet() { const useAuthStore = create(obervseLifecycle((set, get) => ({ - onAuth: () => { + onAuth: async () => { const { startTokenInterval, loadUserPermission } = get() - const { userId } = usingStorage() - loadUserPermission(userId) - startTokenInterval() - }, - - tokenInterval: null, + const { userId, loginToken } = usingStorage() - tokenTimeout: false, - - loginStatus: 0, - - defaltRoute: '', - - permissionList: [], - - isPermitted: (perm) => { - const { permissionList } = get() - // 测试权限使用: - // if (perm === '/account/management') return false - // if (perm === '/account/role/new') return false - // return true - // 以上是 Hardcode 判断 - // 以下是权限列表从数据库读取后使用的方法 - return permissionList.some((value) => { - if (value.indexOf(WILDCARD_TOKEN) == 0) { - return true - } - if (value === perm) { - return true - } - return false - }) + appendRequestParams('token', loginToken) + appendRequestParams('lmi_sn', userId) + await loadUserPermission(userId) + startTokenInterval() }, - validateUserPassword: async (usr, pwd) => { - const { startTokenInterval, loadUserPermission } = get() + authenticate: async (usr, pwd) => { + const { onAuth } = get() const { setStorage } = usingStorage() const { token: loginToken } = await fetchLoginToken(usr, pwd) const userDetail = await fetchUserDetail(loginToken) - await loadUserPermission(userDetail.LMI_SN) + + setStorage(KEY_LOGIN_TOKEN, loginToken) + setStorage(KEY_USER_ID, userDetail.LMI_SN) + setStorage(KEY_TRAVEL_AGENCY_ID, userDetail.LMI_VEI_SN) + + await onAuth() set(() => ({ tokenTimeout: false, loginStatus: 302 })) - - setStorage(KEY_LOGIN_TOKEN, loginToken) - setStorage(KEY_USER_ID, userDetail.LMI_SN) - setStorage(KEY_TRAVEL_AGENCY_ID, userDetail.LMI_VEI_SN) - appendRequestParams('token', loginToken) - appendRequestParams('lmi_sn', userDetail.LMI_SN) - // loadPageSpy(`${json.Result.VName}-${json.Result.LoginName}`) - startTokenInterval() }, loadUserPermission: async(userId) => { @@ -130,6 +99,7 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ clearStorage() clearInterval(tokenInterval) set(() => ({ + defaultRoute: '/', loginStatus: 0, tokenInterval: null, tokenTimeout: true @@ -184,6 +154,35 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ }); }, + isPermitted: (perm) => { + const { permissionList } = get() + // 测试权限使用: + // if (perm === '/account/management') return false + // if (perm === '/account/role/new') return false + // return true + // 以上是 Hardcode 判断 + // 以下是权限列表从数据库读取后使用的方法 + return permissionList.some((value) => { + if (value.indexOf(WILDCARD_TOKEN) == 0) { + return true + } + if (value === perm) { + return true + } + return false + }) + }, + + tokenInterval: null, + + tokenTimeout: false, + + loginStatus: 0, + + defaltRoute: '', + + permissionList: [], + }))) export default useAuthStore \ No newline at end of file diff --git a/src/utils/lifecycle.js b/src/utils/lifecycle.js index a2de514..1e71d68 100644 --- a/src/utils/lifecycle.js +++ b/src/utils/lifecycle.js @@ -15,8 +15,8 @@ export const fireInit = async () => { }) } -export const fireAuth = (obj) => { - authListener.forEach(fn => fn(obj)) +export const fireAuth = async (obj) => { + authListener.forEach(async (fn) => await fn(obj)) } // Zustand 中间件,用于订阅前端应用的生命周期,实验阶段 diff --git a/src/views/App.jsx b/src/views/App.jsx index 516c02d..22b385d 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -29,8 +29,8 @@ function App() { const [password, setPassword] = useState('') const [userDetail, setUserDetail] = useState({}) - const [validateUserPassword, tokenTimeout, isPermitted] = useAuthStore( - (state) => [state.validateUserPassword, state.tokenTimeout, state.isPermitted]) + const [authenticate, tokenTimeout, isPermitted] = useAuthStore( + (state) => [state.authenticate, state.tokenTimeout, state.isPermitted]) const { loginToken } = usingStorage() @@ -61,7 +61,7 @@ function App() { }, [href]) const onSubmit = () => { - validateUserPassword(userDetail?.username, password) + authenticate(userDetail?.username, password) .catch(ex => { console.error(ex) alert(t('Validation.LoginFailed')) diff --git a/src/views/Login.jsx b/src/views/Login.jsx index a994d2d..1ce2cc3 100644 --- a/src/views/Login.jsx +++ b/src/views/Login.jsx @@ -6,8 +6,8 @@ import useAuthStore from '@/stores/Auth' import useNoticeStore from '@/stores/Notice' function Login() { - const [validateUserPassword, loginStatus, defaultRoute] = - useAuthStore((state) => [state.validateUserPassword, state.loginStatus, state.defaultRoute]) + const [authenticate, loginStatus, defaultRoute] = + useAuthStore((state) => [state.authenticate, state.loginStatus, state.defaultRoute]) const { t, i18n } = useTranslation() const { notification } = App.useApp() @@ -21,7 +21,7 @@ function Login() { }, [loginStatus]) const onFinish = (values) => { - validateUserPassword(values.username, values.password) + authenticate(values.username, values.password) .catch(ex => { console.error(ex) notification.error({ diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 9fd988c..92357b1 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -41,7 +41,7 @@ function Management() { render: (text) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD HH:mm:ss')) }, { - title: t('account:action.edit'), + title: t('account:action'), dataIndex: 'account:action', render: actionRender }, From 64f690801ed13221c9c2cdbfbafcde2a5e72fc66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=96=87=E5=BC=BA=40HWQ-PC?= Date: Thu, 27 Jun 2024 16:12:43 +0800 Subject: [PATCH 056/120] =?UTF-8?q?1.=E4=BC=98=E5=8C=96=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=202.=E5=AF=B9=E6=8E=A5=E4=BE=9B=E5=BA=94?= =?UTF-8?q?=E5=95=86=E7=BC=96=E8=BE=91=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/zh/products.json | 2 +- src/views/products/Audit.jsx | 1 - src/views/products/Detail.jsx | 284 +++++++++++++++++--------------- 3 files changed, 149 insertions(+), 138 deletions(-) diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 55005ce..1d7ebff 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -43,7 +43,7 @@ "AuditDate": "审核时间", "productProject": "产品项目", - "Code": "代码", + "Code": "简码", "City": "城市", "Remarks": "备注", "tourTime": "游览时间", diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index 72a5742..9646fd7 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -152,7 +152,6 @@ const TypesPanels = (props) => { setShowTypes(_show); setActiveKey(isEmpty(_show) ? [] : [_show[0].key]); - return () => {}; }, [productsTypes, agencyProducts]); diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx index 716ffa7..1b35f80 100644 --- a/src/views/products/Detail.jsx +++ b/src/views/products/Detail.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef,useMemo } from 'react'; import { Button, Card, Col, Row, Breadcrumb, Table, Popconfirm, Form, Input, InputNumber, Tag, Modal, Select, Tree } from 'antd'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; @@ -6,26 +6,63 @@ import DateComponent from '@/components/date'; import { fetchJSON } from "@/utils/request"; import { type } from 'windicss/utils'; import { searchAgencyAction, getAgencyProductsAction } from '@/stores/Products/Index'; - +import { useProductsTypes } from '@/hooks/useProductsSets'; import Extras from './Detail/Extras'; - - +import { groupBy } from '@/utils/commons'; +import { useParams } from 'react-router-dom'; +import { useHTLanguageSets } from '@/hooks/useHTLanguageSets'; +// import { useDefaultLgc } from '@/i18n/LanguageSwitcher'; function Index() { const { t } = useTranslation(); const [form] = Form.useForm(); const [editingid, setEditingid] = useState(''); const [tags, setTags] = useState(['中文', 'English']); const [isModalVisible, setIsModalVisible] = useState(false); - const [selectedTag, setSelectedTag] = useState('中文'); + const [selectedTag, setSelectedTag] = useState(2); const [saveData, setSaveData] = useState(null); const [datePickerVisible, setDatePickerVisible] = useState(false); const [currentid, setCurrentid] = useState(null); const [languageStatus, setLanguageStatus] = useState([{ "中文": { title: "", description: "" } }, { "English": { title: "", description: "" } }]); - const [formState, setFormState] = useState({}); const [selectedNodeid, setSelectedNodeid] = useState(null); const [selectedDateData, setSelectedDateData] = useState({ dateRange: null, selectedDays: [] }); - const [startValue,setStartValue] = useState(null); - const [endValue,setEndValue] = useState(null); + const [treeData1, setTreeData1] = useState([]); + const productsTypes = useProductsTypes(); + const [productsData, setProductsData] = useState(null); + const [quotation, setQuotation] = useState(null); + const [lgc_details,setLgc_details] = useState(null); + const { travel_agency_id } = useParams(); + + + const fetchData = async () => { + const a = { travel_agency_id }; + const res = await getAgencyProductsAction(a); + + const groupedProducts = groupBy(res.products, (row) => row.info.product_type_id); + + const generateTreeData = (productsTypes, productsData) => { + return productsTypes.map(type => ({ + title: type.label, + key: type.value, + selectable: false, + children: (productsData[type.value] || []).map(product => ({ + title: product.info.title, + key: `${type.value}-${product.info.id}`, + })) + })); + }; + + const treeData = generateTreeData(productsTypes, groupedProducts); + setProductsData(groupedProducts); + setTreeData1(treeData); + + console.log("setTreeData1", treeData); + }; + + + useEffect(() => { + fetchData(); + }, [productsTypes]); + const productProject = [ { code: "code", name: t('products:Code') }, @@ -35,13 +72,6 @@ function Index() { { code: "recommends_rate", name: t('products:recommendationRate') } ]; - const initialData = [ - { id: '1', adult_cost: '1000', child_cost: "800", currency: 'RMB', age_type: '每人', group_size_min: 4, group_size_max: 5, use_dates_start: '2024/06/12',use_dates_end: '2024/07/22', weekdays: '' }, - { id: '2', adult_cost: '1200', child_cost: "900", currency: 'RMB', age_type: '每人', group_size_min: 3, group_size_max: 5, use_dates_start: '2024/06/12',use_dates_end: '2024/07/22', weekdays: '' }, - { id: '3', adult_cost: '1500', child_cost: "1200", currency: 'RMB', age_type: '每人', group_size_min: 2, group_size_max: 5, use_dates_start: '2024/06/12',use_dates_end: '2024/07/22', weekdays: '' }, - { id: '4', adult_cost: '1100', child_cost: "700", currency: 'RMB', age_type: '每人', group_size_min: 1, group_size_max: 5, use_dates_start: '2024/06/12',use_dates_end: '2024/07/22', weekdays: '' }, - ]; - const [quotation, setQuotation] = useState(initialData); const isEditing = (record) => record.id === editingid; const edit = (record) => { @@ -87,12 +117,12 @@ function Index() { id: `${quotation.length + 1}`, value: '', currency: '', - age_type: '', + unit_name: '', weekdays: '', use_dates_start: '', use_dates_end: '', - group_size_min:'', - group_size_max:'' + group_size_min: '', + group_size_max: '' }; setQuotation([...quotation, newData]); }; @@ -105,9 +135,8 @@ function Index() { const handleDateChange = ({ dateRange, selectedDays }) => { // 计算周末 - const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday','Sunday']; + const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; let weekDayCount = selectedDays.map(day => weekdays.indexOf(day) + 1).sort().join(','); - console.log('Weekday Count:', weekDayCount); if (!weekDayCount || weekDayCount.length === 0) { weekDayCount = "全年"; } @@ -124,15 +153,10 @@ function Index() { const handleDateOk = () => { const { dateRange } = selectedDateData; const dateRangeList = dateRange.split('-'); - console.log("dateRangeList",dateRangeList); const use_dates_start = dateRangeList[0]; const use_dates_end = dateRangeList[1]; - - console.log("dateRange",dateRange) - if (currentid !== null) { const newData = [...quotation]; - console.log("newData",newData) const index = newData.findIndex((item) => currentid === item.id); if (index > -1) { newData[index].use_dates_start = use_dates_start; @@ -155,7 +179,7 @@ function Index() { ); } - if (dataIndex === 'age_type' && editing) { + if (dataIndex === 'unit_name' && editing) { inputNode = (
+ ); } @@ -211,27 +234,19 @@ function Index() { ); }; - const handleInputGroupSize = (name,id, dataIndex, value ) => { - + const handleInputGroupSize = (name, id, dataIndex, value) => { + const newData = [...quotation]; - console.log("newData",newData); - console.log("value",value); const index = newData.findIndex((item) => id === item.id); if (index > -1) { const item = newData[index]; - console.log("item",item) - newData[index] = {...item,} - if(name==='group_size_min'){ + newData[index] = { ...item, } + if (name === 'group_size_min') { newData[index] = { ...item, group_size_min: value }; - console.log("newData[index]",newData[index]) - }else{ + } else { newData[index] = { ...item, group_size_max: value }; - console.log("newData[index]",newData[index]) } - // const [groupSizeMin, groupSizeMax] = value.split('-').map(val => parseInt(val.trim())); - setQuotation(newData); - console.log("setQuotation",newData) } } @@ -240,7 +255,7 @@ function Index() { { title: t('products:adultPrice'), dataIndex: 'adult_cost', width: '10%', editable: true }, { title: t('products:childrenPrice'), dataIndex: 'child_cost', width: '10%', editable: true }, { title: t('products:currency'), dataIndex: 'currency', width: '10%', editable: true }, - { title: t('products:Types'), dataIndex: 'age_type', width: '10%', editable: true }, + { title: t('products:Types'), dataIndex: 'unit_name', width: '10%', editable: true }, { title: t('products:number'), dataIndex: 'group_size', @@ -248,15 +263,16 @@ function Index() { editable: true, render: (_, record) => `${record.group_size_min}-${record.group_size_max}` }, - - { title: t('products:validityPeriod'), - dataIndex: 'validityPeriod', - width: '20%', + + { + title: t('products:validityPeriod'), + dataIndex: 'validityPeriod', + width: '20%', editable: true, render: (_, record) => `${record.use_dates_start}-${record.use_dates_end}` }, - { title: t('products:weekend'), dataIndex: 'weekdays', width: '10%' }, + { title: t('products:Weekdays'), dataIndex: 'weekdays', width: '10%' }, { title: t('products:operation'), @@ -271,14 +287,14 @@ function Index() { ) : ( edit(record)} style={{ marginRight: 8 }}>{t('products:edit')} - handleDelete(record.id)}> + handleDelete(record.id)}> {t('products:delete')} ); }, }, - + ]; @@ -301,6 +317,10 @@ function Index() { const handleTagClick = (tag) => { setSelectedTag(tag); + console.log("handleTagClick",tag) + // form.setFieldsValue({ + + // }) }; @@ -337,54 +357,68 @@ function Index() { //树组件方法 - const handleNodeSelect = async (_, { node }) => { - + const handleNodeSelect = (_, { node }) => { + // 如果点击的是同一个节点,不做任何操作 - if (selectedNodeid === node.id) return; - - // 保存当前表单数据 - if (selectedNodeid) { - await handleSaveForm(); - } - - // 更新选中的节点 - setSelectedNodeid(node.id); - - // 加载新节点的表单数据 - if (formState[node.id]) { - form.setFieldsValue(formState[node.id]); - setQuotation(formState[node.id].quotation || []); - setLanguageStatus(formState[node.id].lgc_details || languageStatus); - } else { - form.resetFields(); - setQuotation(initialData); - setLanguageStatus([{ "中文": { title: "", description: "" } }, { "English": { title: "", description: "" } }]); - } - }; + if (selectedNodeid === node.key) return; + + const fatherKey = node.key.split('-')[0]; + let initialQuotationData = null; + let infoData = null; + let lgcDetailsData = null; + productsData[fatherKey].forEach(element => { + if (element.info.title === node.title) { + initialQuotationData = element.quotation; + infoData = element.info; + lgcDetailsData = element.lgc_details; + return true; + } + }); - const handleSaveForm = async () => { - try { - const values = await form.validateFields(); - const newFormState = { - ...formState, - [selectedNodeid]: { - ...values, - quotation, - lgc_details: languageStatus, + console.log("info",infoData) + + // 累积 lgc_details 数据 + let newLgcDetails = {}; + lgcDetailsData.forEach(element => { + newLgcDetails[element.lgc] = element; + }); + + // 一次性更新 lgc_details + setLgc_details(newLgcDetails); + + setQuotation(initialQuotationData); + + // 使用 setTimeout 确保 lgc_details 已经更新 + // setTimeout(() => { + form.setFieldsValue({ + info: { + title: infoData.title, + code: infoData.code, + product_type_name: infoData.product_type_name, + city_name: infoData.city_name, + remarks: infoData.remarks, + open_weekdays: infoData.open_weekdays, + recommends_rate: infoData.recommends_rate, + unit_name: infoData.unit_name }, - }; - setFormState(newFormState); - } catch (error) { - console.error('Validation failed:', error); - } + lgc_details: { + title: newLgcDetails[selectedTag]?.title || '', + descriptions: newLgcDetails[selectedTag]?.descriptions || '' + } + }); + // }, 0); }; + + + + const onSave = (values) => { const tempData = values; tempData['quotation'] = quotation; tempData['extras'] = bindingData; - tempData['lgc_details'] = languageStatus; + // tempData['lgc_details'] = languageStatus; setSaveData(tempData); console.log("保存的数据", tempData) }; @@ -419,7 +453,7 @@ function Index() { }) => { let inputNode = inputType === 'number' ? : ; - if (dataIndex === 'age_type' && editing) { + if (dataIndex === 'unit_name' && editing) { inputNode = ( handleChange('title', e.target.value)} + // value={findLanguageDetails(selectedTag).title} + // onChange={(e) => handleChange('title', e.target.value)} /> - + handleChange('description', e.target.value)} + // value={findLanguageDetails(selectedTag).description} + // onChange={(e) => handleChange('description', e.target.value)} /> @@ -703,8 +715,8 @@ function Index() { - From 9304c4735d0f9cdb34152f66adfa3415d8c2e856 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Fri, 28 Jun 2024 09:06:35 +0800 Subject: [PATCH 057/120] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E4=BD=BF=E7=94=A8=E7=A9=BA=E5=AF=B9=E8=B1=A1=EF=BC=9B?= =?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8D=20lifecycleware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 5 ++--- src/stores/Account.js | 16 ++++++++++++++-- src/stores/Auth.js | 5 +++-- src/utils/lifecycle.js | 14 +++++++------- src/views/account/Management.jsx | 12 +++++++++--- src/views/account/RoleList.jsx | 8 ++++---- 6 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/main.jsx b/src/main.jsx index 7946e02..3733210 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -32,8 +32,7 @@ import AirticketPlan from "@/views/airticket/Plan"; import { ThemeContext } from '@/stores/ThemeContext' import { usingStorage } from '@/hooks/usingStorage' import { isNotEmpty } from '@/utils/commons' -import { appendRequestParams } from '@/utils/request' -import { fireAuth } from "./utils/lifecycle" +import { notifyAuth } from "./utils/lifecycle" import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } from '@/config' @@ -45,7 +44,7 @@ const initAppliction = async () => { const { loginToken, userId } = usingStorage() if (isNotEmpty(userId) && isNotEmpty(loginToken)) { - await fireAuth() + await notifyAuth() } } diff --git a/src/stores/Account.js b/src/stores/Account.js index ee3daca..a57b6db 100644 --- a/src/stores/Account.js +++ b/src/stores/Account.js @@ -90,7 +90,7 @@ const useAccountStore = create((set, get) => ({ return postAccountPassword(formData) }, - newRole: () => { + newEmptyRole: () => { return { role_id: null, role_name: '', @@ -98,6 +98,19 @@ const useAccountStore = create((set, get) => ({ } }, + newEmptyAccount: () => { + return { + accountId: null, + userId: null, + lmi2_sn: null, + username: '', + realname: '', + email: '', + travelAgencyId: null, + roleId: '' + } + }, + saveOrUpdateRole: async (formValues) => { const formData = new FormData() formData.append('role_id', formValues.role_id) @@ -116,7 +129,6 @@ const useAccountStore = create((set, get) => ({ formData.append('user_name', formValues.username) formData.append('real_name', formValues.realname) formData.append('email', formValues.email) - formData.append('travel_agency_id', formValues.travelAgencyId) formData.append('roles', formValues.roleId) diff --git a/src/stores/Auth.js b/src/stores/Auth.js index a2b9a42..56e7a80 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -4,7 +4,7 @@ import { HT_HOST } from "@/config" import { loadPageSpy } from '@/pageSpy' import { usingStorage } from '@/hooks/usingStorage' -import { obervseLifecycle } from '@/utils/lifecycle' +import { lifecycleware } from '@/utils/lifecycle' const KEY_LOGIN_TOKEN = 'G-STR:LOGIN_TOKEN' const KEY_TRAVEL_AGENCY_ID = 'G-INT:TRAVEL_AGENCY_ID' @@ -43,7 +43,7 @@ async function fetchLastRequet() { return errcode !== 0 ? {} : result } -const useAuthStore = create(obervseLifecycle((set, get) => ({ +const useAuthStore = create(lifecycleware((set, get) => ({ onAuth: async () => { const { startTokenInterval, loadUserPermission } = get() @@ -136,6 +136,7 @@ const useAuthStore = create(obervseLifecycle((set, get) => ({ })) }, + // 迁移到 Account.js changeUserPassword: (password, newPassword) => { const { userId } = usingStorage() const formData = new FormData(); diff --git a/src/utils/lifecycle.js b/src/utils/lifecycle.js index 1e71d68..0d3deb1 100644 --- a/src/utils/lifecycle.js +++ b/src/utils/lifecycle.js @@ -1,28 +1,28 @@ const initListener = [] const authListener = [] -export const onInit = (fn) => { +export const addInitLinstener = (fn) => { initListener.push(fn) } -export const onAuth = (fn) => { +export const addAuthLinstener = (fn) => { authListener.push(fn) } -export const fireInit = async () => { +export const notifyInit = async () => { initListener.forEach(async (fn) => { await fn() }) } -export const fireAuth = async (obj) => { +export const notifyAuth = async (obj) => { authListener.forEach(async (fn) => await fn(obj)) } // Zustand 中间件,用于订阅前端应用的生命周期,实验阶段 -export const obervseLifecycle = (fn) => (set, get, store) => { +export const lifecycleware = (fn) => (set, get, store) => { - onInit(() => { + addInitLinstener(() => { if (store.getState().hasOwnProperty('onInit')) { store.getState().onInit() } else { @@ -30,7 +30,7 @@ export const obervseLifecycle = (fn) => (set, get, store) => { } }) - onAuth(() => { + addAuthLinstener(() => { if (store.getState().hasOwnProperty('onAuth')) { store.getState().onAuth() } else { diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 92357b1..94d1a30 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -71,9 +71,9 @@ function Management() { const [currentTravelAgency, setCurrentTravelAgency] = useState(null) const [accountForm] = Form.useForm() - const [searchAccountByCriteria, accountList, toggleAccountStatus, saveOrUpdateAccount, resetAccountPassword] = + const [searchAccountByCriteria, accountList, toggleAccountStatus, saveOrUpdateAccount, resetAccountPassword, newEmptyAccount] = useAccountStore((state) => - [state.searchAccountByCriteria, state.accountList, state.toggleAccountStatus, state.saveOrUpdateAccount, state.resetAccountPassword]) + [state.searchAccountByCriteria, state.accountList, state.toggleAccountStatus, state.saveOrUpdateAccount, state.resetAccountPassword, state.newEmptyAccount]) const formValues = useFormStore(state => state.formValues) const { notification, modal } = App.useApp() @@ -117,6 +117,12 @@ function Management() { setAccountModalOpen(true) } + const onNewAccount = () => { + const emptyAccount = newEmptyAccount() + accountForm.setFieldsValue(emptyAccount) + setAccountModalOpen(true) + } + const onAccountFinish = (values) => { saveOrUpdateAccount(values) .then(() => { @@ -331,7 +337,7 @@ function Management() { - + diff --git a/src/views/account/RoleList.jsx b/src/views/account/RoleList.jsx index 463ba60..b4f9f92 100644 --- a/src/views/account/RoleList.jsx +++ b/src/views/account/RoleList.jsx @@ -108,11 +108,11 @@ function RoleList() { const [roleAllList, setRoleAllList] = useState([]) const [roleForm] = Form.useForm() - const [saveOrUpdateRole, newRole] = + const [saveOrUpdateRole, newEmptyRole] = useAccountStore((state) => - [state.saveOrUpdateRole, state.newRole]) + [state.saveOrUpdateRole, state.newEmptyRole]) - const { notification, modal } = App.useApp() + const { notification } = App.useApp() const onRoleSeleted = (role) => { fetchPermissionListByRoleId({ role_id: role.role_id }) @@ -124,7 +124,7 @@ function RoleList() { } const onNewRole = () => { - const role = newRole() + const role = newEmptyRole() roleForm.setFieldsValue(role) setRoleModalOpen(true) } From 80c1b986714463650f84d49b1b459efadd3d1c2b Mon Sep 17 00:00:00 2001 From: Lei OT Date: Fri, 28 Jun 2024 09:19:46 +0800 Subject: [PATCH 058/120] =?UTF-8?q?=E4=BA=A7=E5=93=81=E7=AE=A1=E7=90=86:?= =?UTF-8?q?=20=E9=99=84=E5=8A=A0=E9=A1=B9=E7=9B=AE=20=E6=B7=BB=E5=8A=A0/?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Products/Index.js | 8 ++++---- src/views/products/Detail/Extras.jsx | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js index fe2fb9f..a5c87cd 100644 --- a/src/stores/Products/Index.js +++ b/src/stores/Products/Index.js @@ -27,18 +27,18 @@ export const getAgencyProductsAction = async (param) => { }; /** - * todo: + * */ export const addProductExtraAction = async (body) => { - const { errcode, result } = await postJSON(`${HT_HOST}/products/extras`, body); + const { errcode, result } = await postJSON(`${HT_HOST}/Service_BaseInfoWeb/products_extras_add`, body); return errcode === 0 ? true : false; }; /** - * todo: + * */ export const delProductExtrasAction = async (body) => { - const { errcode, result } = await postJSON(`${HT_HOST}/products/extras/del`, body); + const { errcode, result } = await postJSON(`${HT_HOST}/Service_BaseInfoWeb/products_extras_del`, body); return errcode === 0 ? true : false; }; diff --git a/src/views/products/Detail/Extras.jsx b/src/views/products/Detail/Extras.jsx index 2ee8587..0f2d23c 100644 --- a/src/views/products/Detail/Extras.jsx +++ b/src/views/products/Detail/Extras.jsx @@ -3,7 +3,7 @@ import { useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { App, Table, Button, Modal, Popconfirm } from 'antd'; import { getAgencyProductExtrasAction, getAgencyProductsAction, addProductExtraAction, delProductExtrasAction } from '@/stores/Products/Index'; -import { cloneDeep } from '@/utils/commons'; +import { cloneDeep, pick } from '@/utils/commons'; import SearchForm from '@/components/SearchForm'; import RequireAuth from '@/components/RequireAuth'; @@ -46,7 +46,7 @@ const NewAddonModal = ({ onPick, ...props }) => { title: t('products:price'), dataIndex: ['quotation', '0', 'adult_cost'], width: '10rem', - render: (_, { quotation }) => `${quotation[0].adult_cost} ${quotation[0].currency} / ${quotation[0].unit_name}`, // todo: 成人 儿童 + render: (_, { quotation }) => `${quotation[0].adult_cost} ${quotation[0].currency} / ${quotation[0].unit_name}`, }, { key: 'action', @@ -120,15 +120,15 @@ const Extras = ({ productId, onChange, ...props }) => { const handleNewAddOn = async (item) => { setExtrasData(prev => [].concat(prev, [item])); // todo: 提交后端; 重复绑定同一个 - const newSuccess = await addProductExtraAction({ travel_agency_id, id: productId, extras: [2] }); - newSuccess ? message.success(t('Action')+t('Success')) : message.error(t('Action')+t('Failed')); + const _item = pick(item.info, ['id', 'title', 'code']); + const newSuccess = await addProductExtraAction({ travel_agency_id, id: productId, extras: [_item] }); + newSuccess ? message.success(`${t('Success')}`) : message.error(`${t('Failed')}`); await handleGetAgencyProductExtras(); } const handleDelAddon = async (item) => { - // todo: 提交后端 - const delSuccess = await delProductExtrasAction({ travel_agency_id, id: productId, extras: [2] }); - delSuccess ? message.success(t('Action')+t('Success')) : message.error(t('Action')+t('Failed')); + const delSuccess = await delProductExtrasAction({ travel_agency_id, id: productId, extras: [item.info.id] }); + delSuccess ? message.success(`${t('Success')}`) : message.error(`${t('Failed')}`); await handleGetAgencyProductExtras(); }; From 579689f3e028a74c27aa8a773745c9c188390073 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Fri, 28 Jun 2024 11:37:56 +0800 Subject: [PATCH 059/120] =?UTF-8?q?feat:=20=E5=88=A0=E9=99=A4=20GA=20JS?= =?UTF-8?q?=EF=BC=9B=E8=8F=9C=E5=8D=95=E6=98=BE=E7=A4=BA=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=A7=93=E5=90=8D=EF=BC=9B=E7=94=A8=E6=88=B7=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=A7=93=E5=90=8D=E5=92=8C=E8=A7=92=E8=89=B2?= =?UTF-8?q?=EF=BC=9BAuth=20=E5=A2=9E=E5=8A=A0=E5=88=9D=E5=A7=8B=E6=8C=87?= =?UTF-8?q?=EF=BC=8C=E7=BB=9F=E4=B8=80=E7=99=BB=E9=99=86=E5=92=8C=E8=B6=85?= =?UTF-8?q?=E6=97=B6=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 7 ----- public/locales/en/common.json | 1 + public/locales/zh/common.json | 1 + src/stores/Account.js | 4 ++- src/stores/Auth.js | 45 +++++++++++--------------------- src/views/App.jsx | 4 +-- src/views/account/Management.jsx | 6 +++-- src/views/account/Profile.jsx | 5 ++-- 8 files changed, 29 insertions(+), 44 deletions(-) diff --git a/index.html b/index.html index 83cb221..c316114 100644 --- a/index.html +++ b/index.html @@ -14,13 +14,6 @@ 100%{-webkit-transform:translate(150px)} } - -
diff --git a/public/locales/en/common.json b/public/locales/en/common.json index a513e2a..85155ad 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -22,6 +22,7 @@ "Login": "Login", "Username": "Username", + "Realname": "Realname", "Password": "Password", "ChangePassword": "Change password", diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 59c417e..1ffb8ae 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -22,6 +22,7 @@ "Login": "登录", "Username": "账号", + "Realname": "姓名", "Password": "密码", "ChangePassword": "修改密码", diff --git a/src/stores/Account.js b/src/stores/Account.js index a57b6db..47abc92 100644 --- a/src/stores/Account.js +++ b/src/stores/Account.js @@ -1,5 +1,6 @@ import { create } from 'zustand' import { fetchJSON, postForm } from '@/utils/request' +import { isEmpty } from '@/utils/commons' import { HT_HOST } from "@/config" import { usingStorage } from '@/hooks/usingStorage' @@ -147,6 +148,7 @@ const useAccountStore = create((set, get) => ({ const resultArray = await fetchAccountList(searchParams) + console.info(resultArray) const mapAccoutList = resultArray.map((r) => { return { accountId: r.wu_id, @@ -160,7 +162,7 @@ const useAccountStore = create((set, get) => ({ travelAgencyId: r.travel_agency_id, disabled: r.wu_limitsign, // 数据库支持逗号分隔多角色(5,6,7),目前界面只需单个。 - roleId: parseInt(r.roles), + roleId: isEmpty(r.roles) ? 0 : parseInt(r.roles), role: r.roles_name, } }) diff --git a/src/stores/Auth.js b/src/stores/Auth.js index 56e7a80..cc47621 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -43,8 +43,18 @@ async function fetchLastRequet() { return errcode !== 0 ? {} : result } +const initialState = { + tokenInterval: null, + tokenTimeout: true, + loginStatus: 0, + defaltRoute: '', + permissionList: [] +} + const useAuthStore = create(lifecycleware((set, get) => ({ + ...initialState, + onAuth: async () => { const { startTokenInterval, loadUserPermission } = get() const { userId, loginToken } = usingStorage() @@ -98,16 +108,11 @@ const useAuthStore = create(lifecycleware((set, get) => ({ const { clearStorage } = usingStorage() clearStorage() clearInterval(tokenInterval) - set(() => ({ - defaultRoute: '/', - loginStatus: 0, - tokenInterval: null, - tokenTimeout: true - })) + set(initialState) }, startTokenInterval: () => { - const { loginTimeout } = get() + const { logout } = get() async function checkTokenTimeout() { const { LastReqDate } = await fetchLastRequet() @@ -116,27 +121,17 @@ const useAuthStore = create(lifecycleware((set, get) => ({ const diffTime = now.getTime() - lastReqDate.getTime() const diffHours = diffTime/1000/60/60 if (diffHours > 1) { - loginTimeout() + logout() } } - const interval = setInterval(() => checkTokenTimeout(), 1000*60*20) + const interval = setInterval(() => checkTokenTimeout(), 1000*60*10) set(() => ({ tokenInterval: interval })) }, - loginTimeout: () => { - const { tokenInterval } = get() - const { clearStorage } = usingStorage() - clearStorage() - clearInterval(tokenInterval) - set(() => ({ - tokenTimeout: true - })) - }, - - // 迁移到 Account.js + // TODO: 迁移到 Account.js changeUserPassword: (password, newPassword) => { const { userId } = usingStorage() const formData = new FormData(); @@ -174,16 +169,6 @@ const useAuthStore = create(lifecycleware((set, get) => ({ }) }, - tokenInterval: null, - - tokenTimeout: false, - - loginStatus: 0, - - defaltRoute: '', - - permissionList: [], - }))) export default useAuthStore \ No newline at end of file diff --git a/src/views/App.jsx b/src/views/App.jsx index 22b385d..564e93e 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -48,6 +48,7 @@ function App() { .then(u => { setUserDetail({ username: u.LoginName, + realname: u.real_name, travelAgencyName: u.VName, }) }) @@ -157,7 +158,6 @@ function App() { items: [...[ { label: {t('ChangePassword')}, key: '0' }, { label: {t('Profile')}, key: '1' }, - { type: 'divider' }, isPermitted(PERM_ACCOUNT_MANAGEMENT) ? { label: {t('account:accountList')}, key: '3' } : null, isPermitted(PERM_ROLE_NEW) ? { label: {t('account:roleList')}, key: '4' } : null, { type: 'divider' }, @@ -169,7 +169,7 @@ function App() { > e.preventDefault()}> - {userDetail?.username} + {userDetail?.realname} diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 94d1a30..7e0d236 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -81,13 +81,15 @@ function Management() { useEffect(() => { fetchRoleList() .then((roleList) => { - setRoleAllList(roleList.map(r => { + const roleListMap = roleList.map(r => { return { value: r.role_id, label: r.role_name, disabled: r.role_id === 1 } - })) + }) + roleListMap.unshift({ value: 0, label: '未设置', disabled: true }); + setRoleAllList(roleListMap) }) }, []) diff --git a/src/views/account/Profile.jsx b/src/views/account/Profile.jsx index 1691081..ce8b3e1 100644 --- a/src/views/account/Profile.jsx +++ b/src/views/account/Profile.jsx @@ -15,7 +15,8 @@ function Profile() { .then(json => { setUserDetail({ username: json.LoginName, - telephone: json.LkPhone, + realname: json.real_name, + rolesName: json.roles_name, emailAddress: json.LMI_listmail, travelAgencyName: json.VName, }) @@ -28,7 +29,7 @@ function Profile() {
{userDetail?.username} - {userDetail?.telephone} + {userDetail?.realname}({userDetail?.rolesName}) {userDetail?.emailAddress} {userDetail?.travelAgencyName} From 90f84fa874644050eaa7aefd0a6b85188d89400a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=96=87=E5=BC=BA=40HWQ-PC?= Date: Fri, 28 Jun 2024 11:48:43 +0800 Subject: [PATCH 060/120] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=EF=BC=8C=E5=A4=9A=E8=AF=AD=E7=A7=8D=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/products/Detail.jsx | 114 +++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 44 deletions(-) diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx index 1b35f80..8f35b06 100644 --- a/src/views/products/Detail.jsx +++ b/src/views/products/Detail.jsx @@ -3,40 +3,51 @@ import { Button, Card, Col, Row, Breadcrumb, Table, Popconfirm, Form, Input, Inp import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import DateComponent from '@/components/date'; -import { fetchJSON } from "@/utils/request"; -import { type } from 'windicss/utils'; import { searchAgencyAction, getAgencyProductsAction } from '@/stores/Products/Index'; import { useProductsTypes } from '@/hooks/useProductsSets'; import Extras from './Detail/Extras'; import { groupBy } from '@/utils/commons'; import { useParams } from 'react-router-dom'; import { useHTLanguageSets } from '@/hooks/useHTLanguageSets'; -// import { useDefaultLgc } from '@/i18n/LanguageSwitcher'; +import { useDefaultLgc } from '@/i18n/LanguageSwitcher'; function Index() { const { t } = useTranslation(); const [form] = Form.useForm(); const [editingid, setEditingid] = useState(''); - const [tags, setTags] = useState(['中文', 'English']); + const [tags, setTags] = useState([]); const [isModalVisible, setIsModalVisible] = useState(false); - const [selectedTag, setSelectedTag] = useState(2); + const [selectedTag, setSelectedTag] = useState(null); const [saveData, setSaveData] = useState(null); const [datePickerVisible, setDatePickerVisible] = useState(false); const [currentid, setCurrentid] = useState(null); - const [languageStatus, setLanguageStatus] = useState([{ "中文": { title: "", description: "" } }, { "English": { title: "", description: "" } }]); + const [languageStatus, setLanguageStatus] = useState(null); const [selectedNodeid, setSelectedNodeid] = useState(null); + const [remainderLanguage, setRemainderLanguage] = useState([]) const [selectedDateData, setSelectedDateData] = useState({ dateRange: null, selectedDays: [] }); const [treeData1, setTreeData1] = useState([]); const productsTypes = useProductsTypes(); const [productsData, setProductsData] = useState(null); const [quotation, setQuotation] = useState(null); const [lgc_details,setLgc_details] = useState(null); + const [languageLabel,setLanguageLabel] = useState(null); const { travel_agency_id } = useParams(); + const {language} = useDefaultLgc(); + const HTLanguageSets = useHTLanguageSets(); + useEffect(() => { + setLanguageStatus(language); + const matchedLanguage = HTLanguageSets.find(HTLanguage => HTLanguage.key === language.toString()); + const languageLabel = matchedLanguage.label + // setTags([languageLabel]) + setLanguageLabel(languageLabel) + setRemainderLanguage(HTLanguageSets.filter(item => item.key !== language.toString())) + }, []); + + const fetchData = async () => { const a = { travel_agency_id }; const res = await getAgencyProductsAction(a); - const groupedProducts = groupBy(res.products, (row) => row.info.product_type_id); const generateTreeData = (productsTypes, productsData) => { @@ -54,8 +65,6 @@ function Index() { const treeData = generateTreeData(productsTypes, groupedProducts); setProductsData(groupedProducts); setTreeData1(treeData); - - console.log("setTreeData1", treeData); }; @@ -317,48 +326,68 @@ function Index() { const handleTagClick = (tag) => { setSelectedTag(tag); - console.log("handleTagClick",tag) - // form.setFieldsValue({ - - // }) + const matchedLanguage = HTLanguageSets.find(language => language.label === tag); + const key = matchedLanguage ? matchedLanguage.key : null; + form.setFieldsValue({ + lgc_details: { + title: lgc_details[key] ? lgc_details[key].title : '', + descriptions: lgc_details[key] ? lgc_details[key].descriptions : '' + } + }); + setLanguageStatus(key) }; const showModal = () => setIsModalVisible(true); - const handleOk = () => setIsModalVisible(false); + const handleOk = () => { + + setTags([...tags,selectedTag]) + + console.log("handleOkvalue") + setIsModalVisible(false); + } const handleCancel = () => setIsModalVisible(false); const handleTagChange = (value) => { - if (!tags.includes(value)) { - setTags([...tags, value]); - setLanguageStatus([...languageStatus, { [value]: { title: "", description: "" } }]); - } + console.log("handleTagChange",value) setSelectedTag(value); - setIsModalVisible(false); + console.log("setSelectedTag",selectedTag) + // setLanguageStatus() + + + // if (!tags.includes(value)) { + // setTags([...tags, value]); + // setLanguageStatus([...languageStatus, { [value]: { title: "", descriptions: "" } }]); + // } + // setSelectedTag(value); + // setIsModalVisible(false); }; const handleChange = (field, value) => { - const updatedLanguageStatus = languageStatus.map(lang => { - if (lang[selectedTag]) { - return { ...lang, [selectedTag]: { ...lang[selectedTag], [field]: value } }; - } - return lang; - }); - setLanguageStatus(updatedLanguageStatus); + console.log("languageStatus",languageStatus) + console.log("...lgc_details[languageStatus]",{...lgc_details[languageStatus]}) + // 更新整个 lgc_details 对象 + const updatedLgcDetails = { + ...lgc_details, + [languageStatus]:{...lgc_details[languageStatus],[field]: value,lgc:languageStatus} + }; + setLgc_details(updatedLgcDetails) + console.log("AAAAAAAAAAAAAA", lgc_details); + }; const findLanguageDetails = (tag) => { const lang = languageStatus.find(lang => lang[tag]); - return lang ? lang[tag] : { title: "", description: "" }; + return lang ? lang[tag] : { title: "", descriptions: "" }; }; //树组件方法 const handleNodeSelect = (_, { node }) => { - + setTags([languageLabel]) // 如果点击的是同一个节点,不做任何操作 if (selectedNodeid === node.key) return; @@ -374,8 +403,6 @@ function Index() { return true; } }); - - console.log("info",infoData) // 累积 lgc_details 数据 let newLgcDetails = {}; @@ -388,8 +415,8 @@ function Index() { setQuotation(initialQuotationData); + console.log("descriptions",lgc_details) // 使用 setTimeout 确保 lgc_details 已经更新 - // setTimeout(() => { form.setFieldsValue({ info: { title: infoData.title, @@ -402,11 +429,10 @@ function Index() { unit_name: infoData.unit_name }, lgc_details: { - title: newLgcDetails[selectedTag]?.title || '', - descriptions: newLgcDetails[selectedTag]?.descriptions || '' + title: newLgcDetails[language]?.title || '', + descriptions: newLgcDetails[language]?.descriptions || '' } }); - // }, 0); }; @@ -649,28 +675,28 @@ function Index() { placeholder="选择语言" optionFilterProp="children" onChange={handleTagChange} - filterOption={(input, option) => - option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0 - } > - Français - Español - Deutsch + + { + remainderLanguage.map((value, label) => ( + + {value.label} + + )) + } handleChange('title', e.target.value)} + onChange={(e) => handleChange('title', e.target.value)} /> handleChange('description', e.target.value)} + onChange={(e) => handleChange('descriptions', e.target.value)} /> From 42cb1d583febd6e986c53e369c2f00fc86f29033 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Fri, 28 Jun 2024 14:15:14 +0800 Subject: [PATCH 061/120] style: products Audit --- src/assets/global.css | 3 +++ src/hooks/useProductsSets.js | 2 +- src/views/products/Audit.jsx | 14 +++++++++++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/assets/global.css b/src/assets/global.css index ca0ad13..65679f1 100644 --- a/src/assets/global.css +++ b/src/assets/global.css @@ -21,3 +21,6 @@ justify-content: center; width: 100%; } +.ant-table-wrapper.border-collapse table { + border-collapse: collapse; +} diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index 015fa8a..2917489 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -51,10 +51,10 @@ export const useProductsTypes = () => { { label: t('products:type.UltraService'), value: 'B', key: 'B' }, { label: t('products:type.Car'), value: 'J', key: 'J' }, { label: t('products:type.Guide'), value: 'Q', key: 'Q' }, - { label: t('products:type.Package'), value: 'D', key: 'D' }, // 包价线路 { label: t('products:type.Attractions'), value: '7', key: '7' }, { label: t('products:type.Meals'), value: 'R', key: 'R' }, { label: t('products:type.Extras'), value: '8', key: '8' }, + { label: t('products:type.Package'), value: 'D', key: 'D' }, // 包价线路 ]; setTypes(newData); }, [i18n.language]); diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index 803507f..825f956 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -84,8 +84,15 @@ const PriceTable = ({ productType, dataSource, refresh }) => { }); }; + const rowStyle = (r, tri) => { + const trCls = tri%2 !== 0 ? ' bg-stone-50' : ''; + const [infoI, quoteI] = r.rowSpanI; + const bigTrCls = quoteI === 0 && tri !== 0 ? 'border-collapse border-double border-0 border-t-4 border-stone-300' : ''; + return [trCls, bigTrCls].join(' '); + }; + const columns = [ - { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan }), render: (text, r) => text || r.lgc_details?.['2']?.title || r.lgc_details?.['1']?.title || '' }, + { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan, }), className: 'bg-white', render: (text, r) => text || r.lgc_details?.['2']?.title || r.lgc_details?.['1']?.title || '' }, ...(productType === 'B' ? [{ key: 'km', dataIndex: ['info', 'km'], title: t('KM')}] : []), { key: 'adult', title: t('AgeType.Adult'), render: (_, { adult_cost, currency, unit_id, unit_name }) => `${adult_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}` }, { key: 'child', title: t('AgeType.Child'), render: (_, { child_cost, currency, unit_id, unit_name }) => `${child_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}` }, @@ -122,7 +129,7 @@ const PriceTable = ({ productType, dataSource, refresh }) => { ) : null, }, ]; - return
{children} - + + handleInputGroupSize('group_size_min', record.id, 'group_size', value)} + style={{ width: '50%', marginRight: '10px' }} + /> + - + handleInputGroupSize('group_size_max', record.id, 'group_size', value)} + style={{ width: '50%', marginLeft: '10px' }} + /> + {editing ? ( @@ -146,13 +208,53 @@ function Index() { ); }; + const handleInputGroupSize = (name,id, dataIndex, value ) => { + + const newData = [...quotation]; + console.log("newData",newData); + console.log("value",value); + const index = newData.findIndex((item) => id === item.id); + if (index > -1) { + const item = newData[index]; + console.log("item",item) + newData[index] = {...item,} + if(name==='group_size_min'){ + newData[index] = { ...item, group_size_min: value }; + console.log("newData[index]",newData[index]) + }else{ + newData[index] = { ...item, group_size_max: value }; + console.log("newData[index]",newData[index]) + } + // const [groupSizeMin, groupSizeMax] = value.split('-').map(val => parseInt(val.trim())); + + setQuotation(newData); + console.log("setQuotation",newData) + } + + } + const columns = [ { title: t('products:adultPrice'), dataIndex: 'adult_cost', width: '10%', editable: true }, { title: t('products:childrenPrice'), dataIndex: 'child_cost', width: '10%', editable: true }, { title: t('products:currency'), dataIndex: 'currency', width: '10%', editable: true }, { title: t('products:Types'), dataIndex: 'age_type', width: '10%', editable: true }, - { title: t('products:number'), dataIndex: 'group_size', width: '10%', editable: true }, - { title: t('products:validityPeriod'), dataIndex: 'validityPeriod', width: '30%', editable: true }, + { + title: t('products:number'), + dataIndex: 'group_size', + width: '20%', + editable: true, + render: (_, record) => `${record.group_size_min}-${record.group_size_max}` + }, + + { title: t('products:validityPeriod'), + dataIndex: 'validityPeriod', + width: '20%', + editable: true, + render: (_, record) => `${record.use_dates_start}-${record.use_dates_end}` + }, + + { title: t('products:weekend'), dataIndex: 'weekdays', width: '10%' }, + { title: t('products:operation'), dataIndex: 'operation', @@ -160,19 +262,20 @@ function Index() { const editable = isEditing(record); return editable ? ( - handleSave(record.key)} style={{ marginRight: 8 }}>{t('products:save')} + handleSave(record.id)} style={{ marginRight: 8 }}>{t('products:save')} {t('products:cancel')} ) : ( - edit(record)} style={{ marginRight: 8 }}>{t('products:edit')} - handleDelete(record.key)}> + edit(record)} style={{ marginRight: 8 }}>{t('products:edit')} + handleDelete(record.id)}> {t('products:delete')} ); }, }, + ]; @@ -234,21 +337,21 @@ function Index() { const handleNodeSelect = async (_, { node }) => { // 如果点击的是同一个节点,不做任何操作 - if (selectedNodeKey === node.key) return; + if (selectedNodeid === node.id) return; // 保存当前表单数据 - if (selectedNodeKey) { + if (selectedNodeid) { await handleSaveForm(); } // 更新选中的节点 - setSelectedNodeKey(node.key); + setSelectedNodeid(node.id); // 加载新节点的表单数据 - if (formState[node.key]) { - form.setFieldsValue(formState[node.key]); - setQuotation(formState[node.key].quotation || []); - setLanguageStatus(formState[node.key].lgc_details || languageStatus); + if (formState[node.id]) { + form.setFieldsValue(formState[node.id]); + setQuotation(formState[node.id].quotation || []); + setLanguageStatus(formState[node.id].lgc_details || languageStatus); } else { form.resetFields(); setQuotation(initialData); @@ -261,7 +364,7 @@ function Index() { const values = await form.validateFields(); const newFormState = { ...formState, - [selectedNodeKey]: { + [selectedNodeid]: { ...values, quotation, lgc_details: languageStatus, @@ -286,21 +389,20 @@ function Index() { //绑定产品 const initBindingData = [ - { key: '1', title: '15', value: "美元", age_type: 'aa' }, - { key: '2', title: '15', value: "美元", age_type: 'aa' }, - { key: '3', title: '15', value: "美元", age_type: 'aa' }, - { key: '4', title: '15', value: "美元", age_type: 'aa' }, + { id: '1', title: '英文导游', value: "100", age_type: '每人' }, + { id: '2', title: '中文导游', value: "200", age_type: '每团' }, + { id: '3', title: '可陪餐费', value: "400", age_type: '每人' }, ] const [bindingData, setBindingData] = useState(initBindingData); - const isEditingBinding = (record) => record.key === editingKeyBinding; - const [editingKeyBinding, setEditingKeyBinding] = useState(''); + const isEditingBinding = (record) => record.id === editingidBinding; + const [editingidBinding, setEditingidBinding] = useState(''); const editBinding = (record) => { form.setFieldsValue({ ...record }); - setEditingKeyBinding(record.key); + setEditingidBinding(record.id); }; const cancelBinding = () => { - setEditingKeyBinding(''); + setEditingidBinding(''); }; const EditableCellBinding = ({ editing, @@ -312,7 +414,16 @@ function Index() { children, ...restProps }) => { - const inputNode = inputType === 'number' ? : ; + let inputNode = inputType === 'number' ? : ; + + if (dataIndex === 'age_type' && editing) { + inputNode = ( + + ); + } return ( {editing ? ( @@ -330,9 +441,9 @@ function Index() { ); }; const bindingColums = [ - { title: t('products:Name'), dataIndex: 'title', width: '15%', editable: true }, - { title: t('products:price'), dataIndex: 'value', width: '15%', editable: true }, - { title: t('products:Types'), dataIndex: 'age_type', width: '40%', editable: true }, + { title: t('products:Name'), dataIndex: 'title', width: '25%', editable: true }, + { title: t('products:price'), dataIndex: 'value', width: '25%', editable: true }, + { title: t('products:Types'), dataIndex: 'age_type', width: '25%', editable: true }, { title: t('products:operation'), dataIndex: 'operation', @@ -340,13 +451,13 @@ function Index() { const editable = isEditingBinding(record); return editable ? ( - handleSaveBinding(record.key)} style={{ marginRight: 8 }}>{t('products:save')} + handleSaveBinding(record.id)} style={{ marginRight: 8 }}>{t('products:save')} {t('products:cancel')} ) : ( - editBinding(record)} style={{ marginRight: 8 }}>{t('products:edit')} - handleDeleteBinding(record.key)}> + editBinding(record)} style={{ marginRight: 8 }}>{t('products:edit')} + handleDeleteBinding(record.id)}> {t('products:delete')} @@ -373,31 +484,31 @@ function Index() { const handleAddBinding = () => { const newData = { - key: `${bindingData.length + 1}`, + id: `${bindingData.length + 1}`, title: '', value: '', age_type: '', }; setBindingData([...bindingData, newData]); - setEditingKeyBinding(''); // 添加这一行 + setEditingidBinding(''); // 添加这一行 }; - const handleSaveBinding = async (key) => { + const handleSaveBinding = async (id) => { try { const row = await form.validateFields(); const newData = [...bindingData]; const { value, title, age_type } = row - const index = newData.findIndex((item) => key === item.key); + const index = newData.findIndex((item) => id === item.id); if (index > -1) { const item = newData[index]; newData.splice(index, 1, { ...item, value, title, age_type }); setBindingData(newData); - setEditingKeyBinding(''); + setEditingidBinding(''); } else { newData.push(row); setBindingData(newData); - setEditingKeyBinding(''); + setEditingidBinding(''); } } catch (errInfo) { console.log('Validate Failed:', errInfo); @@ -405,9 +516,9 @@ function Index() { }; - const handleDeleteBinding = (key) => { + const handleDeleteBinding = (id) => { const newData = [...bindingData]; - const index = newData.findIndex((item) => key === item.key); + const index = newData.findIndex((item) => id === item.id); newData.splice(index, 1); setBindingData(newData); }; @@ -421,18 +532,18 @@ function Index() { const treeData = [ { title: '综费', - key: 'zf', + // id: 'zf', selectable: false, - children: [{ title: '北京怡然假日', key: 'bjyrjr' }] + children: [{ title: '北京怡然假日', id: 'bjyrjr' }] }, { title: '车费', - key: 'cf', + // id: 'cf', selectable: false, children: [ - { title: '北京', key: 'bj' }, - { title: '天津', key: 'tj' }, - { title: '北京-天津', key: 'bj-tj-3-5' } + { title: '北京', id: 'bj' }, + { title: '天津', id: 'tj' }, + { title: '北京-天津', id: 'bj-tj-3-5' } ] } ] @@ -447,16 +558,23 @@ function Index() { }, [saveData]); useEffect(() => { - // const { errcode, result } = fetchJSON('http://127.0.0.1:4523/m1/2602949-1933890-default/Service_BaseInfoWeb/travel_agency_products'); - console.log("get请求") + searchAgencyAction('54,55').then((res)=>{ + console.log("res",res) + }) + const a = { + travel_agency_id:'1511' + } + getAgencyProductsAction(a).then((res1)=>{ + console.log("res1",res1) + }) }, []) useEffect(() => { - if (selectedNodeKey) { + if (selectedNodeid) { handleSaveForm(); } - }, [selectedNodeKey]); + }, [selectedNodeid]); return ( @@ -486,7 +604,7 @@ function Index() {

{t('products:productProject')}

{productProject.map((item, index) => ( -
- handleInputGroupSize('group_size_min', record.id, 'group_size', value)} - style={{ width: '50%', marginRight: '10px' }} - /> - - - handleInputGroupSize('group_size_max', record.id, 'group_size', value)} - style={{ width: '50%', marginLeft: '10px' }} - /> - + handleInputGroupSize('group_size_min', record.id, 'group_size', value)} + style={{ width: '50%', marginRight: '10px' }} + /> + - + handleInputGroupSize('group_size_max', record.id, 'group_size', value)} + style={{ width: '50%', marginLeft: '10px' }} + /> +
r.id} />; + return
r.id} />; }; /** @@ -148,7 +155,7 @@ const TypesPanels = (props) => { // loading={loading} productType={ele.value} dataSource={agencyProducts[ele.value].reduce( - (r, c) => + (r, c, ri) => r.concat( c.quotation.map((q, i) => ({ ...q, @@ -160,6 +167,7 @@ const TypesPanels = (props) => { info: c.info, lgc_details: c.lgc_details.reduce((rlgc, clgc) => ({...r, [clgc.lgc]: clgc}), {}), rowSpan: i === 0 ? c.quotation.length : 0, + rowSpanI: [ri, i], })) ), [] From 6caa17ea4cc137cc54a762753cfb6405494bd37b Mon Sep 17 00:00:00 2001 From: Lei OT Date: Fri, 28 Jun 2024 17:11:57 +0800 Subject: [PATCH 062/120] =?UTF-8?q?feat:=20=E5=A4=8D=E5=88=B6=E4=BE=9B?= =?UTF-8?q?=E5=BA=94=E5=95=86=E4=BA=A7=E5=93=81:=20+=E5=B0=8F=E7=BB=84,=20?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/common.json | 2 ++ public/locales/en/products.json | 6 ++++ public/locales/zh/common.json | 2 ++ public/locales/zh/products.json | 6 ++++ src/components/DeptSelector.jsx | 61 +++++++++++++++++++++++++++++++++ src/components/SearchForm.jsx | 59 +++++++++++++++++++++++++++---- src/hooks/useProductsSets.js | 10 +++--- src/stores/Products/Index.js | 3 +- src/views/products/Manage.jsx | 18 ++++++---- 9 files changed, 148 insertions(+), 19 deletions(-) create mode 100644 src/components/DeptSelector.jsx diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 091a95f..b228ae8 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -33,6 +33,8 @@ "Success": "Success", "Failed": "Failed", + "All": "All", + "Table": { "Total": "Total {{total}} items" }, diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 0e90423..667c143 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -101,5 +101,11 @@ "sureCancel":"Sure you want to cancel?", "sureDelete":"Sure you want to delete?", + "CopyFormMsg": { + "requiredVendor": "Please pick a target vendor", + "requiredTypes": "Please select product types", + "requiredDept": "Please pick a owner department" + }, + "#": "#" } diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index cea837c..dd6e73e 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -33,6 +33,8 @@ "Success": "成功", "Failed": "失败", + "All": "所有", + "Table": { "Total": "共 {{total}} 条" }, diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index 3fe9c47..239af49 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -102,5 +102,11 @@ "sureCancel": "确定取消?", "sureDelete":"确定删除?", + "CopyFormMsg": { + "requiredVendor": "请选择目标供应商", + "requiredTypes": "请选择产品类型", + "requiredDept": "请选择所属小组" + }, + "#": "#" } diff --git a/src/components/DeptSelector.jsx b/src/components/DeptSelector.jsx new file mode 100644 index 0000000..ab8911c --- /dev/null +++ b/src/components/DeptSelector.jsx @@ -0,0 +1,61 @@ +import { Component } from 'react'; +import { Select } from 'antd'; +// import { groups, leafGroup } from '../../libs/ht'; + +/** + * 小组 + */ +export const groups = [ + { value: '1,2,28,7,33', key: '1,2,28,7,33', label: 'GH事业部', code: 'GH', children: [1, 2, 28, 7, 33] }, + { value: '8,9,11,12,20,21', key: '8,9,11,12,20,21', label: '国际事业部', code: 'INT', children: [8, 9, 11, 12, 20, 21] }, + { value: '10,18,16,30', key: '10,18,16,30', label: '孵化学院', code: '', children: [10, 18, 16, 30] }, + { value: '1', key: '1', label: 'CH直销', code: '', children: [] }, + { value: '2', key: '2', label: 'CH大客户', code: '', children: [] }, + { value: '28', key: '28', label: 'AH亚洲项目组', code: 'AH', children: [] }, + { value: '33', key: '33', label: 'GH项目组', code: '', children: [] }, + { value: '7', key: '7', label: '市场推广', code: '', children: [] }, + { value: '8', key: '8', label: '德语', code: '', children: [] }, + { value: '9', key: '9', label: '日语', code: '', children: [] }, + { value: '11', key: '11', label: '法语', code: '', children: [] }, + { value: '12', key: '12', label: '西语', code: '', children: [] }, + { value: '20', key: '20', label: '俄语', code: '', children: [] }, + { value: '21', key: '21', label: '意语', code: '', children: [] }, + { value: '10', key: '10', label: '商旅', code: '', children: [] }, + { value: '18', key: '18', label: 'CT', code: 'CT', children: [] }, + { value: '16', key: '16', label: 'APP', code: 'APP', children: [] }, + { value: '30', key: '30', label: 'Trippest', code: 'TP', children: [] }, + { value: '31', key: '31', label: '花梨鹰', code: '', children: [] }, +]; +export const groupsMappedByCode = groups.reduce((a, c) => ({ ...a, [String(c.code || c.key)]: c }), {}); +export const groupsMappedByKey = groups.reduce((a, c) => ({ ...a, [String(c.key)]: c }), {}); +export const leafGroup = groups.slice(3); +export const overviewGroup = groups.slice(0, 3); // todo: 花梨鹰 APP Trippest + +export const DeptSelector = ({show_all, isLeaf,...props}) => { + const _show_all = ['tags', 'multiple'].includes(props.mode) ? false : show_all; + const options = isLeaf===true ? leafGroup : groups; + + return ( +
+ + + ); +}; + +const SearchForm = ({ initialValue, onSubmit, onReset, confirmText, formName, formLayout, loading, ...props }) => { const { t } = useTranslation(); const presets = useDatePresets(); const [formValues, setFormValues] = useFormStore((state) => [state.formValues, state.setFormValues]); @@ -59,6 +70,19 @@ const SearchForm = ({ initialValue, onSubmit, onReset, confirmText, formName, lo '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 : ''; + }, + }, }; let dest = {}; const { dates, ...omittedValue } = values; @@ -106,9 +130,14 @@ const SearchForm = ({ initialValue, onSubmit, onReset, confirmText, formName, lo setFormValuesToSub(dest); // console.log('form onValuesChange', Object.keys(changedValues), args); }; + + const onFinishFailed = ({ values, errorFields }) => { + console.log('form validate failed', '\nform values:', values, '\nerrorFields', errorFields); + }; + return ( <> -
+ {/* */} {getFields({ sort, initialValue: readValues, hides, shows, fieldProps, fieldComProps, form, presets, t })} @@ -211,7 +240,7 @@ function getFields(props) { 'username', 99, - + , fieldProps?.username?.col || 4 ), @@ -219,7 +248,7 @@ function getFields(props) { 'realname', 99, - + , fieldProps?.realname?.col || 4 ), @@ -253,10 +282,26 @@ function getFields(props) { 'audit_state', 99, - + , fieldProps?.audit_state?.col || 3 ), + item( + 'products_types', + 99, + + + , + fieldProps?.products_types?.col || 6 + ), + item( + 'dept', + 99, + + + , + fieldProps?.dept?.col || 6 + ), ]; baseChildren = baseChildren .map((x) => { diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index 2917489..888ad43 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -18,7 +18,7 @@ import { PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT } from '@/confi * * 酒店 A * * 超公里 B * * 餐费 C - * * 小包价 D + * * 小包价 D // 包价线路 * * 站 X * * 购物 S * * 餐 R (餐厅) @@ -41,11 +41,12 @@ import { PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT } from '@/confi * * 车费 J */ -export const useProductsTypes = () => { +export const useProductsTypes = (showAll = false) => { const [types, setTypes] = useState([]); const { t, i18n } = useTranslation(); useEffect(() => { + const allItem = [{ label: t('All'), value: '', key: '' }]; const newData = [ { label: t('products:type.Experience'), value: '6', key: '6' }, { label: t('products:type.UltraService'), value: 'B', key: 'B' }, @@ -54,9 +55,10 @@ export const useProductsTypes = () => { { label: t('products:type.Attractions'), value: '7', key: '7' }, { label: t('products:type.Meals'), value: 'R', key: 'R' }, { 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' }, ]; - setTypes(newData); + const res = showAll ? [...allItem, ...newData] : newData; + setTypes(res); }, [i18n.language]); return types; diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js index a5c87cd..02989f6 100644 --- a/src/stores/Products/Index.js +++ b/src/stores/Products/Index.js @@ -10,8 +10,7 @@ export const searchAgencyAction = async (param) => { return errcode !== 0 ? [] : result; }; -export const copyAgencyDataAction = async (from, to) => { - const postbody = { source_agency: from, target_agency: to }; +export const copyAgencyDataAction = async (postbody) => { const formData = new FormData(); Object.keys(postbody).forEach((key) => { formData.append(key, postbody[key]); diff --git a/src/views/products/Manage.jsx b/src/views/products/Manage.jsx index 65cfc6b..524a4ea 100644 --- a/src/views/products/Manage.jsx +++ b/src/views/products/Manage.jsx @@ -26,9 +26,11 @@ function Index() { const [copyModalVisible, setCopyModalVisible] = useState(false); const [sourceAgency, setSourceAgency] = useState({}); const [copyLoading, setCopyLoading] = useState(false); - const handleCopyAgency = async (toID) => { + const handleCopyAgency = async (param) => { setCopyLoading(true); - const success = await copyAgencyDataAction(sourceAgency.travel_agency_id, toID); + const postbody = objectMapper(param, { agency: 'target_agency', }); + const toID = postbody.target_agency; + const success = await copyAgencyDataAction({...postbody, source_agency: sourceAgency.travel_agency_id}); setCopyLoading(false); success ? message.success('复制成功') : message.error('复制失败'); @@ -70,7 +72,7 @@ function Index() { { - handleCopyAgency(formVal.agency); + handleCopyAgency(formVal); }} /> From ed651ff3d4e5ec17946919beb751fb7dd7cb5b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=96=87=E5=BC=BA=40HWQ-PC?= Date: Mon, 1 Jul 2024 15:05:14 +0800 Subject: [PATCH 063/120] =?UTF-8?q?1.=E5=A2=9E=E5=8A=A0=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E6=A1=86=202.=E5=8A=A8=E6=80=81=E6=98=BE=E7=A4=BA=E4=BA=A7?= =?UTF-8?q?=E5=93=81=E9=A1=B9=E7=9B=AE=E4=BF=A1=E6=81=AF=203.=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=8F=98=E9=87=8F=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/date.jsx | 4 +- src/views/products/Detail.jsx | 511 ++++++++++++++++------------------ 2 files changed, 237 insertions(+), 278 deletions(-) diff --git a/src/components/date.jsx b/src/components/date.jsx index fec0402..ecc674f 100644 --- a/src/components/date.jsx +++ b/src/components/date.jsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { DatePicker, Button } from 'antd'; -const DateComponent = ({ onDateChange }) => { +const Date = ({ onDateChange }) => { const dateFormat = 'YYYY/MM/DD'; const { RangePicker } = DatePicker; const [dateRange, setDateRange] = useState(null); @@ -48,4 +48,4 @@ const DateComponent = ({ onDateChange }) => { ); }; -export default DateComponent; +export default Date; diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx index 8f35b06..74bac6a 100644 --- a/src/views/products/Detail.jsx +++ b/src/views/products/Detail.jsx @@ -1,8 +1,8 @@ -import React, { useState, useEffect, useRef,useMemo } from 'react'; +import React, { useState, useEffect, useRef, useMemo } from 'react'; import { Button, Card, Col, Row, Breadcrumb, Table, Popconfirm, Form, Input, InputNumber, Tag, Modal, Select, Tree } from 'antd'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import DateComponent from '@/components/date'; +import Date from '@/components/date'; import { searchAgencyAction, getAgencyProductsAction } from '@/stores/Products/Index'; import { useProductsTypes } from '@/hooks/useProductsSets'; import Extras from './Detail/Extras'; @@ -10,7 +10,7 @@ import { groupBy } from '@/utils/commons'; import { useParams } from 'react-router-dom'; import { useHTLanguageSets } from '@/hooks/useHTLanguageSets'; import { useDefaultLgc } from '@/i18n/LanguageSwitcher'; -function Index() { +function Detail() { const { t } = useTranslation(); const [form] = Form.useForm(); const [editingid, setEditingid] = useState(''); @@ -28,11 +28,70 @@ function Index() { const productsTypes = useProductsTypes(); const [productsData, setProductsData] = useState(null); const [quotation, setQuotation] = useState(null); - const [lgc_details,setLgc_details] = useState(null); - const [languageLabel,setLanguageLabel] = useState(null); + const [lgc_details, setLgc_details] = useState(null); + const [languageLabel, setLanguageLabel] = useState(null); const { travel_agency_id } = useParams(); - const {language} = useDefaultLgc(); + const { language } = useDefaultLgc(); const HTLanguageSets = useHTLanguageSets(); + const { Search } = Input; + + + const [expandedKeys, setExpandedKeys] = useState([]); + const [searchValue, setSearchValue] = useState(''); + const [autoExpandParent, setAutoExpandParent] = useState(true); + const [dataList, setDataList] = useState([]); + const [defaultData, setDefaultData] = useState([]); + + + const productProject = { + "6": [], + "B": [ + { code: "code", name: t('products:Code') }, + { code: "city_name", name: t('products:City') }, + { code: "km", name: t('products:KM') }, + { code: "remarks", name: t('products:Remarks') } + ], + "J": [ + { code: "code", name: t('products:Code') }, + { code: "city_name", name: t('products:City') }, + { code: "recommends_rate", name: t('products:recommendationRate') }, + { code: "duration", name: t('products:Duration') }, + { code: "dept_name", name: t('products:Dept') }, + { code: "display_to_c", name: t('products:DisplayToC') }, + { code: "remarks", name: t('products:Remarks') }, + ], + "Q": [ + { code: "code", name: t('products:Code') }, + { code: "city_name", name: t('products:City') }, + { code: "recommends_rate", name: t('products:recommendationRate') }, + { code: "duration", name: t('products:Duration') }, + { code: "dept_name", name: t('products:Dept') }, + { code: "display_to_c", name: t('products:DisplayToC') }, + { code: "remarks", name: t('products:Remarks') }, + ], + "D": [ + { code: "code", name: t('products:Code') }, + { code: "city_name", name: t('products:City') }, + { code: "recommends_rate", name: t('products:recommendationRate') }, + { code: "duration", name: t('products:Duration') }, + { code: "dept_name", name: t('products:Dept') }, + { code: "display_to_c", name: t('products:DisplayToC') }, + { code: "remarks", name: t('products:Remarks') }, + ], + "7": [ + { code: "code", name: t('products:Code') }, + { code: "city_name", name: t('products:City') }, + { code: "recommends_rate", name: t('products:recommendationRate') }, + { code: "duration", name: t('products:Duration') }, + { code: "open_weekdays", name: t('products:OpenWeekdays') }, + { code: "remarks", name: t('products:Remarks') }, + ], + "R": [ + { code: "code", name: t('products:Code') }, + { code: "city_name", name: t('products:City') }, + ] + } + const [selectedCategory, setSelectedCategory] = useState(productProject.B); useEffect(() => { setLanguageStatus(language); @@ -43,43 +102,116 @@ function Index() { setRemainderLanguage(HTLanguageSets.filter(item => item.key !== language.toString())) }, []); - - - const fetchData = async () => { - const a = { travel_agency_id }; - const res = await getAgencyProductsAction(a); - const groupedProducts = groupBy(res.products, (row) => row.info.product_type_id); - - const generateTreeData = (productsTypes, productsData) => { - return productsTypes.map(type => ({ - title: type.label, - key: type.value, - selectable: false, - children: (productsData[type.value] || []).map(product => ({ - title: product.info.title, - key: `${type.value}-${product.info.id}`, - })) - })); - }; - const treeData = generateTreeData(productsTypes, groupedProducts); - setProductsData(groupedProducts); - setTreeData1(treeData); - }; - + useEffect(() => { + const fetchData = async () => { + const a = { travel_agency_id }; + const res = await getAgencyProductsAction(a); + const groupedProducts = groupBy(res.products, (row) => row.info.product_type_id); + + const generateTreeData = (productsTypes, productsData) => { + return productsTypes.map(type => ({ + title: type.label, + key: type.value, + selectable: false, + children: (productsData[type.value] || []).map(product => ({ + title: product.info.title, + key: `${type.value}-${product.info.id}`, + })) + })); + }; + + const treeData = generateTreeData(productsTypes, groupedProducts); + console.log("treeData", treeData) + setTreeData1(treeData); + setProductsData(groupedProducts); + setDefaultData(treeData); + setDataList(flattenTreeData(treeData)); + }; + fetchData(); }, [productsTypes]); + const flattenTreeData = (tree) => { + let flatList = []; + const flatten = (nodes) => { + nodes.forEach((node) => { + flatList.push({ title: node.title, key: node.key }); + if (node.children) { + flatten(node.children); + } + }); + }; + flatten(tree); + return flatList; + }; + + const getParentKey = (key, tree) => { + let parentKey; + for (let i = 0; i < tree.length; i++) { + const node = tree[i]; + if (node.children) { + if (node.children.some((item) => item.key === key)) { + parentKey = node.key; + } else { + const pKey = getParentKey(key, node.children); + if (pKey) { + parentKey = pKey; + } + } + } + } + return parentKey; + }; + + const titleRender = (node) => { + const index = node.title.indexOf(searchValue); + const beforeStr = node.title.substr(0, index); + const afterStr = node.title.substr(index + searchValue.length); + const highlighted = ( + {searchValue} + ); + + return index > -1 ? ( + + {beforeStr} + {highlighted} + {afterStr} + + ) : ( + {node.title} + ); + }; + + const onChange = (e) => { + const { value } = e.target; + const newExpandedKeys = dataList + .filter(item => item.title.includes(value)) + .map(item => getParentKey(item.key, defaultData)) + .filter((item, i, self) => item && self.indexOf(item) === i); + console.log("newExpandedKeys", newExpandedKeys) + setExpandedKeys(newExpandedKeys); + setSearchValue(value); + setAutoExpandParent(true); + }; + + const onExpand = (keys) => { + setExpandedKeys(keys); + setAutoExpandParent(false); + }; + + // const productProject = [ + + // { code: "code", name: t('products:Code') }, + // { code: "city_name", name: t('products:City') }, + // { code: "remarks", name: t('products:Remarks') }, + // { code: "open_hours", name: t('products:tourTime') }, + // { code: "recommends_rate", name: t('products:recommendationRate') } + // ]; + - const productProject = [ - { code: "code", name: t('products:Code') }, - { code: "city_name", name: t('products:City') }, - { code: "remarks", name: t('products:Remarks') }, - { code: "open_hours", name: t('products:tourTime') }, - { code: "recommends_rate", name: t('products:recommendationRate') } - ]; const isEditing = (record) => record.id === editingid; @@ -330,8 +462,8 @@ function Index() { const key = matchedLanguage ? matchedLanguage.key : null; form.setFieldsValue({ lgc_details: { - title: lgc_details[key] ? lgc_details[key].title : '', - descriptions: lgc_details[key] ? lgc_details[key].descriptions : '' + title: lgc_details[key] ? lgc_details[key].title : '', + descriptions: lgc_details[key] ? lgc_details[key].descriptions : '' } }); setLanguageStatus(key) @@ -342,7 +474,7 @@ function Index() { const handleOk = () => { - setTags([...tags,selectedTag]) + setTags([...tags, selectedTag]) console.log("handleOkvalue") setIsModalVisible(false); @@ -352,46 +484,32 @@ function Index() { const handleTagChange = (value) => { - console.log("handleTagChange",value) + console.log("handleTagChange", value) setSelectedTag(value); - console.log("setSelectedTag",selectedTag) - // setLanguageStatus() - - - // if (!tags.includes(value)) { - // setTags([...tags, value]); - // setLanguageStatus([...languageStatus, { [value]: { title: "", descriptions: "" } }]); - // } - // setSelectedTag(value); - // setIsModalVisible(false); + console.log("setSelectedTag", selectedTag) }; const handleChange = (field, value) => { - console.log("languageStatus",languageStatus) - console.log("...lgc_details[languageStatus]",{...lgc_details[languageStatus]}) - // 更新整个 lgc_details 对象 - const updatedLgcDetails = { - ...lgc_details, - [languageStatus]:{...lgc_details[languageStatus],[field]: value,lgc:languageStatus} - }; - setLgc_details(updatedLgcDetails) - console.log("AAAAAAAAAAAAAA", lgc_details); - - }; + console.log("languageStatus", languageStatus) + console.log("...lgc_details[languageStatus]", { ...lgc_details[languageStatus] }) + // 更新整个 lgc_details 对象 + const updatedLgcDetails = { + ...lgc_details, + [languageStatus]: { ...lgc_details[languageStatus], [field]: value, lgc: languageStatus } + }; + setLgc_details(updatedLgcDetails) + console.log("AAAAAAAAAAAAAA", lgc_details); - const findLanguageDetails = (tag) => { - const lang = languageStatus.find(lang => lang[tag]); - return lang ? lang[tag] : { title: "", descriptions: "" }; }; - //树组件方法 const handleNodeSelect = (_, { node }) => { setTags([languageLabel]) // 如果点击的是同一个节点,不做任何操作 if (selectedNodeid === node.key) return; - const fatherKey = node.key.split('-')[0]; + console.log("fatherKey", fatherKey) + setSelectedCategory(productProject[fatherKey]) let initialQuotationData = null; let infoData = null; let lgcDetailsData = null; @@ -403,38 +521,43 @@ function Index() { return true; } }); - + + console.log("infoData", infoData) + // 累积 lgc_details 数据 let newLgcDetails = {}; lgcDetailsData.forEach(element => { newLgcDetails[element.lgc] = element; }); - + // 一次性更新 lgc_details setLgc_details(newLgcDetails); - + setQuotation(initialQuotationData); - - console.log("descriptions",lgc_details) + + console.log("descriptions", lgc_details) // 使用 setTimeout 确保 lgc_details 已经更新 - form.setFieldsValue({ - info: { - title: infoData.title, - code: infoData.code, - product_type_name: infoData.product_type_name, - city_name: infoData.city_name, - remarks: infoData.remarks, - open_weekdays: infoData.open_weekdays, - recommends_rate: infoData.recommends_rate, - unit_name: infoData.unit_name - }, - lgc_details: { - title: newLgcDetails[language]?.title || '', - descriptions: newLgcDetails[language]?.descriptions || '' - } - }); + form.setFieldsValue({ + info: { + title: infoData.title, + code: infoData.code, + product_type_name: infoData.product_type_name, + city_name: infoData.city_name, + remarks: infoData.remarks, + open_weekdays: infoData.open_weekdays, + recommends_rate: infoData.recommends_rate, + duration: infoData.duration, + dept: infoData.dept, + km: infoData.km, + dept_name: infoData.dept_name + }, + lgc_details: { + title: newLgcDetails[language]?.title || '', + descriptions: newLgcDetails[language]?.descriptions || '' + } + }); }; - + @@ -450,182 +573,28 @@ function Index() { }; - //绑定产品 - const initBindingData = [ - { id: '1', title: '英文导游', value: "100", age_type: '每人' }, - { id: '2', title: '中文导游', value: "200", age_type: '每团' }, - { id: '3', title: '可陪餐费', value: "400", age_type: '每人' }, - ] - const [bindingData, setBindingData] = useState(initBindingData); - const isEditingBinding = (record) => record.id === editingidBinding; - const [editingidBinding, setEditingidBinding] = useState(''); - - const editBinding = (record) => { - form.setFieldsValue({ ...record }); - setEditingidBinding(record.id); - }; - const cancelBinding = () => { - setEditingidBinding(''); - }; - const EditableCellBinding = ({ - editing, - dataIndex, - title, - inputType, - record, - index, - children, - ...restProps - }) => { - let inputNode = inputType === 'number' ? : ; - - if (dataIndex === 'unit_name' && editing) { - inputNode = ( - - ); - } - return ( -
- ); - }; - const bindingColums = [ - { title: t('products:Name'), dataIndex: 'title', width: '25%', editable: true }, - { title: t('products:price'), dataIndex: 'value', width: '25%', editable: true }, - { title: t('products:Types'), dataIndex: 'age_type', width: '25%', editable: true }, - { - title: t('products:operation'), - dataIndex: 'operation', - render: (_, record) => { - const editable = isEditingBinding(record); - return editable ? ( - - handleSaveBinding(record.id)} style={{ marginRight: 8 }}>{t('products:save')} - {t('products:cancel')} - - ) : ( - - editBinding(record)} style={{ marginRight: 8 }}>{t('products:edit')} - handleDeleteBinding(record.id)}> - {t('products:delete')} - - - ); - }, - }, - ].map(col => { - if (!col.editable) { - return col; - } - return { - ...col, - onCell: record => ({ - record, - inputType: col.dataIndex === 'value' ? 'number' : 'text', - dataIndex: col.dataIndex, - title: col.title, - editing: isEditingBinding(record), - }), - }; - }); - - - const handleAddBinding = () => { - const newData = { - id: `${bindingData.length + 1}`, - title: '', - value: '', - age_type: '', - }; - setBindingData([...bindingData, newData]); - setEditingidBinding(''); // 添加这一行 - }; - const handleSaveBinding = async (id) => { - try { - const row = await form.validateFields(); - const newData = [...bindingData]; - const { value, title, age_type } = row - const index = newData.findIndex((item) => id === item.id); - if (index > -1) { - const item = newData[index]; - newData.splice(index, 1, { ...item, value, title, age_type }); + //Effect - setBindingData(newData); - setEditingidBinding(''); - } else { - newData.push(row); - setBindingData(newData); - setEditingidBinding(''); - } - } catch (errInfo) { - console.log('Validate Failed:', errInfo); - } - }; - const handleDeleteBinding = (id) => { - const newData = [...bindingData]; - const index = newData.findIndex((item) => id === item.id); - newData.splice(index, 1); - setBindingData(newData); - }; - const componentsBinding = { - body: { - cell: EditableCellBinding, - }, - }; - const treeData = [ - { - title: '综费', - // id: 'zf', - selectable: false, - children: [{ title: '北京怡然假日', id: 'bjyrjr' }] - }, - { - title: '车费', - // id: 'cf', - selectable: false, - children: [ - { title: '北京', id: 'bj' }, - { title: '天津', id: 'tj' }, - { title: '北京-天津', id: 'bj-tj-3-5' } - ] - } - ] - - //Effect - useEffect(() => { - if (saveData) { - - } - }, [saveData]); return (
- + + @@ -644,14 +613,18 @@ function Index() { >

{t('products:productProject')}

- {productProject.map((item, index) => ( + {selectedCategory.map((item, index) => (
- + {item.code === "duration" ? ( + + ) : ( + + )} ))} - + duration @@ -676,13 +649,13 @@ function Index() { optionFilterProp="children" onChange={handleTagChange} > - + { - remainderLanguage.map((value, label) => ( - - {value.label} - - )) + remainderLanguage.map((value, label) => ( + + {value.label} + + )) } @@ -690,13 +663,13 @@ function Index() { handleChange('title', e.target.value)} + onChange={(e) => handleChange('title', e.target.value)} /> handleChange('descriptions', e.target.value)} + onChange={(e) => handleChange('descriptions', e.target.value)} /> @@ -720,20 +693,6 @@ function Index() { - {/*

{t('products:bindingProducts')}

- -
- {editing ? ( - - {inputNode} - - ) : ( - children - )} -
- - */} @@ -755,12 +714,12 @@ function Index() { onOk={handleDateOk} onCancel={() => setDatePickerVisible(false)} > - + )} ); } -export default Index; +export default Detail; From 0eef2cee3a7ab837064d23b3f13afaa95fc54469 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Mon, 1 Jul 2024 15:28:58 +0800 Subject: [PATCH 064/120] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=89=93?= =?UTF-8?q?=E6=A0=87=E7=AD=BE=E5=91=BD=E4=BB=A4=EF=BC=9B=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E4=B8=8D=E5=BF=85=E8=A6=81=E8=A7=92=E8=89=B2=E6=9D=83=E9=99=90?= =?UTF-8?q?=EF=BC=9B=E4=BC=98=E5=8C=96=20main=20asnyc=20=E7=A1=AE=E4=BF=9D?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 30 +++--- doc/RBAC 权限.sql | 8 -- doc/价格管理平台.bmpr | Bin 278528 -> 279552 bytes src/main.jsx | 156 ++++++++++++++++--------------- src/views/reservation/Newest.jsx | 4 - 5 files changed, 95 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 9ff40f3..885d2c2 100644 --- a/README.md +++ b/README.md @@ -7,27 +7,29 @@ Global Highlights Hub 海外供应商平台 2. 运行开发环境:npm run dev 或者 start.bat 3. 打包代码:npm run build 或者 build.bat +// "push:tag": "npm version patch && git push origin master", + ## 相关文档 -需求文档 https://www.kdocs.cn/l/csZrIZlpuF2i -dayjs https://dayjs.gitee.io/docs/zh-CN/manipulate/start-of -antd https://ant-design.antgroup.com/components/upload-cn#uploadfile -反馈表案例 https://www.chinahighlights.com/customerservice/feedback/PostTourSurveyFormToWLGH.asp?LGC=1&COLI_SN=988185&MEI_SN=954295&Email=jennroth18@hotmail.com&ToC=0&ShowType=&page_class=4&dei_sn=28&country=30,490 -国内供应商平台 http://p.mycht.cn/index.aspx -文档预览 https://github.com/cyntler/react-doc-viewer -wps的文档预览 https://wwo.wps.cn/docs/front-end/introduction/quick-start -pdf生成 https://github.com/ivmarcos/react-to-pdf -react-pdf https://react-pdf.org +需求文档 https://www.kdocs.cn/l/csZrIZlpuF2i +dayjs https://dayjs.gitee.io/docs/zh-CN/manipulate/start-of +antd https://ant-design.antgroup.com/components/upload-cn#uploadfile +反馈表案例 https://www.chinahighlights.com/customerservice/feedback/PostTourSurveyFormToWLGH.asp?LGC=1&COLI_SN=988185&MEI_SN=954295&Email=jennroth18@hotmail.com&ToC=0&ShowType=&page_class=4&dei_sn=28&country=30,490 +国内供应商平台 http://p.mycht.cn/index.aspx +文档预览 https://github.com/cyntler/react-doc-viewer +wps的文档预览 https://wwo.wps.cn/docs/front-end/introduction/quick-start +pdf生成 https://github.com/ivmarcos/react-to-pdf +react-pdf https://react-pdf.org ## 阿里云OSS -Bucket 名称:global-highlights-hub -Endpoint:oss-cn-hongkong.aliyuncs.com -global-highlights-hub.oss-cn-hongkong.aliyuncs.com +Bucket 名称:global-highlights-hub +Endpoint:oss-cn-hongkong.aliyuncs.com +global-highlights-hub.oss-cn-hongkong.aliyuncs.com -反馈表测试链接 -http://202.103.68.111:5173/feedback/330948 +反馈表测试链接 +http://202.103.68.111:5173/feedback/330948 --- diff --git a/doc/RBAC 权限.sql b/doc/RBAC 权限.sql index 3118103..2df36eb 100644 --- a/doc/RBAC 权限.sql +++ b/doc/RBAC 权限.sql @@ -86,11 +86,3 @@ VALUES ('产品管理(供应商)', 'route=/products?from', 'page') INSERT INTO [dbo].[auth_permission] ([role_id] ,[res_id]) VALUES (1, 1) -INSERT INTO [dbo].[auth_permission] ([role_id] ,[res_id]) -VALUES (6, 2) -INSERT INTO [dbo].[auth_permission] ([role_id] ,[res_id]) -VALUES (6, 3) -INSERT INTO [dbo].[auth_permission] ([role_id] ,[res_id]) -VALUES (6, 4) -INSERT INTO [dbo].[auth_permission] ([role_id] ,[res_id]) -VALUES (6, 5) diff --git a/doc/价格管理平台.bmpr b/doc/价格管理平台.bmpr index de2695d7d545f1645b1e5f1eacb453cdb73240fa..6f91c6d7396d8547decf5863587d0de70a624879 100644 GIT binary patch delta 31817 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 LXJHDj8`l|<0Zp|<6CkU9;Awt zQQFEQTx775%^DS26U!jWEL0GWon9VTwVVY9o|nYTUs%IHWwQ9TI>%mojJYOU(Y z@}dEu4Nw;=MjCck>X{e-m$Nn5dwdO>!qe}1E9xi^`*k(Vl4BPcn@$7?_QPr?eI~=7 zfe^^7VwXAvD@(FF&CaEHX_hS8y$}M!XiFVIdjHM9X6`we_b`9k#5CY6vRXxTZ3#{& z%ge~E*8R+>v_h6*BEtuyzgnNN3VQ{9eAa)j|xEV)U?tr*nDSMpn z@$${rW} zI?C7$ZZmuAUdZ({GpZ@a5Mbs~u04o>Dw6AF3=SVTZoncyyRF6A>kGd~NCy?H>AsL6 zJN78kRdxswMmvHA22xhqDlr5k7km_^1OYl_mxn1kIX#>?lp;bC0{Dv4C|E(&8lU7+ z@D;&p@res?p{UuCT6~m}R~egBw(_4yJPIeJuZyL$SHzjf)^u)iX_P!S#yI2wfg)@( zfW7lzG9xV_iPf?=1#GnTw4NoQ^)rM>pM&Ck5|ndaVFbMJ>={+&-e^W`mXX?jY=h93OYTX9B0%zMJR^>6cAXi`zh8Ony)C!;oB#9d%`3mzvU2m% zoy2^9uBU1Ibx`Otjc@*e!`DHfZ+!JT1MJ*yX?b(YFa4p*^Xz{4|Ki!@Uejm)za7KZ zKG2dg(7!8NPe!tsKu12MUa{EZv=zDK?2d<&32B0TWvN&z#I|E8%vCwUQgEbkw}&+z z%pkYPId1V48_+?$60@^muc}2F=L zyIS;t#1x?}_?E}a!mxzwIARPFu&^QdZu1gHaacK@LW`s|_{$W_W?;X1_>$*68J#>v zkugpx*?5%(HWrbuBzADXUBA4$K6}p-YRgDPTm(d< zXEWb;i>yk|n#z;>B}vii98OD{?^<*|9ym7X3CCa~$NJgDPB^iN9kQYI1ZoS&Fa87! zhX^jE6+4&{(wiQ*ZotWt++z>$Ev}sb;?=ND9J8dqX2T#xgwt;R+Zk5DX+Bv1^qH(B z$Nc*;3gRP~{Q2#1m@>J90mYbs5#Zy*+BcBNa2D)Hl-Vl(>` zuLv>cLOHnuUeLU!M|Jk9DP;lq??&i7+mOFV+DJQeOe1#iRjSZTWq`>Ap1-svArV=+J)130wAuqZ!L?g~~65vWA%A$HjK*_0;BL>5c+3uLgY7swFcMsogvzxco+&Y#8hBT7?N2fYvZ6e|hXt5GgtRlk?VdF9 zAWeD_I5_(cJ{;&W6&07C#VrR}Y2+m3%cF+=;E`vE$W9JJ8JY6M)Pzi`VX;GF-ffKoV8y&-aLWf3C9)AYYT`W|S%{9o0C0g>aHbM~v#jkL8hA83 zb;!^Ess|W`&;?y_ht8XDv}=Npn-j4`^r4BHR(pdwd+jLgEZ=qOROdG zftLuN6bpMgVrIZoE`fY$-NK7xgkw7VPbFi%Q!OpnHmQkzp7KgcK@lXYYQX;oBh*q7 z96Lk8bxLi7FewxR;fvHc@j_+|XtZR+GY>~o_aeQmha+?Tn=bZ}^x*w`YQl*UQ3c*NoEyd0_B!xu;6D;h#~smiy#r_bUZ|EtF|D$ z$)gNmAutZ9f!05JPhzJ%fW@q5i=zhSU+tZ> z_vU?-_g7|HHa-8&T3?kp`CWhM^FJ{^-~38TV}9zbExC2`_UY!uoBi45U%Z)}X@2X? zEB~tHxyC>I+PB`UtTw;1skd=-+|?=^!?v}2%J=eUmGZqj*sOf-ZhUP0?Ki*oTP@Gt z{Q7G1A45<}Z>{gs{jB1 delta 40240 zcmb{535=~}`JS_rCnu*Iyg&to7EqYkhfj)COwpwNKZ^YNNTHt)}E`Mu8rjCaK5|NpYmP# z+glsV_4a)0tc^bW>T5sP(pBqv`?p^EO4mUskz9Jt_k^K8%{4Ce+t4Ce$ zXq8x;=K8$xn&@vf6rxeTjSTi-27edPMZ%CKJ5 zo~6yXTD>nf$8u?-wq1K#8%nW(6y8e1Jt@$dZ}Vw#tG1R($I{$k7SVh@t=D!_aewvh zs$GP{fjqqvlAl%e?5C-_Jik+$O$!rw8gVD-PJddPtKH=9LanPFddE_5BZay{^Fn&p zp8g-@`+lu051*vqahhCA(|5J;lvqg5$MRmx6WzHwn5O&k-V9OKxjUM*JyTl>XYNDH zex906cZNgIYFax@iP`*}$a7C?_4cZozKXihTo?_N>uIjD_A;c5<}RXEYs@d#19z!V|98^Yk^Fs;YA3_D zg%En4Pg807E`xfNx?iTz?KF9k=6BMC>->L^&#kq3XXtHBZ@bb|M_z+zaI7luG%UQ# z^}bZPoGLBqlMpbOiux-?PNZkE6>+U;{#p1k91^cWJeEHTsdK4mKC{CzzE4-5rM0WH zcar~?^8K`)rWe!Se9W!m(0(6Er>e({DOR84;q~wZN5<3qMH=l0&*nnbamuvkT5p;; zNr~$aKb_hRQg$P6ldeC{j^}+HGS));VQO7W4g2|C-%G!5^7wA5dy&g#@^Gpc%cqW9 z?+Fj~!lVmS3o zhMc(&+LQl7+^b>3O6~ct{Vz>#d~3@WYRyf*U2Fcirr&P)!sp)_e|zQc{)wh<`Ewt8 z{q?s#@&iA$@s}qXKl!BTmmj|H_b(oPec_35|r=huwd>XZEs*vX3=*vhORxzsit67F*4X}ZyqQNGG^J^8;rn-3nnuZr*G(p^|&CO%CMS5w_t z8hMeX+ET774fj;NxJgs%xw4TfRyR3)pOW(#sKs;zFULamU^pSw9CHPsI0e=5>es(qes>lJag`MZ|p$8zl?CHB+H z!SwqmjXlq|*^t{FE?DLJX?(LHi`ZGpy~~iW91=Qf_03S!o^*Q_xkeSL{+9WJX%lnV;RToG)~COr3T|XSP^#>5=_C%yyw&0Rhr)lFV51^VHzGu zt-C27K~ztXi`u=fMoYOt^BMrVmn2cm5}aC&I_25Hp0Sndz!{zk@oDxbE}zlB^g`#190KKCLVn+#Dq6`AL09_KF8ozXnsSI?u& zncg&U9EQ>2&CJpC|0K;_hqdkbc9;g&(tv%eJ)eAE3`a&&EeXGnCOX2ofr#_1?rco}N3ZZ{QAhMknFgEToCvgtUBxsR9A)g)a?KaWEgS#TP1 z?Gl$6jN>@U+wol5O{33JExEOpe%w^OU(8(!$3hxy3z^F)HkP(L2;T|m z{UHId>Z;ShwnOVpnk?$hZkoHugI4oyRnbhS?@GDuT(Rnx@}@%5PU&d9;m%F&U1rG4 zn-}Qv!WL@v z-ZV24TDQ}{SbEYI+KOao9V?&nkUEi^4xizLR=Z^yObe}Xo!@JU<>JHXBuBh4UADsxPFvJ%~IlKCk(aC zl6;3@-ApR%Ofl=%%3sR;?Np=L(GW*RpAK1zdG>ioBPZw5OM`HbvUJ9o)Mmff&z-_K zdjCY~U~o*qH59^N59sg*3^6-lJ`Sw%#?bXqTwxv z01I&;@2NC&6FERldYZGup;^D{nPjg{Z#NG@Aa>GjDT!Dzy#fQw-I5s-;Z!=S7uX%p)^tl6u_}Cheuz zbgE@evR$ZC7g=t%Dcc!dv2C}*W&#`pLDXEN4u!A@Xl4 z53Gg8nG_;MR#Fi?1;sQ5b~sU4y*Z~yB{U(9t2YqVk=Luw*LyTW!`&0QwtS{`4mnwt(k`qCV3 zP^0J5*ikCH&Nrrtr8=L%GoV1y;q((sWe?p*&p;= z99H{C#fR-Ya$XU~b^#Aix_82z?mV}d@6`9%R14`@&Omg8^!s$c8lj@yRIMCk>>*;- z7Oilz9(u^+-8^zu6{e@aYOeEbFMN5Cs=>os=|6i9ay68v$gll04-uKjCzEbGeFD!J zo}-jG&-bfzfR6bh{MyO)o)ARk>rZ_rwfbZ()6y)msq~UKR*WfTIjy93ld0k^#mCe0 z+mt&>)7JWMp0ZWLyDVZGZEuKUMVZL+sbN2*7AxXra=j~0!UV_C>CJSn>tlHU_OO}i zRZ2z`D`p||N1XiB2bP_F03uR?72)jZ>SH%vS!*03)T~hO1<{Qe{<)Fal zq1cq50@~x|!jh$2gIMmTKO6Zwl76pd2<%v}Jdh!yc`$uGt%fDo%T`DS)?>n3GEK<0Z96w4Xw+gvwO z?eZtE$F9uEtK4AlFo`!))mg>~j@T1|mh->u;5f~b;(Or<0I)laLxYKVk1RP(h%)=J$-nZ@B7vIYfH7KA?7ZR zQya`w_GuCKW-^p+B~6c}LKf;x2J9pqK^%L%H3X7l+9SM_frFu zcBtY9;w~AA9ie?FqXHYd3I}d8Bp0d34o=(?9+0%Q^pgs3nkS}Gayd;MhKTd@d?zJr zo@43ZcB=KP!FZmUNv5Z1){c0YIYoP+qfr#E>#6=IJ!Kw{s-zG&2X%0jXQ|S)oqWE| zm8*<9?Q1uU9fdyzXFue0rTnvyGZ3QKCN|Z<+=unB_BwLkx^B(?Oy=&?3n~myW;ro* zP=p7Qp&o8`m;y|q7paI^0FCi?p(1W7mBVPET%i2byqH)0X|5|x+^4VX^YuKlo6k&b z!$j!phrb8;l%4$5{FiZfr@y)7^?Fx0t>Xi!do%5klK@hkg9^eyUER)=+wc}%KwE^M z80y0?;viQ}(?&6LXv2ef-{u)W5EE@LrHH-TP)Y%~$bCp_TRl}#RA~0&X$W|waiH2o z#;4f0HX-7mD^r~LZNt6GecSdy_`2t-`=9-NQ6gizn0C(eQ zU^mV8g^b;bpW~^Mg+-?(4%$;Hra(O`1g`*;yHha@nUZDDmQrve-x;q9>EBf~1i z&`_#nzU<}OSx8z+U!Y(>ddLXu#IP#UpE>{+4FFSvV6}Y(InK^yd?JirUqc zs-ERDAPY3#k@sHSOZjfe4CHaRa(j5fs}afax0AtXY>|A#$fKVlqvUgEU@nL zH1FA+bYwFmE$17}{1F_cz%6Egxq0f-VIc7}l&>tLXX#y;bcbpDK9obkukx37K-KFZ z#*ARD*&=A0MT&K0L>a#z(EWU(Gnt;SGXnKCyklXoyA0tnqlNj|9ooQ2KnReW1w5K2 z21C$68Y{{d(~d&|>jpE?*3(1q+j+_`PA)>=c)CIot>yhZ*T5jxdFCQz0hnd^fxGQ^ zx3xNmeLoh$=Tl%T^b>HKY3L$7#uYmg)eYi*8)6yu{6iGG&XjqYk_c-{pV!jFZpdy; z7iL46Ed{YB)nJ*-G;*%4&J6i|h`6P+Ugsi>*Q{(!GvnzU|JPA9N9QuLRf&~P^X^S) z;G4<|*l(vTmLN$>jXh5-Wwg02izqeP=7R+a`sG3ROs)PROj=8&6X~nzbCR1Ru$62# zcI`H_o~OBu{J)lY(v!cCV|&wFnuDhwgly;$tbvZ)k*92|c5#m0<&eP`fT&YjsSDwV zy$BL&HyH?hjBJ8?D^+9Nar(nGi$k|*kTJ&X3RAsKLo`OpY+w32QOyTi0tES4%B|-! zg|~PPcmcoBb5O^sqf|8+cD&~~U~tkPzut2IH~Z$E19Sf0_Z+YvN&okI4$d=!zUDd5 z|IYOJn|ltBFcVV1S6kshYk0&az-;1&GI^S7JbMf2sjUa+uEKn}-BD^k3CAw-iI-(A z{GurCg-45wRx+?oZ`RX;)l~gF92yP*Qz6l& zVZbN^WBJDHvK!K18FJp7uyk?2-ayF1D;~I|>RMaX@8z@#MVPGBS2NRv2SB^mBL}{a ze$Qq-n7x!9)Y8boPTP4Hh5+xxdRsTs9>4Z72;gi>b3Qe}&1eLzxo>Oc;G4_@9!huV zJhZr0>z@=t5662Et>Y=unMY}9N8!PC-c~QDc{&yGGjO%GRm5p}D`m=b9Sp0V<_>Qg z^WZk`?rQcNhYvt*D+V}PWK11cw;bB&&U%KeT$I%PXCY%CZ%Qn;6R(KH*Os9nI%vN@swq{dbM@5_`0@~ZL zjGCZ>_K*MvS_n6AfqrMv-Q|ux42SaNJi#l}SAFM-q5@l7d@FX)o78p_GVy6W_qx*& zF%S#+w3OyM^FM-uRn$|R2oEt3CfID6DF!%4^-<`BD!>ppV}tVM^LWbdrjK^Z{fbPA z$T2h+vWP&}eF!%I zso#ELK3s%sSkqzc!*h9<6$#l{OAi-Q?O^0U0r1JjP>vSL648rs)qhzIUwYdmF#-)s~Mp z{`UXT^1}~*tL>H74jTI>3;%re3;+0IjgQ^8eE1_J%IxFm8qez2hmZ9={La$%H9q+t zJ^65B^+%ul{l-^6-t=l?{>PuZ{edqEP5XErh~#Vhksp83`tXbYMcVvg%j@s{ME{dt zZV_qqry9TB`K0&Vk9R-$pIhEpZ+`OR;TM1NryD=@YSaJl?jPxR@;90jG12sg(r1x6 z-0=h9!DR;EE<6w@)R9qJO%r@2;Ce9LLB_c~!vU0KO|OU50B!&mvxT3LFamqi`mXZ? z#f2IQ0)&`vR#S6H#K3+=hhLD0WnfVhia};6P38JX9%gJ!Wd`t&pN2fXvHesumd2R7 zR8ekNF^__;V1(@%ZPRffL@{Yt`=Auj1r*iTMD_eBz{hm@F1!LnX@cyBt&|F8`(dtA zrpW}7z!U>G&xS+XR4gAW-f{zB7IcHxl^DATNj%Ez^Sd-nI|kz|&tEWm8g#_w4$!TFiOk-Jd|V4-pJlAweqX2relM7 zZ>7~~9;3~$0=Cqdh5&6$sfqm0@+OeMVoXu=-{k+v^og#-S$mWxXa`TTsLTR3I7?tY z&Gv*Wcm@SzG@nS%`-*dXPZTxqZT-HYQcP-FC@NB()k+ZG=FP{qkUkFOVIC0fhPK=l zR#!G9$nb2{$WA%|fm93ibuy)KkF$;mUer6bHvh4|(R zg>ev_Hfh`1VPKp=Qb#B;9!#A~^KgQd7_CdBl95d1n_C@{q2Vn8N23CwnVifhwM54i2ZCpse}Cfxvfd zl0fGtGXlbkRx;4$Wtor9LrZJMVl3a;IN--hpX+?=dF-|SH)e-VY(GA0|Uge zkV(deZAY`=mTC`4_)1Gn+y_(G@PLd#(BcwD!VO+VQ5Hq+ETjqpNtvViSzDBZ&Qvd= zK~#zeOk3k!z60Z+iPQ?>5;DP9CVFhDIzlVisMXm@(|Kqv7w__dyYRRI7;WnCcaq1< z1KrWQz_vg~#BFBK=xqEQwve81#BhmKWQUBU(Zf6g(Bn>|?{LOYKQC%4_4JaDN}vOW z82w==)sV4}VS0!l_pVG}n*^2SY5IDSfq0oG*)OxHUg#Xz&G*%vQpE=UMpK#dWIrU{ zWymNAfu$KalX(cN+?K}KR}`Y2Fp#qsR1YHLUF9P zed#C22J!?|TgsbK(2;ARkXF-2=r=-*G0&`q1Va?%PZY~cYU0@h=3CAb3}KWTA$6kS z-9nn>tYBcV^=Cptpiod(U)Vx%1y@jM`JwN_de&lZn08Z5K0qd26pA>XQv21&Lu?F; z;A-Y>Z~lr=8A^>fM+%ztJTIb|h#%0ZnUaw3;+f=SgCl})OgIZ^D4$nz4a&mNAinK5 zoDroy8y?MvtnH9%T@(D`5d@R5Nm*vP(~&1ubA2lOWRr5Y5pM_KCF1J9yYBRocHNtX z_`S^Pv3%kQItwwoxptfvKdg;^Eqs~H!$2}YCQIq}Liojq5MV%lJR;mjXTq?`o5{#kvpq-isK;u z+vR)&QhK0P`>#@r1NkryPv#BGwj*?9po9DB zUS!ag!`{U-W969808=}Ki7f1L=Ksfe3TU%5B7 z(=cGq=BejpHJqd=s>*cOe_4&HwL))a^4N@lE21CiJ|bX_E|l}&^Q^|Nxkh% zG+_Q#Shk)9L7$)ke!LQ|eU{H7VSd5uv6VBDjN~rjgyl;-OKB1NH)%-xyES!|I(aVO zum`y>;y3G`SdqXWP|&J0@ixK>J!G81T*R^iaj42`d4ifEZ$lmjYo8iPW1-0sd`~oT4xJ;fC^eFV%^_nh7h#E?_qK zJ`qBx9&{k82LX1TilGk&Rny{Jp)PzKJ*gEA!~1CYdJWTmV9%Yo0dEtB<;r;~V-l0% zU_U0|QLgT%B(sL{xRk*%u>k3yAgUL95ivaFqTSjn(#7_GLHS_BxInkTiP0wN`HD75 zIv%jeP&X2CWGRj2QJ5K7&w0yP261FA!fMCz5|?QWFdYCr>~SjnTIv#*lQ79VcbyVe zhG|R%lZue|6_bxz3}qDcK(R=YSM3)n9&N9i~%Bwdm7-p|JzGbd|ot zq}h&ys&Owq1R63Y2Y>)5=pN|Tk~|D2gvZ{6I7CjRCPSt>BOymHt^^^r*Hmi13RyR~ zGE@z7>>t9{c0rr9ugk(B>Fnj}nOY?vidM)xW~|YJC5AvFAC)knDM(<`tfw5m3~>t{ z;jQLo6Cpt9lEET3N7h;$EFoKk{=-Pn2y?_F6nA@_z5;~=e-S-m__+lDr$i5Ms3Z@G zg8(BK9_sm4h=b)bZz!*OdD@)6$ep!t$xK_Sr{_E)>v@rNB3WibBNI#hTu%yv#`&n) zb4QXuv5J7fN?Vv*hJpy`iJG$Fi7+`_kOv^kUC0%EK_;@WN`QLwzo|URRx?lALsv(t zGz1E=vt2o_iVK?Jrx?n0JAs`;_!!ri1QAlOD5Mv;`z-xk%l+{XR=hIe)?S2sVL*WP zyV@F8$ZQ_8=kY0+<0U4KswptcXr88jCn3dt@H`dHgqegjRqH-Ig*#g{!sgdfem)&K zN;&adP#05Ko=0(`&Zi8|)J8h}5x4*hmiai88@;(~3WEG^-uYwAuRM9Jd8F1n()7P3 z0r+n;z5ez`p1ktbNB+^L8*jeZy#Ky$Z#Dnr=n}Lfk)DzW?FgM|6v$Zo&@6rv7!<@g zWQvoa#W7BX$`2p}rmBIK8^OHM4d5hr;579cXt0xE+)oSChPdD9QZQy51r3sRqF~xW zfM^VEbz7IgU_tN!h~j^iCSBi7B^L2kzRBkUp_n_C0@plCWF#ZNp(u6&-qn!>oqvBm z@Bg~aHw}p_6S*^0F~m6z#0;?vOj(NSK*)hAOr%jtnGpcXL3(WaQft&jEjmhPc)#>p zkg_cf%puMkN&}y$BIN2R#9vP;0K@H;EMra~K*nGi;sBN(L^U-xBydT%qb z8W2NskXUpV=22DwX1qCuXSpJd>M|Dsxm=j;qLT!5(zQ2ITIdwN2nj+UVn3u$5XWUo z31O*=O*WFp>4AO&os5@Y0p3Z=o5x?^F%?Hl8Ks5#vz~HdsYJ5eR|6?>v~W)7iAhcI zgC_~Ug|UbWhh3PO2}KYK52g^aATL*B4k!bR#Uk#=g95AR*{lgZ-m{dIj3TZM!er?k zR*OoEmFY+C7|8#`xv3`Rg+kkx`-YXDSlmEjp-;kepg#){D%qW)76^xtKnNj;3u&e& zB=AE)!9=#&4~Zkfo_!_Sf#b=v;!a?=&?Cf`$||Ama9#gq~AF zP-k3d3;L@vDYb`T=0&b?pwX1OQ(pcQ^Uu}>7n;rU;9?7JCC!RykY6V-r$m-nH_via zs^80WIl7ejo`1i@Hs%0$WUHRo{(7Xo2yz|6bc7bGH#=2<#oaZ+(M(;$m; zI|B&)r}jzT5m#x^Zsf_Kba$Y(P|qz>PFS(fQ_*!`J?RMoOxnZ1%~VUzAS3tya|>Xn$M%Dngxk)iD-ngO`D4Xu3%Cwqz-1yRNfpm zTj9X7{JqO_x=y~6>GOE;zSJ-<11;_An_B+c?J&h0x_7GE+eU-IP&iDcc`{4 ztUx$rITXR>yB6j^QLoYj=TLhH;>85R5hmt~q(d=4R=u+x061qUN4`U*#XljkSQ9WO zAqyk^eE9|*&j zc(I@BfNw4&wgkl$*efBggoXEpjFRp`K#30sw+oiSCgL_VLU3WZ_uIK<8G^D51P_+K zwixIjGw&!&79ixVu?1}KycD}q)-3CyXb=`Ijapg5Iheacd?}0%qi6Yf@fB3MSst(| zD5^Kv+DN=Q?+c6o!#hKo}2a zAXo)f2_BeqqC&(z9p$MQ1&truZvO5%TEt|m{y&?$Q8X4Nwx zzXS%nhppUc)&IBFE=WQ&E~W~{XWR=Wz`e(Xw^&t9Q+k@JL3aj4YI17^(x}oUv#;f8 zj$@K#r(VrqMmp4w*Hg|1uNKtfE@W_iFQnJQp&DW5DM=GB-g@&gZ~L~)c0=@@97mjM z0&59Z04OK$<9y%(uET%9Xk4>`yFn>jH;)n47D9hUk|Dx0Amzz78>TIXzD{T3p?Vp<&aOLTnUpiVHE%w>8gM-+45wwl)hpUNs_o3M<#LZU1% z2gI1sPPLfI)S?5@76;O6u1x?C1(p)hkt?#GAPYcT`zX9@TfV0tc(RL@CodBVv2GyE z2_q3eb&zH`EfFLBn3sGt0|q4o#m=Wn2(i1=Pa%Rl?@$GX78PwMPZUAJ{1geeE_9wl z=^}3e;3ida)t%)5K;B-e6W!9AZ$L-E1tlb&>WxsTxkYCI_Vnb+SQ_U7V0s|lT<7Ei z^YD@jRbg(}O@kL1!^JRBeqIoQR9d1GOU5Q4`Z!&_@h_Ae5OJU(sb<29KK@4_#55jQREgga?A6XJ%0wWV$PG(sCtMNp zW@cy<|FoDCKWn$u8r$++b zm{iVjx=&%-&O(}#9Z0Pdlc~AG^xA@=Y7nbH4-gI-#I-Hw7;LKUbPR08URe2x;4EPC-UC8QV&g@PPHznowseTL)1xYG`eIe(P% zz{IjbrXGdVa5}hDJLF`ws&n*3DC5IpmCCFmt zqi}jspNK#~6CiXa zu{5Q)ChOtjK_2J9v2&H?blC2?oCbR<;!e`A^*50+ppw2+B+S>$+X`{O;she;%1n^04jY4$$LR5`d6%6XL?F98p*?mjFVP!%0vh4XwN-E@N$aHA z3cVn&sl!Am=#QWz%b3Ovj-e2d$`TEa(sZ#@*>;2^CoB!$DMzL%zj~rPG@zljiK3*M)*8rWD%SqJOBeggLo3_VG)42J9AMsk)_OMO`-w?XVNF=nk;lK zbPfq;EtD7q(q6FKQJ!MxL1Unp1Np?+;D9^Zt0gI}g%}|T7>acJ-dd1Z;mE9PmLu(g znIxtQ%E+b2<;)4iB%+Ve#k?YO!fy5|%X=%87JJMjEb@f-#M_y?1uxNl*F&)B`!(f? zmscd#P~Ivnv6TMz&4$4*Ni2yKHB^sc@09e^aA6v@5T<1u!3*He zl+ozZFpGgVkWq9j1_3KPj^cFL_;PFW4>f+Nz4?0^pWA7^eYfS$<}k~8_OkBOCLxll zB)3FP)_mrZ*tqG`!#@nwk{K+FiO$b$C#zE!3p-KJ61V?xYU>GY+_JXeowO_wSbQhF z2jVHg!e(yJqXmQfgr3=8opVIdV0c`mn!enl|4VEI7g!(mmFMZY95*gcK!Qar*1*Ju z>8_-tRaXL+NT;%?jZ|GR<+-+!qA!7n(^51bFVAeMuoMfH&50)k;H;`p=mrJyB1B1- zvy3N024}bG*nlR2h!RjE#EXu>EiN!d<^uTxgacSeBg;XWdM5lrN-_n~A(F*Y#uscK zG&@S}LVKDQ`+{}E8ow~RZ>%aq&ce*O6A;I06vDF-QtP4Jf)qj5pD+lc&u9 zXC4rC>QB@FuMX=K%P^XKLllEnOBy8Cg1uJ24TuoXLO)u`mCam-dWw~x^BN@bV?N(` z-+HT|WB072r4nyMN-d;Qu_)_lqUdE#1}GWT?J3GxvX(+T=?L_i$;)m)SE2%Rrh0Ky zvKh_rtu#22ev77neFBtcQf4%qp@!+2s0_L`Q^g?+v4TW3lK8eDtb^SMkmvEYO@Sa; z`oK1!I#X$yQ(`ntNhRZgvzs_Gi?J?V3@IY4#GP2Kye9}>2xs98lXfeSd3#h#nL8>x+A zCTtdqW*-1W_Uv`8F|L6>#zmOCT~szT(Ni7i*_s}{Ow}dohT6kT2OGLd1wvxQC&O9z z-?=O}ea_R&ZN4LntK&9=n<6ZF$Q=Jag9xg{tl$Kg3omS2VqG_LpQYCe#GZnzdAt>*UR}%Jj8=Aj9Ua2FjbO@9=%p?|ScTq7|egeH&W)<~X=&X=b zmcm#{iucBZaCa#{mCUi>;Vvp*!-AnS~w zk?7pNl7`73hv9L{QmDAS$ms5T5`jaC7-wl{taI#i!UYqo;FCoCc}lOQ0RiH$E~!$~ zOo%4MottBrg$70@d7L$5vJB5aa5L^`s(x%E2o4SMGo zE9e?$fRqX2&-q317W-VRTEPyTd4|zy6-`x~1BlXb*?Ts{^xwYAlRlll@`G4wJgZ^CI!CUsd$7##nU3EXcdNVx1t#0`l764nhP#i&e}I$vtezGA$|P z?3e!VQ13zJ900-+rOlKrmDfGlL_C6B82}N?ATjA3PSrARS5jO8tKKZ<#U`^03nsRI zfpi8c;+(x8_*@Jxjm~tHe`*>E&Lbl=4-l3-X}GwR6Tc`XhTrTgO>U+MaTAAWn5rjw zgf=gzhrxN5&ut-$8Ye{tVlB>(Z6vBmu;FF6DKi~pRKzlCR?>igdzmq$CNDO%;allf z?WtC{&hR_G-uxsYnH4I*8^+AuWAOpHghz@oDTm|Xg%ofGHL1{7HEr9X?mnIu0!F*7 z_-2z5gv!XY!+82U-yI`jVwOPuxItx2YZhv3)kgV6 z7Zs7tZAYOMICG!x6Zvk9(8&!UuME@jH9wFq936 z`~EQfAiw33^Ck6$Sx%D?k4R36EMl+Ns`@i+MXf-iZ!!s;?8PLK!vWP6xesixQ_%9s z2?;jjOlOF_b0nUKAlq>nSEd|f3Rt9H&`b6Q;6XqGX*i!3%U(2!G*;(Oib4?oj6B?Q zz#^eT#+b%1GDx<4WG?(UP5-kbeS8r{gE1+$;)%+3!Us<~AI(Fp6>;WBiC-@VNYABT zv>qYU(oZbRa>$KI4q|1oF6AEADd>?(yA+}%+|i_RB%G~Pcx%~UMV*=JyskFV%RFFO z!lr~?yT)`B-qMpnU{04KwXAaD+1Z>zCrAe%-gUZgpQlAN37k=0WV@8u90@6`4Z8s2 z0tu&bS?&&{e=7~xoUP%4-4ZAQ<7t4PSPOJ6cSZN!<|VRQY%iwwha+rw!wO_7o1e8q zywMaBnk}VnE*imKNpogCV4IUnP>>T@ms5-@TnMNw%9*tVo)noU)Kr*HF|IdK2bo6= zFE4;B_>Hng6W9!gC@1EOERZS;j5(`WhYb|)l$Ojf#6K%b7WVGwpMek}E)BLPNPzxq zuhYmt8u#*qff2<(eV2yGKA`{FFN$+X2~lcsk#1(%VTeogTd<%+61Yu!80qs}NQ3xc zB@IEytwRe8jRP)4Ea3;g)<`Ob4ECjzU!$;Q*i`+w!ha=OaK7psXggpd?^?lHoLG0A zDjbsusga%v?$F?IdM%sdE`+V53}jd2LPyms5)mhwRY}pVaBmTA=D~0pwR*(2!T@{I zA$iz?Qc9<*5rw~+awNA%Llp^Kv~neaInaWXP=;DzQV9-P3-jTdEK8gOtx#fa!#lq# zAxx@dKN8766qdbhjP8&lF)a*D0hCOTUK9BOz zaTg2K6^U75kR)1)uOO^>tevqU;1615@_GScA^+sQSWadDj2rv^Efm?S0} z6vy_>kTZvb*aDX z9qMT%NO{B?cD(Q&VO=aH_Px-tfslET>)j!oW6fUgi~>lc1d1fsoiu3p&<9(a)wQCZ0FsQ74Y z(cJ2Yvk;wZ2&LpuAT{{-*$NXOgax&ja^%KP8a@d5`Oyer@M14zVU}Y(I1t&%U`sMr2U*)t>3JD(c#G$TjI?1zEK7sqH2yh7fCV`#aR@^1aMkpNjoycG#Z_OBe*uVo5#coSP#4eh|4^Etk;m9aaz+y%P;yX++k*-dk^e$7`>?^^v#!-G>@K@yV9${8-PwedkjxPjd2+ z$S-#KPX1@WYKFeThYB->BAXB*9&GqLk(394E?kB3#}>i|(Nf|f^i@LteQJ`gEX_e8 z9f$yAKb!vY<`+#TC!cX=nX>e7rw$P6m}15`*c77{!j`QJg>6At4^cqfNfcb3<@#V4 zi;m;Cxv%D};Lpx9CGy%VVXYt;;z4Z&_>ZzpXrsiNz{HDOo=pWsrDY&GmZwC-^`QVqHjpbv6>-+K-<|;_JbfF0=b>A&b;-XBgIh!P!YJ-zA6b+FJ{9(o*MdyJTx-Moe29Ha-dRuYI* z=A{?x%=WU~IP*%LmhdQ^CR!lk`1(!rM5c3GQEb>+PBf>H!=XT0{1Nl{1PJjvjezm~ zTvr>T+ zl=fo0=serOuEG0xkxv(C%_biB&lzkw*O|v;K2s)ih*TnfSi5|hHUQ2X$S-%X@o>Te zotZYk9m5naVJNn*Y{7ZXs*_P_aR^71oa-PHW-TZ$$6!IlVYvh_5R$kSKt@%QuLZV# zRI$$Uu$d&g3z=fLxQ9>kjI={Kk$UV|nueCK8XX|qo`r;Sq7NVol-aI&sOUET!gjf3eKBf{$BM@xm0+k_bA*=1@nHK@5P z3D(kJ>Z!M-cz+7d<#E)Bsb~ENDg$KF4ISF%#f!qHZ{9IhCGDCcX00&OgAgOH*-^rHl2yGyhc`wiN6juQLE`?;VzDbrE4K<)9E30U+8+n8->!br-X=@3d z?%;?=_=Zt$X5fu@3Y=@rcuJR}2{BsKT5QISG%M!4^qem3Wxtycl0!4**>dLIN*GA$ z-ltjNuth#&w5TcQpM+y*tLQ%g0))QbwQ+LNO!`gc!hu$+A)*-~PBz;4#oV`n*vQr68Gcm~nh8#GC#1{7lAKS|} zNvv#8UZ<%H$9eb*g$%AJSqP%dr^!iMc3_Hu;BOgBrdG^}5F~TisThz{qK@7%k#PjQ zh)!TZ+475JZGg_g32|a~xyJ`9wMNL3)x9g?cs+@o2O;W{_DTupll;c}Sx5c+1#%A_zqJNc#0- zchsA%l%=%85S(&D2NC09-`J^`4?6^Nn>B`srFEKn=Kosiq*TS(WBmC^7&3rrx}#ao zZAWpTqa%)Y2xf2?PXXz~<$$gvnac4OlVt`Wmh8Mv>A_qt z=P3y>U(E~>00*d{pIH@=|9&b<1TsZVv;-N*TRNSFKnb838M;oV20l8siNnSzKfI~X zQu{g7m-J-P!-GXi@URFdmt-)PtB4bLEfYo_NSoiHmGDD}Ek6v!rjQEk>?2T;eXlPK zaNvxltiS~)3M5N0n}u@L5avd zE+*7nr(yb}y`Qqr5;5QT`d}bb6V3?fMZIArg_vtk?7rp6d1+@fgchYEta#jrWcq1= za3rb_@9`3VpX}hQBT|YpR<)2!I-9*)1%tH^tse@mY2PgUi8xXzX>gzZxF4s^c$PWCKH5}T7Ec^dzWNbO8<&Coq8sS zxmevoj{RVOfYlOa$b|H3Ks<%KN}H+K>2d_{dNn}t5^~SNfJ8O;6$0J?Zpx7q1yA1P z;p;$1%5yh+~z2icqWlR^Gpw_pjtV znzxvb;k<|P{@EO9`z?8YAn$L@``a47^U0R)d1WnRtTg^zKIW7=>ViyT4r#WKKt@1K z*&tTKGN*AqM!6QAINDPP0+m5%3+-hqb#TygT6Clq*D0o=`;PM>J67@?iUSN03Uu#e z2B(u1f97aza-YzEuG4SU7chs@np(9H)^WKqC2&b}qWzC{*i4cU#A%@S=o@Z`T4EFLkHQG|LN87u^ zf^DStK&eJ!)*qk5?&-O48NK6k-^aUj`(PPSP~vm;=p|Km+aqhCa?srx{v1bWw1;KnxrN zaSAyV8lBwn;>6GsY1POf0$A7lG<41JRL@TVo)VE}AGH|l*sKv6E1SiOo0;BGR09x; z5MWk$ncXUR-+MUsV|tqk}tzR zB%mA8CC3EnFQNxXCs>LcDkq@#rsP=2p}X@D+=gS?;u$H?f3}(WQZ6!ySR!bCo@&am zn9KR(mqkhKa%gQa2j63_CV}vxP#`k`NI8S2LWE>d<}l>n%dmE2dW-!kNHV!~@qy4S zWyM_Gednt!E&cTmq>R{LgT}a)ie$aZP!~@v#N#OZB&Hx8MuX+R65t0CH|H2KmLq0P zsk#ybqOAQ)CttLK18wdr~;aIs#ch?m$XSsIj*>#&NoXMZco+G?7@ z1ga|R457l-U_kI$Dh_k@B+s;_r{XWUz8(+kvhSX!JN?x-9^}dB45z8ECyzk>Iw}F6 zI46&Srubp{E3bJwWt`9;!cd&31N;Q&QMXAa@wE5VT_>1N<(kADn!}@{*~?ERp4=j_eN*`<-~19K z-53edyM%cIL3>g{`dju4(^QI#6e5s|&6M!-t22xF%ak^*6!5vKv5gR-_Yf{ZQk;c( zARUW9#VKeR_1+B3`PKkpV8cU_p)b%pv-nxk4`z?qQxdZpF!- z#+T7*une7}>|i7%zff5JeF)eN!IA-SSOAYm4JuM*6^Sf<6s84|hJez685?UA`u$EE+p+a5s5kCj#}YPeK*bcVFMZn zJC|}zv*lqVY=j?9ts!SIci;Q5i0fSc|Lw;j0JLT= zDtQ~}Tfk(9$3Q6M9%F0>#Uqq}*J3Q#)GTHp4^V=uT(hOf4erdG@C141q!a6dvP2HF z=PuyRV6#Tje^wwnzksvF4Jy;`IJ3eI2wXC^>BCa5MVy;F!Ww2PGFvM~Po;7{D*=X< z1xSdCxR>ZGn#f`ON0~u)DZ}P6-J_A&Xo(o-QQhVf0D(^$mbn;OQBI0O`HLP(LU2D# zqzRr{!8=9oyvld6#5OP5n6=`VrmkG$1{kPn23;ETY(K*Iq(yS^VOXr{^g&`0C8=P& z3|aF-gekyD4yhlHCIXfvRBak}sZhAvdB}B2k;=F+9BJ>T zlxglFih*9t3S#*n&q7H36iJCtG6&!$u}FB&!EEION>Or*RY9zf0`Y$!Cj1a-N6GEW zcSlAMy^iU&?!lFiG=Yyoc}DWtw1c9xrv@AlIm|cc_%eFYa@xs{MA4xs{OI=chJbh& z0@ce8MsB8xf`Kh%fGi;BWNz z6R(|SCe(@LGxH%r0vTx>I`ivM?}qSo>`O^Ff{@6>;e_7VWjgFHx%}I5E~55NKU7W z(PoH~6e3=@xNh=4Un4B)JWcu~M)883qf2=q+pZwf2#RuirV#T4jQD4(`6kw zASeVV&-W=Gh)dSdnL-OJC)<=}8Ggk>0EQF}zn6JLcWkX1ozGn+Ly3fuyxUo;PI|Ji z9HR?#QZdx6mOk0r#MCp2grfW9MosW=3rpj0RH==tC!dJa^YEZpqX2(MvvV?}@e*$Y z@^D_3ymNMgnV9k96w`Gie<@K$?|F)uwb&;c8JJTX*C0Jxkd&sfr>^u_FTG z`2U>+$#wf!4s1yoL&RX(%1KWGvP+@EP*~`5%ZQl^L^DRSer%{RuvXGvpVz*kIfee*Mgd3xBhvL(&9uO3h7XrJv$3`J`jo-)s}XNK>T@ZN5=zGqr=t z&5`A(3fWlh$gjjdf%&%~^Zi`GIY(3xMea&ve2*7a&lLx)Tlzg3mUX7TqGu06zpxnU z2qgn^p)@RMJ=Cbs28R%eJ7FKf*?E*f21J@rR`!vfrLeWjJ0l~3S%q_PZ`4zPpRnaz zCP*mPL=LyJU|a-9#Dv+DV4woc`DKgaRNkAagldELrQMrq@WkFU+nu^(Xi~me2F`qy zUM^759;1^}=c)KU%L;SsQkUshN#%m|vC26aSZf?>j1Vy0K+4&e@QhHBGt}Dn3;(qF z(_cHppzX=c!(aHDAF4OKkwF=&xX~7ZElL9{4iCN~;DI=zXE-2YJ}sQ*uLZ!CDp8ol zree&n9$ZX)Rpq<&2mO>$whJ50$>(8_7_C%?JcAa*m5XfJ8q;&x5@>)`d3np$(ShhavX#NQE3lQ zIgvN4EbcrCS#fF^=Ih)s_b7OL3~a^XNfai}S67a%qI>j&U(`Lpq-U8fCeLo__ea+-FP7l@U8+gWHXAQSxJSEbXy_QoJStappFkw$lzJ3r%BklPts#~hYU4PBPEVi zF4#j38~+560hEKI45b59c`*oh<}@~GRE&UZZ>;_6R4EZq=r?fZS(>w-fdA$0w5P8k z4CeE=zIIf^!P?vM&2bN65YE#?Nv&Y`g39%vD zmeL||@Zma7#Jpe46qJhtG?bqWIuk}Gyq}>8Om)sWK#eNE;t=nPAAy%?6(Aph z|J_w{0v08cLI4FLn#e*U{sa4IIQ0JQG>27w{eD^y&;Pgksn{Z5&f`}3&G*x9*iP%| zYuRew+fOh5Z}wA(lwZG}mdq5mrtxMJedB&ANxa%mtF`gocAB3d{P*`$k%U(Dd;2K? z&m^&*(jLTD{eRm}zhOHqSvuAwaZ>h88}d!Y^}YR+@0ok_oA0NrN%P!(`gpF{dFBKM)^Y{<%r#$#&Kc!2T z{j?a0@E5~FU4+2pQ0V_Ob5dzUiiV))VlUc4PH)x(>zdLlvM6~ { + return createBrowserRouter([ + { + path: '/', + element: , + errorElement: , + children: [ + { index: true, element: }, + { path: 'account/change-password', element: }, + { path: 'account/profile', element: }, + { path: 'account/management', element: }, + { path: 'account/role-list', 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: 'report', element: }, + { path: 'notice', element: }, + { path: 'notice/:CCP_BLID', element: }, + { path: 'invoice',element:}, + { path: 'invoice/detail/:GMDSN/:GSN',element:}, + { path: 'invoice/paid',element:}, + { path: 'invoice/paid/detail/:flid', element: }, + { path: 'airticket',element: }, + { path: 'airticket/plan/:coli_sn',element:}, + ] + }, + { + element: , + children: [ + { path: '/login', element: }, + { path: '/logout', element: }, + ] + } + ]) +} const initAppliction = async () => { @@ -46,54 +83,19 @@ const initAppliction = async () => { if (isNotEmpty(userId) && isNotEmpty(loginToken)) { await notifyAuth() } -} -await initAppliction() - -const router = createBrowserRouter([ - { - path: "/", - element: , - errorElement: , - children: [ - { index: true, element: }, - { path: "account/change-password", element: }, - { path: "account/profile", element: }, - { path: "account/management", element: }, - { path: "account/role-list", 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: "report", element: }, - { path: "notice", element: }, - { path: "notice/:CCP_BLID", element: }, - { path: "invoice",element:}, - { path: "invoice/detail/:GMDSN/:GSN",element:}, - { path: "invoice/paid",element:}, - { path: "invoice/paid/detail/:flid", element: }, - { path: "airticket",element: }, - { path: "airticket/plan/:coli_sn",element:}, - ] - }, - { - element: , - children: [ - { path: "/login", element: }, - { path: "/logout", element: }, - ] - } -]) + const router = await initRouter() + ReactDOM.createRoot(document.getElementById('root')).render( + // + +
Loading...
} + /> +
+ //
+ ) +} -ReactDOM.createRoot(document.getElementById("root")).render( - // - -
Loading...
} - /> -
- //
-) +initAppliction() diff --git a/src/views/reservation/Newest.jsx b/src/views/reservation/Newest.jsx index d9f3420..9c35592 100644 --- a/src/views/reservation/Newest.jsx +++ b/src/views/reservation/Newest.jsx @@ -206,10 +206,6 @@ function Newest() { Date: Tue, 2 Jul 2024 10:16:08 +0800 Subject: [PATCH 065/120] =?UTF-8?q?feat:=20=E9=9B=86=E6=88=90=20PageSpy?= =?UTF-8?q?=EF=BC=9B=E7=BB=9F=E4=B8=80=E7=94=A8=E6=88=B7=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pageSpy/index.jsx | 4 ++++ src/stores/Auth.js | 33 +++++++++++++++++++++++++++------ src/views/App.jsx | 26 ++++++-------------------- src/views/account/Profile.jsx | 28 ++++++---------------------- 4 files changed, 43 insertions(+), 48 deletions(-) diff --git a/src/pageSpy/index.jsx b/src/pageSpy/index.jsx index 5cf6ac9..0bfcd65 100644 --- a/src/pageSpy/index.jsx +++ b/src/pageSpy/index.jsx @@ -2,11 +2,15 @@ import { loadScript } from '@/utils/commons'; import { PROJECT_NAME } from '@/config'; export const loadPageSpy = (title) => { + + if (window.$pageSpy) return + const PageSpySrc = [ 'https://page-spy.mycht.cn/page-spy/index.min.js', 'https://page-spy.mycht.cn/plugin/data-harbor/index.min.js', 'https://page-spy.mycht.cn/plugin/rrweb/index.min.js', ]; + Promise.all(PageSpySrc.map((src) => loadScript(src))).then(() => { // 注册插件 PageSpy.registerPlugin(new DataHarborPlugin({ maximum: 2 * 1024 * 1024 })); diff --git a/src/stores/Auth.js b/src/stores/Auth.js index cc47621..acfe18f 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -48,6 +48,13 @@ const initialState = { tokenTimeout: true, loginStatus: 0, defaltRoute: '', + currentUser: { + username: '', + realname: '', + rolesName: '', + emailAddress: '', + travelAgencyName: '', + }, permissionList: [] } @@ -57,12 +64,29 @@ const useAuthStore = create(lifecycleware((set, get) => ({ onAuth: async () => { const { startTokenInterval, loadUserPermission } = get() - const { userId, loginToken } = usingStorage() + const { setStorage, loginToken } = usingStorage() appendRequestParams('token', loginToken) - appendRequestParams('lmi_sn', userId) - await loadUserPermission(userId) + const userJson = await fetchUserDetail(loginToken) + + appendRequestParams('lmi_sn', userJson.LMI_SN) + + setStorage(KEY_USER_ID, userJson.LMI_SN) + setStorage(KEY_TRAVEL_AGENCY_ID, userJson.LMI_VEI_SN) + + set(() => ({ + currentUser: { + username: userJson.LoginName, + realname: userJson.real_name, + rolesName: userJson.roles_name, + emailAddress: userJson.LMI_listmail, + travelAgencyName: userJson.VName, + } + })) + + await loadUserPermission(userJson.LMI_SN) startTokenInterval() + loadPageSpy(userJson.real_name) }, authenticate: async (usr, pwd) => { @@ -70,11 +94,8 @@ const useAuthStore = create(lifecycleware((set, get) => ({ const { setStorage } = usingStorage() const { token: loginToken } = await fetchLoginToken(usr, pwd) - const userDetail = await fetchUserDetail(loginToken) setStorage(KEY_LOGIN_TOKEN, loginToken) - setStorage(KEY_USER_ID, userDetail.LMI_SN) - setStorage(KEY_TRAVEL_AGENCY_ID, userDetail.LMI_VEI_SN) await onAuth() diff --git a/src/views/App.jsx b/src/views/App.jsx index 564e93e..2acfb41 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -27,10 +27,9 @@ function App() { const { t, i18n } = useTranslation(); const [password, setPassword] = useState('') - const [userDetail, setUserDetail] = useState({}) - const [authenticate, tokenTimeout, isPermitted] = useAuthStore( - (state) => [state.authenticate, state.tokenTimeout, state.isPermitted]) + const [authenticate, tokenTimeout, isPermitted, currentUser] = useAuthStore( + (state) => [state.authenticate, state.tokenTimeout, state.isPermitted, state.currentUser]) const { loginToken } = usingStorage() @@ -42,19 +41,6 @@ function App() { // 除了路由 /p...以外都需要登陆系统 const needToLogin = href !== '/login' && isEmpty(loginToken) - useEffect(() => { - if (isNotEmpty(loginToken)) { - fetchUserDetail(loginToken) - .then(u => { - setUserDetail({ - username: u.LoginName, - realname: u.real_name, - travelAgencyName: u.VName, - }) - }) - } - }, [loginToken]) - useEffect(() => { if (needToLogin) { navigate('/login') @@ -62,7 +48,7 @@ function App() { }, [href]) const onSubmit = () => { - authenticate(userDetail?.username, password) + authenticate(currentUser?.username, password) .catch(ex => { console.error(ex) alert(t('Validation.LoginFailed')) @@ -109,7 +95,7 @@ function App() { setPassword(e.target.value)} onPressEnter={onSubmit} - addonBefore={userDetail?.username} /> + addonBefore={currentUser?.username} /> @@ -149,7 +135,7 @@ function App() {
- {userDetail?.travelAgencyName} + {currentUser?.travelAgencyName} @@ -169,7 +155,7 @@ function App() { > e.preventDefault()}> - {userDetail?.realname} + {currentUser?.realname} diff --git a/src/views/account/Profile.jsx b/src/views/account/Profile.jsx index ce8b3e1..d40ca83 100644 --- a/src/views/account/Profile.jsx +++ b/src/views/account/Profile.jsx @@ -1,37 +1,21 @@ import { useEffect, useState } from 'react' import { Descriptions, Col, Row } from 'antd'; import { useTranslation } from 'react-i18next' -import { fetchUserDetail } from '@/stores/Auth' -import { usingStorage } from '@/hooks/usingStorage' +import useAuthStore from '@/stores/Auth' function Profile() { const { t } = useTranslation() - const [userDetail, setUserDetail] = useState({}) - const { loginToken } = usingStorage() - - useEffect (() => { - fetchUserDetail(loginToken) - .then(json => { - setUserDetail({ - username: json.LoginName, - realname: json.real_name, - rolesName: json.roles_name, - emailAddress: json.LMI_listmail, - travelAgencyName: json.VName, - }) - }) - }, []) - + const currentUser = useAuthStore(state => state.currentUser) return ( - {userDetail?.username} - {userDetail?.realname}({userDetail?.rolesName}) - {userDetail?.emailAddress} - {userDetail?.travelAgencyName} + {currentUser?.username} + {currentUser?.realname}({currentUser?.rolesName}) + {currentUser?.emailAddress} + {currentUser?.travelAgencyName} From 3b25a9dc7b7812d44497e61ac8b5734a8cac7feb Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Tue, 2 Jul 2024 10:33:39 +0800 Subject: [PATCH 066/120] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E4=BE=9B?= =?UTF-8?q?=E5=BA=94=E5=95=86=E9=BB=98=E8=AE=A4=E9=A6=96=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/RBAC 权限.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/RBAC 权限.sql b/doc/RBAC 权限.sql index 2df36eb..c5d308d 100644 --- a/doc/RBAC 权限.sql +++ b/doc/RBAC 权限.sql @@ -82,7 +82,7 @@ VALUES ('机票订票', 'route=/airticket', 'page') INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) VALUES ('产品管理(客服)', 'route=/products', 'page') INSERT INTO [dbo].[auth_resource] ([res_name] ,[res_pattern], [res_category]) -VALUES ('产品管理(供应商)', 'route=/products?from', 'page') +VALUES ('产品管理(供应商)', 'route=/products/edit', 'page') INSERT INTO [dbo].[auth_permission] ([role_id] ,[res_id]) VALUES (1, 1) From 11b5a80986aa7c551fdcff5d661aaf00d34eaaac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=96=87=E5=BC=BA=40HWQ-PC?= Date: Tue, 2 Jul 2024 14:00:56 +0800 Subject: [PATCH 067/120] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8A=A5=E4=BB=B7=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/BatchImportPrice.jsx | 243 ++++++++++++++++++++++++++++ src/views/products/Detail.jsx | 82 +++++----- 2 files changed, 288 insertions(+), 37 deletions(-) create mode 100644 src/components/BatchImportPrice.jsx diff --git a/src/components/BatchImportPrice.jsx b/src/components/BatchImportPrice.jsx new file mode 100644 index 0000000..0404beb --- /dev/null +++ b/src/components/BatchImportPrice.jsx @@ -0,0 +1,243 @@ +import React, { useState } from 'react'; +import { Table, Input, Button, DatePicker, Row, Col, Tag, Select } from 'antd'; + +const { RangePicker } = DatePicker; +const { Option } = Select; +const BatchImportPrice = () => { + const [startDate, setStartDate] = useState(null); + const [endDate, setEndDate] = useState(null); + const [startPeople, setStartPeople] = useState(1); + const [endPeople, setEndPeople] = useState(5); + const [dateRanges, setDateRanges] = useState([]); + const [peopleRanges, setPeopleRanges] = useState([]); + const [tableData, setTableData] = useState([]); + const [currency, setCurrency] = useState('RMB'); // 默认值为 RMB + const [type, setType] = useState('每人'); // 默认值为 每人 + + const handleGenerateTable = () => { + if (dateRanges.length === 0 || peopleRanges.length === 0) return; + + const newData = dateRanges.flatMap(dateRange => { + const { startDate, endDate } = dateRange; + const dates = generateDateRange(startDate, endDate); + const row = { dateRange: `${startDate.format('YYYY-MM-DD')} ~ ${endDate.format('YYYY-MM-DD')}` }; + + peopleRanges.forEach(peopleRangeString => { + const [start, end] = peopleRangeString.split('-').map(Number); + generatePeopleRange(start, end).forEach(person => { + row[`${person}_adultPrice`] = ''; + row[`${person}_childPrice`] = ''; + }); + }); + + dates.forEach(date => { + row[date] = ''; + }); + + return row; + }); + + setTableData(newData); + }; + + const generateDateRange = (start, end) => { + const dates = []; + let currentDate = start.clone(); + + while (currentDate <= end) { + dates.push(currentDate.format('YYYY-MM-DD')); + currentDate = currentDate.add(1, 'day'); + } + + return dates; + }; + + const generatePeopleRange = (start, end) => { + const range = []; + for (let i = start; i <= end; i++) { + range.push(`人等${i}`); + } + return range; + }; + + const handleCellChange = (value, dateRange, peopleRange, type) => { + const newData = [...tableData]; + const rowIndex = newData.findIndex(row => row.dateRange === dateRange); + newData[rowIndex][`${peopleRange}_${type}`] = value; + setTableData(newData); + }; + + const handleAddDateRange = () => { + if (startDate && endDate) { + const newDateRange = { startDate, endDate }; + // 检查是否已经存在相同的日期范围 + const isDateRangeExist = dateRanges.some(range => ( + range.startDate.isSame(startDate, 'day') && range.endDate.isSame(endDate, 'day') + )); + if (!isDateRangeExist) { + setDateRanges([...dateRanges, newDateRange]); + } + } + }; + + const handleAddPeopleRange = () => { + if (startPeople <= endPeople) { + const newPeopleRange = `${startPeople}-${endPeople}`; + // 检查是否已经存在相同的人员范围 + const isPeopleRangeExist = peopleRanges.includes(newPeopleRange); + if (!isPeopleRangeExist) { + setPeopleRanges([...peopleRanges, newPeopleRange]); + } + } + }; + + const handleRemoveTag = (index, type) => { + if (type === 'date') { + setDateRanges(dateRanges.filter((_, i) => i !== index)); + } else { + const removedPeopleRange = peopleRanges[index]; + setPeopleRanges(peopleRanges.filter(range => range !== removedPeopleRange)); + } + setTableData([]); + }; + + + const [adultPrice, setAdultPrice] = useState(''); + const [childPrice, setChildPrice] = useState(''); + + const handleAdultPriceChange = (value, dateRange) => { + const newData = [...tableData]; + const rowIndex = newData.findIndex(row => row.dateRange === dateRange); + newData[rowIndex]['成人价'] = value; + setTableData(newData); + }; + + const handleChildPriceChange = (value, dateRange) => { + const newData = [...tableData]; + const rowIndex = newData.findIndex(row => row.dateRange === dateRange); + newData[rowIndex]['儿童价'] = value; + setTableData(newData); + }; + + const columns = [ + { + title: '日期\\人等', + dataIndex: 'dateRange', + key: 'dateRange', + }, + ...peopleRanges.flatMap(peopleRange => ([ + { + title: peopleRange, + dataIndex: `${peopleRange}_price`, + key: `${peopleRange}_price`, + render: (text, record) => ( +
+ handleCellChange(e.target.value, record.dateRange, peopleRange, 'adultPrice')} + placeholder="成人价" + style={{ width: '45%' }} + suffix={`${currency}/${type}`} + /> + handleCellChange(e.target.value, record.dateRange, peopleRange, 'childPrice')} + placeholder="儿童价" + style={{ width: '45%' }} + suffix={`${currency}/${type}`} + /> +
+ ), + } + ])), + ]; + + return ( + <> + + +
+ + + + + + + + + { + if (dates && dates.length === 2) { + setStartDate(dates[0]); + setEndDate(dates[1]); + } else { + setStartDate(null); + setEndDate(null); + } + }} + /> + + + + + + + setStartPeople(parseInt(e.target.value, 10))} + /> + + setEndPeople(parseInt(e.target.value, 10))} + /> + + + + + + + + + + {dateRanges.map((dateRange, index) => ( + handleRemoveTag(index, 'date')}> + {`${dateRange.startDate.format('YYYY-MM-DD')} ~ ${dateRange.endDate.format('YYYY-MM-DD')}`} + + ))} + {peopleRanges.map((peopleRange, index) => ( + handleRemoveTag(index, 'people')}> + {peopleRange} + + ))} + + +
+ + ); +}; + +export default BatchImportPrice; + + + diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx index 74bac6a..57cd8e7 100644 --- a/src/views/products/Detail.jsx +++ b/src/views/products/Detail.jsx @@ -10,6 +10,7 @@ import { groupBy } from '@/utils/commons'; import { useParams } from 'react-router-dom'; import { useHTLanguageSets } from '@/hooks/useHTLanguageSets'; import { useDefaultLgc } from '@/i18n/LanguageSwitcher'; +import BatchImportPrice from '@/components/BatchImportPrice'; function Detail() { const { t } = useTranslation(); const [form] = Form.useForm(); @@ -19,12 +20,13 @@ function Detail() { const [selectedTag, setSelectedTag] = useState(null); const [saveData, setSaveData] = useState(null); const [datePickerVisible, setDatePickerVisible] = useState(false); + const [batchImportPriceVisible, setBatchImportPriceVisible] = useState(false); const [currentid, setCurrentid] = useState(null); const [languageStatus, setLanguageStatus] = useState(null); const [selectedNodeid, setSelectedNodeid] = useState(null); const [remainderLanguage, setRemainderLanguage] = useState([]) const [selectedDateData, setSelectedDateData] = useState({ dateRange: null, selectedDays: [] }); - const [treeData1, setTreeData1] = useState([]); + const [treeData, setTreeData] = useState([]); const productsTypes = useProductsTypes(); const [productsData, setProductsData] = useState(null); const [quotation, setQuotation] = useState(null); @@ -99,6 +101,7 @@ function Detail() { const languageLabel = matchedLanguage.label // setTags([languageLabel]) setLanguageLabel(languageLabel) + setSelectedTag(languageLabel) setRemainderLanguage(HTLanguageSets.filter(item => item.key !== language.toString())) }, []); @@ -124,8 +127,7 @@ function Detail() { }; const treeData = generateTreeData(productsTypes, groupedProducts); - console.log("treeData", treeData) - setTreeData1(treeData); + setTreeData(treeData); setProductsData(groupedProducts); setDefaultData(treeData); setDataList(flattenTreeData(treeData)); @@ -202,17 +204,6 @@ function Detail() { setAutoExpandParent(false); }; - // const productProject = [ - - // { code: "code", name: t('products:Code') }, - // { code: "city_name", name: t('products:City') }, - // { code: "remarks", name: t('products:Remarks') }, - // { code: "open_hours", name: t('products:tourTime') }, - // { code: "recommends_rate", name: t('products:recommendationRate') } - // ]; - - - const isEditing = (record) => record.id === editingid; const edit = (record) => { @@ -268,6 +259,10 @@ function Detail() { setQuotation([...quotation, newData]); }; + const handleBatchImport = () => { + setBatchImportPriceVisible(true); + } + const handleDateSelect = (id) => { setCurrentid(id); setDatePickerVisible(true); @@ -309,6 +304,11 @@ function Detail() { setDatePickerVisible(false); } +const handleBatchImportOK = () => { + setBatchImportPriceVisible(false); +} + + const EditableCell = ({ editing, dataIndex, title, inputType, record, children, handleDateSelect, ...restProps }) => { let inputNode = inputType === 'number' ? : ; if (dataIndex === 'validityPeriod' && editing) { @@ -473,10 +473,15 @@ function Detail() { const showModal = () => setIsModalVisible(true); const handleOk = () => { - + if (!selectedTag) return; + if (!remainderLanguage.some(item => item.label === selectedTag)) return; + if (remainderLanguage.includes(selectedTag)) return; + let tempRemainderLanguage = remainderLanguage.filter((item)=>{ + return item.label !== selectedTag; + }) + setRemainderLanguage(tempRemainderLanguage) setTags([...tags, selectedTag]) - - console.log("handleOkvalue") + setSelectedTag(null); setIsModalVisible(false); } @@ -508,8 +513,9 @@ function Detail() { // 如果点击的是同一个节点,不做任何操作 if (selectedNodeid === node.key) return; const fatherKey = node.key.split('-')[0]; - console.log("fatherKey", fatherKey) setSelectedCategory(productProject[fatherKey]) + + console.log("remainderLanguage",remainderLanguage) let initialQuotationData = null; let infoData = null; let lgcDetailsData = null; @@ -558,30 +564,15 @@ function Detail() { }); }; - - - - - const onSave = (values) => { const tempData = values; tempData['quotation'] = quotation; - tempData['extras'] = bindingData; + // tempData['extras'] = bindingData; // tempData['lgc_details'] = languageStatus; setSaveData(tempData); console.log("保存的数据", tempData) }; - - - - //Effect - - - - - - return (
@@ -590,7 +581,7 @@ function Detail() { ))} - duration + @@ -648,7 +639,8 @@ function Detail() { placeholder="选择语言" optionFilterProp="children" onChange={handleTagChange} - > + value={remainderLanguage.some((item) => item.label === selectedTag) ? selectedTag : undefined} + > { remainderLanguage.map((value, label) => ( @@ -689,6 +681,8 @@ function Detail() { + + @@ -717,6 +711,20 @@ function Detail() { )} + + { + batchImportPriceVisible && ( + setBatchImportPriceVisible(false)} + width="80%" + > + + + ) + }
); } From 1b1db0937d6249d01bc02c43c071575ba053991c Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Tue, 2 Jul 2024 14:06:51 +0800 Subject: [PATCH 068/120] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=A0=87=E7=AD=BE?= =?UTF-8?q?=E5=92=8C=20PUSH=20=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 885d2c2..db85873 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ Global Highlights Hub 海外供应商平台 2. 运行开发环境:npm run dev 或者 start.bat 3. 打包代码:npm run build 或者 build.bat -// "push:tag": "npm version patch && git push origin master", +// "push:tag": "npm version patch && git.exe push --progress "origin" main:main" +// "push:tag": "npm version patch && git push origin master" ## 相关文档 From 8b44f5ce5c5bd52a8646b9ea52ec5b533d515312 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 3 Jul 2024 09:29:13 +0800 Subject: [PATCH 069/120] =?UTF-8?q?feat:=20=E8=BF=81=E7=A7=BB=E7=B1=BB?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E5=88=B0=20Tailwind=20CSS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/global.css | 20 -------------------- src/views/App.jsx | 12 +++++++----- src/views/Standlone.jsx | 9 ++++++--- src/views/reservation/Newest.jsx | 2 +- 4 files changed, 14 insertions(+), 29 deletions(-) diff --git a/src/assets/global.css b/src/assets/global.css index ca0ad13..a31e444 100644 --- a/src/assets/global.css +++ b/src/assets/global.css @@ -1,23 +1,3 @@ @import 'tailwindcss/base'; @import 'tailwindcss/components'; @import 'tailwindcss/utilities'; - -.logo { - float: left; - height: 36px; - margin: 16px 24px 16px 0; - background: rgba(255, 255, 255, 0.3); -} - -.reservation-highlight { - color: rgba(255, 255, 255, 1); - background-color: rgba(255, 0, 0, 0.6); -} - -#error-page { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 100%; -} diff --git a/src/views/App.jsx b/src/views/App.jsx index 2acfb41..82e14b4 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -14,7 +14,7 @@ import ErrorBoundary from '@/components/ErrorBoundary' import { BUILD_VERSION, } from '@/config'; import useNoticeStore from '@/stores/Notice'; import useAuthStore from '@/stores/Auth' -import { fetchUserDetail} from '@/stores/Auth' +import { useThemeContext } from '@/stores/ThemeContext' import { usingStorage } from '@/hooks/usingStorage' import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } from '@/config' @@ -24,7 +24,9 @@ const { Title } = Typography; function App() { - const { t, i18n } = useTranslation(); + const { t, i18n } = useTranslation() + + const { colorPrimary } = useThemeContext() const [password, setPassword] = useState('') @@ -76,7 +78,7 @@ function App() { @@ -105,11 +107,11 @@ function App() { style={{ minHeight: '100vh', }}> -
+
- App logo + App logo @@ -26,10 +29,10 @@ function Standlone() { style={{ minHeight: '100vh', }}> -
+
- App logo + App logo Global Highlights Hubdiff --git a/src/views/reservation/Newest.jsx b/src/views/reservation/Newest.jsx index 9c35592..2e37c5e 100644 --- a/src/views/reservation/Newest.jsx +++ b/src/views/reservation/Newest.jsx @@ -21,7 +21,7 @@ function Newest() { const lastDayjs = dayjs().subtract(1, 'day'); const arrivalDatejs = dayjs(record.arrivalDate); const requiredHighlight = (arrivalDatejs.isAfter(lastDayjs) && arrivalDatejs.isBefore(after3Dayjs, 'day')) && isEmpty(record.guide); - const linkClassName = requiredHighlight ? 'reservation-highlight' : ''; + const linkClassName = requiredHighlight ? 'text-white bg-red-500' : ''; return ( {text} ) From 52fcb20dbfb3a4f2ccef29be526c9e9342648efa Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 3 Jul 2024 09:43:27 +0800 Subject: [PATCH 070/120] =?UTF-8?q?feat:=20App=20Layout=20=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E4=BD=BF=E7=94=A8=20Tailwind=20CSS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/App.jsx | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/views/App.jsx b/src/views/App.jsx index 82e14b4..7fdc984 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -65,10 +65,6 @@ function App() { defaultPath = splitPath[1] } - const { - token: { colorBgContainer }, - } = theme.useToken() - const [antdLng, setAntdLng] = useState(enLocale); useEffect(() => { setAntdLng(i18n.language === 'en' ? enLocale : zhLocale); @@ -104,9 +100,7 @@ function App() { + className='min-h-screen'>
@@ -136,9 +130,9 @@ function App() { /> - + <h3 className='text-white mb-0 flex justify-end'> {currentUser?.travelAgencyName} - + - + {needToLogin ? <>login... : }
China Highlights International Travel Service Co., LTD, Version: {BUILD_VERSION}
From e68e0ddd7dc5962759de8b7c1dcd2ad11bacd69b Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 3 Jul 2024 10:06:14 +0800 Subject: [PATCH 071/120] =?UTF-8?q?feat:=20=E5=9B=A2=E8=AE=A1=E5=88=92?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=20Tailwind=20CSS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/zh/account.json | 1 + src/views/account/Management.jsx | 10 ++++----- src/views/reservation/Detail.jsx | 36 +++++++++++++++++--------------- src/views/reservation/Newest.jsx | 19 +++++++---------- 4 files changed, 32 insertions(+), 34 deletions(-) diff --git a/public/locales/zh/account.json b/public/locales/zh/account.json index 3849f07..a1d78c4 100644 --- a/public/locales/zh/account.json +++ b/public/locales/zh/account.json @@ -23,6 +23,7 @@ "detail": "详细信息", "username": "用户名", "realname": "姓名", + "travelAgency": "供应商", "travelAgencyName": "供应商名称", "email": "邮箱地址", "lastLogin": "最后登陆时间", diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 7e0d236..4a32a66 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -23,14 +23,14 @@ function Management() { title: t('account:realname'), dataIndex: 'realname', }, - { - title: t('account:travelAgencyName'), - dataIndex: 'travelAgencyName', - }, { title: t('account:email'), dataIndex: 'email', }, + { + title: t('account:travelAgency'), + dataIndex: 'travelAgencyName', + }, { title: t('account:roleName'), dataIndex: 'role' @@ -283,7 +283,7 @@ function Management() { +
{formattedText}
); @@ -56,7 +56,7 @@ function Detail() { <> {confirm.attachmentList.map(attch => { return ( - }>{attch.file_name} + }>{attch.file_name} )} )} @@ -65,7 +65,7 @@ function Detail() { function confirmRender(text, confirm) { return ( - + ); } @@ -96,7 +96,7 @@ function Detail() { const showConfirmModal = (confirm) => { setIsModalOpen(true); - const formattedText = confirm.PCI_ConfirmText;//.replace(/\;/g, "\n——————————————————————\n"); + const formattedText = confirm.PCI_ConfirmText;//.replace(/\;/g, '\n——————————————————————\n'); setConfirmText(formattedText); selectConfirmation(confirm); }; @@ -140,7 +140,7 @@ function Detail() { {t('group:ConfirmationDetails')}
-
+
{confirmText}
@@ -155,7 +155,7 @@ function Detail() { }} /> - +
{t('group:RefNo')}: {reservationDetail.referenceNumber}; {t('group:ArrivalDate')}: {reservationDetail.arrivalDate}; @@ -165,17 +165,19 @@ function Detail() { - - + + - - + + - +
(isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD')), + render: (text) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD')), }, { title: t('group:Pax'), @@ -43,7 +43,7 @@ function Newest() { { title: t('group:ResSendingDate'), dataIndex: 'reservationDate', - render: (text, record) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD')), + render: (text) => (isEmpty(text) ? '' : dayjs(text).format('YYYY-MM-DD')), }, { title: t('group:Guide'), @@ -52,7 +52,7 @@ function Newest() { }, ]; - function guideRender(text, reservation) { + function guideRender(_, reservation) { if (reservation.guide === '') { return ( @@ -69,13 +69,11 @@ function Newest() { } } - function cityGuideRender(text, city) { + function cityGuideRender(_, city) { return (
- + Date: Wed, 3 Jul 2024 10:39:47 +0800 Subject: [PATCH 072/120] =?UTF-8?q?feat:=20=E5=90=8C=E6=AD=A5=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E6=9D=83=E9=99=90=EF=BC=8C=E9=81=BF=E5=85=8D=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E5=90=8E=E8=B7=AF=E7=94=B1=E6=B2=A1=E6=9C=89=E6=9D=83?= =?UTF-8?q?=E9=99=90=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 4 ++-- src/stores/Auth.js | 15 ++++++++------- src/utils/lifecycle.js | 13 ++++++++----- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/main.jsx b/src/main.jsx index 2d3c53f..1bf374b 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -31,8 +31,8 @@ import Airticket from '@/views/airticket/Index' import AirticketPlan from '@/views/airticket/Plan' import { ThemeContext } from '@/stores/ThemeContext' import { usingStorage } from '@/hooks/usingStorage' +import useAuthStore from './stores/Auth' import { isNotEmpty } from '@/utils/commons' -import { notifyAuth } from './utils/lifecycle' import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } from '@/config' @@ -81,7 +81,7 @@ const initAppliction = async () => { const { loginToken, userId } = usingStorage() if (isNotEmpty(userId) && isNotEmpty(loginToken)) { - await notifyAuth() + await useAuthStore.getState().initAuth() } const router = await initRouter() diff --git a/src/stores/Auth.js b/src/stores/Auth.js index acfe18f..5f86bab 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -58,22 +58,24 @@ const initialState = { permissionList: [] } -const useAuthStore = create(lifecycleware((set, get) => ({ +const useAuthStore = create((set, get) => ({ ...initialState, - onAuth: async () => { + initAuth: async () => { const { startTokenInterval, loadUserPermission } = get() const { setStorage, loginToken } = usingStorage() - appendRequestParams('token', loginToken) const userJson = await fetchUserDetail(loginToken) + appendRequestParams('token', loginToken) appendRequestParams('lmi_sn', userJson.LMI_SN) setStorage(KEY_USER_ID, userJson.LMI_SN) setStorage(KEY_TRAVEL_AGENCY_ID, userJson.LMI_VEI_SN) + await loadUserPermission(userJson.LMI_SN) + set(() => ({ currentUser: { username: userJson.LoginName, @@ -84,20 +86,19 @@ const useAuthStore = create(lifecycleware((set, get) => ({ } })) - await loadUserPermission(userJson.LMI_SN) startTokenInterval() loadPageSpy(userJson.real_name) }, authenticate: async (usr, pwd) => { - const { onAuth } = get() + const { onRefresh } = get() const { setStorage } = usingStorage() const { token: loginToken } = await fetchLoginToken(usr, pwd) setStorage(KEY_LOGIN_TOKEN, loginToken) - await onAuth() + await initAuth() set(() => ({ tokenTimeout: false, @@ -190,6 +191,6 @@ const useAuthStore = create(lifecycleware((set, get) => ({ }) }, -}))) +})) export default useAuthStore \ No newline at end of file diff --git a/src/utils/lifecycle.js b/src/utils/lifecycle.js index 0d3deb1..3b896db 100644 --- a/src/utils/lifecycle.js +++ b/src/utils/lifecycle.js @@ -10,16 +10,19 @@ export const addAuthLinstener = (fn) => { } export const notifyInit = async () => { - initListener.forEach(async (fn) => { - await fn() - }) + for (const listener of initListener) { + await listener() + } } export const notifyAuth = async (obj) => { - authListener.forEach(async (fn) => await fn(obj)) + for (const listener of authListener) { + await listener(obj) + } } -// Zustand 中间件,用于订阅前端应用的生命周期,实验阶段 +// Zustand 中间件,用于订阅前端应用的生命周期,实验阶段。 +// 失败,无法同步调用异步方法! export const lifecycleware = (fn) => (set, get, store) => { addInitLinstener(() => { From ae5548a4d015dc66df4b1fa002088e5a8d604603 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 3 Jul 2024 10:50:21 +0800 Subject: [PATCH 073/120] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=90=8D=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/Auth.js b/src/stores/Auth.js index 5f86bab..d0f4fa2 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -91,7 +91,7 @@ const useAuthStore = create((set, get) => ({ }, authenticate: async (usr, pwd) => { - const { onRefresh } = get() + const { initAuth } = get() const { setStorage } = usingStorage() const { token: loginToken } = await fetchLoginToken(usr, pwd) From 551b0821689ccd8da9da0b53baee95e233821455 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 3 Jul 2024 10:56:26 +0800 Subject: [PATCH 074/120] =?UTF-8?q?fix:=20=E8=A7=A3=E5=86=B3=E4=BA=A7?= =?UTF-8?q?=E5=93=81=E7=AE=A1=E7=90=86=E8=B7=AF=E7=94=B1=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.jsx b/src/main.jsx index 008ed86..126a393 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -67,6 +67,9 @@ const initRouter = async () => { { path: 'invoice/paid/detail/:flid', element: }, { path: 'airticket',element: }, { path: 'airticket/plan/:coli_sn',element:}, + { path: "products",element: }, + { path: "products/:travel_agency_id/:use_year/:audit_state/audit",element:}, + { path: "products/:travel_agency_id/:use_year/:audit_state/edit",element:}, ] }, { From 78017195223e16f35da0fff0855ad64b7e98d7de Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 3 Jul 2024 11:40:58 +0800 Subject: [PATCH 075/120] =?UTF-8?q?feat:=20=E8=B7=AF=E7=94=B1403=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E8=B4=A6=E5=8F=B7=E5=92=8C=E6=9D=83=E9=99=90=E6=8F=90?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/RequireAuth.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/RequireAuth.jsx b/src/components/RequireAuth.jsx index cabef0b..8b06d3b 100644 --- a/src/components/RequireAuth.jsx +++ b/src/components/RequireAuth.jsx @@ -4,7 +4,7 @@ import useAuthStore from '@/stores/Auth' export default function RequireAuth({ children, ...props }) { - const isPermitted = useAuthStore((state) => state.isPermitted) + const [isPermitted, currentUser] = useAuthStore(state => [state.isPermitted, state.currentUser]) const { userId } = usingStorage() if (isPermitted(props.subject)) { @@ -15,7 +15,7 @@ export default function RequireAuth({ children, ...props }) { ) } From e3038e0a9d2dc1b94d87aa484a9ab1ac9738cab1 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 3 Jul 2024 14:37:35 +0800 Subject: [PATCH 076/120] =?UTF-8?q?fix=EF=BC=9A=E8=A7=A3=E5=86=B3=20Forget?= =?UTF-8?q?=20to=20pass=20form=20prop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/account/Management.jsx | 12 +++++------- src/views/account/RoleList.jsx | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 08cb0b1..bb00571 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -224,17 +224,15 @@ function Management() { }} title={t('account:detail')} open={isAccountModalOpen} onOk={() => setAccountModalOpen(false)} onCancel={() => setAccountModalOpen(false)} - destroyOnClose={true} - clearOnDestroy={true} + destroyOnClose + forceRender modalRender={(dom) => ( - + {t('account:accountList')} { handelAccountSearch() diff --git a/src/views/account/RoleList.jsx b/src/views/account/RoleList.jsx index b4f9f92..cbaf23b 100644 --- a/src/views/account/RoleList.jsx +++ b/src/views/account/RoleList.jsx @@ -161,8 +161,8 @@ function RoleList() { }} title={t('account:detail')} open={isRoleModalOpen} onOk={() => setRoleModalOpen(false)} onCancel={() => setRoleModalOpen(false)} - destroyOnClose={true} - clearOnDestroy={true} + destroyOnClose + forceRender modalRender={(dom) => ( Date: Wed, 3 Jul 2024 14:45:00 +0800 Subject: [PATCH 077/120] =?UTF-8?q?feat:=20=E4=BB=B7=E6=A0=BC=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=A2=9E=E5=8A=A0=E8=B7=AF=E7=94=B1=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 2 +- src/views/account/RoleList.jsx | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main.jsx b/src/main.jsx index 126a393..0beac8c 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -69,7 +69,7 @@ const initRouter = async () => { { path: 'airticket/plan/:coli_sn',element:}, { path: "products",element: }, { path: "products/:travel_agency_id/:use_year/:audit_state/audit",element:}, - { path: "products/:travel_agency_id/:use_year/:audit_state/edit",element:}, + { path: "products/:travel_agency_id/:use_year/:audit_state/edit",element:}, ] }, { diff --git a/src/views/account/RoleList.jsx b/src/views/account/RoleList.jsx index cbaf23b..e5a9d1f 100644 --- a/src/views/account/RoleList.jsx +++ b/src/views/account/RoleList.jsx @@ -212,12 +212,10 @@ function RoleList() { treeCheckable={true} showCheckedStrategy={TreeSelect.SHOW_CHILD} placeholder={'Please select'} - style={{ - width: '100%', - }} /> + className='w-full' /> - + {t('account:roleList')} From 81397e7a68141344c035a74f2b02e26a620fc060 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 3 Jul 2024 14:45:47 +0800 Subject: [PATCH 078/120] =?UTF-8?q?fix:=20=E8=A7=A3=E5=86=B3=E5=B8=B8?= =?UTF-8?q?=E9=87=8F=E6=9C=AA=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.jsx b/src/main.jsx index 0beac8c..cb1f8f5 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -37,7 +37,7 @@ import { isNotEmpty } from '@/utils/commons' import ProductsManage from '@/views/products/Manage'; import ProductsDetail from '@/views/products/Detail'; import ProductsAudit from '@/views/products/Audit'; -import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT } from '@/config' +import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT, PERM_PRODUCTS_OFFER_PUT } from '@/config' import './i18n' From 3f4fcc378986ffb9191cd2d821b15888b6b2877f Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 3 Jul 2024 15:27:24 +0800 Subject: [PATCH 079/120] =?UTF-8?q?feat:=20=E6=8A=8A=20style=20=E8=BD=AC?= =?UTF-8?q?=E4=B8=BA=20Tailwind=20CSS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.jsx | 4 +++- src/views/App.jsx | 3 +-- src/views/Login.jsx | 8 +++----- src/views/Standlone.jsx | 15 +++------------ src/views/account/ChangePassword.jsx | 6 ++---- src/views/account/RoleList.jsx | 9 ++------- src/views/reservation/Detail.jsx | 4 ++-- 7 files changed, 16 insertions(+), 33 deletions(-) diff --git a/src/main.jsx b/src/main.jsx index cb1f8f5..9129087 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -41,6 +41,8 @@ import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET, import './i18n' +const { createRoot } = ReactDOM + const initRouter = async () => { return createBrowserRouter([ { @@ -92,7 +94,7 @@ const initAppliction = async () => { const router = await initRouter() - ReactDOM.createRoot(document.getElementById('root')).render( + createRoot(document.getElementById('root')).render( // {t('Submit')} - +
diff --git a/src/views/Login.jsx b/src/views/Login.jsx index 1ce2cc3..647a3fb 100644 --- a/src/views/Login.jsx +++ b/src/views/Login.jsx @@ -38,7 +38,7 @@ function Login() { } return ( - + - diff --git a/src/views/Standlone.jsx b/src/views/Standlone.jsx index 771f45c..9c7b20f 100644 --- a/src/views/Standlone.jsx +++ b/src/views/Standlone.jsx @@ -25,28 +25,19 @@ function Standlone() { algorithm: theme.defaultAlgorithm, }}> - +
App logo - Global Highlights Hub +

Global Highlights Hub

- +
diff --git a/src/views/account/ChangePassword.jsx b/src/views/account/ChangePassword.jsx index 11e63e5..9978c07 100644 --- a/src/views/account/ChangePassword.jsx +++ b/src/views/account/ChangePassword.jsx @@ -41,15 +41,13 @@ function ChangePassword() { return ( <> - +
+ src={reservationPreviewUrl} frameBorder='0' className='w-full h-[600px]'> + src={nameCardPreviewUrl} frameBorder='0' className='w-full h-[600px]'> From 9008ae8511295635d526f103ec13cb0a79ca786f Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Thu, 4 Jul 2024 09:17:53 +0800 Subject: [PATCH 080/120] =?UTF-8?q?fix=EF=BC=9A=E7=99=BB=E5=BD=95=E8=B6=85?= =?UTF-8?q?=E6=97=B6=E5=90=8E=E6=B2=A1=E6=9C=89=E7=94=A8=E6=88=B7=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Auth.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/stores/Auth.js b/src/stores/Auth.js index 0b0a211..8b17a0b 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -126,11 +126,16 @@ const useAuthStore = create((set, get) => ({ }, logout: () => { - const { tokenInterval } = get() + const { tokenInterval, currentUser } = get() const { clearStorage } = usingStorage() clearStorage() clearInterval(tokenInterval) - set(initialState) + set(() => ({ + ...initialState, + currentUser: { + username: currentUser.username + } + })) }, startTokenInterval: () => { From dfd7d2a7490a0c50e8b77f1e783db4884c930360 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Thu, 4 Jul 2024 10:20:00 +0800 Subject: [PATCH 081/120] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7=E5=8F=8A=E8=AE=BE=E7=BD=AE=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 13 +++++++++---- package.json | 4 ++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index db85873..38d3a02 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,15 @@ Global Highlights Hub 海外供应商平台 2. 运行开发环境:npm run dev 或者 start.bat 3. 打包代码:npm run build 或者 build.bat -// "push:tag": "npm version patch && git.exe push --progress "origin" main:main" -// "push:tag": "npm version patch && git push origin master" +## 版本设置 +npm version [ | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git] + +npm version premajor --no-git-tag-version +npm version prerelease --no-git-tag-version +npm version patch --no-git-tag-version + +"push:tag": "npm version patch && git.exe push --progress "origin" main:main" +"push:tag": "npm version patch && git push origin master" ## 相关文档 @@ -27,8 +34,6 @@ Bucket 名称:global-highlights-hub Endpoint:oss-cn-hongkong.aliyuncs.com global-highlights-hub.oss-cn-hongkong.aliyuncs.com - - 反馈表测试链接 http://202.103.68.111:5173/feedback/330948 diff --git a/package.json b/package.json index dd39be7..4d5e297 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "global.highlights.hub", + "name": "global-highlights-hub", "private": true, - "version": "2.0.0", + "version": "1.0.0", "type": "module", "scripts": { "dev": "vite", From 7948d16613607fda3d242de5ffa75f5726666a39 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Thu, 4 Jul 2024 10:20:45 +0800 Subject: [PATCH 082/120] 2.0.0-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4d5e297..79be5b1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "global-highlights-hub", "private": true, - "version": "1.0.0", + "version": "2.0.0-0", "type": "module", "scripts": { "dev": "vite", From 71c24be5964e93b1cfde49e236561ff29c6eb09a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=96=87=E5=BC=BA=40HWQ-PC?= Date: Thu, 4 Jul 2024 14:03:48 +0800 Subject: [PATCH 083/120] =?UTF-8?q?1.=E6=80=8E=E5=8A=A0=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=BD=95=E5=85=A5=E4=BB=B7=E6=A0=BC=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/BatchImportPrice.jsx | 243 ------------- src/views/products/Detail.jsx | 27 +- .../products/Detail/BatchImportPrice1.jsx | 324 ++++++++++++++++++ .../products/Detail}/date.jsx | 0 4 files changed, 347 insertions(+), 247 deletions(-) delete mode 100644 src/components/BatchImportPrice.jsx create mode 100644 src/views/products/Detail/BatchImportPrice1.jsx rename src/{components => views/products/Detail}/date.jsx (100%) diff --git a/src/components/BatchImportPrice.jsx b/src/components/BatchImportPrice.jsx deleted file mode 100644 index 0404beb..0000000 --- a/src/components/BatchImportPrice.jsx +++ /dev/null @@ -1,243 +0,0 @@ -import React, { useState } from 'react'; -import { Table, Input, Button, DatePicker, Row, Col, Tag, Select } from 'antd'; - -const { RangePicker } = DatePicker; -const { Option } = Select; -const BatchImportPrice = () => { - const [startDate, setStartDate] = useState(null); - const [endDate, setEndDate] = useState(null); - const [startPeople, setStartPeople] = useState(1); - const [endPeople, setEndPeople] = useState(5); - const [dateRanges, setDateRanges] = useState([]); - const [peopleRanges, setPeopleRanges] = useState([]); - const [tableData, setTableData] = useState([]); - const [currency, setCurrency] = useState('RMB'); // 默认值为 RMB - const [type, setType] = useState('每人'); // 默认值为 每人 - - const handleGenerateTable = () => { - if (dateRanges.length === 0 || peopleRanges.length === 0) return; - - const newData = dateRanges.flatMap(dateRange => { - const { startDate, endDate } = dateRange; - const dates = generateDateRange(startDate, endDate); - const row = { dateRange: `${startDate.format('YYYY-MM-DD')} ~ ${endDate.format('YYYY-MM-DD')}` }; - - peopleRanges.forEach(peopleRangeString => { - const [start, end] = peopleRangeString.split('-').map(Number); - generatePeopleRange(start, end).forEach(person => { - row[`${person}_adultPrice`] = ''; - row[`${person}_childPrice`] = ''; - }); - }); - - dates.forEach(date => { - row[date] = ''; - }); - - return row; - }); - - setTableData(newData); - }; - - const generateDateRange = (start, end) => { - const dates = []; - let currentDate = start.clone(); - - while (currentDate <= end) { - dates.push(currentDate.format('YYYY-MM-DD')); - currentDate = currentDate.add(1, 'day'); - } - - return dates; - }; - - const generatePeopleRange = (start, end) => { - const range = []; - for (let i = start; i <= end; i++) { - range.push(`人等${i}`); - } - return range; - }; - - const handleCellChange = (value, dateRange, peopleRange, type) => { - const newData = [...tableData]; - const rowIndex = newData.findIndex(row => row.dateRange === dateRange); - newData[rowIndex][`${peopleRange}_${type}`] = value; - setTableData(newData); - }; - - const handleAddDateRange = () => { - if (startDate && endDate) { - const newDateRange = { startDate, endDate }; - // 检查是否已经存在相同的日期范围 - const isDateRangeExist = dateRanges.some(range => ( - range.startDate.isSame(startDate, 'day') && range.endDate.isSame(endDate, 'day') - )); - if (!isDateRangeExist) { - setDateRanges([...dateRanges, newDateRange]); - } - } - }; - - const handleAddPeopleRange = () => { - if (startPeople <= endPeople) { - const newPeopleRange = `${startPeople}-${endPeople}`; - // 检查是否已经存在相同的人员范围 - const isPeopleRangeExist = peopleRanges.includes(newPeopleRange); - if (!isPeopleRangeExist) { - setPeopleRanges([...peopleRanges, newPeopleRange]); - } - } - }; - - const handleRemoveTag = (index, type) => { - if (type === 'date') { - setDateRanges(dateRanges.filter((_, i) => i !== index)); - } else { - const removedPeopleRange = peopleRanges[index]; - setPeopleRanges(peopleRanges.filter(range => range !== removedPeopleRange)); - } - setTableData([]); - }; - - - const [adultPrice, setAdultPrice] = useState(''); - const [childPrice, setChildPrice] = useState(''); - - const handleAdultPriceChange = (value, dateRange) => { - const newData = [...tableData]; - const rowIndex = newData.findIndex(row => row.dateRange === dateRange); - newData[rowIndex]['成人价'] = value; - setTableData(newData); - }; - - const handleChildPriceChange = (value, dateRange) => { - const newData = [...tableData]; - const rowIndex = newData.findIndex(row => row.dateRange === dateRange); - newData[rowIndex]['儿童价'] = value; - setTableData(newData); - }; - - const columns = [ - { - title: '日期\\人等', - dataIndex: 'dateRange', - key: 'dateRange', - }, - ...peopleRanges.flatMap(peopleRange => ([ - { - title: peopleRange, - dataIndex: `${peopleRange}_price`, - key: `${peopleRange}_price`, - render: (text, record) => ( -
- handleCellChange(e.target.value, record.dateRange, peopleRange, 'adultPrice')} - placeholder="成人价" - style={{ width: '45%' }} - suffix={`${currency}/${type}`} - /> - handleCellChange(e.target.value, record.dateRange, peopleRange, 'childPrice')} - placeholder="儿童价" - style={{ width: '45%' }} - suffix={`${currency}/${type}`} - /> -
- ), - } - ])), - ]; - - return ( - <> - - -
- - - - - - - - - { - if (dates && dates.length === 2) { - setStartDate(dates[0]); - setEndDate(dates[1]); - } else { - setStartDate(null); - setEndDate(null); - } - }} - /> - - - - - - - setStartPeople(parseInt(e.target.value, 10))} - /> - - setEndPeople(parseInt(e.target.value, 10))} - /> - - - - - - - - - - {dateRanges.map((dateRange, index) => ( - handleRemoveTag(index, 'date')}> - {`${dateRange.startDate.format('YYYY-MM-DD')} ~ ${dateRange.endDate.format('YYYY-MM-DD')}`} - - ))} - {peopleRanges.map((peopleRange, index) => ( - handleRemoveTag(index, 'people')}> - {peopleRange} - - ))} - - -
- - ); -}; - -export default BatchImportPrice; - - - diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx index 57cd8e7..d1b701c 100644 --- a/src/views/products/Detail.jsx +++ b/src/views/products/Detail.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'; import { Button, Card, Col, Row, Breadcrumb, Table, Popconfirm, Form, Input, InputNumber, Tag, Modal, Select, Tree } from 'antd'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import Date from '@/components/date'; +import Date from '@/views/products/Detail/date'; import { searchAgencyAction, getAgencyProductsAction } from '@/stores/Products/Index'; import { useProductsTypes } from '@/hooks/useProductsSets'; import Extras from './Detail/Extras'; @@ -10,7 +10,7 @@ import { groupBy } from '@/utils/commons'; import { useParams } from 'react-router-dom'; import { useHTLanguageSets } from '@/hooks/useHTLanguageSets'; import { useDefaultLgc } from '@/i18n/LanguageSwitcher'; -import BatchImportPrice from '@/components/BatchImportPrice'; +import BatchImportPrice from './Detail/BatchImportPrice1'; function Detail() { const { t } = useTranslation(); const [form] = Form.useForm(); @@ -43,7 +43,10 @@ function Detail() { const [autoExpandParent, setAutoExpandParent] = useState(true); const [dataList, setDataList] = useState([]); const [defaultData, setDefaultData] = useState([]); - + const [batchImportData, setBatchImportData] = useState([]); + const handleBatchImportData = (data) => { + setBatchImportData(data); + }; const productProject = { "6": [], @@ -225,6 +228,7 @@ function Detail() { newData.splice(index, 1, { ...item, ...restRow }); delete newData[index].quotation delete newData[index].extras + console.log("newData",newData) setQuotation(newData); setEditingid(''); } else { @@ -305,6 +309,20 @@ function Detail() { } const handleBatchImportOK = () => { + console.log("quotation",quotation) + console.log('Batch Import Data:', batchImportData); + + + // 创建一个新的数据副本,删除 tag 和 validPeriod 属性 + const tempBatchImportData = batchImportData.map(item => { + const { tag, validPeriod, ...rest } = item; + return rest; // 返回剩余的属性 + }); + // 将剩余的属性添加到 quotation 中 + const newData = [...quotation, ...tempBatchImportData]; + + // 更新状态来更新页面 + setQuotation(newData); setBatchImportPriceVisible(false); } @@ -711,6 +729,7 @@ const handleBatchImportOK = () => { )} + { batchImportPriceVisible && ( @@ -721,7 +740,7 @@ const handleBatchImportOK = () => { onCancel={() => setBatchImportPriceVisible(false)} width="80%" > - + ) } diff --git a/src/views/products/Detail/BatchImportPrice1.jsx b/src/views/products/Detail/BatchImportPrice1.jsx new file mode 100644 index 0000000..27a4c79 --- /dev/null +++ b/src/views/products/Detail/BatchImportPrice1.jsx @@ -0,0 +1,324 @@ +import React, { useState } from 'react'; +import { Button, Card, Checkbox, Col, DatePicker, Form, Input, Row, Select, Space, Tag, Table, InputNumber } from 'antd'; +import { CloseOutlined } from '@ant-design/icons'; +const { Option } = Select; +const { RangePicker } = DatePicker; + +const BatchImportPrice = ({ onBatchImportData }) => { + const [form] = Form.useForm(); + const [tags, setTags] = useState([]); + const [minPeople, setMinPeople] = useState(''); + const [maxPeople, setMaxPeople] = useState(''); + const [checkedDays, setCheckedDays] = useState([]); + const [tableData, setTableData] = useState([]); + const [sendData, setSendData] = useState(null); + + const handleTagClose = (removedTag) => { + setTags(tags.filter(tag => tag !== removedTag)); + }; + + const handleInputConfirm = () => { + if (minPeople && maxPeople) { + const tag = `${minPeople}-${maxPeople}`; + if (tags.indexOf(tag) === -1) { + setTags([...tags, tag]); + } + } + setMinPeople(''); + setMaxPeople(''); + }; + + const handleCheckboxChange = (checkedValues) => { + setCheckedDays(checkedValues); + }; + + const generateTableData = () => { + + + const values = form.getFieldsValue(); + const weekdays = checkedDays.join(','); + let tempSendData = []; + + // 遍历 items + values.items.forEach((item) => { + // 遍历 validPeriods + let tempValidPeriods = [] + item.validPeriods?.forEach((period) => { + const validPeriod = period.validPeriod.map(date => date.format('YYYY-MM-DD')).join('~'); + tempValidPeriods.push(validPeriod) + // 更新 tempSendData 中每一个 tag 的值 + }); + + let tempData = [] + const unit_name = item.type + const currency = item.currency + tags.forEach((tag) => { + tempValidPeriods.forEach(validPeriod => { + const group_size_min = tag.split('-')[0] + const group_size_max = tag.split('-')[1] + let unit_id = null + const use_dates_start = validPeriod.split('~')[0] + const use_dates_end = validPeriod.split('~')[1] + if(unit_name === "每人"){ + unit_id = 0 + }else{ + unit_id = 1 + } + tempData.push({group_size_min, group_size_max,validPeriod, unit_id,unit_name,use_dates_start,use_dates_end, currency, weekdays,tag}) + }); + }) + tempSendData.push(...tempData) + + }); + + // 设置最终的发送数据 + setSendData([...tempSendData]); // 使用展开操作符确保传递给 setSendData 的是一个新对象 + const data = []; + values.items.forEach((item, index) => { + item.validPeriods?.forEach((period, idx) => { + const row = { + key: `${index}-${idx}`, + priceType: `批量设置价格 ${index + 1} ${item.currency}/${item.type}`, + validPeriod: period.validPeriod.map(date => date.format('YYYY-MM-DD')).join('~'), + currency: item.currency, + type: item.type, + }; + tags.forEach((tag, tagIndex) => { + row[`adultPrice${tagIndex + 1}`] = 0; // Initialize with 0 + row[`childrenPrice${tagIndex + 1}`] = 0; // Initialize with 0 + }); + data.push(row); + }); + }); + setTableData(data); + onBatchImportData(data); // 将生成的初始表格数据传递回父组件 + }; + + + const handleTableChange = (age_type, value, tag, validPeriod) => { + if (age_type === 'adult_cost') { + const updatedSendData = sendData.map((item) => { + if (item.validPeriod === validPeriod && item.tag === tag) { + return { + ...item, + adult_cost: value, // 更新对应的 adult_cost 属性 + }; + } + return item; // 对于不匹配的项,保持不变 + }); + // 更新 sendDataole + onBatchImportData(updatedSendData); + setSendData(updatedSendData); + } else { + const updatedSendData = sendData.map((item) => { + if (item.validPeriod === validPeriod && item.tag === tag) { + return { + ...item, + child_cost: value, // 更新对应的 child_cost 属性 + }; + } + return item; // 对于不匹配的项,保持不变 + }); + // 更新 sendData + onBatchImportData(updatedSendData); + setSendData(updatedSendData); + } + + }; + + const generatePeopleColumns = () => { + const columns = []; + tags.forEach((tag, index) => { + columns.push({ + title: tag, + children: [ + { + title: '成人价', + dataIndex: `adultPrice${index + 1}`, + key: `adultPrice${index + 1}`, + render: (text, record) => ( + `${value}`} + parser={value => value.replace(/[^\d]/g, '')} + onChange={(value) => handleTableChange('adult_cost', value, tag, record.validPeriod)} + /> + ), + }, + { + title: '儿童价', + dataIndex: `childrenPrice${index + 1}`, + key: `childrenPrice${index + 1}`, + render: (text, record) => ( + `${value}`} + parser={value => value.replace(/[^\d]/g, '')} + onChange={(value) => handleTableChange('child_cost', value, tag, record.validPeriod)} + /> + ), + } + ] + }); + }); + return columns; + }; + + const columns = [ + { + title: ' ', + dataIndex: 'priceType', + key: 'priceType', + width: "10%", + render: (text, record, index) => { + const obj = { + children: text, + props: {}, + }; + if (index > 0 && text === tableData[index - 1].priceType) { + obj.props.rowSpan = 0; + } else { + obj.props.rowSpan = tableData.filter(item => item.priceType === text).length; + } + return obj; + }, + }, + { + title: '有效期\\人等', + dataIndex: 'validPeriod', + key: 'validPeriod', + width: "15%" + }, + ...generatePeopleColumns(), + ]; + + return ( +
+ + +
+ + setMinPeople(e.target.value)} + /> + + setMaxPeople(e.target.value)} + /> + + + + + + + +
+ {tags.map((tag) => ( + handleTagClose(tag)}> + {tag} + + ))} +
+ + + + + + + {(fields, { add, remove }) => ( +
+ {fields.map((field, index) => ( + remove(field.name)} />} + > + + + + + + + + + + + {(periodFields, periodOpt) => ( +
+ {periodFields.map((periodField, idx) => ( + + + + + periodOpt.remove(periodField.name)} /> + + ))} + +
+ )} +
+
+
+ ))} + +
+ )} +
+ + + + + {tableData.length > 0 && ( +
+
+ + )} + + ); +}; + +export default BatchImportPrice; diff --git a/src/components/date.jsx b/src/views/products/Detail/date.jsx similarity index 100% rename from src/components/date.jsx rename to src/views/products/Detail/date.jsx From 4bdf085a350b9b625e2b0a56cdc6ac615504a050 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Thu, 4 Jul 2024 14:12:09 +0800 Subject: [PATCH 084/120] =?UTF-8?q?feat:=20=E5=BC=80=E5=8F=91=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E4=B8=8D=E5=8A=A0=E8=BD=BD=20PageSpy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +++++- src/pageSpy/index.jsx | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 38d3a02..1f070c7 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,12 @@ Global Highlights Hub 海外供应商平台 npm version [ | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git] npm version premajor --no-git-tag-version -npm version prerelease --no-git-tag-version +1.0.0 -> 2.0.0-0 +--preid beta | alpha | rc +npm version prerelease --preid beta --no-git-tag-version +2.0.0-0 -> 2.0.0-1 -> 2.0.0-2 ..n -> 2.0.0-n npm version patch --no-git-tag-version +2.0.0-n -> 2.0.0 "push:tag": "npm version patch && git.exe push --progress "origin" main:main" "push:tag": "npm version patch && git push origin master" diff --git a/src/pageSpy/index.jsx b/src/pageSpy/index.jsx index 0bfcd65..10c4c03 100644 --- a/src/pageSpy/index.jsx +++ b/src/pageSpy/index.jsx @@ -3,7 +3,7 @@ import { PROJECT_NAME } from '@/config'; export const loadPageSpy = (title) => { - if (window.$pageSpy) return + if (import.meta.env.DEV || window.$pageSpy) return const PageSpySrc = [ 'https://page-spy.mycht.cn/page-spy/index.min.js', From eca38ae6116335aa991cb0fb9f4ec284b6c0522b Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Thu, 4 Jul 2024 14:14:58 +0800 Subject: [PATCH 085/120] 2.0.0-alpha.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 79be5b1..ac1a9b2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "global-highlights-hub", "private": true, - "version": "2.0.0-0", + "version": "2.0.0-alpha.0", "type": "module", "scripts": { "dev": "vite", From 4fb6273ed10c01acb04c033b48df241d0289ff60 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Thu, 4 Jul 2024 14:16:17 +0800 Subject: [PATCH 086/120] # --- src/views/products/Detail.jsx | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx index 57cd8e7..1b8ba14 100644 --- a/src/views/products/Detail.jsx +++ b/src/views/products/Detail.jsx @@ -307,7 +307,7 @@ function Detail() { const handleBatchImportOK = () => { setBatchImportPriceVisible(false); } - + const EditableCell = ({ editing, dataIndex, title, inputType, record, children, handleDateSelect, ...restProps }) => { let inputNode = inputType === 'number' ? : ; @@ -422,14 +422,14 @@ const handleBatchImportOK = () => { const editable = isEditing(record); return editable ? ( - handleSave(record.id)} style={{ marginRight: 8 }}>{t('products:save')} - {t('products:cancel')} + handleSave(record.id)} style={{ marginRight: 8 }}>{t('Save')} + {t('Cancel')} ) : ( - edit(record)} style={{ marginRight: 8 }}>{t('products:edit')} - handleDelete(record.id)}> - {t('products:delete')} + edit(record)} style={{ marginRight: 8 }}>{t('Edit')} + handleDelete(record.id)}> + {t('Delete')} ); @@ -514,7 +514,7 @@ const handleBatchImportOK = () => { if (selectedNodeid === node.key) return; const fatherKey = node.key.split('-')[0]; setSelectedCategory(productProject[fatherKey]) - + console.log("remainderLanguage",remainderLanguage) let initialQuotationData = null; let infoData = null; @@ -596,16 +596,16 @@ const handleBatchImportOK = () => { style={{ width: "80%" }} title={ 供应商 }, + // { title: 供应商 }, { title: 综费 }, { title: '文章列表' } ]} /> } > -

{t('products:productProject')}

+

{t('products:EditComponents.info')}

{selectedCategory.map((item, index) => ( -
+ {item.code === "duration" ? ( @@ -621,6 +621,7 @@ const handleBatchImportOK = () => {
{tags.map(tag => ( handleTagClick(tag)} color={tag === selectedTag ? 'blue' : undefined} @@ -681,7 +682,7 @@ const handleBatchImportOK = () => { - + @@ -692,7 +693,7 @@ const handleBatchImportOK = () => { + + {/* {t('Cancel')} */} ) : ( @@ -534,6 +540,8 @@ const handleBatchImportOK = () => { setSelectedCategory(productProject[fatherKey]) console.log("remainderLanguage",remainderLanguage) + setEditingProduct(node._raw); + let initialQuotationData = null; let infoData = null; let lgcDetailsData = null; @@ -594,8 +602,8 @@ const handleBatchImportOK = () => { return (
-
- + + { 供应商 }, { title: 综费 }, - { title: '文章列表' } + { title: editingProduct?.info?.title || t('New') } ]} /> } > @@ -624,7 +632,7 @@ const handleBatchImportOK = () => { {selectedCategory.map((item, index) => ( - + {item.code === "duration" ? ( ) : ( @@ -684,12 +692,12 @@ const handleBatchImportOK = () => { /> - + {/* */} - + {/* */}

{t('products:supplierQuotation')}

-
{ - - - - + + + + + {/* */} + + {/* */} - - - @@ -730,7 +738,7 @@ const handleBatchImportOK = () => { )} - + { batchImportPriceVisible && ( From 06db76ce86e1e6758890392bbf7d47e8dc1a31b8 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Thu, 4 Jul 2024 17:18:16 +0800 Subject: [PATCH 089/120] =?UTF-8?q?=E5=90=88=E5=B9=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/products.json | 2 +- public/locales/zh/products.json | 2 +- src/main.jsx | 1 + src/views/products/Manage.jsx | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 7380d76..03bdb46 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -13,7 +13,7 @@ "EditComponents": { "info": "Product Information", "Quotation": "Quotation", - "Extras": "Add-on" + "Extras": "Components" }, "auditState": { "New": "New", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index c0cd87e..dd07133 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -13,7 +13,7 @@ "EditComponents": { "info": "产品信息", "Quotation": "报价", - "Extras": "附加项目" + "Extras": "绑定项目" }, "auditState": { "New": "新增", diff --git a/src/main.jsx b/src/main.jsx index 9129087..ec6f14b 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -72,6 +72,7 @@ const initRouter = async () => { { path: "products",element: }, { path: "products/:travel_agency_id/:use_year/:audit_state/audit",element:}, { path: "products/:travel_agency_id/:use_year/:audit_state/edit",element:}, + { path: "products/edit",element:}, ] }, { diff --git a/src/views/products/Manage.jsx b/src/views/products/Manage.jsx index 524a4ea..0a58e1d 100644 --- a/src/views/products/Manage.jsx +++ b/src/views/products/Manage.jsx @@ -77,6 +77,7 @@ function Index() { agency: { col: 4 }, dates: { label: t('products:CreateDate') }, keyword: { label: t('products:Title'), col: 4 }, + year: { col: 4 }, }, sort: { agency: 1, audit_state: 2, keyword: 100 }, }} From f13241f262f2d33d78ba4bda29191dbd52e90181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=96=87=E5=BC=BA=40HWQ-PC?= Date: Fri, 5 Jul 2024 11:10:38 +0800 Subject: [PATCH 090/120] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=BD=95=E5=85=A5=E7=95=8C=E9=9D=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/products/Detail.jsx | 80 +++++++++---- .../products/Detail/BatchImportPrice1.jsx | 112 +++++++++++++----- .../{date.jsx => addValidityWithWeekend.jsx} | 6 +- 3 files changed, 140 insertions(+), 58 deletions(-) rename src/views/products/Detail/{date.jsx => addValidityWithWeekend.jsx} (93%) diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx index d1b701c..61d2cfa 100644 --- a/src/views/products/Detail.jsx +++ b/src/views/products/Detail.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'; import { Button, Card, Col, Row, Breadcrumb, Table, Popconfirm, Form, Input, InputNumber, Tag, Modal, Select, Tree } from 'antd'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import Date from '@/views/products/Detail/date'; +import AddValidityWithWeekend from '@/views/products/Detail/addValidityWithWeekend'; import { searchAgencyAction, getAgencyProductsAction } from '@/stores/Products/Index'; import { useProductsTypes } from '@/hooks/useProductsSets'; import Extras from './Detail/Extras'; @@ -91,6 +91,10 @@ function Detail() { { code: "open_weekdays", name: t('products:OpenWeekdays') }, { code: "remarks", name: t('products:Remarks') }, ], + "8": [ + { code: "code", name: t('products:Code') }, + { code: "city_name", name: t('products:City') }, + ], "R": [ { code: "code", name: t('products:Code') }, { code: "city_name", name: t('products:City') }, @@ -102,7 +106,6 @@ function Detail() { setLanguageStatus(language); const matchedLanguage = HTLanguageSets.find(HTLanguage => HTLanguage.key === language.toString()); const languageLabel = matchedLanguage.label - // setTags([languageLabel]) setLanguageLabel(languageLabel) setSelectedTag(languageLabel) setRemainderLanguage(HTLanguageSets.filter(item => item.key !== language.toString())) @@ -130,6 +133,7 @@ function Detail() { }; const treeData = generateTreeData(productsTypes, groupedProducts); + console.log("treeData", treeData) setTreeData(treeData); setProductsData(groupedProducts); setDefaultData(treeData); @@ -228,7 +232,7 @@ function Detail() { newData.splice(index, 1, { ...item, ...restRow }); delete newData[index].quotation delete newData[index].extras - console.log("newData",newData) + console.log("newData", newData) setQuotation(newData); setEditingid(''); } else { @@ -308,10 +312,10 @@ function Detail() { setDatePickerVisible(false); } -const handleBatchImportOK = () => { - console.log("quotation",quotation) - console.log('Batch Import Data:', batchImportData); - + const handleBatchImportOK = () => { + console.log("quotation", quotation) + console.log('Batch Import Data:', batchImportData); + // 创建一个新的数据副本,删除 tag 和 validPeriod 属性 const tempBatchImportData = batchImportData.map(item => { @@ -321,11 +325,11 @@ const handleBatchImportOK = () => { // 将剩余的属性添加到 quotation 中 const newData = [...quotation, ...tempBatchImportData]; - // 更新状态来更新页面 - setQuotation(newData); - setBatchImportPriceVisible(false); -} - + // 更新状态来更新页面 + setQuotation(newData); + setBatchImportPriceVisible(false); + } + const EditableCell = ({ editing, dataIndex, title, inputType, record, children, handleDateSelect, ...restProps }) => { let inputNode = inputType === 'number' ? : ; @@ -494,11 +498,24 @@ const handleBatchImportOK = () => { if (!selectedTag) return; if (!remainderLanguage.some(item => item.label === selectedTag)) return; if (remainderLanguage.includes(selectedTag)) return; - let tempRemainderLanguage = remainderLanguage.filter((item)=>{ + let tempRemainderLanguage = remainderLanguage.filter((item) => { return item.label !== selectedTag; }) + console.log("AAAA", lgc_details) + console.log("selectTag", selectedTag) + + const matchedLanguage = HTLanguageSets.find(HTLanguage => HTLanguage.label === selectedTag); + const languageKey = matchedLanguage.key + const tempLgc_details = { + ...lgc_details, [languageKey]: { + title: {}, + } + } + console.log("languageKey", languageKey) + setRemainderLanguage(tempRemainderLanguage) setTags([...tags, selectedTag]) + setSelectedTag(null); setIsModalVisible(false); } @@ -532,8 +549,16 @@ const handleBatchImportOK = () => { if (selectedNodeid === node.key) return; const fatherKey = node.key.split('-')[0]; setSelectedCategory(productProject[fatherKey]) - - console.log("remainderLanguage",remainderLanguage) + + + setLanguageStatus(language); + const matchedLanguage = HTLanguageSets.find(HTLanguage => HTLanguage.key === language.toString()); + const languageLabelRefresh = matchedLanguage.label + setLanguageLabel(languageLabelRefresh) + setSelectedTag(languageLabelRefresh) + setRemainderLanguage(HTLanguageSets.filter(item => item.key !== language.toString())) + + let initialQuotationData = null; let infoData = null; let lgcDetailsData = null; @@ -547,7 +572,6 @@ const handleBatchImportOK = () => { }); console.log("infoData", infoData) - // 累积 lgc_details 数据 let newLgcDetails = {}; lgcDetailsData.forEach(element => { @@ -559,7 +583,6 @@ const handleBatchImportOK = () => { setQuotation(initialQuotationData); - console.log("descriptions", lgc_details) // 使用 setTimeout 确保 lgc_details 已经更新 form.setFieldsValue({ info: { @@ -576,6 +599,7 @@ const handleBatchImportOK = () => { dept_name: infoData.dept_name }, lgc_details: { + lgc: language, title: newLgcDetails[language]?.title || '', descriptions: newLgcDetails[language]?.descriptions || '' } @@ -585,8 +609,6 @@ const handleBatchImportOK = () => { const onSave = (values) => { const tempData = values; tempData['quotation'] = quotation; - // tempData['extras'] = bindingData; - // tempData['lgc_details'] = languageStatus; setSaveData(tempData); console.log("保存的数据", tempData) }; @@ -623,10 +645,16 @@ const handleBatchImportOK = () => {

{t('products:productProject')}

{selectedCategory.map((item, index) => ( -
+ {item.code === "duration" ? ( - + + ) : (item.code === "display_to_c") ? ( + ) : ( )} @@ -658,7 +686,7 @@ const handleBatchImportOK = () => { optionFilterProp="children" onChange={handleTagChange} value={remainderLanguage.some((item) => item.label === selectedTag) ? selectedTag : undefined} - > + > { remainderLanguage.map((value, label) => ( @@ -699,7 +727,7 @@ const handleBatchImportOK = () => { - + @@ -726,10 +754,10 @@ const handleBatchImportOK = () => { onOk={handleDateOk} onCancel={() => setDatePickerVisible(false)} > - + )} - + { batchImportPriceVisible && ( @@ -740,7 +768,7 @@ const handleBatchImportOK = () => { onCancel={() => setBatchImportPriceVisible(false)} width="80%" > - + ) } diff --git a/src/views/products/Detail/BatchImportPrice1.jsx b/src/views/products/Detail/BatchImportPrice1.jsx index 27a4c79..906b68f 100644 --- a/src/views/products/Detail/BatchImportPrice1.jsx +++ b/src/views/products/Detail/BatchImportPrice1.jsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { Button, Card, Checkbox, Col, DatePicker, Form, Input, Row, Select, Space, Tag, Table, InputNumber } from 'antd'; import { CloseOutlined } from '@ant-design/icons'; +import dayjs from 'dayjs'; const { Option } = Select; const { RangePicker } = DatePicker; @@ -13,6 +14,11 @@ const BatchImportPrice = ({ onBatchImportData }) => { const [tableData, setTableData] = useState([]); const [sendData, setSendData] = useState(null); + // 获取当前年份 + const currentYear = new Date().getFullYear(); + const startOfYear = dayjs(new Date(currentYear, 0, 1)); + const endOfYear = dayjs(new Date(currentYear, 11, 31)); + const handleTagClose = (removedTag) => { setTags(tags.filter(tag => tag !== removedTag)); }; @@ -33,22 +39,21 @@ const BatchImportPrice = ({ onBatchImportData }) => { }; const generateTableData = () => { - - const values = form.getFieldsValue(); const weekdays = checkedDays.join(','); let tempSendData = []; - + console.log("values",values) // 遍历 items - values.items.forEach((item) => { + values.items.forEach((item, index) => { // 遍历 validPeriods let tempValidPeriods = [] item.validPeriods?.forEach((period) => { + console.log("period",period) const validPeriod = period.validPeriod.map(date => date.format('YYYY-MM-DD')).join('~'); tempValidPeriods.push(validPeriod) // 更新 tempSendData 中每一个 tag 的值 }); - + const priceType = `批量设置价格 ${index + 1} ${item.currency}/${item.type}` let tempData = [] const unit_name = item.type const currency = item.currency @@ -59,14 +64,15 @@ const BatchImportPrice = ({ onBatchImportData }) => { let unit_id = null const use_dates_start = validPeriod.split('~')[0] const use_dates_end = validPeriod.split('~')[1] - if(unit_name === "每人"){ + if (unit_name === "每人") { unit_id = 0 - }else{ + } else { unit_id = 1 } - tempData.push({group_size_min, group_size_max,validPeriod, unit_id,unit_name,use_dates_start,use_dates_end, currency, weekdays,tag}) + tempData.push({ group_size_min, group_size_max, validPeriod, unit_id, unit_name, use_dates_start, use_dates_end, currency, weekdays, tag, priceType }) }); }) + console.log("tempData", tempData) tempSendData.push(...tempData) }); @@ -90,15 +96,21 @@ const BatchImportPrice = ({ onBatchImportData }) => { data.push(row); }); }); + // setSendData([...tempSendData,data]); setTableData(data); - onBatchImportData(data); // 将生成的初始表格数据传递回父组件 + // onBatchImportData(data); // 将生成的初始表格数据传递回父组件 }; - const handleTableChange = (age_type, value, tag, validPeriod) => { + + const handleTableChange = (age_type, value, tag, priceType) => { if (age_type === 'adult_cost') { + console.log("sendData", sendData) const updatedSendData = sendData.map((item) => { - if (item.validPeriod === validPeriod && item.tag === tag) { + console.log("item.priceType === priceType", item.priceType === priceType) + console.log("item.priceType", item.priceType) + console.log("priceType", priceType) + if (item.priceType === priceType && item.tag === tag) { return { ...item, adult_cost: value, // 更新对应的 adult_cost 属性 @@ -106,12 +118,13 @@ const BatchImportPrice = ({ onBatchImportData }) => { } return item; // 对于不匹配的项,保持不变 }); - // 更新 sendDataole + // 更新 sendData + console.log("updatedSendData", updatedSendData) onBatchImportData(updatedSendData); setSendData(updatedSendData); } else { const updatedSendData = sendData.map((item) => { - if (item.validPeriod === validPeriod && item.tag === tag) { + if (item.priceType === priceType && item.tag === tag) { return { ...item, child_cost: value, // 更新对应的 child_cost 属性 @@ -123,9 +136,9 @@ const BatchImportPrice = ({ onBatchImportData }) => { onBatchImportData(updatedSendData); setSendData(updatedSendData); } - }; + const generatePeopleColumns = () => { const columns = []; tags.forEach((tag, index) => { @@ -136,25 +149,61 @@ const BatchImportPrice = ({ onBatchImportData }) => { title: '成人价', dataIndex: `adultPrice${index + 1}`, key: `adultPrice${index + 1}`, - render: (text, record) => ( - `${value}`} - parser={value => value.replace(/[^\d]/g, '')} - onChange={(value) => handleTableChange('adult_cost', value, tag, record.validPeriod)} - /> - ), + render: (text, record, rowIndex) => { + const sameTagRecords = tableData.filter(item => item.priceType === record.priceType); + const firstTagIndex = tableData.findIndex(item => item.priceType === record.priceType && item.validPeriod === sameTagRecords[0].validPeriod); + + if (rowIndex === firstTagIndex) { + return { + children: ( + `${value}`} + parser={value => value.replace(/[^\d]/g, '')} + onChange={(value) => handleTableChange('adult_cost', value, tag, record.priceType)} + /> + ), + props: { + rowSpan: sameTagRecords.length, + }, + }; + } else { + return { + props: { + rowSpan: 0, + }, + }; + } + }, }, { title: '儿童价', dataIndex: `childrenPrice${index + 1}`, key: `childrenPrice${index + 1}`, - render: (text, record) => ( - `${value}`} - parser={value => value.replace(/[^\d]/g, '')} - onChange={(value) => handleTableChange('child_cost', value, tag, record.validPeriod)} - /> - ), + render: (text, record, rowIndex) => { + const sameTagRecords = tableData.filter(item => item.priceType === record.priceType); + const firstTagIndex = tableData.findIndex(item => item.priceType === record.priceType && item.validPeriod === sameTagRecords[0].validPeriod); + + if (rowIndex === firstTagIndex) { + return { + children: ( + `${value}`} + parser={value => value.replace(/[^\d]/g, '')} + onChange={(value) => handleTableChange('child_cost', value, tag, record.priceType)} + /> + ), + props: { + rowSpan: sameTagRecords.length, + }, + }; + } else { + return { + props: { + rowSpan: 0, + }, + }; + } + }, } ] }); @@ -162,6 +211,7 @@ const BatchImportPrice = ({ onBatchImportData }) => { return columns; }; + const columns = [ { title: ' ', @@ -280,8 +330,10 @@ const BatchImportPrice = ({ onBatchImportData }) => {
{periodFields.map((periodField, idx) => ( - - + + periodOpt.remove(periodField.name)} /> diff --git a/src/views/products/Detail/date.jsx b/src/views/products/Detail/addValidityWithWeekend.jsx similarity index 93% rename from src/views/products/Detail/date.jsx rename to src/views/products/Detail/addValidityWithWeekend.jsx index ecc674f..0ad9a74 100644 --- a/src/views/products/Detail/date.jsx +++ b/src/views/products/Detail/addValidityWithWeekend.jsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { DatePicker, Button } from 'antd'; -const Date = ({ onDateChange }) => { +const addValidityWithWeekend = ({ onDateChange }) => { const dateFormat = 'YYYY/MM/DD'; const { RangePicker } = DatePicker; const [dateRange, setDateRange] = useState(null); @@ -17,6 +17,8 @@ const Date = ({ onDateChange }) => { onDateChange({ dateRange: range, selectedDays }); }; + + const handleDayClick = (day) => { setSelectedDays((prevSelectedDays) => { const updatedDays = prevSelectedDays.includes(day) @@ -48,4 +50,4 @@ const Date = ({ onDateChange }) => { ); }; -export default Date; +export default addValidityWithWeekend; From 70f5332839dd0716e492294c61fb6c6d15eea8a1 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Fri, 5 Jul 2024 16:30:31 +0800 Subject: [PATCH 091/120] =?UTF-8?q?feat:=20=E7=99=BB=E9=99=86=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E5=A2=9E=E5=8A=A0=E7=89=88=E6=9C=AC=E5=8F=B7=EF=BC=9B?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=95=8C=E9=9D=A2=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/App.jsx | 1 - src/views/Login.jsx | 9 +++------ src/views/Standlone.jsx | 14 +++++--------- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/views/App.jsx b/src/views/App.jsx index e31a19d..a9c4937 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -38,7 +38,6 @@ function App() { const noticeUnRead = useNoticeStore((state) => state.noticeUnRead) const href = useHref() const navigate = useNavigate() - const location = useLocation() // 除了路由 /p...以外都需要登陆系统 const needToLogin = href !== '/login' && isEmpty(loginToken) diff --git a/src/views/Login.jsx b/src/views/Login.jsx index 647a3fb..43119f5 100644 --- a/src/views/Login.jsx +++ b/src/views/Login.jsx @@ -40,14 +40,15 @@ function Login() { return (
+ App logo -

Global Highlights Hub

+

Global Highlights Hub

@@ -40,7 +36,7 @@ function Standlone() { -
+
China Highlights International Travel Service Co., LTD, Version: {BUILD_VERSION}
From 4350f37a6a92e94f19755b79a04b4db3be9938dc Mon Sep 17 00:00:00 2001 From: Lei OT Date: Mon, 8 Jul 2024 11:03:22 +0800 Subject: [PATCH 092/120] =?UTF-8?q?=E8=B6=85=E5=85=AC=E9=87=8C=E4=B8=8D?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E5=85=AC=E9=87=8C=E6=95=B0=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/products/Audit.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index d9ec03d..69f4722 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -98,7 +98,7 @@ const PriceTable = ({ productType, dataSource, refresh }) => { const title = text || r.lgc_details?.['2']?.title || r.lgc_details?.['1']?.title || ''; return setEditingProduct(r.info)}>{title}; } }, - ...(productType === 'B' ? [{ key: 'km', dataIndex: ['info', 'km'], title: t('KM')}] : []), + // ...(productType === 'B' ? [{ key: 'km', dataIndex: ['info', 'km'], title: t('KM')}] : []), { key: 'adult', title: t('AgeType.Adult'), render: (_, { adult_cost, currency, unit_id, unit_name }) => `${adult_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}` }, { key: 'child', title: t('AgeType.Child'), render: (_, { child_cost, currency, unit_id, unit_name }) => `${child_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}` }, // {key: 'unit', title: t('Unit'), }, From 67a21477c6e21085f7e215cc5eb2624f94191ca8 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Mon, 8 Jul 2024 11:26:41 +0800 Subject: [PATCH 093/120] =?UTF-8?q?=E7=BB=91=E5=AE=9A=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E5=8F=AA=E6=98=BE=E7=A4=BA=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/products/Detail/Extras.jsx | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/views/products/Detail/Extras.jsx b/src/views/products/Detail/Extras.jsx index e3b162c..e3e16a3 100644 --- a/src/views/products/Detail/Extras.jsx +++ b/src/views/products/Detail/Extras.jsx @@ -43,19 +43,19 @@ const NewAddonModal = ({ onPick, ...props }) => { const searchResultColumns = [ { key: 'ptype', dataIndex: ['info', 'product_type_id'], width: '6rem', title: t('products:ProductType'), render: (text, r) => productsTypesMapVal[text].label }, { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('products:Title') }, - { - title: t('products:price'), - dataIndex: ['quotation', '0', 'adult_cost'], - width: '10rem', - render: (_, { quotation }) => `${quotation[0].adult_cost} ${quotation[0].currency} / ${quotation[0].unit_name}`, - }, + // { + // title: t('products:price'), + // dataIndex: ['quotation', '0', 'adult_cost'], + // width: '10rem', + // render: (_, { quotation }) => `${quotation[0].adult_cost} ${quotation[0].currency} / ${quotation[0].unit_name}`, + // }, { key: 'action', title: '', width: 150, render: (_, record) => ( ), }, @@ -141,13 +141,13 @@ const Extras = ({ productId, onChange, ...props }) => { const columns = [ { title: t('products:Title'), dataIndex: ['info', 'title'], width: '16rem', }, - { - title: t('products:Offer'), - dataIndex: ['quotation', '0', 'value'], - width: '10rem', + // { + // title: t('products:Offer'), + // dataIndex: ['quotation', '0', 'value'], + // width: '10rem', - render: (_, { quotation }) => `${quotation[0].adult_cost} ${quotation[0].currency} / ${quotation[0].unit_name}`, // todo: 成人 儿童 - }, + // render: (_, { quotation }) => `${quotation[0].adult_cost} ${quotation[0].currency} / ${quotation[0].unit_name}`, + // }, // { title: t('products:Types'), dataIndex: 'age_type', width: '40%', }, { title: '', From 5baeb5162f7769e9affe2d989f815e3f931784a1 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Mon, 8 Jul 2024 11:27:06 +0800 Subject: [PATCH 094/120] style: primary, danger, muted --- src/hooks/useProductsSets.js | 4 ++-- tailwind.config.js | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index 888ad43..6f7fbe5 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -75,10 +75,10 @@ export const useProductsAuditStates = () => { useEffect(() => { const newData = [ - { key: '-1', value: '-1', label: t('products:auditState.New'), color: 'gray-500' }, + { key: '-1', value: '-1', label: t('products:auditState.New'), color: 'muted' }, { key: '0', value: '0', label: t('products:auditState.Pending'), color: '' }, { key: '2', value: '2', label: t('products:auditState.Approved'), color: 'primary' }, - { key: '3', value: '3', label: t('products:auditState.Rejected'), color: 'red-500' }, + { key: '3', value: '3', label: t('products:auditState.Rejected'), color: 'danger' }, { key: '1', value: '1', label: t('products:auditState.Published'), color: 'primary' }, // ELSE 未知 ]; diff --git a/tailwind.config.js b/tailwind.config.js index 9af3619..e4494b9 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -7,6 +7,8 @@ export default { colors: { ...colors, 'primary': '#00b96b', + 'danger': '#ef4444', + 'muted': '#6b7280', }, extend: {}, }, From 05a4106fd80ab79b209ab8fddf10e34cf0a56b69 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Mon, 8 Jul 2024 16:41:12 +0800 Subject: [PATCH 095/120] =?UTF-8?q?conf:=20=E5=AE=A1=E6=A0=B8=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/products/Audit.jsx | 46 +++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index 69f4722..9a7333a 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -6,7 +6,10 @@ import SecondHeaderWrapper from '@/components/SecondHeaderWrapper'; import { useTranslation } from 'react-i18next'; import useProductsStore, { postProductsQuoteAuditAction, } from '@/stores/Products/Index'; import { isEmpty } from '@/utils/commons'; +import useAuthStore from '@/stores/Auth'; +import RequireAuth from '@/components/RequireAuth'; // import PrintContractPDF from './PrintContractPDF'; +import { PERM_PRODUCTS_OFFER_AUDIT, PERM_PRODUCTS_OFFER_PUT } from '@/config'; const Header = ({ title, agency, refresh, ...props }) => { const { travel_agency_id, use_year, audit_state } = useParams(); @@ -35,20 +38,32 @@ const Header = ({ title, agency, refresh, ...props }) => { return (
-

{title}{(use_year || '').replace('all', '')}

+

+ {title} + + {(use_year || '').replace('all', '')} +

{/* */} {/* */} - {t('Edit')} - + + + {t('Edit')} + + + + + {/* */} - + + + {/* todo: export, 审核完成之后才能导出 */} {/* */} @@ -59,6 +74,7 @@ const Header = ({ title, agency, refresh, ...props }) => { const PriceTable = ({ productType, dataSource, refresh }) => { const { t } = useTranslation('products'); const { travel_agency_id, use_year, audit_state } = useParams(); + const isPermitted = useAuthStore(state => state.isPermitted); const [loading, activeAgency] = useProductsStore((state) => [state.loading, state.activeAgency]); const [setEditingProduct] = useProductsStore((state) => [state.setEditingProduct]); const { message, notification } = App.useApp(); @@ -96,7 +112,7 @@ const PriceTable = ({ productType, dataSource, refresh }) => { const columns = [ { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan, }), className: 'bg-white', render: (text, r) => { const title = text || r.lgc_details?.['2']?.title || r.lgc_details?.['1']?.title || ''; - return setEditingProduct(r.info)}>{title}; + return isPermitted(PERM_PRODUCTS_OFFER_PUT) ? setEditingProduct(r.info)}>{title} : title; } }, // ...(productType === 'B' ? [{ key: 'km', dataIndex: ['info', 'km'], title: t('KM')}] : []), { key: 'adult', title: t('AgeType.Adult'), render: (_, { adult_cost, currency, unit_id, unit_name }) => `${adult_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}` }, @@ -119,7 +135,7 @@ const PriceTable = ({ productType, dataSource, refresh }) => { key: 'state', title: t('State'), render: (_, r) => { - const stateCls = `text-${stateMapVal[`${r.audit_state_id}`]?.color} `; + const stateCls = ` text-${stateMapVal[`${r.audit_state_id}`]?.color} `; return {stateMapVal[`${r.audit_state_id}`]?.label}; }, }, @@ -128,10 +144,12 @@ const PriceTable = ({ productType, dataSource, refresh }) => { key: 'action', render: (_, r) => r.audit_state_id <= 0 ? ( - - - - + + + + + + ) : null, }, ]; From b7d7c863199abdc79a63c36d356c2d2cbbdc7f8d Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Mon, 8 Jul 2024 16:55:53 +0800 Subject: [PATCH 096/120] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=A0=B9=E6=8D=AE?= =?UTF-8?q?=E4=BE=9B=E5=BA=94=E5=95=86=E6=90=9C=E7=B4=A2=E8=B4=A6=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Account.js | 4 ++-- src/views/account/Management.jsx | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/stores/Account.js b/src/stores/Account.js index 47abc92..2b5e8d8 100644 --- a/src/stores/Account.js +++ b/src/stores/Account.js @@ -139,16 +139,16 @@ const useAccountStore = create((set, get) => ({ }, searchAccountByCriteria: async (formValues) => { - + const travel_agency_ids = formValues.agency.map((ele) => ele.key).join(',') const searchParams = { username: formValues.username, realname: formValues.realname, + travel_agency_ids: travel_agency_ids, lgc: 2 } const resultArray = await fetchAccountList(searchParams) - console.info(resultArray) const mapAccoutList = resultArray.map((r) => { return { accountId: r.wu_id, diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index bb00571..06e631b 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -324,12 +324,13 @@ function Management() { {t('account:accountList')} { handelAccountSearch() From 2a20ed0f14c57e5aa34e388f926cb3900e256dd0 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Tue, 9 Jul 2024 09:41:05 +0800 Subject: [PATCH 097/120] =?UTF-8?q?feat:=20=E5=AF=BC=E8=88=AA=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=BA=A7=E5=93=81=E7=AE=A1=E7=90=86=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Account.js | 7 +++++-- src/views/App.jsx | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/stores/Account.js b/src/stores/Account.js index 2b5e8d8..22c96ca 100644 --- a/src/stores/Account.js +++ b/src/stores/Account.js @@ -1,6 +1,6 @@ import { create } from 'zustand' import { fetchJSON, postForm } from '@/utils/request' -import { isEmpty } from '@/utils/commons' +import { isEmpty, isNotEmpty } from '@/utils/commons' import { HT_HOST } from "@/config" import { usingStorage } from '@/hooks/usingStorage' @@ -139,7 +139,10 @@ const useAccountStore = create((set, get) => ({ }, searchAccountByCriteria: async (formValues) => { - const travel_agency_ids = formValues.agency.map((ele) => ele.key).join(',') + let travel_agency_ids = null + if (isNotEmpty(formValues.agency)) { + travel_agency_ids = formValues.agency.map((ele) => ele.key).join(',') + } const searchParams = { username: formValues.username, realname: formValues.realname, diff --git a/src/views/App.jsx b/src/views/App.jsx index a9c4937..73abe43 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -17,7 +17,7 @@ import useAuthStore from '@/stores/Auth' import { useThemeContext } from '@/stores/ThemeContext' import { usingStorage } from '@/hooks/usingStorage' -import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET } from '@/config' +import { PERM_ACCOUNT_MANAGEMENT, PERM_ROLE_NEW, PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT } from '@/config' const { Header, Content, Footer } = Layout; const { Title } = Typography; @@ -117,7 +117,7 @@ function App() { isPermitted(PERM_OVERSEA) ? { key: 'feedback', label: {t('menu.Feedback')} } : null, isPermitted(PERM_OVERSEA) ? { key: 'report', label: {t('menu.Report')} } : null, isPermitted(PERM_AIR_TICKET) ? { key: 'airticket', label: {t('menu.Airticket')} } : null, - { key: 'products', label: {t('menu.Products')} }, + isPermitted(PERM_PRODUCTS_MANAGEMENT) ? { key: 'products', label: {t('menu.Products')} } : null, { key: 'notice', label: ( From 395e2e44db1e55deb3f87a5b7ba4688e5c58173c Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Tue, 9 Jul 2024 14:43:38 +0800 Subject: [PATCH 098/120] =?UTF-8?q?feat:=20=E5=9B=A2=E8=AE=A1=E5=88=92?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E3=80=90=E6=9C=AA=E7=A1=AE=E8=AE=A4=E3=80=91?= =?UTF-8?q?=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/group.json | 1 + public/locales/zh/group.json | 2 +- src/components/SearchForm.jsx | 10 +++++++++- src/stores/Reservation.js | 13 +++++++------ src/views/reservation/Newest.jsx | 3 ++- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/public/locales/en/group.json b/public/locales/en/group.json index 4286b80..2083c6e 100644 --- a/public/locales/en/group.json +++ b/public/locales/en/group.json @@ -1,6 +1,7 @@ { "ArrivalDate": "Arrival Date", "RefNo": "Reference number", + "unconfirmed": "Unconfirmed", "Pax": "Pax", "Status": "Status", "City": "City", diff --git a/public/locales/zh/group.json b/public/locales/zh/group.json index 2f25071..5b18f89 100644 --- a/public/locales/zh/group.json +++ b/public/locales/zh/group.json @@ -1,6 +1,7 @@ { "ArrivalDate": "抵达日期", "RefNo": "团号", + "unconfirmed": "未确认", "Pax": "人数", "Status": "状态", "City": "城市", @@ -11,6 +12,5 @@ "ConfirmationDate": "确认日期", "ConfirmationDetails": "确认信息", "PNR": "旅客订座记录", - "#": "#" } diff --git a/src/components/SearchForm.jsx b/src/components/SearchForm.jsx index 1dcafe2..153677d 100644 --- a/src/components/SearchForm.jsx +++ b/src/components/SearchForm.jsx @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { Form, Input, Row, Col, Select, DatePicker, Space, Button } from 'antd'; +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'; @@ -301,6 +301,14 @@ function getFields(props) { , fieldProps?.dept?.col || 6 ), + item( + 'unconfirmed', + 99, + + {t('group:unconfirmed')} + , + fieldProps?.unconfirmed?.col || 2 + ), ]; baseChildren = baseChildren .map((x) => { diff --git a/src/stores/Reservation.js b/src/stores/Reservation.js index 8990ae7..f777162 100644 --- a/src/stores/Reservation.js +++ b/src/stores/Reservation.js @@ -91,17 +91,18 @@ const useReservationStore = create((set, get) => ({ })) }, - fetchReservationList: (formVal, current=1) => { + fetchReservationList: (formValues, current=1) => { const { travelAgencyId } = usingStorage() const { reservationPage } = get() // 设置为 0,后端会重新计算总数,当跳转第 X 页时可用原来的总数。 - const totalNum = current == 1 ? 0 : reservationPage.total; + const totalNum = current == 1 ? 0 : reservationPage.total + const notConfirmValue = formValues.notConfirm ? 1 : 0 const fetchUrl = prepareUrl(HT_HOST + '/service-cusservice/GetPlanSearchList') .append('VEI_SN', travelAgencyId) - .append('GroupNo', formVal.referenceNo) - .append('DateStart', formVal.startdate) - .append('DateEnd', formVal.enddate) - .append('NotConfirm', '')//status)// Todo: 待解决 + .append('GroupNo', formValues.referenceNo) + .append('DateStart', formValues.startdate) + .append('DateEnd', formValues.enddate) + .append('NotConfirm', notConfirmValue) .append('TotalNum', totalNum) .append('PageSize', reservationPage.size) .append('PageIndex', current) diff --git a/src/views/reservation/Newest.jsx b/src/views/reservation/Newest.jsx index b2330ab..9a02816 100644 --- a/src/views/reservation/Newest.jsx +++ b/src/views/reservation/Newest.jsx @@ -201,8 +201,9 @@ function Newest() { Date: Tue, 9 Jul 2024 15:41:24 +0800 Subject: [PATCH 099/120] =?UTF-8?q?1.=E5=A2=9E=E5=8A=A0=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E4=BA=A7=E5=93=81=E5=8A=9F=E8=83=BD=202.=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E5=85=A8=E5=B9=B4=E6=97=A5=E6=9C=9F=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/products/Detail.jsx | 577 ++++++++++++++---- .../products/Detail/BatchImportPrice1.jsx | 10 +- .../Detail/addValidityWithWeekend.jsx | 38 +- 3 files changed, 496 insertions(+), 129 deletions(-) diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx index cab7064..7655105 100644 --- a/src/views/products/Detail.jsx +++ b/src/views/products/Detail.jsx @@ -1,9 +1,9 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'; -import { Button, Card, Col, Row, Breadcrumb, Table, Popconfirm, Form, Input, InputNumber, Tag, Modal, Select, Tree } from 'antd'; +import { Button, Card, Col, Row, Breadcrumb, Table, Popconfirm, Form, Input, InputNumber, Tag, Modal, Select, Tree, FloatButton, DatePicker } from 'antd'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import AddValidityWithWeekend from '@/views/products/Detail/addValidityWithWeekend'; -import { searchAgencyAction, getAgencyProductsAction } from '@/stores/Products/Index'; +import { getAgencyProductsAction } from '@/stores/Products/Index'; import { useProductsTypes } from '@/hooks/useProductsSets'; import Extras from './Detail/Extras'; import { groupBy } from '@/utils/commons'; @@ -12,9 +12,16 @@ import useProductsStore from '@/stores/Products/Index'; import { useHTLanguageSets } from '@/hooks/useHTLanguageSets'; import { useDefaultLgc } from '@/i18n/LanguageSwitcher'; import BatchImportPrice from './Detail/BatchImportPrice1'; +import dayjs from 'dayjs'; +import { PlusCircleFilled } from '@ant-design/icons'; +import { info } from 'autoprefixer'; +import { DeptSelector } from '@/components/DeptSelector'; +import { useDatePresets } from '@/hooks/useDatePresets'; + function Detail() { const { t } = useTranslation(); const [form] = Form.useForm(); + const { RangePicker } = DatePicker; const [editingid, setEditingid] = useState(''); const [tags, setTags] = useState([]); const [isModalVisible, setIsModalVisible] = useState(false); @@ -22,6 +29,7 @@ function Detail() { const [saveData, setSaveData] = useState(null); const [datePickerVisible, setDatePickerVisible] = useState(false); const [batchImportPriceVisible, setBatchImportPriceVisible] = useState(false); + const [quotationTableVisible, setQuotationTableVisible] = useState(false) const [currentid, setCurrentid] = useState(null); const [languageStatus, setLanguageStatus] = useState(null); const [selectedNodeid, setSelectedNodeid] = useState(null); @@ -33,25 +41,40 @@ function Detail() { const [quotation, setQuotation] = useState(null); const [lgc_details, setLgc_details] = useState(null); const [languageLabel, setLanguageLabel] = useState(null); + const [infoDataForId, setInfoDataForId] = useState(null); const { travel_agency_id } = useParams(); const { language } = useDefaultLgc(); const HTLanguageSets = useHTLanguageSets(); const { Search } = Input; - + const [addProductVisible, setAddProductVisible] = useState(false); const [editingProduct, setEditingProduct] = useProductsStore((state) => [state.editingProduct, state.setEditingProduct]); - + const [agencyProducts, setAgencyProducts] = useProductsStore((state) => [state.agencyProducts, state.setAgencyProducts]); + const { getAgencyProducts } = useProductsStore(); const [expandedKeys, setExpandedKeys] = useState([]); const [searchValue, setSearchValue] = useState(''); const [autoExpandParent, setAutoExpandParent] = useState(true); const [dataList, setDataList] = useState([]); const [defaultData, setDefaultData] = useState([]); const [batchImportData, setBatchImportData] = useState([]); + const [addProductType, setAddProductType] = useState(''); + const [addproductName, setAddProductName] = useState(''); + const [dataFetched, setDataFetched] = useState(false); // 添加一个标志位 + const [selectedNodeKey, setSelectedNodeKey] = useState(null); + const [selectedDays, setSelectedDays] = useState([]); + const [currentQuotationRecord, setCurrentQuotationRecord] = useState(null); + const presets = useDatePresets(); const handleBatchImportData = (data) => { setBatchImportData(data); }; + const days = [ + 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' + ]; const productProject = { - "6": [], + "6": [ + { code: "code", name: t('products:Code'), nameKey: 'products:Code' }, + { code: "city_name", name: t('products:City'), nameKey: 'products:City' }, + ], "B": [ { code: "code", name: t('products:Code'), nameKey: 'products:Code' }, { code: "city_name", name: t('products:City'), nameKey: 'products:City' }, @@ -110,42 +133,45 @@ function Detail() { const languageLabel = matchedLanguage.label setLanguageLabel(languageLabel) setSelectedTag(languageLabel) - setRemainderLanguage(HTLanguageSets.filter(item => item.key !== language.toString())) - }, []); + // setRemainderLanguage(HTLanguageSets.filter(item => item.key !== language.toString())) + }, []); useEffect(() => { const fetchData = async () => { - const a = { travel_agency_id }; - const res = await getAgencyProductsAction(a); - const groupedProducts = groupBy(res.products, (row) => row.info.product_type_id); - - const generateTreeData = (productsTypes, productsData) => { - return productsTypes.map(type => ({ - title: type.label, - key: type.value, - selectable: false, - children: (productsData[type.value] || []).map(product => ({ - title: product.info.title, - key: `${type.value}-${product.info.id}`, - _raw: product, - })) - })); - }; - - - const treeData = generateTreeData(productsTypes, groupedProducts); - console.log("treeData", treeData) - setTreeData(treeData); - setProductsData(groupedProducts); - setDefaultData(treeData); - setDataList(flattenTreeData(treeData)); + if (productsTypes && !dataFetched) { + const agency_id = { travel_agency_id }; + await getAgencyProducts(agency_id); + console.log("agencyProducts", agencyProducts) + console.log("productsTypes", productsTypes) + const generateTreeData = (productsTypes, productsData) => { + return productsTypes.map(type => ({ + title: type.label, + key: type.value, + selectable: false, + children: (productsData[type.value] || []).map(product => ({ + title: product.info.title, + key: `${type.value}-${product.info.id}`, + _raw: product, + })) + })); + }; + + const treeData = generateTreeData(productsTypes, agencyProducts); + console.log("treeData", treeData) + setDataFetched(true); // 设置标志位为 true,表示数据已获取 + setTreeData(treeData); + setProductsData(agencyProducts); + setDefaultData(treeData); + setDataList(flattenTreeData(treeData)); + } + }; fetchData(); - }, [productsTypes]); + }, [agencyProducts, dataFetched]); const flattenTreeData = (tree) => { let flatList = []; @@ -218,6 +244,9 @@ function Detail() { const isEditing = (record) => record.id === editingid; const edit = (record) => { + // setQuotationTableVisible(true); + // setCurrentQuotationRecord(record); + console.log("record", record) form.setFieldsValue({ ...record }); setEditingid(record.id); }; @@ -237,7 +266,17 @@ function Detail() { delete newData[index].quotation delete newData[index].extras console.log("newData", newData) - setQuotation(newData); + + //按人等范围排序 + const sortedData = [...newData].sort((a, b) => { + // 首先按照 group_size_min 升序排序 + if (a.group_size_min !== b.group_size_min) { + return a.group_size_min - b.group_size_min; + } + // 如果 group_size_min 相同,则按照 group_size_max 升序排序 + return a.group_size_max - b.group_size_max; + }); + setQuotation(sortedData); setEditingid(''); } else { newData.push(restRow); @@ -253,12 +292,23 @@ function Detail() { const newData = [...quotation]; const index = newData.findIndex((item) => id === item.id); newData.splice(index, 1); - setQuotation(newData); + + //按人等范围排序 + const sortedData = [...newData].sort((a, b) => { + // 首先按照 group_size_min 升序排序 + if (a.group_size_min !== b.group_size_min) { + return a.group_size_min - b.group_size_min; + } + // 如果 group_size_min 相同,则按照 group_size_max 升序排序 + return a.group_size_max - b.group_size_max; + }); + + setQuotation(sortedData); }; const handleAdd = () => { const newData = { - id: `${quotation.length + 1}`, + // id: `${quotation.length + 1}`, value: '', currency: '', unit_name: '', @@ -280,8 +330,18 @@ function Detail() { setDatePickerVisible(true); }; + const quotationTableVisibleOK = () => { + setQuotationTableVisible(false); + } + const quotationTableVisibleCancel = () => { + setQuotationTableVisible(false); + } const handleDateChange = ({ dateRange, selectedDays }) => { + console.log("dateRange", dateRange) + console.log("selectedDays", selectedDays) + + // 计算周末 const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; let weekDayCount = selectedDays.map(day => weekdays.indexOf(day) + 1).sort().join(','); @@ -299,20 +359,22 @@ function Detail() { }; const handleDateOk = () => { - const { dateRange } = selectedDateData; - const dateRangeList = dateRange.split('-'); - const use_dates_start = dateRangeList[0]; - const use_dates_end = dateRangeList[1]; + let { dateRange } = selectedDateData; + console.log("handleDateOk_dateRange", dateRange) + const use_dates_start = dateRange[0]; + const use_dates_end = dateRange[1]; if (currentid !== null) { const newData = [...quotation]; const index = newData.findIndex((item) => currentid === item.id); if (index > -1) { newData[index].use_dates_start = use_dates_start; newData[index].use_dates_end = use_dates_end; + console.log("newData", newData) setQuotation(newData); setCurrentid(null); } } + setSelectedDateData({ dateRange: null, selectedDays: [] }) setDatePickerVisible(false); } @@ -329,8 +391,18 @@ function Detail() { // 将剩余的属性添加到 quotation 中 const newData = [...quotation, ...tempBatchImportData]; + //按人等范围排序 + const sortedData = [...newData].sort((a, b) => { + // 首先按照 group_size_min 升序排序 + if (a.group_size_min !== b.group_size_min) { + return a.group_size_min - b.group_size_min; + } + // 如果 group_size_min 相同,则按照 group_size_max 升序排序 + return a.group_size_max - b.group_size_max; + }); + // 更新状态来更新页面 - setQuotation(newData); + setQuotation(sortedData); setBatchImportPriceVisible(false); } @@ -358,8 +430,8 @@ function Detail() { if (dataIndex === 'currency' && editing) { inputNode = ( ); } @@ -448,10 +520,8 @@ function Detail() { const editable = isEditing(record); return editable ? ( - {/* handleSave(record.id)} style={{ marginRight: 8 }}>{t('Save')} */} - {/* {t('Cancel')} */} ) : ( @@ -507,17 +577,20 @@ function Detail() { let tempRemainderLanguage = remainderLanguage.filter((item) => { return item.label !== selectedTag; }) - console.log("AAAA", lgc_details) - console.log("selectTag", selectedTag) + const matchedLanguage = HTLanguageSets.find(HTLanguage => HTLanguage.label === selectedTag); - const languageKey = matchedLanguage.key - const tempLgc_details = { - ...lgc_details, [languageKey]: { - title: {}, + const languageKey = parseInt(matchedLanguage.key) + if (!(languageKey in lgc_details)) { + const tempLgc_details = { + ...lgc_details, [languageKey]: { + title: "", + lgc: languageKey, + descriptions: "" + } } + setLgc_details(tempLgc_details) } - console.log("languageKey", languageKey) setRemainderLanguage(tempRemainderLanguage) setTags([...tags, selectedTag]) @@ -532,37 +605,78 @@ function Detail() { const handleTagChange = (value) => { console.log("handleTagChange", value) setSelectedTag(value); - console.log("setSelectedTag", selectedTag) }; const handleChange = (field, value) => { - console.log("languageStatus", languageStatus) - console.log("...lgc_details[languageStatus]", { ...lgc_details[languageStatus] }) // 更新整个 lgc_details 对象 const updatedLgcDetails = { ...lgc_details, [languageStatus]: { ...lgc_details[languageStatus], [field]: value, lgc: languageStatus } }; + console.log("updatedLgcDetails", updatedLgcDetails) setLgc_details(updatedLgcDetails) - console.log("AAAAAAAAAAAAAA", lgc_details); }; + const handleDayClick = (day) => { + setSelectedDays((prevSelectedDays) => { + const updatedDays = prevSelectedDays.includes(day) + ? prevSelectedDays.filter((d) => d !== day) + : [...prevSelectedDays, day]; + return updatedDays; + }); + }; + //树组件方法 const handleNodeSelect = (_, { node }) => { + if (!node._raw.info.id) { + console.log("nodeNoID", node) + const infoData = node._raw.info + const newLgcDetails = node._raw.lgc_details + const fatherKey = node.key.split('-')[0]; + setSelectedNodeid(node.key); + setSelectedNodeKey(fatherKey); + form.setFieldsValue({ + info: { + title: infoData.title, + code: infoData.code, + product_type_name: infoData.product_type_name, + city_name: infoData.city_name, + remarks: infoData.remarks, + open_weekdays: infoData.open_weekdays, + recommends_rate: infoData.recommends_rate, + duration: infoData.duration, + dept: infoData.dept, + km: infoData.km, + dept_name: infoData.dept_name, + }, + lgc_details: { + lgc: language, + title: newLgcDetails[language]?.title || '', + descriptions: newLgcDetails[language]?.descriptions || '' + } + }) + return + } + + setTags([languageLabel]) // 如果点击的是同一个节点,不做任何操作 if (selectedNodeid === node.key) return; - const fatherKey = node.key.split('-')[0]; - setSelectedCategory(productProject[fatherKey]) + setSelectedNodeid(node.key); + const fatherKey = node.key.split('-')[0]; + setSelectedNodeKey(fatherKey); + console.log("node.key", node.key); + console.log("fatherKey", fatherKey) + setSelectedCategory(productProject[fatherKey]); setLanguageStatus(language); const matchedLanguage = HTLanguageSets.find(HTLanguage => HTLanguage.key === language.toString()); - const languageLabelRefresh = matchedLanguage.label - setLanguageLabel(languageLabelRefresh) - setSelectedTag(languageLabelRefresh) - setRemainderLanguage(HTLanguageSets.filter(item => item.key !== language.toString())) + const languageLabelRefresh = matchedLanguage.label; + setLanguageLabel(languageLabelRefresh); + setSelectedTag(languageLabelRefresh); + setRemainderLanguage(HTLanguageSets.filter(item => item.key !== language.toString())); setEditingProduct(node._raw); @@ -570,55 +684,186 @@ function Detail() { let initialQuotationData = null; let infoData = null; let lgcDetailsData = null; + console.log("productsData", productsData) + console.log("productsData[fatherKey]", productsData[fatherKey]) + console.log("node", node) + // console.log("node",node._raw) productsData[fatherKey].forEach(element => { - if (element.info.title === node.title) { + if (element.info.id === node._raw.info.id) { initialQuotationData = element.quotation; infoData = element.info; lgcDetailsData = element.lgc_details; - return true; } }); - console.log("infoData", infoData) + if (!node._raw.info.id) { + + } + + console.log("lgcDetailsData", lgcDetailsData) // 累积 lgc_details 数据 let newLgcDetails = {}; - lgcDetailsData.forEach(element => { - newLgcDetails[element.lgc] = element; - }); + if (lgcDetailsData) { + lgcDetailsData.forEach(element => { + newLgcDetails[element.lgc] = element; + }); + } + console.log("infoData", infoData) + console.log("laug", language) - // 一次性更新 lgc_details + if (node._raw.info.id) { + setInfoDataForId(infoData.id) + } setLgc_details(newLgcDetails); - setQuotation(initialQuotationData); // 使用 setTimeout 确保 lgc_details 已经更新 - form.setFieldsValue({ + if (node._raw.info.id) { + form.setFieldsValue({ + info: { + title: infoData.title, + code: infoData.code, + product_type_name: infoData.product_type_name, + city_name: infoData.city_name, + remarks: infoData.remarks, + open_weekdays: infoData.open_weekdays, + recommends_rate: infoData.recommends_rate, + duration: infoData.duration, + dept: infoData.dept, + km: infoData.km, + dept_name: infoData.dept_name, + }, + lgc_details: { + lgc: language, + title: newLgcDetails[language]?.title || '', + descriptions: newLgcDetails[language]?.descriptions || '' + } + }); + } + + }; + + const handelAddProduct = () => { + // 找到对应的产品类型节点 + const productTypeNode = treeData.find(item => item.key === addProductType); + console.log("productTypeNode", productTypeNode); + + if (productTypeNode) { + // 在 children 数组中插入新的产品节点 + const newChildren = [ + ...productTypeNode.children, + { + title: addproductName, + key: `${addProductType}-${Date.now()}`, // 使用时间戳作为唯一的 key + _raw: { + info: { code: '' }, + lgc_details: [], + quotation: [] + } + } + ]; + // 创建新的 treeData 数组,确保 React 能够检测到更改 + const newTreeData = treeData.map(item => { + if (item.key === addProductType) { + return { + ...item, + children: newChildren, + }; + } + return item; + }); + // 更新 treeData + setEditingProduct(null) + setTreeData(newTreeData); + } + + console.log("productData", productsData) + console.log("addProductType", addProductType) + let tempProductDataList = productsData[addProductType]; + + //初始化产品数据 + const newProduct = { info: { - title: infoData.title, - code: infoData.code, - product_type_name: infoData.product_type_name, - city_name: infoData.city_name, - remarks: infoData.remarks, - open_weekdays: infoData.open_weekdays, - recommends_rate: infoData.recommends_rate, - duration: infoData.duration, - dept: infoData.dept, - km: infoData.km, - dept_name: infoData.dept_name + code: 'addProduct' }, - lgc_details: { - lgc: language, - title: newLgcDetails[language]?.title || '', - descriptions: newLgcDetails[language]?.descriptions || '' - } - }); + quotation: [], + lgc_details: [] + } + tempProductDataList.push(newProduct); + console.log("tempProductDataList", tempProductDataList) + const newProductsData = { + ...productsData, // 假设使用了展开运算符来复制现有数组 + [addProductType]: tempProductDataList + }; + setProductsData(newProductsData); + console.log("newProductsData", newProductsData) + setAddProductVisible(false); }; const onSave = (values) => { - const tempData = values; - tempData['quotation'] = quotation; - setSaveData(tempData); - console.log("保存的数据", tempData) + // 找到匹配的树节点 + let matchedTreeData = treeData.find(treeKey => treeKey.key === selectedNodeKey); + if (matchedTreeData) { + // 找到匹配的子节点 + const matchedTreeDataChildren = matchedTreeData.children; + // 检查是否已存在具有 selectedNodeid 的子节点 + console.log("matchedTreeDataChildren", matchedTreeDataChildren) + console.log("selectedNodeid", selectedNodeid) + // if (matchedTreeDataChildren.some(child => child.key === selectedNodeid)) { + // console.log("Child with this ID already exists."); + // return; + // } + let tempTreeDataChildrenData = matchedTreeDataChildren.find(element => element.key === selectedNodeid); + console.log("tempTreeDataChildrenData", tempTreeDataChildrenData); + // if (tempTreeDataChildrenData) { + tempTreeDataChildrenData._raw = values; + console.log("tempTreeDataChildrenData改", tempTreeDataChildrenData) + console.log("treeData111111", treeData) + console.log("lgc_details", lgc_details) + tempTreeDataChildrenData._raw.lgc_details = lgc_details + // console.log("matchedTreeData", matchedTreeData) + // if (!matchedTreeData.children.some(element => element.key === selectedNodeid)) { + // console.log("重复了"); + // matchedTreeData.children.push(tempTreeDataChildrenData) + // console.log("matchedTreeData改", matchedTreeData) + // return; + // } + + + // } else { + // console.log("No matching child node found."); + // } + } else { + console.log("No matching tree node found."); + } + + + + + // if (infoDataForId) { + // // 创建新的 tempData 对象 + // const tempData = { + // ...values, + // info: { ...values.info, id: infoDataForId }, + // quotation: quotation, + // lgc_details: Object.values(lgc_details) + // }; + + // setSaveData(tempData); + // console.log("保存的数据", tempData); + // } else { + // // 创建新的 tempData 对象 + // const tempData = { + // ...values, + // info: { ...values.info }, + // quotation: quotation, + // lgc_details: Object.values(lgc_details) + // }; + + // setSaveData(tempData); + // console.log("保存的数据", tempData); + // } + }; return ( @@ -626,7 +871,10 @@ function Detail() {
- + + + +

{t('products:EditComponents.info')}

- {selectedCategory.map((item, index) => ( - -
- - {item.code === "duration" ? ( - - ) : (item.code === "display_to_c") ? ( - - ) : ( - - )} - - - ))} + {selectedCategory.map((item, index) => { + // const key = `${item.code}-${index}`; + // console.log(key); + return ( + + + {item.code === "duration" ? ( + + ) : (item.code === "display_to_c") ? ( + + ) : (item.code === "dept_name") ? ( + + ) : ( + + )} + + + ); + })} - {/* */} + {/* */} - {/* */} + {/* */}

{t('products:supplierQuotation')}

提交审核 - - - - {/* */} - - {/* */} + + + {/* */} + + {/* */} + } onClick={() => setAddProductVisible(true)} /> @@ -782,6 +1035,94 @@ function Detail() { ) } + + { + addProductVisible && ( + setAddProductVisible(false)} + > +

选择产品类别

+ + + +

新增产品名称

+ setAddProductName(e.target.value)} + /> +
+ ) + } + + {/* { + quotationTableVisible && ( + +

成人价

+ +

儿童价

+ +

币种

+ +

类型

+ + +

人等

+
+

有效期

+ +

周末

+ {days.map((day, index) => ( + + ))} + + + + ) + } */} ); } diff --git a/src/views/products/Detail/BatchImportPrice1.jsx b/src/views/products/Detail/BatchImportPrice1.jsx index 906b68f..71269ca 100644 --- a/src/views/products/Detail/BatchImportPrice1.jsx +++ b/src/views/products/Detail/BatchImportPrice1.jsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { Button, Card, Checkbox, Col, DatePicker, Form, Input, Row, Select, Space, Tag, Table, InputNumber } from 'antd'; import { CloseOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; +import { useDatePresets } from '@/hooks/useDatePresets'; const { Option } = Select; const { RangePicker } = DatePicker; @@ -13,7 +14,7 @@ const BatchImportPrice = ({ onBatchImportData }) => { const [checkedDays, setCheckedDays] = useState([]); const [tableData, setTableData] = useState([]); const [sendData, setSendData] = useState(null); - + const presets = useDatePresets(); // 获取当前年份 const currentYear = new Date().getFullYear(); const startOfYear = dayjs(new Date(currentYear, 0, 1)); @@ -330,10 +331,11 @@ const BatchImportPrice = ({ onBatchImportData }) => {
{periodFields.map((periodField, idx) => ( - - + {/* + /> */} + periodOpt.remove(periodField.name)} /> diff --git a/src/views/products/Detail/addValidityWithWeekend.jsx b/src/views/products/Detail/addValidityWithWeekend.jsx index 0ad9a74..8d90ee3 100644 --- a/src/views/products/Detail/addValidityWithWeekend.jsx +++ b/src/views/products/Detail/addValidityWithWeekend.jsx @@ -1,20 +1,22 @@ + import React, { useState } from 'react'; import { DatePicker, Button } from 'antd'; - +import dayjs from 'dayjs'; +import { useDatePresets } from '@/hooks/useDatePresets'; const addValidityWithWeekend = ({ onDateChange }) => { const dateFormat = 'YYYY/MM/DD'; const { RangePicker } = DatePicker; const [dateRange, setDateRange] = useState(null); const [selectedDays, setSelectedDays] = useState([]); - + const presets = useDatePresets(); const days = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ]; + const handleChange = (date, dateString) => { - const range = dateString[0] + "-" + dateString[1]; - setDateRange(range); - onDateChange({ dateRange: range, selectedDays }); + console.log("dateString",dateString) + onDateChange({ dateRange: dateString, selectedDays }); }; @@ -24,7 +26,7 @@ const addValidityWithWeekend = ({ onDateChange }) => { const updatedDays = prevSelectedDays.includes(day) ? prevSelectedDays.filter((d) => d !== day) : [...prevSelectedDays, day]; - onDateChange({ dateRange, selectedDays: updatedDays }); + onDateChange({ dateRange, selectedDays: updatedDays }); return updatedDays; }); }; @@ -32,7 +34,7 @@ const addValidityWithWeekend = ({ onDateChange }) => { return (

Data

- + {}

Weekdays

{days.map((day, index) => ( @@ -51,3 +53,25 @@ const addValidityWithWeekend = ({ onDateChange }) => { }; export default addValidityWithWeekend; + + +// import React, { useState } from 'react'; +// import { DatePicker, Button } from 'antd'; +// import dayjs from 'dayjs'; +// import { useDatePresets } from '@/hooks/useDatePresets'; +// // import 'dayjs/locale/zh-cn'; // 引入中文语言包 + +// // const { RangePicker } = DatePicker; + +// const DateRangePicker = () => { +// const { RangePicker } = DatePicker; +// const presets = useDatePresets(); + + +// return ( +// <> +// +// ); +// }; + +// export default DateRangePicker; From a19d787fab5858fd7662bc04e52ca2e0104f363b Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Tue, 9 Jul 2024 15:43:12 +0800 Subject: [PATCH 100/120] =?UTF-8?q?feat:=20=E8=B4=A6=E5=8F=B7=E5=88=A0?= =?UTF-8?q?=E9=99=A4=20realname=20=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/SearchForm.jsx | 8 -------- src/stores/Account.js | 1 - src/views/account/Management.jsx | 7 +++---- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/components/SearchForm.jsx b/src/components/SearchForm.jsx index 153677d..acfd6a3 100644 --- a/src/components/SearchForm.jsx +++ b/src/components/SearchForm.jsx @@ -243,14 +243,6 @@ function getFields(props) { , fieldProps?.username?.col || 4 ), - item( - 'realname', - 99, - - - , - fieldProps?.realname?.col || 4 - ), /** * */ diff --git a/src/stores/Account.js b/src/stores/Account.js index 22c96ca..f11f545 100644 --- a/src/stores/Account.js +++ b/src/stores/Account.js @@ -145,7 +145,6 @@ const useAccountStore = create((set, get) => ({ } const searchParams = { username: formValues.username, - realname: formValues.realname, travel_agency_ids: travel_agency_ids, lgc: 2 } diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 06e631b..5a8abdc 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -324,13 +324,12 @@ function Management() { {t('account:accountList')} { handelAccountSearch() From ce921312afc677561b0dd79cb9066baaa4d12f5f Mon Sep 17 00:00:00 2001 From: Lei OT Date: Tue, 9 Jul 2024 16:38:45 +0800 Subject: [PATCH 101/120] =?UTF-8?q?=E8=A1=A8=E5=8D=95=E7=9A=84=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E5=80=BC=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/SearchForm.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/SearchForm.jsx b/src/components/SearchForm.jsx index acfd6a3..e53caec 100644 --- a/src/components/SearchForm.jsx +++ b/src/components/SearchForm.jsx @@ -52,6 +52,7 @@ const SearchForm = ({ initialValue, onSubmit, onReset, confirmText, formName, fo 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) : '') }, From 2f2c86026afc10f52b326e3e25ecd94030fa6b74 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Tue, 9 Jul 2024 16:39:19 +0800 Subject: [PATCH 102/120] =?UTF-8?q?feat:=20=E7=BB=91=E5=AE=9A=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE:=20=E6=90=9C=E7=B4=A2=E5=B7=B2=E5=8F=91=E5=B8=83?= =?UTF-8?q?=E7=9A=84=E4=BA=A7=E5=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Products/Index.js | 10 ++++++++++ src/views/products/Detail/Extras.jsx | 19 +++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js index 139d030..5e4ccbf 100644 --- a/src/stores/Products/Index.js +++ b/src/stores/Products/Index.js @@ -10,6 +10,16 @@ export const searchAgencyAction = async (param) => { return errcode !== 0 ? [] : result; }; +/** + * 搜索所有产品, 返回产品列表 + * ! 只有审核通过, 已发布的 + * @param {object} params { keyword, use_year, product_types, travel_agency_id, city } + */ +export const searchPublishedProductsAction = async (param) => { + const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/web_products_search`, param); + return errcode !== 0 ? [] : result; +}; + export const copyAgencyDataAction = async (postbody) => { const formData = new FormData(); Object.keys(postbody).forEach((key) => { diff --git a/src/views/products/Detail/Extras.jsx b/src/views/products/Detail/Extras.jsx index e3e16a3..6446db5 100644 --- a/src/views/products/Detail/Extras.jsx +++ b/src/views/products/Detail/Extras.jsx @@ -2,7 +2,7 @@ import { useEffect, useState, useSyncExternalStore } from 'react'; import { useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { App, Table, Button, Modal, Popconfirm } from 'antd'; -import { getAgencyProductExtrasAction, getAgencyProductsAction, addProductExtraAction, delProductExtrasAction } from '@/stores/Products/Index'; +import { getAgencyProductExtrasAction, searchPublishedProductsAction, addProductExtraAction, delProductExtrasAction } from '@/stores/Products/Index'; import { cloneDeep, pick } from '@/utils/commons'; import SearchForm from '@/components/SearchForm'; @@ -27,10 +27,9 @@ const NewAddonModal = ({ onPick, ...props }) => { const { starttime, endtime, year, ...param } = copyObject; setSearchLoading(true); setSearchResult([]); - // debug: audit_state: '1', const search_year = year || use_year; - const result = await getAgencyProductsAction({ ...param, travel_agency_id, use_year: search_year, audit_state: '0', }); - setSearchResult(result?.products || []); + const result = await searchPublishedProductsAction({ ...param, use_year: search_year, }); + setSearchResult(result); setSearchLoading(false); }; const handleAddExtras = async (item) => { @@ -39,10 +38,10 @@ const NewAddonModal = ({ onPick, ...props }) => { } }; - // todo: 如何显示价格表 const searchResultColumns = [ - { key: 'ptype', dataIndex: ['info', 'product_type_id'], width: '6rem', title: t('products:ProductType'), render: (text, r) => productsTypesMapVal[text].label }, - { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('products:Title') }, + { key: 'ptype', dataIndex: 'type', width: '6rem', title: t('products:ProductType'), render: (text, r) => productsTypesMapVal[text]?.label || text }, + { key: 'code', dataIndex: 'code', width: '6rem', title: t('products:Code') }, + { key: 'title', dataIndex: 'title', width: '16rem', title: t('products:Title') }, // { // title: t('products:price'), // dataIndex: ['quotation', '0', 'adult_cost'], @@ -72,7 +71,7 @@ const NewAddonModal = ({ onPick, ...props }) => { setOpen(false)} destroyOnClose> { const handleNewAddOn = async (item) => { setExtrasData(prev => [].concat(prev, [item])); // todo: 提交后端; 重复绑定同一个 - const _item = pick(item.info, ['id', 'title', 'code']); + const _item = pick(item, ['id', 'title', 'code']); const newSuccess = await addProductExtraAction({ travel_agency_id, id: productId, extras: [_item] }); newSuccess ? message.success(`${t('Success')}`) : message.error(`${t('Failed')}`); await handleGetAgencyProductExtras(); } const handleDelAddon = async (item) => { - const delSuccess = await delProductExtrasAction({ travel_agency_id, id: productId, extras: [item.info.id] }); + const delSuccess = await delProductExtrasAction({ travel_agency_id, id: productId, extras: [item.id] }); delSuccess ? message.success(`${t('Success')}`) : message.error(`${t('Failed')}`); await handleGetAgencyProductExtras(); }; From 472273396c76431ea6288b1709d4f4556029eaa8 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Tue, 9 Jul 2024 16:49:16 +0800 Subject: [PATCH 103/120] =?UTF-8?q?=E8=A1=A8=E5=8D=95=E7=9A=84=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E5=80=BC=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/SearchForm.jsx | 1 + src/stores/Reservation.js | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/SearchForm.jsx b/src/components/SearchForm.jsx index e53caec..0d26ccc 100644 --- a/src/components/SearchForm.jsx +++ b/src/components/SearchForm.jsx @@ -84,6 +84,7 @@ const SearchForm = ({ initialValue, onSubmit, onReset, confirmText, formName, fo 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; diff --git a/src/stores/Reservation.js b/src/stores/Reservation.js index f777162..d57ac56 100644 --- a/src/stores/Reservation.js +++ b/src/stores/Reservation.js @@ -96,13 +96,12 @@ const useReservationStore = create((set, get) => ({ const { reservationPage } = get() // 设置为 0,后端会重新计算总数,当跳转第 X 页时可用原来的总数。 const totalNum = current == 1 ? 0 : reservationPage.total - const notConfirmValue = formValues.notConfirm ? 1 : 0 const fetchUrl = prepareUrl(HT_HOST + '/service-cusservice/GetPlanSearchList') .append('VEI_SN', travelAgencyId) .append('GroupNo', formValues.referenceNo) .append('DateStart', formValues.startdate) .append('DateEnd', formValues.enddate) - .append('NotConfirm', notConfirmValue) + .append('NotConfirm', formValues.unconfirmed) .append('TotalNum', totalNum) .append('PageSize', reservationPage.size) .append('PageIndex', current) @@ -272,4 +271,4 @@ const useReservationStore = create((set, get) => ({ } })) -export default useReservationStore \ No newline at end of file +export default useReservationStore From e31c4903c4940f74d199275aa1a064c2a51261c3 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 10 Jul 2024 14:26:08 +0800 Subject: [PATCH 104/120] =?UTF-8?q?feat:=20=E8=B0=83=E6=95=B4=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E5=AE=BD=E5=BA=A6=EF=BC=8C=E9=80=82=E5=BA=94=E5=B0=8F?= =?UTF-8?q?=E5=B1=8F=E5=B9=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/App.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/App.jsx b/src/views/App.jsx index 73abe43..57f3919 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -103,7 +103,7 @@ function App() {
-
+ App logo @@ -130,7 +130,7 @@ function App() { ]} /> - +

{currentUser?.travelAgencyName}

From c6a868503cbf07b806046870cec61b4c9750b90f Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 10 Jul 2024 15:04:08 +0800 Subject: [PATCH 105/120] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4=20Mobx=20?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Auth.js | 18 ++++++++---------- src/stores/Reservation.js | 38 +++++++++++++++++--------------------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/src/stores/Auth.js b/src/stores/Auth.js index 8b17a0b..ef05fdc 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -4,8 +4,6 @@ import { HT_HOST } from "@/config" import { loadPageSpy } from '@/pageSpy' import { usingStorage } from '@/hooks/usingStorage' -import { lifecycleware } from '@/utils/lifecycle' - const KEY_LOGIN_TOKEN = 'G-STR:LOGIN_TOKEN' const KEY_TRAVEL_AGENCY_ID = 'G-INT:TRAVEL_AGENCY_ID' const KEY_USER_ID = 'G-INT:USER_ID' @@ -161,20 +159,20 @@ const useAuthStore = create((set, get) => ({ // TODO: 迁移到 Account.js changeUserPassword: (password, newPassword) => { const { userId } = usingStorage() - const formData = new FormData(); - formData.append('UserID', userId); - formData.append('Password', password); - formData.append('NewPassword', newPassword); - const postUrl = HT_HOST + '/service-CooperateSOA/SetPassword'; + const formData = new FormData() + formData.append('UserID', userId) + formData.append('Password', password) + formData.append('NewPassword', newPassword) + const postUrl = HT_HOST + '/service-CooperateSOA/SetPassword' return postForm(postUrl, formData) .then(json => { if (json.errcode == 0) { - return json; + return json } else { - throw new Error(json.errmsg + ': ' + json.errcode); + throw new Error(json.errmsg + ': ' + json.errcode) } - }); + }) }, isPermitted: (perm) => { diff --git a/src/stores/Reservation.js b/src/stores/Reservation.js index d57ac56..2984fe2 100644 --- a/src/stores/Reservation.js +++ b/src/stores/Reservation.js @@ -8,7 +8,7 @@ export const fetchCityList = async (travelAgencyId, reservationId) => { const { errcode, Result } = await fetchJSON( `${HT_HOST}/service-cusservice/PTGetCityGuide`, { VEI_SN: travelAgencyId, GRI_SN: reservationId, LGC: 1 }) - return errcode !== 0 ? {} : Result; + return errcode !== 0 ? {} : Result } export const fetchPlanDetail = async (travelAgencyId, reservationId) => { @@ -31,7 +31,7 @@ export const fetchAttachList = async (reservationId) => { const { errcode, result } = await fetchJSON( `${HT_HOST}/service-fileServer/PlanChangeFileList`, { GRI_SN: reservationId }) - return errcode !== 0 ? {} : result; + return errcode !== 0 ? {} : result } const useReservationStore = create((set, get) => ({ @@ -105,12 +105,12 @@ const useReservationStore = create((set, get) => ({ .append('TotalNum', totalNum) .append('PageSize', reservationPage.size) .append('PageIndex', current) - .build(); + .build() return fetchJSON(fetchUrl) .then(json => { if (json.errcode == 0) { - const mapReservationList = (json?.Result??[]).map((data, index) => { + const mapReservationList = (json?.Result??[]).map((data) => { return { key: data.vas_gri_sn, reservationId: data.vas_gri_sn, @@ -132,32 +132,32 @@ const useReservationStore = create((set, get) => ({ } })) } else { - throw new Error(json.errmsg + ': ' + json.errcode); + throw new Error(json.errmsg + ': ' + json.errcode) } - }); + }) }, fetchAllGuideList: () => { const { travelAgencyId } = usingStorage() const fetchUrl = prepareUrl(HT_HOST + '/service-cusservice/PTGetGuideList') .append('VEI_SN', travelAgencyId) - .build(); + .build() return fetchJSON(fetchUrl) .then(json => { if (json.errcode == 0) { - const guideList = (json?.Result??[]).map((data, index) => { + const guideList = (json?.Result??[]).map((data) => { return { guideId: data.TGI_SN, guideName: data.TGI2_Name, mobileNo: data.TGI_Mobile } - }); - return guideList; + }) + return guideList } else { - throw new Error(json.errmsg + ': ' + json.errcode); + throw new Error(json.errmsg + ': ' + json.errcode) } - }); + }) }, getReservationDetail: async (reservationId) => { @@ -167,8 +167,8 @@ const useReservationStore = create((set, get) => ({ const mapConfirmationList = planChangeList.map((data) => { const filterAttchList = attachListJson.filter(attch => { - return attch.PCI_SN === data.PCI_SN; - }); + return attch.PCI_SN === data.PCI_SN + }) return { key: data.PCI_SN, PCI_Changetext: data.PCI_Changetext, @@ -209,7 +209,7 @@ const useReservationStore = create((set, get) => ({ getReservationDetail(travelAgencyId, reservationDetail.reservationId) return json } - }); + }) }, setupCityGuide: (cityId, guideId) => { @@ -229,7 +229,7 @@ const useReservationStore = create((set, get) => ({ if (json.errcode != 0) { throw new Error(json.errmsg + ': ' + json.errcode) } - }); + }) }, @@ -241,7 +241,7 @@ const useReservationStore = create((set, get) => ({ .append('VEI_SN', travelAgencyId) .append('GRI_SN', selectedReservation.reservationId) .append('LGC', 1) - .build(); + .build() return fetchJSON(fetchUrl) .then(json => { @@ -252,10 +252,6 @@ const useReservationStore = create((set, get) => ({ return data.GuideName }).join(',') - runInAction(() => { - selectedReservation.guide = reservationGuide - }) - set((state) => ({ selectedReservation: { ...state.selectedReservation, From aac7effc0cf38d551cb12466860c0dbe11073a9b Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 10 Jul 2024 15:38:58 +0800 Subject: [PATCH 106/120] =?UTF-8?q?feat:=E8=B4=A6=E5=8F=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=95=8C=E9=9D=A2=E5=A2=9E=E5=8A=A0=E4=B8=AD=E8=8B=B1?= =?UTF-8?q?=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/account.json | 26 +++++++++++++++++++++++++- public/locales/zh/account.json | 1 + src/views/account/Management.jsx | 5 +++-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/public/locales/en/account.json b/public/locales/en/account.json index 7abb5cf..e8ea251 100644 --- a/public/locales/en/account.json +++ b/public/locales/en/account.json @@ -8,5 +8,29 @@ "CurrentPassword": "Please input your password.", "NewPassword": "Please input your new password.", "ReenterPassword": "Please reenter your password." - } + }, + "createdOn": "Created on", + "action": "Action", + "action.edit": "Edit", + "action.enable": "Enable", + "action.disable": "Disable", + "action.enable.title": "Do you want to enable account?", + "action.disable.title": "Do you want to disable account?", + "action.resetPassword": "Reset Password", + "action.resetPassword.tile": "Do you want to reset password?", + + "accountList": "Account List", + "newAccount": "New Account", + "detail": "Detail", + "username": "Username", + "realname": "Realname", + "travelAgency": "Travel Agency", + "travelAgencyName": "Travel Agency Name", + "email": "Email", + "lastLogin": "Last Login", + + "roleList": "Role List", + "newRole": "New Role", + "roleName": "Role Name", + "permission": "Permission" } \ No newline at end of file diff --git a/public/locales/zh/account.json b/public/locales/zh/account.json index a1d78c4..b38946c 100644 --- a/public/locales/zh/account.json +++ b/public/locales/zh/account.json @@ -17,6 +17,7 @@ "action.enable.title": "确定启用该账号吗?", "action.disable.title": "确定禁用该账号吗?", "action.resetPassword": "重置密码", + "action.resetPassword.tile": "确定重置账号密码吗?", "accountList": "管理账号", "newAccount": "新增账号", diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 5a8abdc..7081be3 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -193,11 +193,12 @@ function Management() { } const showResetPasswordConfirm = (account) => { + const confirmTitle = t('account:action.resetPassword.tile') const randomPassword = account.username + '@' + (Math.floor(Math.random() * 900) + 100) modal.confirm({ - title: 'Do you want to reset password?', + title: confirmTitle, icon: , - content: `Username: ${account.username}, Realname: ${account.realname}`, + content: t('account:username') + ': ' + account.username + ', ' + t('account:realname') + ': ' + account.realname, onOk() { resetAccountPassword(account.userId, randomPassword) .then(() => { From d1ca39eccce8fbd06adb85e50ad05760e75e5aad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=96=87=E5=BC=BA=40HWQ-PC?= Date: Wed, 10 Jul 2024 16:34:08 +0800 Subject: [PATCH 107/120] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8A=A5=E4=BB=B7?= =?UTF-8?q?=E6=8A=A5=E4=BB=B7=E7=9A=84=E7=BC=96=E8=BE=91=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/products/Detail.jsx | 394 +++++++----------- ...hImportPrice1.jsx => BatchImportPrice.jsx} | 7 - .../Detail/addValidityWithWeekend.jsx | 22 - 3 files changed, 145 insertions(+), 278 deletions(-) rename src/views/products/Detail/{BatchImportPrice1.jsx => BatchImportPrice.jsx} (97%) diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx index 7655105..f624d56 100644 --- a/src/views/products/Detail.jsx +++ b/src/views/products/Detail.jsx @@ -2,19 +2,15 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'; import { Button, Card, Col, Row, Breadcrumb, Table, Popconfirm, Form, Input, InputNumber, Tag, Modal, Select, Tree, FloatButton, DatePicker } from 'antd'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import AddValidityWithWeekend from '@/views/products/Detail/addValidityWithWeekend'; -import { getAgencyProductsAction } from '@/stores/Products/Index'; import { useProductsTypes } from '@/hooks/useProductsSets'; import Extras from './Detail/Extras'; -import { groupBy } from '@/utils/commons'; import { useParams } from 'react-router-dom'; import useProductsStore from '@/stores/Products/Index'; import { useHTLanguageSets } from '@/hooks/useHTLanguageSets'; import { useDefaultLgc } from '@/i18n/LanguageSwitcher'; -import BatchImportPrice from './Detail/BatchImportPrice1'; +import BatchImportPrice from './Detail/BatchImportPrice'; import dayjs from 'dayjs'; import { PlusCircleFilled } from '@ant-design/icons'; -import { info } from 'autoprefixer'; import { DeptSelector } from '@/components/DeptSelector'; import { useDatePresets } from '@/hooks/useDatePresets'; @@ -27,7 +23,6 @@ function Detail() { const [isModalVisible, setIsModalVisible] = useState(false); const [selectedTag, setSelectedTag] = useState(null); const [saveData, setSaveData] = useState(null); - const [datePickerVisible, setDatePickerVisible] = useState(false); const [batchImportPriceVisible, setBatchImportPriceVisible] = useState(false); const [quotationTableVisible, setQuotationTableVisible] = useState(false) const [currentid, setCurrentid] = useState(null); @@ -61,7 +56,22 @@ function Detail() { const [dataFetched, setDataFetched] = useState(false); // 添加一个标志位 const [selectedNodeKey, setSelectedNodeKey] = useState(null); const [selectedDays, setSelectedDays] = useState([]); - const [currentQuotationRecord, setCurrentQuotationRecord] = useState(null); + const [weekdays,setWeekdays] = useState([]); + const [currentQuotationRecord, setCurrentQuotationRecord] = useState({ + use_dates_start: null, + use_dates_end: null + }); + const formatDate = (date) => (date ? dayjs(date) : null); + + + const startDate = currentQuotationRecord.use_dates_start && dayjs(currentQuotationRecord.use_dates_start).isValid() + ? formatDate(currentQuotationRecord.use_dates_start) + : null; + const endDate = currentQuotationRecord.use_dates_end && dayjs(currentQuotationRecord.use_dates_end).isValid() + ? formatDate(currentQuotationRecord.use_dates_end) + : null; + + const [editIndex, setEditIndex] = useState(null); const presets = useDatePresets(); const handleBatchImportData = (data) => { setBatchImportData(data); @@ -158,11 +168,13 @@ function Detail() { })) })); }; - + const tempExpandedKeys = productsTypes.map(item => item.key) + console.log("tempExpandedKeys", tempExpandedKeys) const treeData = generateTreeData(productsTypes, agencyProducts); console.log("treeData", treeData) setDataFetched(true); // 设置标志位为 true,表示数据已获取 setTreeData(treeData); + setExpandedKeys(tempExpandedKeys) setProductsData(agencyProducts); setDefaultData(treeData); setDataList(flattenTreeData(treeData)); @@ -173,6 +185,7 @@ function Detail() { fetchData(); }, [agencyProducts, dataFetched]); + const flattenTreeData = (tree) => { let flatList = []; const flatten = (nodes) => { @@ -224,6 +237,7 @@ function Detail() { ); }; + const onChange = (e) => { const { value } = e.target; const newExpandedKeys = dataList @@ -243,72 +257,40 @@ function Detail() { const isEditing = (record) => record.id === editingid; - const edit = (record) => { - // setQuotationTableVisible(true); - // setCurrentQuotationRecord(record); - console.log("record", record) - form.setFieldsValue({ ...record }); - setEditingid(record.id); + const edit = (record, index) => { + setQuotationTableVisible(true); + setEditIndex(index); + // record.use_dates_start = dayjs(record.use_dates_start); + // record.use_dates_end = dayjs(record.use_dates_end); + setCurrentQuotationRecord(record); }; const cancel = () => { setEditingid(''); }; - const handleSave = async (id) => { - try { - const { info, ...restRow } = await form.validateFields(); - const newData = [...quotation]; - const index = newData.findIndex((item) => id === item.id); - if (index > -1) { - const item = newData[index]; - newData.splice(index, 1, { ...item, ...restRow }); - delete newData[index].quotation - delete newData[index].extras - console.log("newData", newData) - - //按人等范围排序 - const sortedData = [...newData].sort((a, b) => { - // 首先按照 group_size_min 升序排序 - if (a.group_size_min !== b.group_size_min) { - return a.group_size_min - b.group_size_min; - } - // 如果 group_size_min 相同,则按照 group_size_max 升序排序 - return a.group_size_max - b.group_size_max; - }); - setQuotation(sortedData); - setEditingid(''); - } else { - newData.push(restRow); - setQuotation(newData); - setEditingid(''); - } - } catch (errInfo) { - console.log('Validate Failed:', errInfo); - } - }; - const handleDelete = (id) => { const newData = [...quotation]; const index = newData.findIndex((item) => id === item.id); newData.splice(index, 1); - //按人等范围排序 const sortedData = [...newData].sort((a, b) => { - // 首先按照 group_size_min 升序排序 - if (a.group_size_min !== b.group_size_min) { - return a.group_size_min - b.group_size_min; + const aValidPeriod = dayjs(a.use_dates_end).diff(dayjs(a.use_dates_start)); + const bValidPeriod = dayjs(b.use_dates_end).diff(dayjs(b.use_dates_start)); + if (aValidPeriod !== bValidPeriod) { + return aValidPeriod - bValidPeriod; } - // 如果 group_size_min 相同,则按照 group_size_max 升序排序 - return a.group_size_max - b.group_size_max; - }); + const aGroupSize = a.group_size_max - a.group_size_min; + const bGroupSize = b.group_size_max - b.group_size_min; + + return aGroupSize - bGroupSize; + }); setQuotation(sortedData); }; const handleAdd = () => { const newData = { - // id: `${quotation.length + 1}`, value: '', currency: '', unit_name: '', @@ -331,164 +313,60 @@ function Detail() { }; const quotationTableVisibleOK = () => { + currentQuotationRecord.use_dates_start = dayjs(currentQuotationRecord.use_dates_start).format('YYYY-MM-DD') + currentQuotationRecord.use_dates_end = dayjs(currentQuotationRecord.use_dates_end).format('YYYY-MM-DD') + console.log("currentQuotationRecord", currentQuotationRecord); + console.log("qqqqq", quotation) + const tempQuotation = [...quotation]; + tempQuotation[editIndex] = { ...currentQuotationRecord,weekdays:weekdays }; + console.log("tempQuotation", tempQuotation) + const sortedData = [...tempQuotation].sort((a, b) => { + const aValidPeriod = dayjs(a.use_dates_end).diff(dayjs(a.use_dates_start)); + const bValidPeriod = dayjs(b.use_dates_end).diff(dayjs(b.use_dates_start)); + + if (aValidPeriod !== bValidPeriod) { + return aValidPeriod - bValidPeriod; + } + const aGroupSize = a.group_size_max - a.group_size_min; + const bGroupSize = b.group_size_max - b.group_size_min; + + return aGroupSize - bGroupSize; + }); + console.log("sortedData",sortedData) + + setQuotation(sortedData); setQuotationTableVisible(false); } const quotationTableVisibleCancel = () => { setQuotationTableVisible(false); } - const handleDateChange = ({ dateRange, selectedDays }) => { - - console.log("dateRange", dateRange) - console.log("selectedDays", selectedDays) - - // 计算周末 - const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; - let weekDayCount = selectedDays.map(day => weekdays.indexOf(day) + 1).sort().join(','); - if (!weekDayCount || weekDayCount.length === 0) { - weekDayCount = "全年"; - } - const newData = [...quotation]; - const index = newData.findIndex((item) => currentid === item.id); - if (index > -1) { - newData[index].weekdays = weekDayCount; - setQuotation(newData); - } - - setSelectedDateData({ dateRange, selectedDays }) - }; - - const handleDateOk = () => { - let { dateRange } = selectedDateData; - console.log("handleDateOk_dateRange", dateRange) - const use_dates_start = dateRange[0]; - const use_dates_end = dateRange[1]; - if (currentid !== null) { - const newData = [...quotation]; - const index = newData.findIndex((item) => currentid === item.id); - if (index > -1) { - newData[index].use_dates_start = use_dates_start; - newData[index].use_dates_end = use_dates_end; - console.log("newData", newData) - setQuotation(newData); - setCurrentid(null); - } - } - setSelectedDateData({ dateRange: null, selectedDays: [] }) - setDatePickerVisible(false); - } const handleBatchImportOK = () => { console.log("quotation", quotation) console.log('Batch Import Data:', batchImportData); - // 创建一个新的数据副本,删除 tag 和 validPeriod 属性 + const tempBatchImportData = batchImportData.map(item => { const { tag, validPeriod, ...rest } = item; - return rest; // 返回剩余的属性 + return rest; }); - // 将剩余的属性添加到 quotation 中 const newData = [...quotation, ...tempBatchImportData]; - - //按人等范围排序 const sortedData = [...newData].sort((a, b) => { - // 首先按照 group_size_min 升序排序 if (a.group_size_min !== b.group_size_min) { return a.group_size_min - b.group_size_min; } - // 如果 group_size_min 相同,则按照 group_size_max 升序排序 + return a.group_size_max - b.group_size_max; }); - // 更新状态来更新页面 + setQuotation(sortedData); setBatchImportPriceVisible(false); } - const EditableCell = ({ editing, dataIndex, title, inputType, record, children, handleDateSelect, ...restProps }) => { - let inputNode = inputType === 'number' ? : ; - if (dataIndex === 'validityPeriod' && editing) { - return ( - - ); - } - - if (dataIndex === 'unit_name' && editing) { - inputNode = ( - - ); - } - - if (dataIndex === 'currency' && editing) { - inputNode = ( - - ); - } - - if (dataIndex === 'group_size' && editing) { - return ( - - ); - } - - return ( - - ); - }; - - const handleInputGroupSize = (name, id, dataIndex, value) => { - - const newData = [...quotation]; - const index = newData.findIndex((item) => id === item.id); - if (index > -1) { - const item = newData[index]; - newData[index] = { ...item, } - if (name === 'group_size_min') { - newData[index] = { ...item, group_size_min: value }; - } else { - newData[index] = { ...item, group_size_max: value }; - } - setQuotation(newData); - } - - } const columns = [ { title: t('products:adultPrice'), dataIndex: 'adult_cost', width: '10%', editable: true }, @@ -516,43 +394,37 @@ function Detail() { { title: t('products:operation'), dataIndex: 'operation', - render: (_, record) => { - const editable = isEditing(record); - return editable ? ( - - - - - ) : ( + render: (_, record, index) => { + return ( - edit(record)} style={{ marginRight: 8 }}>{t('Edit')} + edit(record, index)} style={{ marginRight: 8 }}>{t('Edit')} handleDelete(record.id)}> {t('Delete')} - ); + ) }, }, ]; - const mergedColumns = columns.map((col) => { - if (!col.editable) { - return col; - } - return { - ...col, - onCell: (record) => ({ - record, - inputType: col.dataIndex === 'age' ? 'number' : 'text', - dataIndex: col.dataIndex, - title: col.title, - editing: isEditing(record), - handleDateSelect: handleDateSelect, - }), - }; - }); + // const mergedColumns = columns.map((col) => { + // if (!col.editable) { + // return col; + // } + // return { + // ...col, + // onCell: (record) => ({ + // record, + // inputType: col.dataIndex === 'age' ? 'number' : 'text', + // dataIndex: col.dataIndex, + // title: col.title, + // editing: isEditing(record), + // handleDateSelect: handleDateSelect, + // }), + // }; + // }); const handleTagClick = (tag) => { setSelectedTag(tag); @@ -618,11 +490,17 @@ function Detail() { }; - const handleDayClick = (day) => { + const handleDayClick = (dayIndex) => { + const dayOfWeek = (dayIndex % 7) + 1; + setSelectedDays((prevSelectedDays) => { - const updatedDays = prevSelectedDays.includes(day) - ? prevSelectedDays.filter((d) => d !== day) - : [...prevSelectedDays, day]; + const updatedDays = prevSelectedDays.includes(dayOfWeek) + ? prevSelectedDays.filter((d) => d !== dayOfWeek) + : [...prevSelectedDays, dayOfWeek]; + console.log("updatedDays",updatedDays); + const weekdaysString = updatedDays.sort().join(','); + console.log("weekdaysString",weekdaysString) + setWeekdays(weekdaysString) return updatedDays; }); }; @@ -631,6 +509,7 @@ function Detail() { const handleNodeSelect = (_, { node }) => { if (!node._raw.info.id) { console.log("nodeNoID", node) + setQuotation([]) const infoData = node._raw.info const newLgcDetails = node._raw.lgc_details const fatherKey = node.key.split('-')[0]; @@ -715,9 +594,26 @@ function Detail() { setInfoDataForId(infoData.id) } setLgc_details(newLgcDetails); - setQuotation(initialQuotationData); - // 使用 setTimeout 确保 lgc_details 已经更新 + + const sortedData = [...initialQuotationData].sort((a, b) => { + // 计算有效期范围大小 + const aValidPeriod = dayjs(a.use_dates_end).diff(dayjs(a.use_dates_start)); + const bValidPeriod = dayjs(b.use_dates_end).diff(dayjs(b.use_dates_start)); + + // 按照有效期范围大小升序排序 + if (aValidPeriod !== bValidPeriod) { + return aValidPeriod - bValidPeriod; + } + + // 如果有效期范围相同,则按照人数范围大小升序排序 + const aGroupSize = a.group_size_max - a.group_size_min; + const bGroupSize = b.group_size_max - b.group_size_min; + + return aGroupSize - bGroupSize; + }); + setQuotation(sortedData); + if (node._raw.info.id) { form.setFieldsValue({ info: { @@ -870,12 +766,12 @@ function Detail() {
- + - {t('products:supplierQuotation')}
+ handleInputGroupSize('group_size_min', currentQuotationRecord.id, 'group_size', value)} + style={{ width: '50%', marginRight: '10px' }} + /> + - + handleInputGroupSize('group_size_max', currentQuotationRecord.id, 'group_size', value)} + style={{ width: '50%', marginLeft: '10px' }} + /> +
- {children} - - - handleInputGroupSize('group_size_min', record.id, 'group_size', value)} - style={{ width: '50%', marginRight: '10px' }} - /> - - - handleInputGroupSize('group_size_max', record.id, 'group_size', value)} - style={{ width: '50%', marginLeft: '10px' }} - /> - - {editing ? ( - - {inputNode} - - ) : ( - children - )} -
@@ -1010,18 +905,6 @@ function Detail() { - {datePickerVisible && ( - setDatePickerVisible(false)} - > - - - )} - - { batchImportPriceVisible && (

成人价

- + setCurrentQuotationRecord({ ...currentQuotationRecord, adult_cost: e })} />

儿童价

- + setCurrentQuotationRecord({ ...currentQuotationRecord, child_cost: e })} />

币种

- setCurrentQuotationRecord({ ...currentQuotationRecord, currency: e })}> + RMB USD

类型

- setCurrentQuotationRecord({ ...currentQuotationRecord, unit_name: e })}> 每人 每团 @@ -1093,26 +976,39 @@ function Detail() {
-

有效期

- -

周末

+

有效期

+ { + setCurrentQuotationRecord({ + ...currentQuotationRecord, + use_dates_start: dates[0], + use_dates_end: dates[1] + }); + }} + /> +

周末

{days.map((day, index) => ( + + ), }, @@ -72,19 +80,19 @@ function Index() { { handleSearchAgency(formVal); @@ -93,28 +101,16 @@ function Index() {
handleInputGroupSize('group_size_min', currentQuotationRecord.id, 'group_size', value)} + defaultValue={currentQuotationRecord.group_size_min} + onChange={(e) => setCurrentQuotationRecord({ ...currentQuotationRecord, group_size_min: e })} style={{ width: '50%', marginRight: '10px' }} /> - handleInputGroupSize('group_size_max', currentQuotationRecord.id, 'group_size', value)} + defaultValue={currentQuotationRecord.group_size_max} + onChange={(e) => setCurrentQuotationRecord({ ...currentQuotationRecord, group_size_max: e })} style={{ width: '50%', marginLeft: '10px' }} />
{/* 复制弹窗 */} - setCopyModalVisible(false)} destroyOnClose> -
复制源: {sourceAgency.travel_agency_name}
- { - handleCopyAgency(formVal); - }} - /> -
+ setCopyModalVisible(false)} + onSubmit={(formVal) => { + handleCopyAgency(formVal); + }} + {...{copyModalVisible, setCopyModalVisible}} + /> ); } From e1ec006772305d248bf9a62b87e383fc043349b9 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 10 Jul 2024 17:24:34 +0800 Subject: [PATCH 109/120] =?UTF-8?q?=E5=AE=A1=E6=A0=B8=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E5=92=8C=E5=90=8E=E7=AB=AF=E7=BB=9F=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/products.json | 1 + public/locales/zh/products.json | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/public/locales/en/products.json b/public/locales/en/products.json index 281e485..384e1f5 100644 --- a/public/locales/en/products.json +++ b/public/locales/en/products.json @@ -96,6 +96,7 @@ }, "CopyFormMsg": { + "Source": "Source ", "target": "Target ", "requiredVendor": "Please pick a target vendor", "requiredTypes": "Please select product types", diff --git a/public/locales/zh/products.json b/public/locales/zh/products.json index e43e37e..c8b56a4 100644 --- a/public/locales/zh/products.json +++ b/public/locales/zh/products.json @@ -18,8 +18,8 @@ "auditState": { "New": "新增", "Pending": "待审核", - "Approved": "已通过", - "Rejected": "已拒绝", + "Approved": "已审核", + "Rejected": "未通过", "Published": "已发布" }, "auditStateAction": { @@ -96,6 +96,7 @@ }, "CopyFormMsg": { + "Source": "源", "target": "目标", "requiredVendor": "请选择目标供应商", "requiredTypes": "请选择产品类型", From c6739e5279a5c7a54b31f76f93735d08d4fbecb7 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 10 Jul 2024 17:25:04 +0800 Subject: [PATCH 110/120] =?UTF-8?q?perf:=20=E5=85=AC=E5=85=B1=E5=87=BD?= =?UTF-8?q?=E6=95=B0:=20=E6=B7=B1=E6=8B=B7=E8=B4=9D.=20=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E5=86=85=E5=AD=98=E6=B3=84=E6=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/commons.js | 56 +++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/src/utils/commons.js b/src/utils/commons.js index 2e83226..29fd1d6 100644 --- a/src/utils/commons.js +++ b/src/utils/commons.js @@ -255,23 +255,47 @@ export function omit(object, keysToOmit) { /** * 深拷贝 */ -export function cloneDeep(value) { - // return structuredClone(value); - if (typeof value !== "object" || value === null) { - return value; - } - - const result = Array.isArray(value) ? [] : {}; - - for (const key in value) { - if (Object.prototype.hasOwnProperty.call(value, key)) { - result[key] = cloneDeep(value[key]); - } - } - - return result; +export function cloneDeep(value, visited = new WeakMap()) { + // 处理循环引用 + if (visited.has(value)) { + return visited.get(value); + } + + // 特殊对象和基本类型处理 + if (value instanceof Date) { + return new Date(value); + } + if (value instanceof RegExp) { + return new RegExp(value.source, value.flags); + } + if (value === null || typeof value !== 'object') { + return value; + } + + // 创建一个新的WeakMap项以避免内存泄漏 + let result; + if (Array.isArray(value)) { + result = []; + visited.set(value, result); + } else { + result = {}; + visited.set(value, result); + } + + for (const key of Object.getOwnPropertySymbols(value)) { + // 处理Symbol属性 + result[key] = cloneDeep(value[key], visited); + } + + for (const key in value) { + if (Object.prototype.hasOwnProperty.call(value, key)) { + // 处理普通属性 + result[key] = cloneDeep(value[key], visited); + } + } + + return result; } - /** * 向零四舍五入, 固定精度设置 */ From 9491ada91ace42a71c2391b63761d44d8fb3a48d Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 10 Jul 2024 17:25:52 +0800 Subject: [PATCH 111/120] =?UTF-8?q?=E5=AE=A1=E6=A0=B8=E4=BB=B7=E6=A0=BC:?= =?UTF-8?q?=20=E5=8D=95=E8=A1=8C=E5=AE=A1=E6=A0=B8:=20=E5=8F=96=E6=B6=88?= =?UTF-8?q?=E6=AF=8F=E6=AC=A1=E4=BB=8E=E5=90=8E=E7=AB=AF=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=95=B0=E6=8D=AE,=20=E4=BB=85=E5=89=8D=E7=AB=AF=E5=93=8D?= =?UTF-8?q?=E5=BA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/products/Audit.jsx | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index 9a7333a..2228b64 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -5,7 +5,7 @@ import { useProductsTypes, useProductsAuditStatesMapVal } from '@/hooks/useProdu import SecondHeaderWrapper from '@/components/SecondHeaderWrapper'; import { useTranslation } from 'react-i18next'; import useProductsStore, { postProductsQuoteAuditAction, } from '@/stores/Products/Index'; -import { isEmpty } from '@/utils/commons'; +import { cloneDeep, isEmpty } from '@/utils/commons'; import useAuthStore from '@/stores/Auth'; import RequireAuth from '@/components/RequireAuth'; // import PrintContractPDF from './PrintContractPDF'; @@ -80,15 +80,22 @@ const PriceTable = ({ productType, dataSource, refresh }) => { const { message, notification } = App.useApp(); const stateMapVal = useProductsAuditStatesMapVal(); + const [renderData, setRenderData] = useState(dataSource); + // console.log(dataSource); - const handleAuditPriceItem = (state, row) => { + const handleAuditPriceItem = (state, row, rowIndex) => { postProductsQuoteAuditAction(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(); + // refresh(); // debug: 不要刷新, 等太久 + // const newData = structuredClone(renderData); + const newData = cloneDeep(renderData); + newData.splice(rowIndex, 1, {...row, audit_state_id: state, }); + setRenderData(newData); } } }) @@ -142,18 +149,18 @@ const PriceTable = ({ productType, dataSource, refresh }) => { { title: '', key: 'action', - render: (_, r) => - r.audit_state_id <= 0 ? ( + render: (_, r, ri) => + (Number(r.audit_state_id)) === 0 ? ( - - + + ) : null, }, ]; - return
r.id} />; + return
r.id} />; }; /** From b5953c5c121aa723baebd37ec25391ff8a5de91f Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 10 Jul 2024 17:26:33 +0800 Subject: [PATCH 112/120] =?UTF-8?q?style:=20=E9=A1=B6=E9=83=A8:=20?= =?UTF-8?q?=E8=B4=A6=E6=88=B7=E5=90=8D,=20=E5=A4=AA=E9=95=BF=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E6=8D=A2=E8=A1=8C,=20=E6=A0=B7=E5=BC=8F=E6=92=91?= =?UTF-8?q?=E5=BC=80=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/App.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/App.jsx b/src/views/App.jsx index 73abe43..1f8ffbe 100644 --- a/src/views/App.jsx +++ b/src/views/App.jsx @@ -152,7 +152,7 @@ function App() { > e.preventDefault()}> - {currentUser?.realname} +
{currentUser?.realname}
From b22e9d2eddecddbf72194a17e73df884ea7abbd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E6=96=87=E5=BC=BA=40HWQ-PC?= Date: Wed, 10 Jul 2024 16:34:08 +0800 Subject: [PATCH 113/120] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8A=A5=E4=BB=B7?= =?UTF-8?q?=E6=8A=A5=E4=BB=B7=E7=9A=84=E7=BC=96=E8=BE=91=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/products/Detail.jsx | 394 +++++++----------- ...hImportPrice1.jsx => BatchImportPrice.jsx} | 7 - .../Detail/addValidityWithWeekend.jsx | 22 - 3 files changed, 145 insertions(+), 278 deletions(-) rename src/views/products/Detail/{BatchImportPrice1.jsx => BatchImportPrice.jsx} (97%) diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx index 7655105..f624d56 100644 --- a/src/views/products/Detail.jsx +++ b/src/views/products/Detail.jsx @@ -2,19 +2,15 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'; import { Button, Card, Col, Row, Breadcrumb, Table, Popconfirm, Form, Input, InputNumber, Tag, Modal, Select, Tree, FloatButton, DatePicker } from 'antd'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import AddValidityWithWeekend from '@/views/products/Detail/addValidityWithWeekend'; -import { getAgencyProductsAction } from '@/stores/Products/Index'; import { useProductsTypes } from '@/hooks/useProductsSets'; import Extras from './Detail/Extras'; -import { groupBy } from '@/utils/commons'; import { useParams } from 'react-router-dom'; import useProductsStore from '@/stores/Products/Index'; import { useHTLanguageSets } from '@/hooks/useHTLanguageSets'; import { useDefaultLgc } from '@/i18n/LanguageSwitcher'; -import BatchImportPrice from './Detail/BatchImportPrice1'; +import BatchImportPrice from './Detail/BatchImportPrice'; import dayjs from 'dayjs'; import { PlusCircleFilled } from '@ant-design/icons'; -import { info } from 'autoprefixer'; import { DeptSelector } from '@/components/DeptSelector'; import { useDatePresets } from '@/hooks/useDatePresets'; @@ -27,7 +23,6 @@ function Detail() { const [isModalVisible, setIsModalVisible] = useState(false); const [selectedTag, setSelectedTag] = useState(null); const [saveData, setSaveData] = useState(null); - const [datePickerVisible, setDatePickerVisible] = useState(false); const [batchImportPriceVisible, setBatchImportPriceVisible] = useState(false); const [quotationTableVisible, setQuotationTableVisible] = useState(false) const [currentid, setCurrentid] = useState(null); @@ -61,7 +56,22 @@ function Detail() { const [dataFetched, setDataFetched] = useState(false); // 添加一个标志位 const [selectedNodeKey, setSelectedNodeKey] = useState(null); const [selectedDays, setSelectedDays] = useState([]); - const [currentQuotationRecord, setCurrentQuotationRecord] = useState(null); + const [weekdays,setWeekdays] = useState([]); + const [currentQuotationRecord, setCurrentQuotationRecord] = useState({ + use_dates_start: null, + use_dates_end: null + }); + const formatDate = (date) => (date ? dayjs(date) : null); + + + const startDate = currentQuotationRecord.use_dates_start && dayjs(currentQuotationRecord.use_dates_start).isValid() + ? formatDate(currentQuotationRecord.use_dates_start) + : null; + const endDate = currentQuotationRecord.use_dates_end && dayjs(currentQuotationRecord.use_dates_end).isValid() + ? formatDate(currentQuotationRecord.use_dates_end) + : null; + + const [editIndex, setEditIndex] = useState(null); const presets = useDatePresets(); const handleBatchImportData = (data) => { setBatchImportData(data); @@ -158,11 +168,13 @@ function Detail() { })) })); }; - + const tempExpandedKeys = productsTypes.map(item => item.key) + console.log("tempExpandedKeys", tempExpandedKeys) const treeData = generateTreeData(productsTypes, agencyProducts); console.log("treeData", treeData) setDataFetched(true); // 设置标志位为 true,表示数据已获取 setTreeData(treeData); + setExpandedKeys(tempExpandedKeys) setProductsData(agencyProducts); setDefaultData(treeData); setDataList(flattenTreeData(treeData)); @@ -173,6 +185,7 @@ function Detail() { fetchData(); }, [agencyProducts, dataFetched]); + const flattenTreeData = (tree) => { let flatList = []; const flatten = (nodes) => { @@ -224,6 +237,7 @@ function Detail() { ); }; + const onChange = (e) => { const { value } = e.target; const newExpandedKeys = dataList @@ -243,72 +257,40 @@ function Detail() { const isEditing = (record) => record.id === editingid; - const edit = (record) => { - // setQuotationTableVisible(true); - // setCurrentQuotationRecord(record); - console.log("record", record) - form.setFieldsValue({ ...record }); - setEditingid(record.id); + const edit = (record, index) => { + setQuotationTableVisible(true); + setEditIndex(index); + // record.use_dates_start = dayjs(record.use_dates_start); + // record.use_dates_end = dayjs(record.use_dates_end); + setCurrentQuotationRecord(record); }; const cancel = () => { setEditingid(''); }; - const handleSave = async (id) => { - try { - const { info, ...restRow } = await form.validateFields(); - const newData = [...quotation]; - const index = newData.findIndex((item) => id === item.id); - if (index > -1) { - const item = newData[index]; - newData.splice(index, 1, { ...item, ...restRow }); - delete newData[index].quotation - delete newData[index].extras - console.log("newData", newData) - - //按人等范围排序 - const sortedData = [...newData].sort((a, b) => { - // 首先按照 group_size_min 升序排序 - if (a.group_size_min !== b.group_size_min) { - return a.group_size_min - b.group_size_min; - } - // 如果 group_size_min 相同,则按照 group_size_max 升序排序 - return a.group_size_max - b.group_size_max; - }); - setQuotation(sortedData); - setEditingid(''); - } else { - newData.push(restRow); - setQuotation(newData); - setEditingid(''); - } - } catch (errInfo) { - console.log('Validate Failed:', errInfo); - } - }; - const handleDelete = (id) => { const newData = [...quotation]; const index = newData.findIndex((item) => id === item.id); newData.splice(index, 1); - //按人等范围排序 const sortedData = [...newData].sort((a, b) => { - // 首先按照 group_size_min 升序排序 - if (a.group_size_min !== b.group_size_min) { - return a.group_size_min - b.group_size_min; + const aValidPeriod = dayjs(a.use_dates_end).diff(dayjs(a.use_dates_start)); + const bValidPeriod = dayjs(b.use_dates_end).diff(dayjs(b.use_dates_start)); + if (aValidPeriod !== bValidPeriod) { + return aValidPeriod - bValidPeriod; } - // 如果 group_size_min 相同,则按照 group_size_max 升序排序 - return a.group_size_max - b.group_size_max; - }); + const aGroupSize = a.group_size_max - a.group_size_min; + const bGroupSize = b.group_size_max - b.group_size_min; + + return aGroupSize - bGroupSize; + }); setQuotation(sortedData); }; const handleAdd = () => { const newData = { - // id: `${quotation.length + 1}`, value: '', currency: '', unit_name: '', @@ -331,164 +313,60 @@ function Detail() { }; const quotationTableVisibleOK = () => { + currentQuotationRecord.use_dates_start = dayjs(currentQuotationRecord.use_dates_start).format('YYYY-MM-DD') + currentQuotationRecord.use_dates_end = dayjs(currentQuotationRecord.use_dates_end).format('YYYY-MM-DD') + console.log("currentQuotationRecord", currentQuotationRecord); + console.log("qqqqq", quotation) + const tempQuotation = [...quotation]; + tempQuotation[editIndex] = { ...currentQuotationRecord,weekdays:weekdays }; + console.log("tempQuotation", tempQuotation) + const sortedData = [...tempQuotation].sort((a, b) => { + const aValidPeriod = dayjs(a.use_dates_end).diff(dayjs(a.use_dates_start)); + const bValidPeriod = dayjs(b.use_dates_end).diff(dayjs(b.use_dates_start)); + + if (aValidPeriod !== bValidPeriod) { + return aValidPeriod - bValidPeriod; + } + const aGroupSize = a.group_size_max - a.group_size_min; + const bGroupSize = b.group_size_max - b.group_size_min; + + return aGroupSize - bGroupSize; + }); + console.log("sortedData",sortedData) + + setQuotation(sortedData); setQuotationTableVisible(false); } const quotationTableVisibleCancel = () => { setQuotationTableVisible(false); } - const handleDateChange = ({ dateRange, selectedDays }) => { - - console.log("dateRange", dateRange) - console.log("selectedDays", selectedDays) - - // 计算周末 - const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; - let weekDayCount = selectedDays.map(day => weekdays.indexOf(day) + 1).sort().join(','); - if (!weekDayCount || weekDayCount.length === 0) { - weekDayCount = "全年"; - } - const newData = [...quotation]; - const index = newData.findIndex((item) => currentid === item.id); - if (index > -1) { - newData[index].weekdays = weekDayCount; - setQuotation(newData); - } - - setSelectedDateData({ dateRange, selectedDays }) - }; - - const handleDateOk = () => { - let { dateRange } = selectedDateData; - console.log("handleDateOk_dateRange", dateRange) - const use_dates_start = dateRange[0]; - const use_dates_end = dateRange[1]; - if (currentid !== null) { - const newData = [...quotation]; - const index = newData.findIndex((item) => currentid === item.id); - if (index > -1) { - newData[index].use_dates_start = use_dates_start; - newData[index].use_dates_end = use_dates_end; - console.log("newData", newData) - setQuotation(newData); - setCurrentid(null); - } - } - setSelectedDateData({ dateRange: null, selectedDays: [] }) - setDatePickerVisible(false); - } const handleBatchImportOK = () => { console.log("quotation", quotation) console.log('Batch Import Data:', batchImportData); - // 创建一个新的数据副本,删除 tag 和 validPeriod 属性 + const tempBatchImportData = batchImportData.map(item => { const { tag, validPeriod, ...rest } = item; - return rest; // 返回剩余的属性 + return rest; }); - // 将剩余的属性添加到 quotation 中 const newData = [...quotation, ...tempBatchImportData]; - - //按人等范围排序 const sortedData = [...newData].sort((a, b) => { - // 首先按照 group_size_min 升序排序 if (a.group_size_min !== b.group_size_min) { return a.group_size_min - b.group_size_min; } - // 如果 group_size_min 相同,则按照 group_size_max 升序排序 + return a.group_size_max - b.group_size_max; }); - // 更新状态来更新页面 + setQuotation(sortedData); setBatchImportPriceVisible(false); } - const EditableCell = ({ editing, dataIndex, title, inputType, record, children, handleDateSelect, ...restProps }) => { - let inputNode = inputType === 'number' ? : ; - if (dataIndex === 'validityPeriod' && editing) { - return ( -
- ); - } - - if (dataIndex === 'unit_name' && editing) { - inputNode = ( - - ); - } - - if (dataIndex === 'currency' && editing) { - inputNode = ( - - ); - } - - if (dataIndex === 'group_size' && editing) { - return ( - - ); - } - - return ( - - ); - }; - - const handleInputGroupSize = (name, id, dataIndex, value) => { - - const newData = [...quotation]; - const index = newData.findIndex((item) => id === item.id); - if (index > -1) { - const item = newData[index]; - newData[index] = { ...item, } - if (name === 'group_size_min') { - newData[index] = { ...item, group_size_min: value }; - } else { - newData[index] = { ...item, group_size_max: value }; - } - setQuotation(newData); - } - - } const columns = [ { title: t('products:adultPrice'), dataIndex: 'adult_cost', width: '10%', editable: true }, @@ -516,43 +394,37 @@ function Detail() { { title: t('products:operation'), dataIndex: 'operation', - render: (_, record) => { - const editable = isEditing(record); - return editable ? ( - - - - - ) : ( + render: (_, record, index) => { + return ( - edit(record)} style={{ marginRight: 8 }}>{t('Edit')} + edit(record, index)} style={{ marginRight: 8 }}>{t('Edit')} handleDelete(record.id)}> {t('Delete')} - ); + ) }, }, ]; - const mergedColumns = columns.map((col) => { - if (!col.editable) { - return col; - } - return { - ...col, - onCell: (record) => ({ - record, - inputType: col.dataIndex === 'age' ? 'number' : 'text', - dataIndex: col.dataIndex, - title: col.title, - editing: isEditing(record), - handleDateSelect: handleDateSelect, - }), - }; - }); + // const mergedColumns = columns.map((col) => { + // if (!col.editable) { + // return col; + // } + // return { + // ...col, + // onCell: (record) => ({ + // record, + // inputType: col.dataIndex === 'age' ? 'number' : 'text', + // dataIndex: col.dataIndex, + // title: col.title, + // editing: isEditing(record), + // handleDateSelect: handleDateSelect, + // }), + // }; + // }); const handleTagClick = (tag) => { setSelectedTag(tag); @@ -618,11 +490,17 @@ function Detail() { }; - const handleDayClick = (day) => { + const handleDayClick = (dayIndex) => { + const dayOfWeek = (dayIndex % 7) + 1; + setSelectedDays((prevSelectedDays) => { - const updatedDays = prevSelectedDays.includes(day) - ? prevSelectedDays.filter((d) => d !== day) - : [...prevSelectedDays, day]; + const updatedDays = prevSelectedDays.includes(dayOfWeek) + ? prevSelectedDays.filter((d) => d !== dayOfWeek) + : [...prevSelectedDays, dayOfWeek]; + console.log("updatedDays",updatedDays); + const weekdaysString = updatedDays.sort().join(','); + console.log("weekdaysString",weekdaysString) + setWeekdays(weekdaysString) return updatedDays; }); }; @@ -631,6 +509,7 @@ function Detail() { const handleNodeSelect = (_, { node }) => { if (!node._raw.info.id) { console.log("nodeNoID", node) + setQuotation([]) const infoData = node._raw.info const newLgcDetails = node._raw.lgc_details const fatherKey = node.key.split('-')[0]; @@ -715,9 +594,26 @@ function Detail() { setInfoDataForId(infoData.id) } setLgc_details(newLgcDetails); - setQuotation(initialQuotationData); - // 使用 setTimeout 确保 lgc_details 已经更新 + + const sortedData = [...initialQuotationData].sort((a, b) => { + // 计算有效期范围大小 + const aValidPeriod = dayjs(a.use_dates_end).diff(dayjs(a.use_dates_start)); + const bValidPeriod = dayjs(b.use_dates_end).diff(dayjs(b.use_dates_start)); + + // 按照有效期范围大小升序排序 + if (aValidPeriod !== bValidPeriod) { + return aValidPeriod - bValidPeriod; + } + + // 如果有效期范围相同,则按照人数范围大小升序排序 + const aGroupSize = a.group_size_max - a.group_size_min; + const bGroupSize = b.group_size_max - b.group_size_min; + + return aGroupSize - bGroupSize; + }); + setQuotation(sortedData); + if (node._raw.info.id) { form.setFieldsValue({ info: { @@ -870,12 +766,12 @@ function Detail() {
- + - {t('products:supplierQuotation')}
- {children} - - - handleInputGroupSize('group_size_min', record.id, 'group_size', value)} - style={{ width: '50%', marginRight: '10px' }} - /> - - - handleInputGroupSize('group_size_max', record.id, 'group_size', value)} - style={{ width: '50%', marginLeft: '10px' }} - /> - - {editing ? ( - - {inputNode} - - ) : ( - children - )} -
@@ -1010,18 +905,6 @@ function Detail() { - {datePickerVisible && ( - setDatePickerVisible(false)} - > - - - )} - - { batchImportPriceVisible && (

成人价

- + setCurrentQuotationRecord({ ...currentQuotationRecord, adult_cost: e })} />

儿童价

- + setCurrentQuotationRecord({ ...currentQuotationRecord, child_cost: e })} />

币种

- setCurrentQuotationRecord({ ...currentQuotationRecord, currency: e })}> + RMB USD

类型

- setCurrentQuotationRecord({ ...currentQuotationRecord, unit_name: e })}> 每人 每团 @@ -1093,26 +976,39 @@ function Detail() {
-

有效期

- -

周末

+

有效期

+ { + setCurrentQuotationRecord({ + ...currentQuotationRecord, + use_dates_start: dates[0], + use_dates_end: dates[1] + }); + }} + /> +

周末

{days.map((day, index) => (
+ App logo @@ -130,7 +130,7 @@ function App() { ]} /> - +

{currentUser?.travelAgencyName}

From d7118b07912bf6b26d41c3f3745a1ffe93a55861 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 10 Jul 2024 15:04:08 +0800 Subject: [PATCH 115/120] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4=20Mobx=20?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Auth.js | 18 ++++++++---------- src/stores/Reservation.js | 38 +++++++++++++++++--------------------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/src/stores/Auth.js b/src/stores/Auth.js index 8b17a0b..ef05fdc 100644 --- a/src/stores/Auth.js +++ b/src/stores/Auth.js @@ -4,8 +4,6 @@ import { HT_HOST } from "@/config" import { loadPageSpy } from '@/pageSpy' import { usingStorage } from '@/hooks/usingStorage' -import { lifecycleware } from '@/utils/lifecycle' - const KEY_LOGIN_TOKEN = 'G-STR:LOGIN_TOKEN' const KEY_TRAVEL_AGENCY_ID = 'G-INT:TRAVEL_AGENCY_ID' const KEY_USER_ID = 'G-INT:USER_ID' @@ -161,20 +159,20 @@ const useAuthStore = create((set, get) => ({ // TODO: 迁移到 Account.js changeUserPassword: (password, newPassword) => { const { userId } = usingStorage() - const formData = new FormData(); - formData.append('UserID', userId); - formData.append('Password', password); - formData.append('NewPassword', newPassword); - const postUrl = HT_HOST + '/service-CooperateSOA/SetPassword'; + const formData = new FormData() + formData.append('UserID', userId) + formData.append('Password', password) + formData.append('NewPassword', newPassword) + const postUrl = HT_HOST + '/service-CooperateSOA/SetPassword' return postForm(postUrl, formData) .then(json => { if (json.errcode == 0) { - return json; + return json } else { - throw new Error(json.errmsg + ': ' + json.errcode); + throw new Error(json.errmsg + ': ' + json.errcode) } - }); + }) }, isPermitted: (perm) => { diff --git a/src/stores/Reservation.js b/src/stores/Reservation.js index d57ac56..2984fe2 100644 --- a/src/stores/Reservation.js +++ b/src/stores/Reservation.js @@ -8,7 +8,7 @@ export const fetchCityList = async (travelAgencyId, reservationId) => { const { errcode, Result } = await fetchJSON( `${HT_HOST}/service-cusservice/PTGetCityGuide`, { VEI_SN: travelAgencyId, GRI_SN: reservationId, LGC: 1 }) - return errcode !== 0 ? {} : Result; + return errcode !== 0 ? {} : Result } export const fetchPlanDetail = async (travelAgencyId, reservationId) => { @@ -31,7 +31,7 @@ export const fetchAttachList = async (reservationId) => { const { errcode, result } = await fetchJSON( `${HT_HOST}/service-fileServer/PlanChangeFileList`, { GRI_SN: reservationId }) - return errcode !== 0 ? {} : result; + return errcode !== 0 ? {} : result } const useReservationStore = create((set, get) => ({ @@ -105,12 +105,12 @@ const useReservationStore = create((set, get) => ({ .append('TotalNum', totalNum) .append('PageSize', reservationPage.size) .append('PageIndex', current) - .build(); + .build() return fetchJSON(fetchUrl) .then(json => { if (json.errcode == 0) { - const mapReservationList = (json?.Result??[]).map((data, index) => { + const mapReservationList = (json?.Result??[]).map((data) => { return { key: data.vas_gri_sn, reservationId: data.vas_gri_sn, @@ -132,32 +132,32 @@ const useReservationStore = create((set, get) => ({ } })) } else { - throw new Error(json.errmsg + ': ' + json.errcode); + throw new Error(json.errmsg + ': ' + json.errcode) } - }); + }) }, fetchAllGuideList: () => { const { travelAgencyId } = usingStorage() const fetchUrl = prepareUrl(HT_HOST + '/service-cusservice/PTGetGuideList') .append('VEI_SN', travelAgencyId) - .build(); + .build() return fetchJSON(fetchUrl) .then(json => { if (json.errcode == 0) { - const guideList = (json?.Result??[]).map((data, index) => { + const guideList = (json?.Result??[]).map((data) => { return { guideId: data.TGI_SN, guideName: data.TGI2_Name, mobileNo: data.TGI_Mobile } - }); - return guideList; + }) + return guideList } else { - throw new Error(json.errmsg + ': ' + json.errcode); + throw new Error(json.errmsg + ': ' + json.errcode) } - }); + }) }, getReservationDetail: async (reservationId) => { @@ -167,8 +167,8 @@ const useReservationStore = create((set, get) => ({ const mapConfirmationList = planChangeList.map((data) => { const filterAttchList = attachListJson.filter(attch => { - return attch.PCI_SN === data.PCI_SN; - }); + return attch.PCI_SN === data.PCI_SN + }) return { key: data.PCI_SN, PCI_Changetext: data.PCI_Changetext, @@ -209,7 +209,7 @@ const useReservationStore = create((set, get) => ({ getReservationDetail(travelAgencyId, reservationDetail.reservationId) return json } - }); + }) }, setupCityGuide: (cityId, guideId) => { @@ -229,7 +229,7 @@ const useReservationStore = create((set, get) => ({ if (json.errcode != 0) { throw new Error(json.errmsg + ': ' + json.errcode) } - }); + }) }, @@ -241,7 +241,7 @@ const useReservationStore = create((set, get) => ({ .append('VEI_SN', travelAgencyId) .append('GRI_SN', selectedReservation.reservationId) .append('LGC', 1) - .build(); + .build() return fetchJSON(fetchUrl) .then(json => { @@ -252,10 +252,6 @@ const useReservationStore = create((set, get) => ({ return data.GuideName }).join(',') - runInAction(() => { - selectedReservation.guide = reservationGuide - }) - set((state) => ({ selectedReservation: { ...state.selectedReservation, From 2d9e5778593f22425f233e88f3b2264b0da3ab25 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 10 Jul 2024 15:38:58 +0800 Subject: [PATCH 116/120] =?UTF-8?q?feat:=E8=B4=A6=E5=8F=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=95=8C=E9=9D=A2=E5=A2=9E=E5=8A=A0=E4=B8=AD=E8=8B=B1?= =?UTF-8?q?=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/locales/en/account.json | 26 +++++++++++++++++++++++++- public/locales/zh/account.json | 1 + src/views/account/Management.jsx | 5 +++-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/public/locales/en/account.json b/public/locales/en/account.json index 7abb5cf..e8ea251 100644 --- a/public/locales/en/account.json +++ b/public/locales/en/account.json @@ -8,5 +8,29 @@ "CurrentPassword": "Please input your password.", "NewPassword": "Please input your new password.", "ReenterPassword": "Please reenter your password." - } + }, + "createdOn": "Created on", + "action": "Action", + "action.edit": "Edit", + "action.enable": "Enable", + "action.disable": "Disable", + "action.enable.title": "Do you want to enable account?", + "action.disable.title": "Do you want to disable account?", + "action.resetPassword": "Reset Password", + "action.resetPassword.tile": "Do you want to reset password?", + + "accountList": "Account List", + "newAccount": "New Account", + "detail": "Detail", + "username": "Username", + "realname": "Realname", + "travelAgency": "Travel Agency", + "travelAgencyName": "Travel Agency Name", + "email": "Email", + "lastLogin": "Last Login", + + "roleList": "Role List", + "newRole": "New Role", + "roleName": "Role Name", + "permission": "Permission" } \ No newline at end of file diff --git a/public/locales/zh/account.json b/public/locales/zh/account.json index a1d78c4..b38946c 100644 --- a/public/locales/zh/account.json +++ b/public/locales/zh/account.json @@ -17,6 +17,7 @@ "action.enable.title": "确定启用该账号吗?", "action.disable.title": "确定禁用该账号吗?", "action.resetPassword": "重置密码", + "action.resetPassword.tile": "确定重置账号密码吗?", "accountList": "管理账号", "newAccount": "新增账号", diff --git a/src/views/account/Management.jsx b/src/views/account/Management.jsx index 5a8abdc..7081be3 100644 --- a/src/views/account/Management.jsx +++ b/src/views/account/Management.jsx @@ -193,11 +193,12 @@ function Management() { } const showResetPasswordConfirm = (account) => { + const confirmTitle = t('account:action.resetPassword.tile') const randomPassword = account.username + '@' + (Math.floor(Math.random() * 900) + 100) modal.confirm({ - title: 'Do you want to reset password?', + title: confirmTitle, icon: , - content: `Username: ${account.username}, Realname: ${account.realname}`, + content: t('account:username') + ': ' + account.username + ', ' + t('account:realname') + ': ' + account.realname, onOk() { resetAccountPassword(account.userId, randomPassword) .then(() => { From 93572e659464d05e84673dbfe8ebfc567496e685 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Thu, 11 Jul 2024 10:05:13 +0800 Subject: [PATCH 117/120] =?UTF-8?q?feat:=20=E5=9F=8E=E5=B8=82=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CitySelector.jsx | 29 ++++++++++++++++++++++++++++ src/components/SearchForm.jsx | 15 ++++++++++++++ src/views/products/Detail/Extras.jsx | 3 ++- 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 src/components/CitySelector.jsx diff --git a/src/components/CitySelector.jsx b/src/components/CitySelector.jsx new file mode 100644 index 0000000..692cfbf --- /dev/null +++ b/src/components/CitySelector.jsx @@ -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 fetchCityList = async (q) => { + const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/search_cities`, { q }); + return errcode !== 0 ? [] : result; +}; + +const CitySelector = ({ ...props }) => { + const { t } = useTranslation(); + return ( + <> + + + ); +}; +export default CitySelector; diff --git a/src/components/SearchForm.jsx b/src/components/SearchForm.jsx index e62177c..ab0be78 100644 --- a/src/components/SearchForm.jsx +++ b/src/components/SearchForm.jsx @@ -10,6 +10,7 @@ import SearchInput from './SearchInput'; import AuditStateSelector from './AuditStateSelector'; import DeptSelector from './DeptSelector'; import ProductsTypesSelector, { fetchVendorList } from './ProductsTypesSelector'; +import CitySelector from '@/components/CitySelector'; const { RangePicker } = DatePicker; @@ -65,6 +66,12 @@ const SearchForm = ({ initialValue, onSubmit, onReset, confirmText, formName, fo 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 = {}; @@ -276,6 +283,14 @@ function getFields(props) { , fieldProps?.dept?.col || 6 ), + item( + 'city', + 99, + + + , + fieldProps?.city?.col || 4 + ), item( 'unconfirmed', 99, diff --git a/src/views/products/Detail/Extras.jsx b/src/views/products/Detail/Extras.jsx index 6446db5..203a35b 100644 --- a/src/views/products/Detail/Extras.jsx +++ b/src/views/products/Detail/Extras.jsx @@ -71,7 +71,7 @@ const NewAddonModal = ({ onPick, ...props }) => { setOpen(false)} destroyOnClose> {
handleInputGroupSize('group_size_min', currentQuotationRecord.id, 'group_size', value)} + defaultValue={currentQuotationRecord.group_size_min} + onChange={(e) => setCurrentQuotationRecord({ ...currentQuotationRecord, group_size_min: e })} style={{ width: '50%', marginRight: '10px' }} /> - handleInputGroupSize('group_size_max', currentQuotationRecord.id, 'group_size', value)} + defaultValue={currentQuotationRecord.group_size_max} + onChange={(e) => setCurrentQuotationRecord({ ...currentQuotationRecord, group_size_max: e })} style={{ width: '50%', marginLeft: '10px' }} />
Date: Thu, 11 Jul 2024 10:18:56 +0800 Subject: [PATCH 118/120] =?UTF-8?q?=E7=BB=91=E5=AE=9A=E4=BA=A7=E5=93=81:?= =?UTF-8?q?=20=E5=AE=8C=E6=AF=95=E4=B9=8B=E5=90=8E=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/products/Detail/Extras.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/products/Detail/Extras.jsx b/src/views/products/Detail/Extras.jsx index 203a35b..f9d82ec 100644 --- a/src/views/products/Detail/Extras.jsx +++ b/src/views/products/Detail/Extras.jsx @@ -119,7 +119,7 @@ const Extras = ({ productId, onChange, ...props }) => { }; const handleNewAddOn = async (item) => { - setExtrasData(prev => [].concat(prev, [item])); + // setExtrasData(prev => [].concat(prev, [item])); // todo: 提交后端; 重复绑定同一个 const _item = pick(item, ['id', 'title', 'code']); const newSuccess = await addProductExtraAction({ travel_agency_id, id: productId, extras: [_item] }); From 5b9f8eeb7f5dc3e1b9de3d15dadac356153ec01a Mon Sep 17 00:00:00 2001 From: Lei OT Date: Thu, 11 Jul 2024 13:45:48 +0800 Subject: [PATCH 119/120] =?UTF-8?q?test:=20=E7=BB=91=E5=AE=9A=E4=BA=A7?= =?UTF-8?q?=E5=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Products/Index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js index 6a92b8a..4d0f040 100644 --- a/src/stores/Products/Index.js +++ b/src/stores/Products/Index.js @@ -39,6 +39,7 @@ export const getAgencyProductsAction = async (param) => { * */ export const addProductExtraAction = async (body) => { + return true; // test: 先不更新到HT const { errcode, result } = await postJSON(`${HT_HOST}/Service_BaseInfoWeb/products_extras_add`, body); return errcode === 0 ? true : false; }; From 7a411ebfa9cac49e857c45397edff5af620ecda3 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Thu, 11 Jul 2024 13:55:49 +0800 Subject: [PATCH 120/120] =?UTF-8?q?test:=20=E7=BB=91=E5=AE=9A=E4=BA=A7?= =?UTF-8?q?=E5=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Products/Index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js index 4d0f040..a06850e 100644 --- a/src/stores/Products/Index.js +++ b/src/stores/Products/Index.js @@ -48,6 +48,7 @@ export const addProductExtraAction = async (body) => { * */ export const delProductExtrasAction = async (body) => { + return true; // test: 先不更新到HT const { errcode, result } = await postJSON(`${HT_HOST}/Service_BaseInfoWeb/products_extras_del`, body); return errcode === 0 ? true : false; };