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

perf/export-docx
YCC 1 year ago
commit 606c54a4f9

@ -44,6 +44,7 @@ http://202.103.68.111:5173/feedback/330948
--- ---
```mermaid ```mermaid
--- ---
title: GHHub 开发管理 - 机票和价格管理 title: GHHub 开发管理 - 机票和价格管理
@ -51,6 +52,7 @@ title: GHHub 开发管理 - 机票和价格管理
gitGraph TB: gitGraph TB:
commit commit
commit commit
branch release
branch feature/i18n branch feature/i18n
checkout main checkout main
commit id: "release" tag: "1.0.5.31" commit id: "release" tag: "1.0.5.31"
@ -65,16 +67,19 @@ gitGraph TB:
checkout main checkout main
commit commit
commit commit
commit id: "账户体系"
checkout feature/price_manager checkout feature/price_manager
commit commit
commit id: "完成价格" commit
checkout main commit
commit tag: "1.1.0" id: "完成机票"
branch pre-release
merge feature/price_manager id: "合并机票+价格" type:HIGHLIGHT
checkout main checkout main
merge pre-release id: "合并发布" tag: "1.2.0" merge feature/price_manager id: "合并价格" type:HIGHLIGHT
commit
commit
commit tag: "2.0.0" id: "完成机票"
commit
commit
commit id: "pre-release" tag: "pre-release" type:HIGHLIGHT
commit commit
``` ```

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

@ -15,7 +15,7 @@ import VendorSelector from '@/components/VendorSelector';
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
const SearchForm = ({ initialValue, onSubmit, onReset, confirmText, formName, formLayout, loading, ...props }) => { const SearchForm = ({ initialValue, onSubmit, onReset, onMounted, confirmText, formName, formLayout, loading, ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const presets = useDatePresets(); const presets = useDatePresets();
const [formValues, setFormValues] = useFormStore((state) => [state.formValues, state.setFormValues]); const [formValues, setFormValues] = useFormStore((state) => [state.formValues, state.setFormValues]);
@ -89,8 +89,14 @@ const SearchForm = ({ initialValue, onSubmit, onReset, confirmText, formName, fo
}; };
useEffect(() => { useEffect(() => {
const dest = formValuesMapper(formValues); setFormValues(readValues);
const dest = formValuesMapper(readValues);
setFormValuesToSub(dest); setFormValuesToSub(dest);
if (typeof onMounted === 'function') {
onMounted(dest)
}
return () => {}; return () => {};
}, []); }, []);

@ -64,6 +64,8 @@ const useAuthStore = create((set, get) => ({
const { startTokenInterval, loadUserPermission } = get() const { startTokenInterval, loadUserPermission } = get()
const { setStorage, loginToken } = usingStorage() const { setStorage, loginToken } = usingStorage()
// Dev 模式使用 localStorage会有 token 失效情况,需要手动删除
// Prod 环境没有该问题
const userJson = await fetchUserDetail(loginToken) const userJson = await fetchUserDetail(loginToken)
appendRequestParams('token', loginToken) appendRequestParams('token', loginToken)
@ -142,9 +144,9 @@ const useAuthStore = create((set, get) => ({
async function checkTokenTimeout() { async function checkTokenTimeout() {
// TODOToken 失效后要跳转到登录页面 // TODOToken 失效后要跳转到登录页面
const { LastReqDate } = await fetchLastRequet() const { LastReqDate } = await fetchLastRequet()
const lastReqDate = new Date(LastReqDate) const lastReqDateTime = new Date(LastReqDate).getTime()
const now = new Date() const now = new Date()
const diffTime = now.getTime() - lastReqDate.getTime() const diffTime = now.getTime() - lastReqDateTime
const diffHours = diffTime/1000/60/60 const diffHours = diffTime/1000/60/60
if (diffHours > 1) { if (diffHours > 1) {
logout() logout()

@ -30,7 +30,7 @@ export const copyAgencyDataAction = async (postbody) => {
}; };
export const getAgencyProductsAction = async (param) => { export const getAgencyProductsAction = async (param) => {
const _param = { ...param, use_year: String(param.use_year || '').replace('all', ''), audit_state: (param.audit_state || '').replace('all', '') }; const _param = { ...param, use_year: String(param.use_year || '').replace('all', ''), audit_state: String(param.audit_state || '').replace('all', '') };
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/travel_agency_products`, _param); const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/travel_agency_products`, _param);
return errcode !== 0 ? { agency: {}, products: [] } : result; return errcode !== 0 ? { agency: {}, products: [] } : result;
}; };
@ -95,13 +95,18 @@ export const postProductsAuditAction = async (auditState, infoRow) => {
// return errcode !== 0 ? {} : result; // return errcode !== 0 ? {} : result;
}; };
export const postProductsSave = async (products) => {
const { errcode, result } = await postJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_save`, products);
return { errcode, result };
}
const initialState = { const initialState = {
loading: false, loading: false,
searchValues: {}, searchValues: {}, // 客服首页: 搜索条件
agencyList: [], agencyList: [], // 客服首页: 搜索结果
activeAgency: {}, activeAgency: {}, // 审核/编辑 页: 当前的供应商
agencyProducts: {}, agencyProducts: {}, // 审核/编辑 页: 供应商产品列表
editingProduct: {}, editingProduct: {}, // 编辑页: 当前编辑的产品
}; };
export const useProductsStore = create( export const useProductsStore = create(
devtools((set, get) => ({ devtools((set, get) => ({
@ -130,7 +135,9 @@ export const useProductsStore = create(
getAgencyProducts: async (param) => { getAgencyProducts: async (param) => {
const { setLoading, setActiveAgency, setAgencyProducts } = get(); const { setLoading, setActiveAgency, setAgencyProducts } = get();
setLoading(true); setLoading(true);
setAgencyProducts({});
const res = await getAgencyProductsAction(param); 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); setAgencyProducts(productsData);
setActiveAgency(res.agency); setActiveAgency(res.agency);

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useParams, Link, Navigate, useNavigate } from 'react-router-dom'; import { useParams, Link } from 'react-router-dom';
import { App, Empty, Button, Collapse, Table, Space, Divider, Select } from 'antd'; import { App, Empty, Button, Collapse, Table, Space } from 'antd';
import { useProductsTypes, useProductsAuditStatesMapVal } from '@/hooks/useProductsSets'; import { useProductsTypes, useProductsAuditStatesMapVal } from '@/hooks/useProductsSets';
import SecondHeaderWrapper from '@/components/SecondHeaderWrapper'; import SecondHeaderWrapper from '@/components/SecondHeaderWrapper';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -8,112 +8,8 @@ import useProductsStore, { postProductsQuoteAuditAction, } from '@/stores/Produc
import { cloneDeep, isEmpty } from '@/utils/commons'; import { cloneDeep, isEmpty } from '@/utils/commons';
import useAuthStore from '@/stores/Auth'; import useAuthStore from '@/stores/Auth';
import RequireAuth from '@/components/RequireAuth'; import RequireAuth from '@/components/RequireAuth';
// import PrintContractPDF from './PrintContractPDF';
import { PERM_PRODUCTS_OFFER_AUDIT, PERM_PRODUCTS_OFFER_PUT } from '@/config'; import { PERM_PRODUCTS_OFFER_AUDIT, PERM_PRODUCTS_OFFER_PUT } from '@/config';
import dayjs from 'dayjs'; import Header from './Detail/Header';
import VendorSelector from '@/components/VendorSelector';
const Header = ({ refresh, ...props }) => {
const { travel_agency_id, use_year, audit_state } = useParams();
const { t } = useTranslation();
const isPermitted = useAuthStore(state => state.isPermitted);
const [activeAgency, setActiveAgency] = useProductsStore((state) => [state.activeAgency, state.setActiveAgency]);
const { message, notification } = App.useApp();
const navigate = useNavigate();
const yearOptions = [];
const currentYear = dayjs().year();
const baseYear = Number(use_year === 'all' ? currentYear : use_year);
for (let i = baseYear - 3; i <= baseYear + 3; i++) {
yearOptions.push({ label: i, value: i, });
}
const [param, setParam] = useState({ pick_year: baseYear, pick_agency: travel_agency_id, });
const [pickYear, setPickYear] = useState(baseYear);
useEffect(() => {
refresh(param);
navigate(`/products/${activeAgency.travel_agency_id}/${pickYear}/${audit_state}/audit`);
return () => {};
}, [param]);
const handleYearChange = (value) => {
setPickYear(value);
setParam((pre) => ({ ...pre, ...{ pick_year: value } }));
};
const handleAgencyChange = ({ label, value }) => {
setActiveAgency({ travel_agency_id: value, travel_agency_name: label });
setParam((pre) => ({ ...pre, ...{ pick_agency: value } }));
};
const handleAuditItem = (state, row) => {
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(param);
}
}
})
.catch((ex) => {
notification.error({
message: 'Notification',
description: ex.message,
placement: 'top',
duration: 4,
});
});
};
return (
<div className='flex justify-end items-center gap-4 h-full'>
<div className='grow'>
<h2 className='m-0 leading-tight'>
{isPermitted(PERM_PRODUCTS_OFFER_AUDIT) ? (
<VendorSelector
value={{ label: activeAgency.travel_agency_name, value: activeAgency.travel_agency_id }}
onChange={handleAgencyChange}
mode={null}
className='w-72'
size='large'
variant={'borderless'}
/>
) : (
activeAgency.travel_agency_name
)}
<Divider type={'vertical'} />
<Select options={yearOptions} variant={'borderless'} className='w-24' size='large' value={pickYear} onChange={handleYearChange} />
{/* <Divider type={'vertical'} />
{(use_year || '').replace('all', '')} */}
</h2>
</div>
{/* <Button size='small'>{t('Copy')}</Button> */}
{/* <Button size='small'>{t('Import')}</Button> */}
<RequireAuth subject={PERM_PRODUCTS_OFFER_PUT}>
<Link className='px-2' to={`/products/${activeAgency.travel_agency_id}/${pickYear}/${audit_state}/edit`}>
{t('Edit')}
</Link>
</RequireAuth>
<RequireAuth subject={PERM_PRODUCTS_OFFER_AUDIT}>
<Button size='small' type={'primary'} onClick={() => handleAuditItem('2', activeAgency)}>
{t('products:auditStateAction.Published')}
</Button>
</RequireAuth>
{/* <Button size='small' type={'primary'} ghost onClick={() => handleAuditItem('2', agency)}>
{t('products:auditStateAction.Approved')}
</Button> */}
<RequireAuth subject={PERM_PRODUCTS_OFFER_AUDIT}>
<Button size='small' type={'primary'} danger ghost onClick={() => handleAuditItem('3', activeAgency)}>
{t('products:auditStateAction.Rejected')}
</Button>
</RequireAuth>
{/* todo: export, 审核完成之后才能导出 */}
<Button size='small'>{t('Print')} PDF</Button>
{/* <PrintContractPDF /> */}
</div>
);
};
const PriceTable = ({ productType, dataSource, refresh }) => { const PriceTable = ({ productType, dataSource, refresh }) => {
const { t } = useTranslation('products'); const { t } = useTranslation('products');
const { travel_agency_id, use_year, audit_state } = useParams(); const { travel_agency_id, use_year, audit_state } = useParams();
@ -266,15 +162,16 @@ const Audit = ({ ...props }) => {
const { travel_agency_id, use_year, audit_state } = useParams(); const { travel_agency_id, use_year, audit_state } = useParams();
const [loading, activeAgency, getAgencyProducts] = useProductsStore((state) => [state.loading, state.activeAgency, state.getAgencyProducts]); const [loading, activeAgency, getAgencyProducts] = useProductsStore((state) => [state.loading, state.activeAgency, state.getAgencyProducts]);
const handleGetAgencyProducts = ({pick_year, pick_agency}={}) => { const handleGetAgencyProducts = ({pick_year, pick_agency, pick_state}={}) => {
const year = pick_year || use_year; const year = pick_year || use_year;
const agency = pick_agency || travel_agency_id; const agency = pick_agency || travel_agency_id;
getAgencyProducts({ travel_agency_id: agency, use_year: year, audit_state }); const state = pick_state ?? audit_state;
getAgencyProducts({ travel_agency_id: agency, use_year: year, audit_state: state });
}; };
return ( return (
<> <>
<SecondHeaderWrapper header={<Header title={activeAgency.travel_agency_name} refresh={handleGetAgencyProducts} />} loading={loading} backTo={`/products`}> <SecondHeaderWrapper loading={loading} backTo={`/products`} header={<Header title={activeAgency.travel_agency_name} refresh={handleGetAgencyProducts} />}>
{/* <PrintContractPDF /> */} {/* <PrintContractPDF /> */}
<TypesPanels refresh={handleGetAgencyProducts} /> <TypesPanels refresh={handleGetAgencyProducts} />

@ -1,25 +1,34 @@
import React, { useState, useEffect, useRef, useMemo } from 'react'; import React, { useState, useEffect } from 'react';
import { Button, Card, Col, Row, Breadcrumb, Table, Popconfirm, Form, Input, InputNumber, Tag, Modal, Select, Tree, FloatButton, DatePicker, Spin, message } from 'antd'; import { Tooltip, Button, Card, Col, Row, Breadcrumb, Table, Popconfirm, Form, Input, InputNumber, Tag, Modal, Select, Tree, FloatButton, DatePicker, Spin, message, Divider,Empty } from 'antd';
import { Link } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useProductsTypes } from '@/hooks/useProductsSets'; import { useProductsTypes, useProductsAuditStatesMapVal } from '@/hooks/useProductsSets';
import Extras from './Detail/Extras'; import Extras from './Detail/Extras';
import { isEmpty } from '@/utils/commons';
import SecondHeaderWrapper from '@/components/SecondHeaderWrapper';
import Header from './Detail/Header';
import YearSelector from './Detail/YearSelector'
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import useProductsStore from '@/stores/Products/Index'; import useProductsStore from '@/stores/Products/Index';
import postProductsSave from '@/stores/Products/Index';
import { useHTLanguageSets } from '@/hooks/useHTLanguageSets'; import { useHTLanguageSets } from '@/hooks/useHTLanguageSets';
import { useDefaultLgc } from '@/i18n/LanguageSwitcher'; import { useDefaultLgc } from '@/i18n/LanguageSwitcher';
import BatchImportPrice from './Detail/BatchImportPrice'; import BatchImportPrice from './Detail/BatchImportPrice';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { PlusCircleFilled } from '@ant-design/icons'; import { PlusCircleFilled, FileAddOutlined, ExportOutlined } from '@ant-design/icons';
import { DeptSelector } from '@/components/DeptSelector'; import { DeptSelector } from '@/components/DeptSelector';
import { useDatePresets } from '@/hooks/useDatePresets'; import { useDatePresets } from '@/hooks/useDatePresets';
import CitySelector from '@/components/CitySelector'; import CitySelector from '@/components/CitySelector';
import { HT_HOST } from '@/config'; import { HT_HOST } from '@/config';
import { postJSON } from '@/utils/request'; import { postJSON, postForm } from '@/utils/request';
import RequireAuth from '@/components/RequireAuth'
import { PERM_ROLE_NEW } from '@/config'
import { create } from 'zustand'; import { create } from 'zustand';
import { PERM_PRODUCTS_MANAGEMENT } from '@/config';
function Detail() { function Detail() {
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const navigate = useNavigate()
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
const [tags, setTags] = useState([]); const [tags, setTags] = useState([]);
const [isModalVisible, setIsModalVisible] = useState(false); const [isModalVisible, setIsModalVisible] = useState(false);
@ -35,14 +44,13 @@ function Detail() {
const [quotation, setQuotation] = useState(null); const [quotation, setQuotation] = useState(null);
const [lgc_details, setLgc_details] = useState(null); const [lgc_details, setLgc_details] = useState(null);
const [languageLabel, setLanguageLabel] = useState(null); const [languageLabel, setLanguageLabel] = useState(null);
const [infoDataForId, setInfoDataForId] = useState(null); const { travel_agency_id, audit_state, use_year } = useParams();
const { travel_agency_id } = useParams();
const { language } = useDefaultLgc(); const { language } = useDefaultLgc();
const HTLanguageSets = useHTLanguageSets(); const HTLanguageSets = useHTLanguageSets();
const { Search } = Input; const { Search } = Input;
const [addProductVisible, setAddProductVisible] = useState(false); const [addProductVisible, setAddProductVisible] = useState(false);
const [editingProduct, setEditingProduct] = useProductsStore((state) => [state.editingProduct, state.setEditingProduct]); const [editingProduct, setEditingProduct] = useProductsStore((state) => [state.editingProduct, state.setEditingProduct]);
const [agencyProducts, setAgencyProducts] = useProductsStore((state) => [state.agencyProducts, state.setAgencyProducts]); const [agencyProducts, setAgencyProducts,loading] = useProductsStore((state) => [state.agencyProducts, state.setAgencyProducts, state.loading]);
const { getAgencyProducts, activeAgency } = useProductsStore(); const { getAgencyProducts, activeAgency } = useProductsStore();
const [expandedKeys, setExpandedKeys] = useState([]); const [expandedKeys, setExpandedKeys] = useState([]);
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
@ -53,17 +61,40 @@ function Detail() {
const [addProductType, setAddProductType] = useState(''); const [addProductType, setAddProductType] = useState('');
const [addproductName, setAddProductName] = useState(''); const [addproductName, setAddProductName] = useState('');
const [dataFetched, setDataFetched] = useState(false); // const [dataFetched, setDataFetched] = useState(false); //
const [selectedNodeKey, setSelectedNodeKey] = useState(null);
const [selectedDays, setSelectedDays] = useState([]); const [selectedDays, setSelectedDays] = useState([]);
const [weekdays, setWeekdays] = useState([]); const [weekdays, setWeekdays] = useState([]);
const [info, setInfo] = useState(); const [info, setInfo] = useState();
const yearOptions = [];
const currentYear = dayjs().year();
const baseYear = Number(use_year === 'all' ? currentYear : use_year);
for (let i = baseYear - 3; i <= baseYear + 3; i++) {
yearOptions.push({ label: i, value: i, });
}
const [param, setParam] = useState({ pick_year: baseYear, pick_agency: travel_agency_id });
const [pickYear, setPickYear] = useState(baseYear);
const handleYearChange = (value) => {
setPickYear(value);
setParam((pre) => ({ ...pre, ...{ pick_year: value } }));
};
const handleGetAgencyProducts = ({pick_year, pick_agency}={}) => {
const year = pick_year || use_year;
const agency = pick_agency || travel_agency_id;
console.log("loading",loading);
getAgencyProducts({ travel_agency_id: agency, use_year: year, audit_state });
console.log("loading",loading);
console.log("AgencyProducts",agencyProducts);
// navigate(`/products/${agency}/${year}/${audit_state}/edit`);
};
const travel_agency_name = activeAgency.travel_agency_name; const travel_agency_name = activeAgency.travel_agency_name;
const audit_state_id = activeAgency.audit_state_id;
let isCanEditable = !(audit_state_id === 1 || audit_state_id === 3 || audit_state_id === -1)
const [currentQuotationRecord, setCurrentQuotationRecord] = useState({ const [currentQuotationRecord, setCurrentQuotationRecord] = useState({
use_dates_start: null, use_dates_start: null,
use_dates_end: null use_dates_end: null
}); });
const formatDate = (date) => (date ? dayjs(date) : null); const formatDate = (date) => (date ? dayjs(date) : null);
const stateMapVal = useProductsAuditStatesMapVal();
const startDate = currentQuotationRecord.use_dates_start && dayjs(currentQuotationRecord.use_dates_start).isValid() const startDate = currentQuotationRecord.use_dates_start && dayjs(currentQuotationRecord.use_dates_start).isValid()
? formatDate(currentQuotationRecord.use_dates_start) ? formatDate(currentQuotationRecord.use_dates_start)
@ -72,6 +103,7 @@ function Detail() {
? formatDate(currentQuotationRecord.use_dates_end) ? formatDate(currentQuotationRecord.use_dates_end)
: null; : null;
const [editIndex, setEditIndex] = useState(null); const [editIndex, setEditIndex] = useState(null);
const presets = useDatePresets(); const presets = useDatePresets();
const handleBatchImportData = (data) => { const handleBatchImportData = (data) => {
@ -80,7 +112,6 @@ function Detail() {
const days = [ const days = [
'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'
]; ];
const productProject = { const productProject = {
"6": [ "6": [
{ code: "code", name: t('products:Code'), nameKey: 'products:Code' }, { code: "code", name: t('products:Code'), nameKey: 'products:Code' },
@ -90,42 +121,162 @@ function Detail() {
{ code: "code", name: t('products:Code'), nameKey: 'products:Code' }, { code: "code", name: t('products:Code'), nameKey: 'products:Code' },
{ code: "city_name", name: t('products:City'), nameKey: 'products:City' }, { code: "city_name", name: t('products:City'), nameKey: 'products:City' },
{ code: "km", name: t('products:KM'), nameKey: 'products:KM' }, { code: "km", name: t('products:KM'), nameKey: 'products:KM' },
{ code: "remarks", name: t('products:Remarks'), nameKey: 'products:Remarks' } {
code: "remarks",
name: (
<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>
{t('products:Remarks')}
</RequireAuth>
),
nameKey: 'products:Remarks',
},
], ],
"J": [ "J": [
{ code: "code", name: t('products:Code'), nameKey: 'products:Code' }, { code: "code", name: t('products:Code'), nameKey: 'products:Code' },
{ code: "city_name", name: t('products:City'), nameKey: 'products:City' }, { code: "city_name", name: t('products:City'), nameKey: 'products:City' },
{ code: "recommends_rate", name: t('products:recommendationRate'), nameKey: 'products:recommendationRate' }, {
code: "recommends_rate",
name: (
<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>
{t('products:recommendationRate')}
</RequireAuth>
),
nameKey: 'products:recommendationRate',
},
{ code: "duration", name: t('products:Duration'), nameKey: 'products:Duration' }, { code: "duration", name: t('products:Duration'), nameKey: 'products:Duration' },
{ code: "dept_name", name: t('products:Dept'), nameKey: 'products:Dept' }, {
{ code: "display_to_c", name: t('products:DisplayToC'), nameKey: 'products:DisplayToC' }, code: "dept_name",
{ code: "remarks", name: t('products:Remarks'), nameKey: 'products:Remarks' }, name: (
<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>
{t('products:Dept')}
</RequireAuth>
),
nameKey: 'products:Dept',
},
{
code: "display_to_c",
name: (
<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>
{t('products:DisplayToC')}
</RequireAuth>
),
nameKey: 'products:DisplayToC',
},
{
code: "remarks",
name: (
<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>
{t('products:Remarks')}
</RequireAuth>
),
nameKey: 'products:Remarks',
},
], ],
"Q": [ "Q": [
{ code: "code", name: t('products:Code'), nameKey: 'products:Code' }, { code: "code", name: t('products:Code'), nameKey: 'products:Code' },
{ code: "city_name", name: t('products:City'), nameKey: 'products:City' }, { code: "city_name", name: t('products:City'), nameKey: 'products:City' },
{ code: "recommends_rate", name: t('products:recommendationRate'), nameKey: 'products:recommendationRate' }, {
code: "recommends_rate",
name: (
<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>
{t('products:recommendationRate')}
</RequireAuth>
),
nameKey: 'products:recommendationRate',
},
{ code: "duration", name: t('products:Duration'), nameKey: 'products:Duration' }, { code: "duration", name: t('products:Duration'), nameKey: 'products:Duration' },
{ code: "dept_name", name: t('products:Dept'), nameKey: 'products:Dept' }, {
{ code: "display_to_c", name: t('products:DisplayToC'), nameKey: 'products:DisplayToC' }, code: "dept_name",
{ code: "remarks", name: t('products:Remarks'), nameKey: 'products:Remarks' }, name: (
<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>
{t('products:Dept')}
</RequireAuth>
),
nameKey: 'products:Dept',
},
{
code: "display_to_c",
name: (
<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>
{t('products:DisplayToC')}
</RequireAuth>
),
nameKey: 'products:DisplayToC',
},
{
code: "remarks",
name: (
<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>
{t('products:Remarks')}
</RequireAuth>
),
nameKey: 'products:Remarks',
},
], ],
"D": [ "D": [
{ code: "code", name: t('products:Code'), nameKey: 'products:Code' }, { code: "code", name: t('products:Code'), nameKey: 'products:Code' },
{ code: "city_name", name: t('products:City'), nameKey: 'products:City' }, { code: "city_name", name: t('products:City'), nameKey: 'products:City' },
{ code: "recommends_rate", name: t('products:recommendationRate'), nameKey: 'products:recommendationRate' }, {
code: "recommends_rate",
name: (
<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>
{t('products:recommendationRate')}
</RequireAuth>
),
nameKey: 'products:recommendationRate',
},
{ code: "duration", name: t('products:Duration'), nameKey: 'products:Duration' }, { code: "duration", name: t('products:Duration'), nameKey: 'products:Duration' },
{ code: "dept_name", name: t('products:Dept'), nameKey: 'products:Dept' }, {
{ code: "display_to_c", name: t('products:DisplayToC'), nameKey: 'products:DisplayToC' }, code: "dept_name",
{ code: "remarks", name: t('products:Remarks'), nameKey: 'products:Remarks' }, name: (
<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>
{t('products:Dept')}
</RequireAuth>
),
nameKey: 'products:Dept',
},
{
code: "display_to_c",
name: (
<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>
{t('products:DisplayToC')}
</RequireAuth>
),
nameKey: 'products:DisplayToC',
},
{
code: "remarks",
name: (
<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>
{t('products:Remarks')}
</RequireAuth>
),
nameKey: 'products:Remarks',
},
], ],
"7": [ "7": [
{ code: "code", name: t('products:Code'), nameKey: 'products:Code' }, { code: "code", name: t('products:Code'), nameKey: 'products:Code' },
{ code: "city_name", name: t('products:City'), nameKey: 'products:City' }, { code: "city_name", name: t('products:City'), nameKey: 'products:City' },
{ code: "recommends_rate", name: t('products:recommendationRate'), nameKey: 'products:recommendationRate' }, {
code: "recommends_rate",
name: (
<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>
{t('products:recommendationRate')}
</RequireAuth>
),
nameKey: 'products:recommendationRate',
},
{ code: "duration", name: t('products:Duration'), nameKey: 'products:Duration' }, { code: "duration", name: t('products:Duration'), nameKey: 'products:Duration' },
{ code: "open_weekdays", name: t('products:OpenWeekdays'), nameKey: 'products:OpenWeekdays' }, { code: "open_weekdays", name: t('products:OpenWeekdays'), nameKey: 'products:OpenWeekdays' },
{ code: "remarks", name: t('products:Remarks'), nameKey: 'products:Remarks' }, {
code: "remarks",
name: (
<RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>
{t('products:Remarks')}
</RequireAuth>
),
nameKey: 'products:Remarks',
},
], ],
"8": [ "8": [
{ code: "code", name: t('products:Code') }, { code: "code", name: t('products:Code') },
@ -136,7 +287,7 @@ function Detail() {
{ code: "city_name", name: t('products:City'), nameKey: 'products:City' }, { code: "city_name", name: t('products:City'), nameKey: 'products:City' },
] ]
} }
const [selectedCategory, setSelectedCategory] = useState(productProject.B); const [selectedCategory, setSelectedCategory] = useState([]);
useEffect(() => { useEffect(() => {
setLanguageStatus(language); setLanguageStatus(language);
@ -148,13 +299,63 @@ function Detail() {
}, []); }, []);
useEffect(()=>{
handleGetAgencyProducts(param);
},[param]);
useEffect(() => {
editingProductQuotation();
}, [treeData, editingProduct])
const editingProductQuotation = () => {
console.log("editingProduct", editingProduct)
const editingProductID = editingProduct.id;
console.log("editingProductID", editingProductID)
let stopProgram = false;
for (const element of treeData) {
if (stopProgram) {
return;
}
const childList = element.children;
for (const product of childList) {
const childrenID = product.key.split('-')[1];
if (editingProductID == childrenID) {
const fatherKey = product.key.split('-')[0];
setSelectedCategory(productProject[fatherKey]);
stopProgram = true;
const tempInfo = product._raw.info;
const tempLgc_details = product._raw.lgc_details.find(record => record.lgc === 2);
console.log("tempLgc_details", tempLgc_details);
const tempQuotation = product._raw.quotation;
console.log({
info: tempInfo,
lgc_details: {
title: tempLgc_details.title,
description: tempLgc_details.descriptions
},
})
setQuotation(tempQuotation);
setTags([languageLabel]);
setRemainderLanguage(HTLanguageSets.filter(item => item.key !== language.toString()));
form.setFieldsValue({
info: tempInfo,
lgc_details: {
title: tempLgc_details.title,
description: tempLgc_details.descriptions
},
})
break;
}
}
}
}
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
if (productsTypes && !dataFetched) { if (productsTypes.length > 0 && travel_agency_id && Object.keys(agencyProducts).length > 0) {
const agency_id = { travel_agency_id };
await getAgencyProducts(agency_id);
const generateTreeData = (productsTypes, productsData) => { const generateTreeData = (productsTypes, productsData) => {
return productsTypes.map(type => ({ return productsTypes.map(type => ({
title: type.label, title: type.label,
@ -164,24 +365,21 @@ function Detail() {
title: product.info.title, title: product.info.title,
key: `${type.value}-${product.info.id}`, key: `${type.value}-${product.info.id}`,
_raw: product, _raw: product,
})) })),
})); }));
}; };
const tempExpandedKeys = productsTypes.map(item => item.key) const tempExpandedKeys = productsTypes.map(item => item.key);
const treeData = generateTreeData(productsTypes, agencyProducts); const treeData = generateTreeData(productsTypes, agencyProducts);
setDataFetched(true); // true
setTreeData(treeData); setTreeData(treeData);
setExpandedKeys(tempExpandedKeys); setExpandedKeys(tempExpandedKeys);
console.log("agencyProducts", agencyProducts)
setProductsData(agencyProducts); setProductsData(agencyProducts);
setDefaultData(treeData); setDefaultData(treeData);
setDataList(flattenTreeData(treeData)); setDataList(flattenTreeData(treeData));
setDataFetched(true);
} }
}; };
fetchData(); fetchData();
}, [agencyProducts, dataFetched]); }, [productsTypes, travel_agency_id, use_year, audit_state, agencyProducts]);
const flattenTreeData = (tree) => { const flattenTreeData = (tree) => {
let flatList = []; let flatList = [];
@ -257,13 +455,9 @@ function Detail() {
setCurrentQuotationRecord(record); setCurrentQuotationRecord(record);
}; };
const cancel = () => {
setEditingid('');
};
const handleDelete = (index) => { const handleDelete = (index) => {
const newData = [...quotation]; const newData = [...quotation];
// // const index = newData.findIndex((item) => id === item.id);
newData.splice(index, 1); newData.splice(index, 1);
const sortedData = [...newData].sort((a, b) => { const sortedData = [...newData].sort((a, b) => {
const aValidPeriod = dayjs(a.use_dates_end).diff(dayjs(a.use_dates_start)); const aValidPeriod = dayjs(a.use_dates_end).diff(dayjs(a.use_dates_start));
@ -276,7 +470,6 @@ function Detail() {
return aGroupSize - bGroupSize; return aGroupSize - bGroupSize;
}); });
setQuotation(sortedData); setQuotation(sortedData);
}; };
@ -295,6 +488,8 @@ function Detail() {
tempKey: Math.random() tempKey: Math.random()
}; };
setQuotation([...quotation, newData]); setQuotation([...quotation, newData]);
const index = [...quotation, newData].length - 1
edit(newData, index);
}; };
const handleBatchImport = () => { const handleBatchImport = () => {
@ -376,23 +571,34 @@ function Detail() {
{ title: t('products:Weekdays'), dataIndex: 'weekdays', width: '10%' }, { title: t('products:Weekdays'), dataIndex: 'weekdays', width: '10%' },
{ ];
//
if (!isCanEditable) {
columns.push({
title: t('products:operation'), title: t('products:operation'),
dataIndex: 'operation', dataIndex: 'operation',
render: (_, record, index) => { render: (_, record, index) => {
const canEdit = record.audit_state_id === -1;
return ( return (
<span> <span>
{canEdit ? (
<a onClick={() => edit(record, index)} style={{ marginRight: 8 }}>{t('Edit')}</a> <a onClick={() => edit(record, index)} style={{ marginRight: 8 }}>{t('Edit')}</a>
) : (
<span style={{ color: 'gray', marginRight: 8 }}>{t('Edit')}</span>
)}
{canEdit ? (
<Popconfirm title={t('sureDelete')} onConfirm={() => handleDelete(index)}> <Popconfirm title={t('sureDelete')} onConfirm={() => handleDelete(index)}>
<a>{t('Delete')}</a> <a>{t('Delete')}</a>
</Popconfirm> </Popconfirm>
) : (
<span style={{ color: 'gray' }}>{t('Delete')}</span>
)}
</span> </span>
) );
}, }
}, });
}
];
const handleTagClick = (tag) => { const handleTagClick = (tag) => {
setSelectedTag(tag); setSelectedTag(tag);
@ -479,7 +685,6 @@ function Detail() {
const handleNodeSelect = (_, { node }) => { const handleNodeSelect = (_, { node }) => {
setSelectedNodeid(node.key); setSelectedNodeid(node.key);
const fatherKey = node.key.split('-')[0]; const fatherKey = node.key.split('-')[0];
setSelectedNodeKey(fatherKey);
setSelectedCategory(productProject[fatherKey]); setSelectedCategory(productProject[fatherKey]);
setTags([languageLabel]); setTags([languageLabel]);
// //
@ -490,23 +695,19 @@ function Detail() {
setLanguageLabel(languageLabelRefresh); setLanguageLabel(languageLabelRefresh);
setSelectedTag(languageLabelRefresh); setSelectedTag(languageLabelRefresh);
setRemainderLanguage(HTLanguageSets.filter(item => item.key !== language.toString())); setRemainderLanguage(HTLanguageSets.filter(item => item.key !== language.toString()));
setEditingProduct(node._raw); // setEditingProduct(node._raw);
if (!node._raw.info.id) { if (!node._raw.info.id) {
let infoData = node._raw.info; let infoData = node._raw.info;
setInfo(node._raw.info); setInfo(node._raw.info);
console.log("node._raw",node._raw)
console.log("没有id");
const newLgcDetails = node._raw.lgc_details const newLgcDetails = node._raw.lgc_details
const fatherKey = node.key.split('-')[0];
setSelectedNodeid(node.key); setSelectedNodeid(node.key);
setSelectedNodeKey(fatherKey);
form.setFieldsValue({ form.setFieldsValue({
info: { info: {
id: infoData.id || "", id: infoData.id || "",
title: infoData.title || "", title: infoData.title || "",
code: infoData.code || "", code: infoData.code || "",
type: infoData.product_type_id || "", type: infoData.product_type_id || "",
audit_state:"-1", audit_state: "-1",
create_date: infoData.create_date || "", create_date: infoData.create_date || "",
created_by: infoData.created_by || "", created_by: infoData.created_by || "",
travel_agency_id: travel_agency_id || "", travel_agency_id: travel_agency_id || "",
@ -548,12 +749,10 @@ function Detail() {
}]) }])
return return
} else { } else {
let initialQuotationData = null; let initialQuotationData = null;
let infoData = null; let infoData = null;
let lgcDetailsData = null; let lgcDetailsData = null;
console.log("")
productsData[fatherKey].forEach(element => { productsData[fatherKey].forEach(element => {
if (element.info.id === node._raw.info.id) { if (element.info.id === node._raw.info.id) {
initialQuotationData = element.quotation; initialQuotationData = element.quotation;
@ -576,7 +775,7 @@ function Detail() {
}; };
delete updatedObject.unit_name; delete updatedObject.unit_name;
delete updatedObject.unit_id; delete updatedObject.unit_id;
delete updatedObject.audit_state_id; // delete updatedObject.audit_state_id;
delete updatedObject.audit_state_name; delete updatedObject.audit_state_name;
return updatedObject; return updatedObject;
}); });
@ -593,9 +792,6 @@ function Detail() {
}); });
} }
if (node._raw.info.id) {
setInfoDataForId(infoData.id)
}
setLgc_details(newLgcDetails); setLgc_details(newLgcDetails);
@ -651,12 +847,11 @@ function Detail() {
} }
}); });
} }
} }
}; };
//
const handelAddProduct = () => { const handelAddProduct = () => {
// //
const productTypeNode = treeData.find(item => item.key === addProductType); const productTypeNode = treeData.find(item => item.key === addProductType);
@ -711,8 +906,7 @@ function Detail() {
return item; return item;
}); });
// treeData // treeData
setEditingProduct(null) // setEditingProduct(null);
console.log("newTreeData", newTreeData);
setTreeData(newTreeData); setTreeData(newTreeData);
let tempProductDataList = productsData[addProductType]; let tempProductDataList = productsData[addProductType];
@ -725,63 +919,40 @@ function Detail() {
setAddProductVisible(false); setAddProductVisible(false);
} }
// let tempProductDataList = productsData[addProductType];
// //
// const newProduct = {
// info: {
// code: 'addProduct'
// },
// quotation: [],
// lgc_details: []
// }
// tempProductDataList.push(newProduct);
// const newProductsData = {
// ...productsData, // 使
// [addProductType]: tempProductDataList
// };
// setProductsData(newProductsData);
// setAddProductVisible(false);
// };
const renderFormItem = (item) => { const renderFormItem = (item) => {
switch (item.code) { switch (item.code) {
case "duration": case "duration":
return <Input suffix="H" />; return <Input suffix="H" disabled={isCanEditable} />;
case "display_to_c": case "display_to_c":
return ( return (
<Select> <Select disabled={isCanEditable}>
<Select.Option value={0}>在计划显示不在报价信显示</Select.Option> <Select.Option value={0}>在计划显示不在报价信显示</Select.Option>
<Select.Option value={1}>计划和报价信都要显示</Select.Option> <Select.Option value={1}>计划和报价信都要显示</Select.Option>
<Select.Option value={2}>计划和报价信都不用显示</Select.Option> <Select.Option value={2}>计划和报价信都不用显示</Select.Option>
</Select> </Select>
); );
case "dept_name": case "dept_name":
return <DeptSelector />; return <DeptSelector disabled={isCanEditable} />;
case "city_name": case "city_name":
return <CitySelector />; return <CitySelector disabled={isCanEditable} />;
default: default:
return <Input />; return <Input disabled={isCanEditable} />;
} }
}; };
//
const onSave = async (values) => { const onSave = async (values) => {
let tempInfo let tempInfo
console.log("values", values)
console.log("info", info)
if (info.id === "") { if (info.id === "") {
tempInfo = { tempInfo = {
...info, ...info,
...values.info, ...values.info,
city_name: values.info.city_name.label, city_name: values.info.city_name.label,
audit_state:"-1" audit_state: "-1"
} }
delete tempInfo.product_type_name; delete tempInfo.product_type_name;
delete tempInfo.dept_name; delete tempInfo.dept_name;
console.log("新增")
let tempQuotation = quotation.map(element => { let tempQuotation = quotation.map(element => {
const updateData = { const updateData = {
...element, ...element,
@ -790,18 +961,17 @@ function Detail() {
delete updateData.tempKey delete updateData.tempKey
return updateData return updateData
}) })
let tempLgc_details = [{...lgc_details}] let tempLgc_details = [{ ...lgc_details }]
console.log("tempLgc_details",tempLgc_details)
const tempData = { const tempData = {
travel_agency_id, travel_agency_id,
info: tempInfo, info: tempInfo,
quotation: tempQuotation, quotation: tempQuotation,
lgc_details: Object.values(lgc_details) lgc_details: Object.values(lgc_details)
}; };
console.log("tempData",tempData); console.log("tempData", tempData);
const { errcode, result } = await postJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_save`, tempData); const { errcode, result } = await postJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_save`, tempData);
console.log("result",result) console.log("result", result);
if (errcode === 0) { if (errcode === 0) {
message.success("保存成功"); message.success("保存成功");
setDataFetched(false); setDataFetched(false);
@ -809,13 +979,13 @@ function Detail() {
message.error(`保存失败: ${result}`); message.error(`保存失败: ${result}`);
} }
return return
} else { }
tempInfo = { tempInfo = {
...info, ...info,
...values.info, ...values.info,
audit_state: "-1" audit_state: "-1"
} }
}
console.log("tempInfo", tempInfo) console.log("tempInfo", tempInfo)
@ -836,8 +1006,8 @@ function Detail() {
}; };
console.log("tempData", tempData) console.log("tempData", tempData)
// const { errcode, result } = await postProductsSave(tempData);
const { errcode, result } = await postJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_save`, tempData); const { errcode, result } = await postJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_save`, tempData);
if (errcode === 0) { if (errcode === 0) {
message.success("保存成功"); message.success("保存成功");
setDataFetched(false); setDataFetched(false);
@ -849,14 +1019,47 @@ function Detail() {
}; };
//
const submitReview = async () => {
const formData = new FormData();
formData.append('use_year', use_year);
formData.append('travel_agency_id', travel_agency_id);
try {
const { errcode, result } = await postForm(`${HT_HOST}/Service_BaseInfoWeb/agency_submit`, formData);
console.log("errcode", errcode);
if (errcode === 0) {
message.success("提交审核成功");
navigate(`/products/${travel_agency_id}/${use_year}/${audit_state}/audit`);
} else {
message.error("提交审核失败");
}
console.log("result", result);
} catch (error) {
console.error("提交审核请求失败", error);
message.error("提交审核请求失败");
}
};
const handleStateChange = (newState) => {
console.log("newState",newState)
if(newState === 'addProducts'){
setAddProductVisible(true);
}
if(newState === 'submitReview'){
submitReview();
}
};
return ( return (
<Spin spinning={treeData.length === 0}>
<div > // <Spin spinning={treeData.length === 0}>
<SecondHeaderWrapper loading={loading} backTo={`/products`} header={<YearSelector title={activeAgency.travel_agency_name} refresh={handleGetAgencyProducts} onStateChange={handleStateChange}/>}>
{isEmpty(agencyProducts) ? <Empty /> :<div>
<Row> <Row>
<Col span={6} className=' relative'> <Col span={6} className=' relative'>
<Card className='w-[inherit] fixed overflow-y-auto max-h-[80%] max-w-[22%] overflow-x-auto'> <Card className='w-[inherit] fixed overflow-y-auto max-h-[80%] max-w-[22%] overflow-x-auto'>
<Row> <Row>
<Search style={{ marginBottom: 8 }} placeholder="Search" onChange={onChange} /> <Search placeholder="Search" onChange={onChange} />
</Row> </Row>
<Tree <Tree
style={{ overflowX: 'auto' }} style={{ overflowX: 'auto' }}
@ -897,7 +1100,6 @@ function Detail() {
})} })}
</Row> </Row>
{/* <Card title={ */}
<div style={{ marginBottom: "1%", marginLeft: "3%" }}> <div style={{ marginBottom: "1%", marginLeft: "3%" }}>
{tags.map(tag => ( {tags.map(tag => (
<Tag <Tag
@ -912,7 +1114,6 @@ function Detail() {
))} ))}
<Tag onClick={showModal} style={{ cursor: 'pointer' }}>+</Tag> <Tag onClick={showModal} style={{ cursor: 'pointer' }}>+</Tag>
</div> </div>
{/* }> */}
<Modal title="选择语言" visible={isModalVisible} onOk={handleOk} onCancel={handleCancel}> <Modal title="选择语言" visible={isModalVisible} onOk={handleOk} onCancel={handleCancel}>
<Select <Select
showSearch showSearch
@ -937,12 +1138,14 @@ function Detail() {
<Input <Input
style={{ width: "30%" }} style={{ width: "30%" }}
onChange={(e) => handleChange('title', e.target.value)} onChange={(e) => handleChange('title', e.target.value)}
disabled={isCanEditable}
/> />
</Form.Item> </Form.Item>
<Form.Item label={t('products:Description')} name={['lgc_details', 'description']}> <Form.Item label={t('products:Description')} name={['lgc_details', 'description']}>
<Input.TextArea <Input.TextArea
rows={4} rows={4}
onChange={(e) => handleChange('description', e.target.value)} onChange={(e) => handleChange('description', e.target.value)}
disabled={isCanEditable}
/> />
</Form.Item> </Form.Item>
{/* </Card> */} {/* </Card> */}
@ -956,33 +1159,60 @@ function Detail() {
dataSource={quotation} dataSource={quotation}
columns={columns} columns={columns}
rowClassName="editable-row" rowClassName="editable-row"
pagination={{ onChange: cancel }} // pagination={{ onChange: cancel }}
/> />
<Button onClick={handleAdd} type="primary" style={{ marginBottom: 16 }}> {
!isCanEditable &&
<Button onClick={handleAdd} type="primary" style={{ marginTop: 16 }}>
{t('products:addQuotation')} {t('products:addQuotation')}
</Button> </Button>
}
<Button onClick={handleBatchImport} type="primary" style={{ marginBottom: 16 }}>批量添加</Button> {
!isCanEditable &&
<Button onClick={handleBatchImport} type="primary" style={{ marginTop: 16, marginLeft: 16 }}>批量添加</Button>
}
</Form.Item> </Form.Item>
<Button type="primary" htmlType="submit" style={{ marginTop: 16, float: "right", marginRight: "20%" }}> <Button type="primary" htmlType="submit" style={{ marginTop: 16, float: "right", marginRight: "20%" }}>
{t('Save')} {t('Save')}
</Button> </Button>
<Button type="primary" htmlType="submit" style={{ marginTop: 16, float: "right", marginRight: "5%" }}>
提交审核
</Button>
</Card> </Card>
</Form> </Form>
{/* <Card style={{ width: "80%" }}> */} {/* <Card style={{ width: "80%" }}> */}
<Extras productId={2} /> <Extras productId={2} />
{/* </Card> */} {/* </Card> */}
<FloatButton icon={<PlusCircleFilled />} onClick={() => setAddProductVisible(true)} />
{/* <FloatButton icon={<PlusCircleFilled />} onClick={() => setAddProductVisible(true)} /> */}
<FloatButton.Group
trigger="hover"
type="primary"
style={{ right: 80 }}
icon={<PlusCircleFilled />}
>
<Tooltip title="增加产品">
<FloatButton icon={<FileAddOutlined />} onClick={() => setAddProductVisible(true)} />
</Tooltip>
<Tooltip title="提交审核">
<FloatButton icon={<ExportOutlined />} onClick={async () => {
const formData = new FormData();
formData.append('use_year', use_year);
formData.append('travel_agency_id', travel_agency_id);
const { errcode, result } = await postForm(`${HT_HOST}/Service_BaseInfoWeb/agency_submit`, formData);
console.log("errcode", errcode);
if (errcode === 0) {
message.success("提交审核成功");
navigate(`/products/${travel_agency_id}/${use_year}/${audit_state}/audit`);
}
console.log("result", result);
}} />
</Tooltip>
</FloatButton.Group>
</Col> </Col>
</Row> </Row>
{
(
<Modal <Modal
title="批量添加价格" title="批量添加价格"
visible={batchImportPriceVisible} visible={batchImportPriceVisible}
@ -992,11 +1222,7 @@ function Detail() {
> >
<BatchImportPrice onBatchImportData={handleBatchImportData} /> <BatchImportPrice onBatchImportData={handleBatchImportData} />
</Modal> </Modal>
)
}
{
(
<Modal <Modal
title="增加新产品" title="增加新产品"
visible={addProductVisible} visible={addProductVisible}
@ -1022,11 +1248,7 @@ function Detail() {
onChange={(e) => setAddProductName(e.target.value)} onChange={(e) => setAddProductName(e.target.value)}
/> />
</Modal> </Modal>
)
}
{
(
<Modal <Modal
title="编辑供应商报价" title="编辑供应商报价"
visible={quotationTableVisible} visible={quotationTableVisible}
@ -1090,13 +1312,10 @@ function Detail() {
{day} {day}
</Button> </Button>
))} ))}
</Modal> </Modal>
</div>}
) </SecondHeaderWrapper>
} // </Spin>
</div>
</Spin>
); );
} }
export default Detail; export default Detail;

@ -3,10 +3,9 @@ import { App, Form, Modal, DatePicker, Divider, Switch } from 'antd';
import { isEmpty, objectMapper } from '@/utils/commons'; import { isEmpty, objectMapper } from '@/utils/commons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import SearchInput from '@/components/SearchInput';
import DeptSelector from '@/components/DeptSelector'; import DeptSelector from '@/components/DeptSelector';
import ProductsTypesSelector from '@/components/ProductsTypesSelector'; import ProductsTypesSelector from '@/components/ProductsTypesSelector';
import { fetchVendorList } from '@/components/VendorSelector'; import VendorSelector from '@/components/VendorSelector';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import arraySupport from 'dayjs/plugin/arraySupport'; import arraySupport from 'dayjs/plugin/arraySupport';
import { copyAgencyDataAction } from '@/stores/Products/Index'; import { copyAgencyDataAction } from '@/stores/Products/Index';
@ -31,13 +30,7 @@ export const CopyProductsForm = ({ action, initialValues, onFormInstanceReady, s
return ( return (
<Form layout='horizontal' form={form} name='form_in_modal' initialValues={initialValues} onValuesChange={onValuesChange} > <Form layout='horizontal' form={form} name='form_in_modal' initialValues={initialValues} onValuesChange={onValuesChange} >
{action === '#' && <Form.Item name='agency' label={`${t('products:CopyFormMsg.target')}${t('products:Vendor')}`} rules={[{ required: true, message: t('products:CopyFormMsg.requiredVendor') }]}> {action === '#' && <Form.Item name='agency' label={`${t('products:CopyFormMsg.target')}${t('products:Vendor')}`} rules={[{ required: true, message: t('products:CopyFormMsg.requiredVendor') }]}>
<SearchInput <VendorSelector mode={null} placeholder={t('products:Vendor')} />
placeholder={t('products:Vendor')}
mode={null}
maxTagCount={0}
fetchOptions={fetchVendorList}
map={{ travel_agency_name: 'label', travel_agency_id: 'value' }}
/>
</Form.Item>} </Form.Item>}
<Form.Item name={`products_types`} label={t('products:ProductType')} > <Form.Item name={`products_types`} label={t('products:ProductType')} >
<ProductsTypesSelector maxTagCount={1} mode={'multiple'} placeholder={t('All')} /> <ProductsTypesSelector maxTagCount={1} mode={'multiple'} placeholder={t('All')} />

@ -0,0 +1,137 @@
import { useEffect, useState } from 'react';
import { useParams, Link, useNavigate } from 'react-router-dom';
import { App, Button, Divider, Select } from 'antd';
import { useProductsAuditStatesMapVal } from '@/hooks/useProductsSets';
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';
import dayjs from 'dayjs';
import VendorSelector from '@/components/VendorSelector';
import AuditStateSelector from '@/components/AuditStateSelector';
const Header = ({ refresh, ...props }) => {
const { travel_agency_id, use_year, audit_state } = useParams();
const { t } = useTranslation();
const isPermitted = useAuthStore(state => state.isPermitted);
const [activeAgency, setActiveAgency] = useProductsStore((state) => [state.activeAgency, state.setActiveAgency]);
const stateMapVal = useProductsAuditStatesMapVal();
const { message, notification } = App.useApp();
const navigate = useNavigate();
const yearOptions = [];
const currentYear = dayjs().year();
const baseYear = Number(use_year === 'all' ? currentYear : use_year);
for (let i = baseYear - 3; i <= baseYear + 3; i++) {
yearOptions.push({ label: i, value: i, });
}
const [param, setParam] = useState({ pick_year: baseYear, pick_agency: travel_agency_id, });
const [pickYear, setPickYear] = useState(baseYear);
const [pickAgency, setPickAgency] = useState({value: activeAgency.travel_agency_id, label: activeAgency.travel_agency_name });
const [pickAuditState, setPickAuditState] = useState();
useEffect(() => {
refresh(param);
return () => {};
}, [param]);
const emptyPickState = { value: '', label: t('products:State') };
useEffect(() => {
const baseState = audit_state === 'all' ? emptyPickState : stateMapVal[`${audit_state}`];
if (isEmpty(pickAuditState)) {
setPickAuditState(baseState);
}
return () => {};
}, [audit_state, stateMapVal])
const handleYearChange = (value) => {
setPickYear(value);
setParam((pre) => ({ ...pre, ...{ pick_year: value } }));
};
const handleAuditStateChange = (labelValue) => {
const { value } = labelValue || emptyPickState;
setPickAuditState(labelValue || emptyPickState);
setParam((pre) => ({ ...pre, ...{ pick_state: value } }));
};
const handleAgencyChange = ({ label, value }) => {
setPickAgency({ label, value });
setActiveAgency({ travel_agency_id: value, travel_agency_name: label });
setParam((pre) => ({ ...pre, ...{ pick_agency: value } }));
};
const handleAuditItem = (state, row) => {
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(param);
}
}
})
.catch((ex) => {
notification.error({
message: 'Notification',
description: ex.message,
placement: 'top',
duration: 4,
});
});
};
return (
<div className='flex justify-end items-center gap-4 h-full'>
<div className='grow'>
<h2 className='m-0 leading-tight'>
{isPermitted(PERM_PRODUCTS_OFFER_AUDIT) ? (
<VendorSelector
value={{label: activeAgency.travel_agency_name, value: activeAgency.travel_agency_id}}
onChange={handleAgencyChange}
allowClear={false}
mode={null}
className='w-72'
size='large'
variant={'borderless'}
/>
) : (
activeAgency.travel_agency_name
)}
<Divider type={'vertical'} />
<Select options={yearOptions} variant={'borderless'} className='w-24' size='large' value={pickYear} onChange={handleYearChange} />
<Divider type={'vertical'} />
<AuditStateSelector variant={'borderless'} className='w-32' size='large' value={pickAuditState} onChange={handleAuditStateChange} />
{/* <Divider type={'vertical'} />
{(use_year || '').replace('all', '')} */}
</h2>
</div>
{/* <Button size='small'>{t('Copy')}</Button> */}
{/* <Button size='small'>{t('Import')}</Button> */}
<RequireAuth subject={PERM_PRODUCTS_OFFER_PUT}>
<Link className='px-2' to={`/products/${activeAgency.travel_agency_id}/${pickYear}/${audit_state}/edit`}>
{t('Edit')}
</Link>
</RequireAuth>
<RequireAuth subject={PERM_PRODUCTS_OFFER_AUDIT}>
<Button size='small' type={'primary'} onClick={() => handleAuditItem('2', activeAgency)}>
{t('products:auditStateAction.Published')}
</Button>
</RequireAuth>
{/* <Button size='small' type={'primary'} ghost onClick={() => handleAuditItem('2', agency)}>
{t('products:auditStateAction.Approved')}
</Button> */}
<RequireAuth subject={PERM_PRODUCTS_OFFER_AUDIT}>
<Button size='small' type={'primary'} danger ghost onClick={() => handleAuditItem('3', activeAgency)}>
{t('products:auditStateAction.Rejected')}
</Button>
</RequireAuth>
{/* todo: export, 审核完成之后才能导出 */}
<Button size='small'>{t('Print')} PDF</Button>
{/* <PrintContractPDF /> */}
</div>
);
};
export default Header;

@ -0,0 +1,81 @@
import { useEffect, useState } from 'react';
import { useParams, Link, useNavigate } from 'react-router-dom';
import { App, Button, Divider, Select } from 'antd';
import { useProductsAuditStatesMapVal } from '@/hooks/useProductsSets';
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';
import dayjs from 'dayjs';
import AuditStateSelector from '@/components/AuditStateSelector';
const YearSelector = ({ refresh,onStateChange }) => {
const { travel_agency_id, use_year, audit_state } = useParams();
const { t } = useTranslation();
const stateMapVal = useProductsAuditStatesMapVal();
const navigate = useNavigate();
const yearOptions = [];
const currentYear = dayjs().year();
const baseYear = Number(use_year === 'all' ? currentYear : use_year);
for (let i = baseYear - 3; i <= baseYear + 3; i++) {
yearOptions.push({ label: i, value: i, });
}
const [param, setParam] = useState({ pick_year: baseYear, pick_agency: travel_agency_id, });
const [pickYear, setPickYear] = useState(baseYear);
const [pickAuditState, setPickAuditState] = useState();
useEffect(() => {
refresh(param);
return () => {};
}, [param]);
const emptyPickState = { value: '', label: t('products:State') };
useEffect(() => {
const baseState = audit_state === 'all' ? emptyPickState : stateMapVal[`${audit_state}`];
if (isEmpty(pickAuditState)) {
setPickAuditState(baseState);
}
return () => {};
}, [audit_state, stateMapVal])
const handleYearChange = (value) => {
setPickYear(value);
setParam((pre) => ({ ...pre, ...{ pick_year: value } }));
};
const handleAuditStateChange = (labelValue) => {
const { value } = labelValue || emptyPickState;
setPickAuditState(labelValue || emptyPickState);
setParam((pre) => ({ ...pre, ...{ pick_state: value } }));
};
const addproducts = () => {
onStateChange('addProducts');
};
const submitreview = () => {
onStateChange('submitReview');
}
return (
<div className='flex justify-end items-center gap-4 h-full'>
<div className='grow'>
<h2 className='m-0 leading-tight'>
<Divider type={'vertical'} />
<Select options={yearOptions} variant={'borderless'} className='w-24' size='large' value={pickYear} onChange={handleYearChange} />
<Divider type={'vertical'} />
<AuditStateSelector variant={'borderless'} className='w-32' size='large' value={pickAuditState} onChange={handleAuditStateChange} />
</h2>
</div>
<Button size='small' type={'primary'} onClick={addproducts}>
增加产品
</Button>
<Button size='small' type={'primary'} onClick={submitreview}>
提交审核
</Button>
</div>
);
};
export default YearSelector;

@ -82,11 +82,12 @@ function Index() {
render: (_, r) => ( render: (_, r) => (
<Space size={'large'}> <Space size={'large'}>
<Link to={`/products/${r.travel_agency_id}/${searchValues.use_year || 'all'}/${searchValues.audit_state || 'all'}/edit`}>{t('Edit')}</Link> <Link to={`/products/${r.travel_agency_id}/${searchValues.use_year || 'all'}/${searchValues.audit_state || 'all'}/edit`}>{t('Edit')}</Link>
{r.audit_state_id >= 0 ? ( <Link to={`/products/${r.travel_agency_id}/${searchValues.use_year || 'all'}/${searchValues.audit_state || 'all'}/audit`}>{t('Audit')}</Link>
{/* {r.audit_state_id >= 0 ? (
<Link to={`/products/${r.travel_agency_id}/${searchValues.use_year || 'all'}/${searchValues.audit_state || 'all'}/audit`}>{t('Audit')}</Link> <Link to={`/products/${r.travel_agency_id}/${searchValues.use_year || 'all'}/${searchValues.audit_state || 'all'}/audit`}>{t('Audit')}</Link>
) : ( ) : (
<span className='text-muted'>{t('Audit')}</span> <span className='text-muted'>{t('Audit')}</span>
)} )} */}
<Button type='link' size={'small'} onClick={() => openCopyModal(r, '#')}> <Button type='link' size={'small'} onClick={() => openCopyModal(r, '#')}>
{t('Copy') + '-' + t('products:#')} {t('Copy') + '-' + t('products:#')}
</Button> </Button>

@ -42,7 +42,7 @@ function Detail() {
}, },
]; ];
function detailTextRender(text, confirm) { function detailTextRender(_, confirm) {
const formattedText = confirm.PCI_ConfirmText;//.replace(/\;/g, '\n\n'); const formattedText = confirm.PCI_ConfirmText;//.replace(/\;/g, '\n\n');
return ( return (
<div className='whitespace-pre-line'> <div className='whitespace-pre-line'>
@ -51,19 +51,21 @@ function Detail() {
); );
} }
function attachmentRender(text, confirm) { function attachmentRender(_, confirm) {
return ( return (
<> <>
{confirm.attachmentList.map(attch => { {confirm.attachmentList.map(attch => {
return ( return (
<Tag bordered={false} icon={<FileOutlined />}><a href={attch.file_url} target='_blank'>{attch.file_name}</a></Tag> <Tag key={attch.file_name} bordered={false} icon={<FileOutlined />}>
<a href={attch.file_url} target='_blank' rel='noreferrer'>{attch.file_name}</a>
</Tag>
)} )}
)} )}
</> </>
); );
} }
function confirmRender(text, confirm) { function confirmRender(_, confirm) {
return ( return (
<Button type='link' onClick={() => showConfirmModal(confirm)}>{t('Confirm')}</Button> <Button type='link' onClick={() => showConfirmModal(confirm)}>{t('Confirm')}</Button>
); );
@ -128,7 +130,7 @@ function Detail() {
.finally(() => { .finally(() => {
setDataLoading(false); setDataLoading(false);
}); });
}, [reservationId]); }, [reservationId, getReservationDetail, notification]);
return ( return (
<> <>

@ -1,4 +1,4 @@
import { NavLink, useLocation } from 'react-router-dom' import { NavLink } from 'react-router-dom'
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { Row, Col, Space, Button, Table, Typography, Modal, App, Select } from 'antd' import { Row, Col, Space, Button, Table, Typography, Modal, App, Select } from 'antd'
import dayjs from 'dayjs' import dayjs from 'dayjs'
@ -90,13 +90,11 @@ function Newest() {
) )
} }
const location = useLocation()
const [isModalOpen, setIsModalOpen] = useState(false) const [isModalOpen, setIsModalOpen] = useState(false)
const [dataLoading, setDataLoading] = useState(false) const [dataLoading, setDataLoading] = useState(false)
const [guideSelectOptions, setGuideSelectOptions] = useState([]) const [guideSelectOptions, setGuideSelectOptions] = useState([])
const formValuesToSub = useFormStore((state) => state.formValuesToSub) const formValuesToSub = useFormStore((state) => state.formValuesToSub)
const [fetchAllGuideList, fetchReservationList, reservationList, reservationPage, cityList, selectReservation, getCityListByReservationId, setupCityGuide, updateReservationGuide] = const [fetchAllGuideList, fetchReservationList, reservationList, reservationPage, cityList, selectReservation, getCityListByReservationId, setupCityGuide, updateReservationGuide] =
useReservationStore((state) => useReservationStore((state) =>
[state.fetchAllGuideList, state.fetchReservationList, state.reservationList, state.reservationPage, state.cityList, state.selectReservation, state.getCityListByReservationId, state.setupCityGuide, state.updateReservationGuide]) [state.fetchAllGuideList, state.fetchReservationList, state.reservationList, state.reservationPage, state.cityList, state.selectReservation, state.getCityListByReservationId, state.setupCityGuide, state.updateReservationGuide])
@ -104,13 +102,9 @@ function Newest() {
const { notification } = App.useApp() const { notification } = App.useApp()
useEffect (() => { useEffect (() => {
if (location.search !== '?back') {
//
onSearchClick(1, 1)
}
fetchAllGuideList() fetchAllGuideList()
.then((guideList) => { .then((guideList) => {
const selectOptions = guideList.map((data, index) => { const selectOptions = guideList.map((data) => {
return { return {
value: data.guideId, value: data.guideId,
label: data.guideName label: data.guideName
@ -118,7 +112,7 @@ function Newest() {
}) })
setGuideSelectOptions(selectOptions) setGuideSelectOptions(selectOptions)
}) })
}, []) }, [fetchAllGuideList])
const showCityGuideModal = (reservation) => { const showCityGuideModal = (reservation) => {
setDataLoading(true) setDataLoading(true)
@ -137,6 +131,7 @@ function Newest() {
setDataLoading(false); setDataLoading(false);
}) })
} }
const handleOk = () => { const handleOk = () => {
updateReservationGuide() updateReservationGuide()
.finally(() => { .finally(() => {
@ -144,15 +139,18 @@ function Newest() {
setDataLoading(false); setDataLoading(false);
}) })
} }
const handleCancel = () => { const handleCancel = () => {
setIsModalOpen(false); setIsModalOpen(false);
setDataLoading(false); setDataLoading(false);
} }
// //
const onSearchClick = (current=1, status=null) => { const searchReservation = (submitValues, current=1) => {
setDataLoading(true) setDataLoading(true)
fetchReservationList(formValuesToSub, current) console.info('onSearchClick')
console.info(submitValues)
fetchReservationList(submitValues, current)
.catch(ex => { .catch(ex => {
notification.error({ notification.error({
message: `Notification`, message: `Notification`,
@ -208,20 +206,11 @@ function Newest() {
dates: { label: t('group:ArrivalDate') }, dates: { label: t('group:ArrivalDate') },
}, },
}} }}
onSubmit={(err, formVal, filedsVal) => { onMounted={(initialValue) => {
setDataLoading(true) searchReservation(initialValue)
fetchReservationList(formVal) }}
.catch(ex => { onSubmit={() => {
notification.error({ searchReservation(formValuesToSub)
message: 'Notification',
description: ex.message,
placement: 'top',
duration: 4,
})
})
.finally(() => {
setDataLoading(false)
})
}} }}
/> />
<Title level={3}></Title> <Title level={3}></Title>
@ -238,7 +227,9 @@ function Newest() {
total: reservationPage.total, total: reservationPage.total,
simple: true simple: true
}} }}
onChange={(pagination) => {onSearchClick(pagination.current)}} onChange={(pagination) => {
searchReservation(formValuesToSub, pagination.current)
}}
columns={reservationListColumns} dataSource={reservationList} columns={reservationListColumns} dataSource={reservationList}
/> />
</Col> </Col>

Loading…
Cancel
Save