todo: 产品编辑页面: 组件

perf/export-docx
Lei OT 11 months ago
parent ab50a891f2
commit 1dfc2d23d7

@ -16,7 +16,7 @@ const HeaderWrapper = ({ children, header, loading, backTo, ...props }) => {
<Flex justify={'space-between'} align={'center'} className='h-full'> <Flex justify={'space-between'} align={'center'} className='h-full'>
{/* {header} */} {/* {header} */}
<div className='grow h-full'>{header}</div> <div className='grow h-full'>{header}</div>
<BackBtn to={backTo} /> {backTo!==false && <BackBtn to={backTo} />}
</Flex> </Flex>
</Header> </Header>
<Divider className='my-2' /> <Divider className='my-2' />

@ -12,3 +12,9 @@ export const useHTLanguageSets = () => {
return newData; return newData;
}; };
export const useHTLanguageSetsMapVal = () => {
const stateSets = useHTLanguageSets();
const stateMapVal = stateSets.reduce((r, c) => ({ ...r, [`${c.value}`]: c }), {});
return stateMapVal;
};

@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import useAuthStore from '@/stores/Auth'; import useAuthStore from '@/stores/Auth';
import { PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT } from '@/config'; import { PERM_OVERSEA, PERM_AIR_TICKET, PERM_PRODUCTS_MANAGEMENT } from '@/config';
import { isEmpty } from '@/utils/commons';
/** /**
* 产品管理 相关的预设数据 * 产品管理 相关的预设数据
@ -100,18 +101,21 @@ export const useProductsAuditStatesMapVal = (value) => {
export const useProductsTypesFieldsets = (type) => { export const useProductsTypesFieldsets = (type) => {
const [isPermitted] = useAuthStore((state) => [state.isPermitted]); const [isPermitted] = useAuthStore((state) => [state.isPermitted]);
const infoDefault = [['code'], ['title']]; const infoDefault = [['code'], ['title']];
const infoAdmin = ['remarks', 'dept', 'display_to_c']; const infoAdmin = ['remarks', 'dept', ]; // 'display_to_c'
const infoTypesMap = { const infoTypesMap = {
'6': [[],[]], '6': [['city',],[]],
'B': [['city_id', 'km'], []], 'B': [['city', 'km'], []],
'J': [['city_id', 'recommends_rate', 'duration', 'display_to_c'], ['description',]], 'J': [['city', 'recommends_rate', 'duration', 'display_to_c'], ['description',]],
'Q': [['city_id', 'duration', ], ['description',]], 'Q': [['city', 'recommends_rate', 'duration', 'display_to_c'], ['description',]],
'D': [['city_id', 'recommends_rate','duration',], ['description',]], 'D': [['city', 'recommends_rate','duration', 'display_to_c'], ['description',]],
'7': [['city_id', 'recommends_rate', 'duration', 'display_to_c', 'open_weekdays'], ['description',]], // todo: 怎么是2个图 '7': [['city', 'recommends_rate', 'duration', 'display_to_c', 'open_weekdays'], ['description',]],
'R': [['city_id',], ['description',]], 'R': [['city',], ['description',]],
'8': [[],[]], // todo: ? '8': [['display_to_c'],[]], // todo: ?
}; };
const thisTypeFieldset = (_type) => { const thisTypeFieldset = (_type) => {
if (isEmpty(_type)) {
return infoDefault;
}
const adminSet = isPermitted(PERM_PRODUCTS_MANAGEMENT) ? infoAdmin : []; const adminSet = isPermitted(PERM_PRODUCTS_MANAGEMENT) ? infoAdmin : [];
return [ return [
[...infoDefault[0], ...infoTypesMap[_type][0], ...adminSet], [...infoDefault[0], ...infoTypesMap[_type][0], ...adminSet],

@ -391,7 +391,7 @@ export function at(obj, path) {
const indexes = path.split(".").map(i => i); const indexes = path.split(".").map(i => i);
result = [obj]; result = [obj];
for (let i = 0; i < indexes.length; i++) { for (let i = 0; i < indexes.length; i++) {
result = [result[0][indexes[i]]]; result = [result[0]?.[indexes[i]] || undefined];
} }
} }
return result; return result;

@ -1,8 +1,8 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
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 { Tooltip, Button, Card, Col, Row, Breadcrumb, Table, Popconfirm, Form, Input, InputNumber, Tag, Modal, Select, Tree, FloatButton, DatePicker, Spin, message, Divider,Empty, Flex } from 'antd';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useProductsTypes, useProductsAuditStatesMapVal } from '@/hooks/useProductsSets'; import { useProductsTypes, useProductsAuditStatesMapVal, useProductsTypesMapVal } from '@/hooks/useProductsSets';
import Extras from './Detail/Extras'; import Extras from './Detail/Extras';
import { isEmpty } from '@/utils/commons'; import { isEmpty } from '@/utils/commons';
import SecondHeaderWrapper from '@/components/SecondHeaderWrapper'; import SecondHeaderWrapper from '@/components/SecondHeaderWrapper';
@ -25,8 +25,12 @@ import RequireAuth from '@/components/RequireAuth'
import { PERM_ROLE_NEW } from '@/config' import { PERM_ROLE_NEW } from '@/config'
import { create } from 'zustand'; import { create } from 'zustand';
import { PERM_PRODUCTS_MANAGEMENT } from '@/config'; import { PERM_PRODUCTS_MANAGEMENT } from '@/config';
import { usingStorage } from '@/hooks/usingStorage';
import ProductsTree from './Detail/ProductsTree';
import ProductInfo from './Detail/ProductInfo';
function Detail() { function Detail() {
const { t } = useTranslation(); const { t } = useTranslation();
const productsTypesMapVal = useProductsTypesMapVal();
const [form] = Form.useForm(); const [form] = Form.useForm();
const navigate = useNavigate() const navigate = useNavigate()
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
@ -76,11 +80,14 @@ function Detail() {
setPickYear(value); setPickYear(value);
setParam((pre) => ({ ...pre, ...{ pick_year: value } })); setParam((pre) => ({ ...pre, ...{ pick_year: value } }));
}; };
const handleGetAgencyProducts = ({pick_year, pick_agency}={}) => {
const year = pick_year || use_year; const { travelAgencyId } = usingStorage();
const agency = pick_agency || travel_agency_id; const handleGetAgencyProducts = ({pick_year, pick_agency, pick_state}={}) => {
const year = pick_year || use_year || dayjs().year();
const agency = pick_agency || travel_agency_id || travelAgencyId;
const state = pick_state ?? audit_state;
console.log("loading",loading); console.log("loading",loading);
getAgencyProducts({ travel_agency_id: agency, use_year: year, audit_state }); getAgencyProducts({ travel_agency_id: agency, use_year: year, audit_state: state });
console.log("loading",loading); console.log("loading",loading);
console.log("AgencyProducts",agencyProducts); console.log("AgencyProducts",agencyProducts);
// navigate(`/products/${agency}/${year}/${audit_state}/edit`); // navigate(`/products/${agency}/${year}/${audit_state}/edit`);
@ -353,101 +360,101 @@ function Detail() {
} }
} }
useEffect(() => { // useEffect(() => {
const fetchData = async () => { // const fetchData = async () => {
if (productsTypes.length > 0 && travel_agency_id && Object.keys(agencyProducts).length > 0) { // if (productsTypes.length > 0 && travel_agency_id && Object.keys(agencyProducts).length > 0) {
const generateTreeData = (productsTypes, productsData) => { // const generateTreeData = (productsTypes, productsData) => {
return productsTypes.map(type => ({ // return productsTypes.map(type => ({
title: type.label, // title: type.label,
key: type.value, // key: type.value,
selectable: false, // selectable: false,
children: (productsData[type.value] || []).map(product => ({ // children: (productsData[type.value] || []).map(product => ({
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);
setTreeData(treeData); // setTreeData(treeData);
setExpandedKeys(tempExpandedKeys); // setExpandedKeys(tempExpandedKeys);
setProductsData(agencyProducts); // setProductsData(agencyProducts);
setDefaultData(treeData); // setDefaultData(treeData);
setDataList(flattenTreeData(treeData)); // setDataList(flattenTreeData(treeData));
setDataFetched(true); // setDataFetched(true);
} // }
}; // };
fetchData(); // fetchData();
}, [productsTypes, travel_agency_id, use_year, audit_state, agencyProducts]); // }, [productsTypes, travel_agency_id, use_year, audit_state, agencyProducts]);
const flattenTreeData = (tree) => { // const flattenTreeData = (tree) => {
let flatList = []; // let flatList = [];
const flatten = (nodes) => { // const flatten = (nodes) => {
nodes.forEach((node) => { // nodes.forEach((node) => {
flatList.push({ title: node.title, key: node.key }); // flatList.push({ title: node.title, key: node.key });
if (node.children) { // if (node.children) {
flatten(node.children); // flatten(node.children);
} // }
}); // });
}; // };
flatten(tree); // flatten(tree);
return flatList; // return flatList;
}; // };
const getParentKey = (key, tree) => { // const getParentKey = (key, tree) => {
let parentKey; // let parentKey;
for (let i = 0; i < tree.length; i++) { // for (let i = 0; i < tree.length; i++) {
const node = tree[i]; // const node = tree[i];
if (node.children) { // if (node.children) {
if (node.children.some((item) => item.key === key)) { // if (node.children.some((item) => item.key === key)) {
parentKey = node.key; // parentKey = node.key;
} else { // } else {
const pKey = getParentKey(key, node.children); // const pKey = getParentKey(key, node.children);
if (pKey) { // if (pKey) {
parentKey = pKey; // parentKey = pKey;
} // }
} // }
} // }
} // }
return parentKey; // return parentKey;
}; // };
const titleRender = (node) => { // const titleRender = (node) => {
const index = node.title.indexOf(searchValue); // const index = node.title.indexOf(searchValue);
const beforeStr = node.title.substr(0, index); // const beforeStr = node.title.substr(0, index);
const afterStr = node.title.substr(index + searchValue.length); // const afterStr = node.title.substr(index + searchValue.length);
const highlighted = ( // const highlighted = (
<span style={{ color: 'red' }}>{searchValue}</span> // <span style={{ color: 'red' }}>{searchValue}</span>
); // );
return index > -1 ? ( // return index > -1 ? (
<span> // <span>
{beforeStr} // {beforeStr}
{highlighted} // {highlighted}
{afterStr} // {afterStr}
</span> // </span>
) : ( // ) : (
<span>{node.title}</span> // <span>{node.title}</span>
); // );
}; // };
const onChange = (e) => { // const onChange = (e) => {
const { value } = e.target; // const { value } = e.target;
const newExpandedKeys = dataList // const newExpandedKeys = dataList
.filter(item => item.title.includes(value)) // .filter(item => item.title.includes(value))
.map(item => getParentKey(item.key, defaultData)) // .map(item => getParentKey(item.key, defaultData))
.filter((item, i, self) => item && self.indexOf(item) === i); // .filter((item, i, self) => item && self.indexOf(item) === i);
setExpandedKeys(newExpandedKeys); // setExpandedKeys(newExpandedKeys);
setSearchValue(value); // setSearchValue(value);
setAutoExpandParent(true); // setAutoExpandParent(true);
}; // };
const onExpand = (keys) => { // const onExpand = (keys) => {
setExpandedKeys(keys); // setExpandedKeys(keys);
setAutoExpandParent(false); // setAutoExpandParent(false);
}; // };
const edit = (record, index) => { const edit = (record, index) => {
setQuotationTableVisible(true); setQuotationTableVisible(true);
@ -684,7 +691,8 @@ 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];
const fatherKey = node._raw.info.product_type_id;
setSelectedCategory(productProject[fatherKey]); setSelectedCategory(productProject[fatherKey]);
setTags([languageLabel]); setTags([languageLabel]);
// //
@ -753,100 +761,100 @@ function Detail() {
let infoData = null; let infoData = null;
let lgcDetailsData = null; let lgcDetailsData = null;
console.log("") 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;
infoData = element.info; // infoData = element.info;
lgcDetailsData = element.lgc_details.map(item => { // lgcDetailsData = element.lgc_details.map(item => {
const newItem = { // const newItem = {
...item, // ...item,
description: item.descriptions, // description: item.descriptions,
}; // };
delete newItem.descriptions; // delete newItem.descriptions;
return newItem; // return newItem;
}); // });
} // }
}); // });
const quotationData = initialQuotationData.map(element => { // const quotationData = initialQuotationData.map(element => {
const updatedObject = { // const updatedObject = {
...element, // ...element,
unit: element.unit_id, // unit: element.unit_id,
tempKey: Math.random() // tempKey: Math.random()
}; // };
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;
}); // });
if (!node._raw.info.id) { // if (!node._raw.info.id) {
} // }
// lgc_details // // lgc_details
let newLgcDetails = {}; // let newLgcDetails = {};
if (lgcDetailsData) { // if (lgcDetailsData) {
lgcDetailsData.forEach(element => { // lgcDetailsData.forEach(element => {
newLgcDetails[element.lgc] = element; // newLgcDetails[element.lgc] = element;
}); // });
} // }
setLgc_details(newLgcDetails); // setLgc_details(newLgcDetails);
let sortedData = [...quotationData].sort((a, b) => { // let sortedData = [...quotationData].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));
const bValidPeriod = dayjs(b.use_dates_end).diff(dayjs(b.use_dates_start)); // const bValidPeriod = dayjs(b.use_dates_end).diff(dayjs(b.use_dates_start));
// // //
if (aValidPeriod !== bValidPeriod) { // if (aValidPeriod !== bValidPeriod) {
return aValidPeriod - bValidPeriod; // return aValidPeriod - bValidPeriod;
} // }
// // //
const aGroupSize = a.group_size_max - a.group_size_min; // const aGroupSize = a.group_size_max - a.group_size_min;
const bGroupSize = b.group_size_max - b.group_size_min; // const bGroupSize = b.group_size_max - b.group_size_min;
return aGroupSize - bGroupSize; // return aGroupSize - bGroupSize;
}); // });
const tempInfo = { // const tempInfo = {
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 || "",
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 || "",
travel_agency_name: activeAgency.travel_agency_name || "", // travel_agency_name: activeAgency.travel_agency_name || "",
lastedit_changed: infoData.lastedit_changed || "", // lastedit_changed: infoData.lastedit_changed || "",
remarks: infoData.remarks || "", // remarks: infoData.remarks || "",
duration: infoData.duration || "", // duration: infoData.duration || "",
duration_unit: infoData.duration_unit || "", // duration_unit: infoData.duration_unit || "",
open_weekdays: infoData.open_weekdays || "", // open_weekdays: infoData.open_weekdays || "",
recommends_rate: infoData.recommends_rate || "", // recommends_rate: infoData.recommends_rate || "",
dept: infoData.dept_id || "", // dept: infoData.dept_id || "",
display_to_c: infoData.display_to_c || "", // display_to_c: infoData.display_to_c || "",
km: infoData.km || "", // km: infoData.km || "",
city_name: infoData.city_name || "", // city_name: infoData.city_name || "",
city_id: infoData.city_id || "", // city_id: infoData.city_id || "",
product_type_name: infoData.product_type_name || "", // product_type_name: infoData.product_type_name || "",
dept_name: infoData.dept_name || "", // dept_name: infoData.dept_name || "",
}; // };
setInfo(tempInfo); // setInfo(tempInfo);
setQuotation(sortedData); // setQuotation(sortedData);
if (node._raw.info.id) { // if (node._raw.info.id) {
form.setFieldsValue({ // form.setFieldsValue({
info: tempInfo, // info: tempInfo,
lgc_details: { // lgc_details: {
lgc: language, // lgc: language,
title: newLgcDetails[language]?.title || '', // title: newLgcDetails[language]?.title || '',
description: newLgcDetails[language]?.description || '' // description: newLgcDetails[language]?.description || ''
} // }
}); // });
} // }
} }
}; };
@ -1040,24 +1048,33 @@ function Detail() {
} }
}; };
const handleStateChange = (newState) => { // const handleStateChange = (newState) => {
console.log("newState",newState) // console.log("newState",newState)
if(newState === 'addProducts'){ // if(newState === 'addProducts'){
setAddProductVisible(true); // setAddProductVisible(true);
} // }
if(newState === 'submitReview'){ // if(newState === 'submitReview'){
submitReview(); // submitReview();
} // }
}; // };
return ( return (
// <Spin spinning={treeData.length === 0}> <SecondHeaderWrapper loading={loading} backTo={false} header={
<SecondHeaderWrapper loading={loading} backTo={`/products`} header={<YearSelector title={activeAgency.travel_agency_name} refresh={handleGetAgencyProducts} onStateChange={handleStateChange}/>}> <Header title={activeAgency.travel_agency_name} refresh={handleGetAgencyProducts} handleNewProduct={() => setAddProductVisible(true)} handleSubmitForAudit={submitReview} />
{isEmpty(agencyProducts) ? <Empty /> :<div> // <YearSelector title={activeAgency.travel_agency_name} refresh={handleGetAgencyProducts} onStateChange={handleStateChange}/>
<Row> }>
<Col span={6} className=' relative'> {isEmpty(agencyProducts) ? <Empty /> :
<Card className='w-[inherit] fixed overflow-y-auto max-h-[80%] max-w-[22%] overflow-x-auto'> <div>
<Flex gap={10}>
{/* onNodeSelect={handleNodeSelect} */}
<ProductsTree className='basis-64 sticky top-16 overflow-y-auto shrink-0' style={{ height: 'calc(100vh - 182px)' }} />
<Divider type={'vertical'} className='mx-1 h-auto' />
<div className=' flex-auto'>
<ProductInfo />
{/* <Row> */}
{/* <Col span={6} className=' relative'> */}
{/* <Card className='w-[inherit] fixed overflow-y-auto max-h-[80%] max-w-[22%] overflow-x-auto'>
<Row> <Row>
<Search placeholder="Search" onChange={onChange} /> <Search placeholder="Search" onChange={onChange} />
</Row> </Row>
@ -1070,26 +1087,22 @@ function Detail() {
onExpand={onExpand} onExpand={onExpand}
titleRender={titleRender} titleRender={titleRender}
/> />
</Card> </Card> */}
</Col> {/* </Col> */}
<Col span={18}> {/* <Col span={18}> */}
<Form form={form} name="control-hooks" onFinish={onSave}> <Form form={form} name="control-hooks" onFinish={onSave}>
<Card {/* <Card
// style={{ width: "80%" }}
title={ title={
<Breadcrumb items={[ <Breadcrumb items={[
// { title: <Link to={'/'}></Link> }, { title: productsTypesMapVal[editingProduct?.info?.product_type_id]?.label || editingProduct?.info?.product_type_name },
{ title: <Link to={'/products'}>综费</Link> },
{ title: editingProduct?.info?.title || t('New') } { title: editingProduct?.info?.title || t('New') }
]} /> ]} />
} }
> > */}
<h2>{t('products:EditComponents.info')}</h2> {/* <h2>{t('products:EditComponents.info')}</h2>
<Row gutter={16}> <Row gutter={16}>
{selectedCategory.map((item, index) => { {selectedCategory.map((item, index) => {
// const key = `${item.code}-${index}`;
// console.log(key);
return ( return (
<Col span={8} id={index} key={`${item.code}-${index}`}> <Col span={8} id={index} key={`${item.code}-${index}`}>
<Form.Item name={['info', item.code]} label={item.name}> <Form.Item name={['info', item.code]} label={item.name}>
@ -1098,9 +1111,9 @@ function Detail() {
</Col> </Col>
); );
})} })}
</Row> </Row> */}
<div style={{ marginBottom: "1%", marginLeft: "3%" }}> {/* <div style={{ marginBottom: "1%", marginLeft: "3%" }}>
{tags.map(tag => ( {tags.map(tag => (
<Tag <Tag
key={tag} key={tag}
@ -1114,7 +1127,7 @@ 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="选择语言" open={isModalVisible} onOk={handleOk} onCancel={handleCancel}>
<Select <Select
showSearch showSearch
style={{ width: "80%" }} style={{ width: "80%" }}
@ -1132,9 +1145,9 @@ function Detail() {
)) ))
} }
</Select> </Select>
</Modal> </Modal> */}
<Form.Item label={t('products:Name')} name={['lgc_details', 'title']}> {/* <Form.Item label={t('products:Name')} name={['lgc_details', 'title']}>
<Input <Input
style={{ width: "30%" }} style={{ width: "30%" }}
onChange={(e) => handleChange('title', e.target.value)} onChange={(e) => handleChange('title', e.target.value)}
@ -1147,7 +1160,7 @@ function Detail() {
onChange={(e) => handleChange('description', e.target.value)} onChange={(e) => handleChange('description', e.target.value)}
disabled={isCanEditable} disabled={isCanEditable}
/> />
</Form.Item> </Form.Item> */}
{/* </Card> */} {/* </Card> */}
{/* </Card> */} {/* </Card> */}
@ -1174,18 +1187,18 @@ function Detail() {
} }
</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> */}
</Card> {/* </Card> */}
</Form> </Form>
{/* <Card style={{ width: "80%" }}> */} {/* <Card style={{ width: "80%" }}> */}
<Extras productId={2} /> <Extras productId={editingProduct?.info?.id} />
{/* </Card> */} {/* </Card> */}
{/* <FloatButton icon={<PlusCircleFilled />} onClick={() => setAddProductVisible(true)} /> */} {/* <FloatButton icon={<PlusCircleFilled />} onClick={() => setAddProductVisible(true)} /> */}
{/*
<FloatButton.Group <FloatButton.Group
trigger="hover" trigger="hover"
type="primary" type="primary"
@ -1209,13 +1222,15 @@ function Detail() {
console.log("result", result); console.log("result", result);
}} /> }} />
</Tooltip> </Tooltip>
</FloatButton.Group> </FloatButton.Group> */}
</Col> {/* </Col> */}
</Row> {/* </Row> */}
</div>
</Flex>
<Modal <Modal
title="批量添加价格" title="批量添加价格"
visible={batchImportPriceVisible} open={batchImportPriceVisible}
onOk={handleBatchImportOK} onOk={handleBatchImportOK}
onCancel={() => setBatchImportPriceVisible(false)} onCancel={() => setBatchImportPriceVisible(false)}
width="80%" width="80%"
@ -1225,7 +1240,7 @@ function Detail() {
<Modal <Modal
title="增加新产品" title="增加新产品"
visible={addProductVisible} open={addProductVisible}
onOk={handelAddProduct} onOk={handelAddProduct}
onCancel={() => setAddProductVisible(false)} onCancel={() => setAddProductVisible(false)}
> >
@ -1315,7 +1330,6 @@ function Detail() {
</Modal> </Modal>
</div>} </div>}
</SecondHeaderWrapper> </SecondHeaderWrapper>
// </Spin>
); );
} }
export default Detail; export default Detail;

@ -9,6 +9,7 @@ import SearchForm from '@/components/SearchForm';
import RequireAuth from '@/components/RequireAuth'; import RequireAuth from '@/components/RequireAuth';
import { PERM_PRODUCTS_MANAGEMENT } from '@/config'; import { PERM_PRODUCTS_MANAGEMENT } from '@/config';
import { useProductsTypesMapVal } from '@/hooks/useProductsSets'; import { useProductsTypesMapVal } from '@/hooks/useProductsSets';
import { usingStorage } from '@/hooks/usingStorage';
const NewAddonModal = ({ onPick, ...props }) => { const NewAddonModal = ({ onPick, ...props }) => {
const { travel_agency_id, use_year } = useParams(); const { travel_agency_id, use_year } = useParams();
@ -110,12 +111,15 @@ const Extras = ({ productId, onChange, ...props }) => {
const { notification, message } = App.useApp(); const { notification, message } = App.useApp();
const { travel_agency_id, use_year } = useParams(); const { travel_agency_id, use_year } = useParams();
const { travelAgencyId } = usingStorage();
const [extrasData, setExtrasData] = useState([]); const [extrasData, setExtrasData] = useState([]);
const handleGetAgencyProductExtras = async () => { const handleGetAgencyProductExtras = async () => {
const data = await getAgencyProductExtrasAction({ id: productId, travel_agency_id, use_year }); setExtrasData([]);
setExtrasData(data); console.log('handleGetAgencyProductExtras', productId);
// const data = await getAgencyProductExtrasAction({ id: productId, travel_agency_id: travel_agency_id || travelAgencyId, use_year });
// setExtrasData(data);
}; };
const handleNewAddOn = async (item) => { const handleNewAddOn = async (item) => {
@ -134,21 +138,13 @@ const Extras = ({ productId, onChange, ...props }) => {
}; };
useEffect(() => { useEffect(() => {
handleGetAgencyProductExtras(); if (productId) handleGetAgencyProductExtras();
return () => {}; return () => {};
}, []); }, [productId]);
const columns = [ const columns = [
{ title: t('products:Title'), dataIndex: ['info', 'title'], width: '16rem', }, { title: t('products:Title'), dataIndex: ['info', 'title'], width: '16rem', },
// {
// title: t('products:Offer'),
// dataIndex: ['quotation', '0', 'value'],
// width: '10rem',
// render: (_, { quotation }) => `${quotation[0].adult_cost} ${quotation[0].currency} / ${quotation[0].unit_name}`,
// },
// { title: t('products:Types'), dataIndex: 'age_type', width: '40%', },
{ {
title: '', title: '',
dataIndex: 'operation', dataIndex: 'operation',

@ -3,7 +3,7 @@ import { useParams, Link, useNavigate } from 'react-router-dom';
import { App, Button, Divider, Select } from 'antd'; import { App, Button, Divider, Select } from 'antd';
import { useProductsAuditStatesMapVal } from '@/hooks/useProductsSets'; import { useProductsAuditStatesMapVal } from '@/hooks/useProductsSets';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import useProductsStore, { postProductsQuoteAuditAction, } from '@/stores/Products/Index'; import useProductsStore, { postProductsQuoteAuditAction } from '@/stores/Products/Index';
import { isEmpty } from '@/utils/commons'; import { isEmpty } from '@/utils/commons';
import useAuthStore from '@/stores/Auth'; import useAuthStore from '@/stores/Auth';
import RequireAuth from '@/components/RequireAuth'; import RequireAuth from '@/components/RequireAuth';
@ -13,9 +13,9 @@ import dayjs from 'dayjs';
import VendorSelector from '@/components/VendorSelector'; import VendorSelector from '@/components/VendorSelector';
import AuditStateSelector from '@/components/AuditStateSelector'; import AuditStateSelector from '@/components/AuditStateSelector';
const Header = ({ refresh, ...props }) => { const Header = ({ refresh, ...props }) => {
const { travel_agency_id, use_year, audit_state } = useParams(); const { travel_agency_id, use_year, audit_state } = useParams();
const { t } = useTranslation(); const { t } = useTranslation();
const isPermitted = useAuthStore(state => state.isPermitted); const isPermitted = useAuthStore((state) => state.isPermitted);
const [activeAgency, setActiveAgency] = useProductsStore((state) => [state.activeAgency, state.setActiveAgency]); const [activeAgency, setActiveAgency] = useProductsStore((state) => [state.activeAgency, state.setActiveAgency]);
const stateMapVal = useProductsAuditStatesMapVal(); const stateMapVal = useProductsAuditStatesMapVal();
const { message, notification } = App.useApp(); const { message, notification } = App.useApp();
@ -25,12 +25,12 @@ const Header = ({ refresh, ...props }) => {
const currentYear = dayjs().year(); const currentYear = dayjs().year();
const baseYear = Number(use_year === 'all' ? currentYear : use_year); const baseYear = Number(use_year === 'all' ? currentYear : use_year);
for (let i = baseYear - 3; i <= baseYear + 3; i++) { for (let i = baseYear - 3; i <= baseYear + 3; i++) {
yearOptions.push({ label: i, value: i, }); yearOptions.push({ label: i, value: i });
} }
const [param, setParam] = useState({ pick_year: baseYear, pick_agency: travel_agency_id, }); const [param, setParam] = useState({ pick_year: baseYear, pick_agency: travel_agency_id });
const [pickYear, setPickYear] = useState(baseYear); const [pickYear, setPickYear] = useState(baseYear);
const [pickAgency, setPickAgency] = useState({value: activeAgency.travel_agency_id, label: activeAgency.travel_agency_name }); const [pickAgency, setPickAgency] = useState({ value: activeAgency.travel_agency_id, label: activeAgency.travel_agency_name });
const [pickAuditState, setPickAuditState] = useState(); const [pickAuditState, setPickAuditState] = useState();
useEffect(() => { useEffect(() => {
refresh(param); refresh(param);
@ -45,8 +45,7 @@ const Header = ({ refresh, ...props }) => {
setPickAuditState(baseState); setPickAuditState(baseState);
} }
return () => {}; return () => {};
}, [audit_state, stateMapVal]) }, [audit_state, stateMapVal]);
const handleYearChange = (value) => { const handleYearChange = (value) => {
setPickYear(value); setPickYear(value);
@ -89,7 +88,7 @@ const Header = ({ refresh, ...props }) => {
<h2 className='m-0 leading-tight'> <h2 className='m-0 leading-tight'>
{isPermitted(PERM_PRODUCTS_OFFER_AUDIT) ? ( {isPermitted(PERM_PRODUCTS_OFFER_AUDIT) ? (
<VendorSelector <VendorSelector
value={{label: activeAgency.travel_agency_name, value: activeAgency.travel_agency_id}} value={{ label: activeAgency.travel_agency_name, value: activeAgency.travel_agency_id }}
onChange={handleAgencyChange} onChange={handleAgencyChange}
allowClear={false} allowClear={false}
mode={null} mode={null}
@ -110,7 +109,8 @@ const Header = ({ refresh, ...props }) => {
</div> </div>
{/* <Button size='small'>{t('Copy')}</Button> */} {/* <Button size='small'>{t('Copy')}</Button> */}
{/* <Button size='small'>{t('Import')}</Button> */} {/* <Button size='small'>{t('Import')}</Button> */}
<RequireAuth subject={PERM_PRODUCTS_OFFER_PUT}> {/* PERM_PRODUCTS_OFFER_PUT */}
<RequireAuth subject={PERM_PRODUCTS_OFFER_AUDIT}>
<Link className='px-2' to={`/products/${activeAgency.travel_agency_id}/${pickYear}/${audit_state}/edit`}> <Link className='px-2' to={`/products/${activeAgency.travel_agency_id}/${pickYear}/${audit_state}/edit`}>
{t('Edit')} {t('Edit')}
</Link> </Link>
@ -129,8 +129,21 @@ const Header = ({ refresh, ...props }) => {
</Button> </Button>
</RequireAuth> </RequireAuth>
{/* todo: export, 审核完成之后才能导出 */} {/* todo: export, 审核完成之后才能导出 */}
<Button size='small'>{t('Print')} PDF</Button> <RequireAuth subject={PERM_PRODUCTS_OFFER_AUDIT}>
{/* <PrintContractPDF /> */} <Button size='small'>{t('Print')} PDF</Button>
{/* <PrintContractPDF /> */}
</RequireAuth>
{/* 编辑 */}
<RequireAuth subject={PERM_PRODUCTS_OFFER_PUT}>
<Button size='small' type={'primary'} onClick={props.handleNewProduct}>
{t('New')}{t('products:#')}
</Button>
</RequireAuth>
<RequireAuth subject={PERM_PRODUCTS_OFFER_PUT}>
<Button size='small' type={'primary'} onClick={props.handleSubmitForAudit}>
提交审核
</Button>
</RequireAuth>
</div> </div>
); );
}; };

@ -0,0 +1,217 @@
import { createContext, useEffect, useState } from 'react';
import { Breadcrumb, Form, Divider, Button, Input, Select, Row, Col } from 'antd';
import { useTranslation } from 'react-i18next';
import { useProductsTypesMapVal, } from '@/hooks/useProductsSets';
import useProductsStore from '@/stores/Products/Index';
import RequireAuth from '@/components/RequireAuth';
import { PERM_PRODUCTS_MANAGEMENT, PERM_PRODUCTS_OFFER_AUDIT, PERM_PRODUCTS_OFFER_PUT } from '@/config';
import DeptSelector from '@/components/DeptSelector';
import CitySelector from '@/components/CitySelector';
import { at, isEmpty } from '@/utils/commons';
import ProductInfoForm from './ProductInfoForm';
const ProductInfo = ({ ...props }) => {
const { t } = useTranslation();
const [form] = Form.useForm();
const productsTypesMapVal = useProductsTypesMapVal();
const [agencyProducts, editingProduct, setEditingProduct] = useProductsStore((state) => [state.agencyProducts, state.editingProduct, state.setEditingProduct]);
useEffect(() => {
return () => {
}
}, [editingProduct])
const productTypeFormItems = {
'6': [
{ key: 'code', name: t('products:Code'), nameKey: 'products:Code' },
{ key: 'city_name', name: t('products:City'), nameKey: 'products:City' },
],
'B': [
{ key: 'code', name: t('products:Code'), nameKey: 'products:Code' },
{ key: 'city_name', name: t('products:City'), nameKey: 'products:City' },
{ key: 'km', name: t('products:KM'), nameKey: 'products:KM' },
{
key: 'remarks',
name: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>{t('products:Remarks')}</RequireAuth>,
nameKey: 'products:Remarks',
},
],
'J': [
{ key: 'code', name: t('products:Code'), nameKey: 'products:Code' },
{ key: 'city_name', name: t('products:City'), nameKey: 'products:City' },
{
key: 'recommends_rate',
name: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>{t('products:recommendationRate')}</RequireAuth>,
nameKey: 'products:recommendationRate',
},
{ key: 'duration', name: t('products:Duration'), nameKey: 'products:Duration' },
{
key: 'dept_name',
name: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>{t('products:Dept')}</RequireAuth>,
nameKey: 'products:Dept',
},
{
key: 'display_to_c',
name: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>{t('products:DisplayToC')}</RequireAuth>,
nameKey: 'products:DisplayToC',
},
{
key: 'remarks',
name: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>{t('products:Remarks')}</RequireAuth>,
nameKey: 'products:Remarks',
},
],
'Q': [
{ key: 'code', name: t('products:Code'), nameKey: 'products:Code' },
{ key: 'city_name', name: t('products:City'), nameKey: 'products:City' },
{
key: 'recommends_rate',
name: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>{t('products:recommendationRate')}</RequireAuth>,
nameKey: 'products:recommendationRate',
},
{ key: 'duration', name: t('products:Duration'), nameKey: 'products:Duration' },
{
key: 'dept_name',
name: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>{t('products:Dept')}</RequireAuth>,
nameKey: 'products:Dept',
},
{
key: 'display_to_c',
name: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>{t('products:DisplayToC')}</RequireAuth>,
nameKey: 'products:DisplayToC',
},
{
key: 'remarks',
name: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>{t('products:Remarks')}</RequireAuth>,
nameKey: 'products:Remarks',
},
],
'D': [
{ key: 'code', name: t('products:Code'), nameKey: 'products:Code' },
{ key: 'city_name', name: t('products:City'), nameKey: 'products:City' },
{
key: 'recommends_rate',
name: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>{t('products:recommendationRate')}</RequireAuth>,
nameKey: 'products:recommendationRate',
},
{ key: 'duration', name: t('products:Duration'), nameKey: 'products:Duration' },
{
key: 'dept_name',
name: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>{t('products:Dept')}</RequireAuth>,
nameKey: 'products:Dept',
},
{
key: 'display_to_c',
name: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>{t('products:DisplayToC')}</RequireAuth>,
nameKey: 'products:DisplayToC',
},
{
key: 'remarks',
name: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>{t('products:Remarks')}</RequireAuth>,
nameKey: 'products:Remarks',
},
],
'7': [
{ key: 'code', name: t('products:Code'), nameKey: 'products:Code' },
{ key: 'city_name', name: t('products:City'), nameKey: 'products:City' },
{
key: 'recommends_rate',
name: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>{t('products:recommendationRate')}</RequireAuth>,
nameKey: 'products:recommendationRate',
},
{ key: 'duration', name: t('products:Duration'), nameKey: 'products:Duration' },
{ key: 'open_weekdays', name: t('products:OpenWeekdays'), nameKey: 'products:OpenWeekdays' },
{
key: 'remarks',
name: <RequireAuth subject={PERM_PRODUCTS_MANAGEMENT}>{t('products:Remarks')}</RequireAuth>,
nameKey: 'products:Remarks',
},
],
'8': [
{ key: 'code', name: t('products:Code'), nameKey: 'products:Code' },
{ key: 'city_name', name: t('products:City'), nameKey: 'products:City' },
],
'R': [
{ key: 'code', name: t('products:Code'), nameKey: 'products:Code' },
{ key: 'city_name', name: t('products:City'), nameKey: 'products:City' },
],
};
const isCanEditable = false;
const renderFormItem = (item) => {
// const item = { key: 'code', name: t('products:Code'), nameKey: 'products:Code' };
// console.log(editingProduct?.info);
if (isEmpty(editingProduct)) {
return null;
}
console.log(item.key, at(editingProduct?.info, item.key)[0]);
let InputCom = <Input disabled={isCanEditable} />;
switch (item.key) {
case 'duration':
InputCom = <Input suffix='H' disabled={isCanEditable} />;
break;
case 'display_to_c':
InputCom = (
<Select disabled={isCanEditable}>
<Select.Option value={0}>在计划显示不在报价信显示</Select.Option>
<Select.Option value={1}>计划和报价信都要显示</Select.Option>
<Select.Option value={2}>计划和报价信都不用显示</Select.Option>
</Select>
);
break;
case 'dept_name':
InputCom = <DeptSelector disabled={isCanEditable} />;
break;
case 'city_name':
InputCom = <CitySelector disabled={isCanEditable} />;
break;
case 'remarks':
InputCom = <Input.TextArea rows={2} disabled={isCanEditable} />;
break;
default:
InputCom = <Input disabled={isCanEditable} />;
}
return (
<Col span={item.key === 'remarks' ? 24 : 8} key={`${item.key}`}>
<Form.Item name={item.key} label={t(item.nameKey)} >
{/* {renderFormItem(item)} */}
<InputCom />
</Form.Item>
</Col>
);
};
const onValuesChange = (changedValues, allValues) => {
// const dest = formValuesMapper(allValues);
console.log('form onValuesChange', Object.keys(changedValues), );
};
const onSave = (e) => {
console.log(e);
};
return (
<>
<Breadcrumb
items={[
{ title: productsTypesMapVal[editingProduct?.info?.product_type_id]?.label || editingProduct?.info?.product_type_name },
{ title: editingProduct?.info?.title || t('New') },
]}
/>
<Divider className='my-1' />
<h2>{t('products:EditComponents.info')}</h2>
<ProductInfoForm editable showSubmit initialValues={editingProduct?.info} />
{/* <Form form={form} name='product-edit' initialValues={editingProduct?.info} onValuesChange={onValuesChange} onFinish={onSave}>
<Form.Item>
<div className='flex justify-around'>
<Button type='primary' htmlType='submit'>
{t('Save')}
</Button>
</div>
</Form.Item>
</Form> */}
<Divider className='my-1' />
</>
);
};
export default ProductInfo;

@ -0,0 +1,331 @@
import { useEffect } from 'react';
import { Form, Input, Row, Col, Select, DatePicker, Space, Button, InputNumber, Radio, Checkbox, Divider } from 'antd';
import { objectMapper, at } from '@/utils/commons';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from '@/config';
// import useFormStore from '@/stores/Form';
import { useTranslation } from 'react-i18next';
import { useWeekdays } from '@/hooks/useDatePresets';
import DeptSelector from '@/components/DeptSelector';
import CitySelector from '@/components/CitySelector';
import { fetchJSON } from '@/utils/request';
import { HT_HOST } from '@/config';
// import SearchInput from './SearchInput';
// import AuditStateSelector from './AuditStateSelector';
import { useProductsTypesMapVal, useProductsTypesFieldsets } from '@/hooks/useProductsSets';
import useProductsStore from '@/stores/Products/Index';
import ProductInfoLgc from './ProductInfoLgc';
const { RangePicker } = DatePicker;
const InfoForm = ({ formInstance, onSubmit, onReset, onValuesChange, editable, showSubmit, confirmText, formName, loading, ...props }) => {
const { t } = useTranslation('products');
const [agencyProducts, editingProduct, setEditingProduct] = useProductsStore((state) => [state.agencyProducts, state.editingProduct, state.setEditingProduct]);
const weekdays = useWeekdays();
// const [formValues, setFormValues] = useFormStore((state) => [state.formValues, state.setFormValues]);
// const [formValuesToSub, setFormValuesToSub] = useFormStore((state) => [state.formValuesToSub, state.setFormValuesToSub]);
const [form] = Form.useForm();
const { sort, hides, fieldProps, fieldComProps } = {
sort: '',
fieldProps: '',
fieldComProps: '',
hides: [],
shows: [],
...props.fieldsConfig,
};
const filedsets = useProductsTypesFieldsets(editingProduct?.info?.product_type_id);
const shows = filedsets[0];
// console.log('filedsets', filedsets, shows);
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) : '') },
{ key: 'enddate', transform: (arrVal) => (arrVal ? arrVal[1].format(DATE_FORMAT) : '') },
{ key: 'starttime', transform: (arrVal) => (arrVal ? arrVal[0].format(DATE_FORMAT) : '') },
{ key: 'endtime', transform: (arrVal) => (arrVal ? arrVal[1].format(SMALL_DATETIME_FORMAT) : '') },
],
'invoiceStatus': { key: 'invoiceStatus', transform: (value) => value?.value || value?.key || '', default: '' },
'audit_state': { key: 'audit_state', 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 : '';
},
},
'year': [{ key: 'year', transform: (arrVal) => (arrVal ? arrVal.format('YYYY') : '') }],
};
let dest = {};
const { dates, ...omittedValue } = values;
dest = { ...omittedValue, ...objectMapper(values, destinationObject) };
for (const key in dest) {
if (Object.prototype.hasOwnProperty.call(dest, key)) {
dest[key] = typeof dest[key] === 'string' ? (dest[key] || '').trim() : dest[key];
}
}
// omit empty
// Object.keys(dest).forEach((key) => (dest[key] == null || dest[key] === '' || dest[key].length === 0) && delete dest[key]);
return dest;
};
useEffect(() => {
// const dest = formValuesMapper(formValues);
// setFormValuesToSub(dest);
form.resetFields();
form.setFieldsValue(editingProduct?.info);
form.setFieldValue('city', { value: editingProduct?.info?.city_id, label: editingProduct?.info?.city_name });
// form.setFieldValue('open_weekdays', ['1', '2', '3', '4', '5', '6', '7']);
return () => {};
}, [editingProduct?.info?.id]);
const onFinish = (values) => {
console.log('Received values of form, origin form value: \n', values);
const dest = formValuesMapper(values);
console.log('form value send to onSubmit:\n', dest);
const str = new URLSearchParams(dest).toString();
// setFormValues(values);
// setFormValuesToSub(dest);
if (typeof onSubmit === 'function') {
onSubmit(null, dest, values, str);
}
};
const handleReset = () => {
form.setFieldsValue({
// 'DateType': undefined,
});
if (typeof onReset === 'function') {
onReset();
}
};
const onIValuesChange = (changedValues, allValues) => {
const dest = formValuesMapper(allValues);
// setFormValues(allValues);
// setFormValuesToSub(dest);
// console.log('form onValuesChange', Object.keys(changedValues), args);
if (typeof onValuesChange === 'function') {
onValuesChange(dest);
}
};
return (
<>
<Form form={form} disabled={!(editable || false)} name={formName || 'product_info'} onFinish={onFinish} onValuesChange={onIValuesChange}>
<Row gutter={16}>
{getFields({ sort, initialValue: editingProduct?.info, hides, shows, fieldProps, fieldComProps, form, t, dataSets: { weekdays } })}
{/* {showSubmit && (
<Col flex='1 0 90px' className='flex justify-end items-start'>
<Space align='center'>
<Button size={'middle'} type='primary' htmlType='submit' loading={loading}>
{confirmText || t('common:Save')}
</Button>
</Space>
</Col>
)} */}
</Row>
{/* <Divider className='my-1' /> */}
<ProductInfoLgc formInstance={form} />
<Form.Item hidden name={'id'} label={'ID'}>
<Input />
</Form.Item>
{showSubmit && (
<Form.Item>
<div className='flex justify-around'>
<Button type='primary' htmlType='submit'>
{t('common:Save')}
</Button>
</div>
</Form.Item>
)}
</Form>
</>
);
};
function getFields(props) {
const { fieldProps, fieldComProps, form, t, dataSets } = props;
console.log('getFields', props.initialValue);
const bigCol = 4 * 2;
const midCol = 6;
const layoutProps = {
// gutter: { xs: 8, sm: 8, lg: 16 },
lg: { span: 4 },
md: { span: 8 },
sm: { span: 12 },
xs: { span: 24 },
};
const item = (name, sort = 0, render, col) => {
const customCol = col || 4;
const mdCol = customCol * 2;
return {
'key': '',
sort,
name,
render,
'hide': false,
'col': { lg: { span: customCol }, md: { span: mdCol < 8 ? 10 : mdCol }, flex: mdCol < 8 ? '1 0' : '' },
};
};
let baseChildren = [];
baseChildren = [
item(
'code',
99,
<Form.Item name='code' label={t('Code')} {...fieldProps.code} initialValue={at(props, 'initialValue.code')[0]}>
<Input allowClear {...fieldComProps.code} />
</Form.Item>,
fieldProps?.code?.col || 8
),
item(
'city', // todo:
99,
<Form.Item name='city' label={t('City')} {...fieldProps.city} initialValue={at(props, 'initialValue.city')[0]}>
<CitySelector />
</Form.Item>,
fieldProps?.city?.col || 8
),
item(
'dept', // todo:
99,
<Form.Item name='dept' label={t('Dept')} {...fieldProps.dept}>
<DeptSelector labelInValue={false} />
</Form.Item>,
fieldProps?.dept?.col || 8
),
item(
'duration',
99,
<Form.Item name='duration' label={t('Duration')} {...fieldProps.duration}>
<InputNumber suffix={'H'} max={24} />
{/* <Input allowClear {...fieldComProps.duration} suffix={'H'} /> */}
</Form.Item>,
fieldProps?.duration?.col || 8
),
item(
'km',
99,
<Form.Item name='km' label={t('KM')} {...fieldProps.km}>
<InputNumber suffix={'KM'} min={1} />
</Form.Item>,
fieldProps?.km?.col || 8
),
item(
'recommends_rate', // todo:
99,
<Form.Item name='recommends_rate' label={t('RecommendsRate')} {...fieldProps.recommends_rate}>
{/* <Input placeholder={t('RecommendsRate')} allowClear /> */}
<Select
style={{ width: '100%' }}
labelInValue
options={[
{ value: '1', label: 'Top 1' },
{ value: '2', label: '2' },
{ value: '3', label: '3' },
{ value: '4', label: '4' },
{ value: '5', label: '5' },
]}
/>
</Form.Item>,
fieldProps?.recommends_rate?.col || 8
),
item(
'open_weekdays',
99,
<Form.Item name='open_weekdays' label={t('OpenWeekdays')} initialValue={['1', '2', '3', '4', '5', '6', '7']} {...fieldProps.open_weekdays}>
{/* 默认全部 */}
<Checkbox.Group options={dataSets.weekdays} />
</Form.Item>,
fieldProps?.open_weekdays?.col || 24
),
item(
'display_to_c',
99,
<Form.Item
name='display_to_c'
label={t('DisplayToC')}
{...fieldProps.display_to_c}
// rules={[
// () => ({
// validator(_, value) {
// if ((value || []).includes(153002) && !(value || []).includes(153001)) {
// return Promise.reject(new Error(''));
// }
// return Promise.resolve();
// },
// }),
// ]}
>
{/* <Checkbox.Group
options={[
{ value: 153001, label: '报价信不显示' },
{ value: 153002, label: '计划不显示' },
]}
/> */}
<Select labelInValue={false} options={[
{ value: '153001', label: '在计划显示,不在报价信显示' },
{ value: 0, label: '计划和报价信都要显示' },
{ value: '153001, 153002', label: '计划和报价信都不用显示' },
]} />
</Form.Item>,
fieldProps?.display_to_c?.col || 8
),
item(
'remarks',
99,
<Form.Item name='remarks' label={t('Remarks')} {...fieldProps.remarks}>
<Input.TextArea allowClear rows={2} maxLength={2000} {...fieldComProps.remarks} />
</Form.Item>,
fieldProps?.remarks?.col || 24
),
];
baseChildren = baseChildren
.map((x) => {
x.hide = false;
if (props.sort === undefined) {
return x;
}
const tmpSort = props.sort;
for (const key in tmpSort) {
if (Object.prototype.hasOwnProperty.call(tmpSort, key)) {
if (x.name === key) {
x.sort = tmpSort[key];
}
}
}
return x;
})
.map((x) => {
if (props.hides.length === 0 && props.shows.length === 0) {
return x;
}
if (props.hides.length === 0) {
x.hide = !props.shows.includes(x.name);
} else if (props.shows.length === 0) {
x.hide = props.hides.includes(x.name);
}
return x;
})
.filter((x) => !x.hide)
.sort((a, b) => {
return a.sort < b.sort ? -1 : 1;
});
const children = [];
const leftStyle = {}; // { borderRight: '1px solid #dedede' };
for (let i = 0; i < baseChildren.length; i++) {
let style = {}; // { padding: '0px 2px' };
style = i % 2 === 0 && baseChildren[i].col === 12 ? { ...style, ...leftStyle } : style;
style = !baseChildren[i].hide ? { ...style, display: 'block' } : { ...style, display: 'none' };
const Item = (
<Col key={String(i)} style={style} {...layoutProps} {...baseChildren[i].col}>
{baseChildren[i].render}
</Col>
);
children.push(Item);
}
return children;
}
export default InfoForm;

@ -0,0 +1,163 @@
import { createContext, useEffect, useState, useRef } from 'react';
import { Input, Tabs, Modal, Select, Form } from 'antd';
import useProductsStore from '@/stores/Products/Index';
import { useHTLanguageSets, useHTLanguageSetsMapVal } from '@/hooks/useHTLanguageSets';
import RequireAuth from '@/components/RequireAuth';
import useAuthStore from '@/stores/Auth';
import { PERM_PRODUCTS_MANAGEMENT, PERM_PRODUCTS_OFFER_AUDIT, PERM_PRODUCTS_OFFER_PUT } from '@/config';
import { useTranslation } from 'react-i18next';
import { useDefaultLgc } from '@/i18n/LanguageSwitcher';
import { cloneDeep, isEmpty } from '@/utils/commons';
const ProductInfoLgc = ({ formInstance, ...props }) => {
const { t } = useTranslation();
const { language: languageHT } = useDefaultLgc();
const [isPermitted, currentUser] = useAuthStore((state) => [state.isPermitted, state.currentUser]);
const HTLanguageSetsMapVal = useHTLanguageSetsMapVal();
const allLgcOptions = useHTLanguageSets();
const [agencyProducts, editingProduct, setEditingProduct] = useProductsStore((state) => [state.agencyProducts, state.editingProduct, state.setEditingProduct]);
const initialItems = [
{
label: 'Tab 1',
children: 'Content of Tab 1',
key: '1',
},
];
const [activeKey, setActiveKey] = useState();
const [items, setItems] = useState([]);
const newTabIndex = useRef(0);
useEffect(() => {
const existsLgc = (editingProduct?.lgc_details || []).map((ele) => ({
...ele,
label: HTLanguageSetsMapVal[ele.lgc].label,
key: `${editingProduct.info.id}-${ele.id}`,
closable: false, // isPermitted(PERM_PRODUCTS_MANAGEMENT) ? true : false,
children: (
<>
<Form.Item name={[`${ele.lgc}`, 'title']} label={t('products:Title')} initialValue={ele.title}>
<Input
// onChange={(e) => handleChange('title', e.target.value)}
// disabled={isCanEditable}
disabled={!isEmpty(ele.title)}
/>
</Form.Item>
<Form.Item name={[`${ele.lgc}`, 'description']} label={t('products:Description')} initialValue={ele.descriptions}>
<Input.TextArea
rows={3}
// onChange={(e) => handleChange('description', e.target.value)}
disabled={!isEmpty(ele.descriptions)}
/>
</Form.Item>
<Form.Item hidden name={[`${ele.lgc}`, 'lgc']} initialValue={ele.lgc}>
<Input />
</Form.Item>
<Form.Item hidden name={[`${ele.lgc}`, 'id']} initialValue={ele.id}>
<Input />
</Form.Item>
</>
),
}));
setItems(existsLgc);
const pageDefaultLgcI = (editingProduct?.lgc_details || []).findIndex((ele) => ele.lgc === languageHT);
setActiveKey(existsLgc?.[pageDefaultLgcI || 0]?.key);
// formInstance.validateFields();
const filterLgcOptions = allLgcOptions.filter((ele) => !existsLgc.some((item) => `${item.lgc}` === ele.value));
setLgcOptions(filterLgcOptions);
return () => {};
}, [editingProduct]);
const onChange = (newActiveKey) => {
setActiveKey(newActiveKey);
};
const addLgc = (lgcItem) => {
const newActiveKey = lgcItem.value; // `newTab${newTabIndex.current++}`;
const newPanes = [...items];
newPanes.push({
...lgcItem,
children: (
<>
<Form.Item name={[`${lgcItem.value}`, 'title']} label={t('products:Title')}>
<Input />
</Form.Item>
<Form.Item name={[`${lgcItem.value}`, 'description']} label={t('products:Description')}>
<Input.TextArea rows={3} />
</Form.Item>
<Form.Item hidden name={[`${lgcItem.value}`, 'lgc']} initialValue={lgcItem.value}>
<Input />
</Form.Item>
<Form.Item hidden name={[`${lgcItem.value}`, 'id']}>
<Input />
</Form.Item>
</>
),
});
setItems(newPanes);
setActiveKey(newActiveKey);
const currentLgcOptions = cloneDeep(lgcOptions);
currentLgcOptions.splice(currentLgcOptions.findIndex(ele => ele.value === lgcItem.value), 1);
setLgcOptions(currentLgcOptions);
setSelectNewLgc(null);
};
const remove = (targetKey) => {
let newActiveKey = activeKey;
let lastIndex = -1;
items.forEach((item, i) => {
if (item.key === targetKey) {
lastIndex = i - 1;
}
});
const newPanes = items.filter((item) => item.key !== targetKey);
if (newPanes.length && newActiveKey === targetKey) {
if (lastIndex >= 0) {
newActiveKey = newPanes[lastIndex].key;
} else {
newActiveKey = newPanes[0].key;
}
}
setItems(newPanes);
setActiveKey(newActiveKey);
setLgcOptions([...lgcOptions, ...items.filter(item => item.key === targetKey)]);
};
const onEdit = (targetKey, action) => {
if (action === 'add') {
setNewLgcModalVisible(true);
} else {
remove(targetKey);
}
};
const [newLgcModalVisible, setNewLgcModalVisible] = useState(false);
const [selectNewLgc, setSelectNewLgc] = useState();
const [lgcOptions, setLgcOptions] = useState(allLgcOptions);
const handleOk = () => {
addLgc(selectNewLgc);
setNewLgcModalVisible(false);
};
const handleCancel = () => {
setNewLgcModalVisible(false);
};
const onSelectNewLgc = (lgcItem) => {
setSelectNewLgc(lgcItem);
};
return (
<>
<Form.List name={'lgc_details'}>
{(fields, { add, remove }) => <Tabs type='editable-card' size='small' onChange={onChange} activeKey={activeKey} onEdit={onEdit} items={items} />}
</Form.List>
<Modal title='选择语言' open={newLgcModalVisible} onOk={handleOk} onCancel={() => setNewLgcModalVisible(false)}>
<Select
showSearch
labelInValue
style={{ width: '80%' }}
placeholder='选择语言'
optionFilterProp='children'
options={lgcOptions}
onChange={onSelectNewLgc}
value={selectNewLgc}
/>
</Modal>
</>
);
};
export default ProductInfoLgc;

@ -0,0 +1,146 @@
import { createContext, useEffect, useState } from 'react';
import { Tree, Input } from 'antd';
import { useTranslation } from 'react-i18next';
import useProductsStore from '@/stores/Products/Index';
import { useProductsTypes, useProductsAuditStatesMapVal } from '@/hooks/useProductsSets';
const flattenTreeFun = (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 ProductsTree = ({ onNodeSelect, ...props }) => {
const { t } = useTranslation();
const [agencyProducts, editingProduct, setEditingProduct] = useProductsStore((state) => [state.agencyProducts, state.editingProduct, state.setEditingProduct]);
const productsTypes = useProductsTypes();
const [treeData, setTreeData] = useState([]);
const [rawTreeData, setRawTreeData] = useState([]);
const [flattenTreeData, setFlattenTreeData] = useState([]);
const [expandedKeys, setExpandedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
useEffect(() => {
// ; , ; ,
const hasDataTypes = Object.keys(agencyProducts);
const _show = productsTypes
.filter((kk) => hasDataTypes.includes(kk.value))
.map((ele) => ({
...ele,
title: ele.label,
key: ele.value,
children: (agencyProducts[ele.value] || []).map(product => ({
title: product.info.title,
// key: `${ele.value}-${product.info.id}`,
key: product.info.id,
_raw: product,
})),
}));
setTreeData(_show);
setRawTreeData(_show);
setFlattenTreeData(flattenTreeFun(_show));
setExpandedKeys(productsTypes.map(item => item.key)); //
// setActiveKey(isEmpty(_show) ? [] : [_show[0].key]);
return () => {};
}, [productsTypes, agencyProducts]);
const [searchValue, setSearchValue] = useState('');
const onSearch = ({target: {value}}) => {
// const { value } = e.target;
const newExpandedKeys = flattenTreeData
.filter(item => item.title.includes(value))
.map(item => getParentKey(item.key, rawTreeData))
.filter((item, i, self) => item && self.indexOf(item) === i);
setExpandedKeys(newExpandedKeys);
setSearchValue(value);
setAutoExpandParent(true);
};
const handleNodeSelect = (_, { node }) => {
if (node._raw) {
setEditingProduct(node._raw);
}
if (typeof onNodeSelect === 'function') {
onNodeSelect(_, { node });
}
// setSelectedNodeid(node.key);
// const fatherKey = node.key.split('-')[0];
// setSelectedCategory(productProject[fatherKey]);
// setTags([languageLabel]);
// //
// if (selectedNodeid === node.key) return;
// 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 onExpand = (keys) => {
setExpandedKeys(keys);
setAutoExpandParent(false);
};
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 = (
<span style={{ color: 'red' }}>{searchValue}</span>
);
return index > -1 ? (
<span>
{beforeStr}
{highlighted}
{afterStr}
</span>
) : (
<span>{node.title}</span>
);
};
return (
<div className={`${props.className} relative`} style={props.style}>
<Input.Search placeholder='Search' onChange={onSearch} allowClear className='sticky top-1 z-20 mb-3' />
<Tree blockNode
onSelect={handleNodeSelect}
treeData={treeData}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
onExpand={onExpand}
titleRender={titleRender}
/>
</div>
);
};
export default ProductsTree;
Loading…
Cancel
Save