diff --git a/public/locales/en/common.json b/public/locales/en/common.json index f5dd835..0b75042 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -28,6 +28,7 @@ "sureCancel": "Are you sure to cancel?", "sureDelete":"Are you sure to delete?", + "sureSubmit":"Are you sure to submit?", "Yes": "Yes", "No": "No", diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 5c120b0..851fe85 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -28,6 +28,7 @@ "sureCancel": "确定取消?", "sureDelete":"确定删除?", + "sureSubmit":"确定提交?", "Yes": "是", "No": "否", diff --git a/src/hooks/useProductsSets.js b/src/hooks/useProductsSets.js index 8c588bf..904c653 100644 --- a/src/hooks/useProductsSets.js +++ b/src/hooks/useProductsSets.js @@ -101,7 +101,7 @@ export const useProductsAuditStatesMapVal = (value) => { export const useProductsTypesFieldsets = (type) => { const [isPermitted] = useAuthStore((state) => [state.isPermitted]); const infoDefault = [['city'], ['title']]; - const infoAdmin = ['code', 'remarks', 'dept']; // 'display_to_c' + const infoAdmin = ['title', 'code', 'remarks', 'dept']; // 'display_to_c' const infoDisplay = isPermitted(PERM_PRODUCTS_MANAGEMENT) ? ['display_to_c'] : []; const infoRecDisplay = isPermitted(PERM_PRODUCTS_MANAGEMENT) ? ['recommends_rate'] : []; const infoTypesMap = { diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js index 06771d6..19f0408 100644 --- a/src/stores/Products/Index.js +++ b/src/stores/Products/Index.js @@ -113,6 +113,7 @@ const initialState = { searchValues: {}, // 客服首页: 搜索条件 agencyList: [], // 客服首页: 搜索结果 activeAgency: {}, // 审核/编辑 页: 当前的供应商 + activeAgencyState: null, agencyProducts: {}, // 审核/编辑 页: 供应商产品列表 editingProduct: {}, // 编辑页: 当前编辑的产品 quotationList: [], // 编辑页: 当前产品报价列表 @@ -129,6 +130,7 @@ export const useProductsStore = create( setSearchValues: (searchValues) => set({ searchValues }), setAgencyList: (agencyList) => set({ agencyList }), setActiveAgency: (activeAgency) => set({ activeAgency }), + setActiveAgencyState: (activeAgencyState) => set({ activeAgencyState }), setAgencyProducts: (agencyProducts) => set({ agencyProducts }), // TODO:产品和价格会分开查询编辑, setEditingProduct: (product) => { @@ -214,7 +216,7 @@ export const useProductsStore = create( }, getAgencyProducts: async (param) => { - const { setLoading, setActiveAgency, setAgencyProducts, editingProduct, setEditingProduct } = get(); + const { setLoading, setActiveAgency, setActiveAgencyState, setAgencyProducts, editingProduct, setEditingProduct } = get(); setLoading(true); setAgencyProducts({}); // setEditingProduct({}); @@ -223,6 +225,7 @@ export const useProductsStore = create( const productsData = groupBy(res.products, (row) => row.info.product_type_id); setAgencyProducts(productsData); setActiveAgency(res.agency); + setActiveAgencyState(res.agency.audit_state_id); if (editingProduct?.info?.id) { const item = (productsData[editingProduct.info.product_type_id] || []).find((item) => item.info.id === editingProduct.info.id); setEditingProduct(item); diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index 4498cb3..8da780b 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -52,10 +52,11 @@ const PriceTable = ({ productType, dataSource, refresh }) => { }; const rowStyle = (r, tri) => { - const trCls = tri%2 !== 0 ? ' bg-stone-50' : ''; + const trCls = tri%2 !== 0 ? ' bg-stone-50' : ''; // 奇偶行 const [infoI, quoteI] = r.rowSpanI; - const bigTrCls = quoteI === 0 && tri !== 0 ? 'border-collapse border-double border-0 border-t-4 border-stone-300' : ''; - return [trCls, bigTrCls].join(' '); + const bigTrCls = quoteI === 0 && tri !== 0 ? 'border-collapse border-double border-0 border-t-4 border-stone-300' : ''; // 合并行双下划线 + const editedCls = (r.audit_state_id === 0 && isNotEmpty(r.lastedit_changed)) ? 'bg-red-100' : ''; // 待审核, 变更: 红色 + return [trCls, bigTrCls, editedCls].join(' '); }; const columns = [ @@ -163,16 +164,26 @@ const TypesPanels = (props) => { }; const Audit = ({ ...props }) => { + const { notification, modal } = App.useApp() const isPermitted = useAuthStore(state => state.isPermitted); const { travel_agency_id, use_year, audit_state } = useParams(); - const [loading, activeAgency, getAgencyProducts] = useProductsStore((state) => [state.loading, state.activeAgency, state.getAgencyProducts]); + const [activeAgency, getAgencyProducts] = useProductsStore((state) => [state.activeAgency, state.getAgencyProducts]); + const [loading, setLoading] = useProductsStore(state => [state.loading, state.setLoading]); const { travelAgencyId } = usingStorage(); 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; - getAgencyProducts({ travel_agency_id: agency, use_year: year, audit_state: state }); + getAgencyProducts({ travel_agency_id: agency, use_year: year, audit_state: state }).catch(ex => { + setLoading(false); + notification.error({ + message: 'Notification', + description: ex.message, + placement: 'top', + duration: 4, + }) + }); }; return ( diff --git a/src/views/products/Detail.jsx b/src/views/products/Detail.jsx index cdba40f..a2f8c97 100644 --- a/src/views/products/Detail.jsx +++ b/src/views/products/Detail.jsx @@ -1,53 +1,42 @@ -import { useState, useEffect } from 'react'; -import { Divider, Empty, Flex } from 'antd'; -import { useNavigate } from 'react-router-dom'; +import { useState } from 'react'; +import { App, Divider, Empty, Flex } from 'antd'; import { isEmpty } from '@/utils/commons'; import SecondHeaderWrapper from '@/components/SecondHeaderWrapper'; import Header from './Detail/Header'; import { useParams } from 'react-router-dom'; import useProductsStore from '@/stores/Products/Index'; import dayjs from 'dayjs'; -import { PERM_PRODUCTS_MANAGEMENT } from '@/config'; import { usingStorage } from '@/hooks/usingStorage'; import ProductsTree from './Detail/ProductsTree'; import ProductInfo from './Detail/ProductInfo'; -import useAuthStore from '@/stores/Auth'; import NewProductModal from './Detail/NewProductModal'; function Detail() { - const navigate = useNavigate(); + const { notification, modal } = App.useApp() const { travel_agency_id, audit_state, use_year } = useParams(); const [addProductVisible, setAddProductVisible] = useState(false); - const [agencyProducts, loading] = useProductsStore((state) => [state.agencyProducts, state.loading]); + const [agencyProducts, switchParams] = useProductsStore((state) => [state.agencyProducts, state.switchParams]); const [getAgencyProducts, activeAgency] = useProductsStore((state) => [state.getAgencyProducts, state.activeAgency]); - const [setSwitchParams] = useProductsStore((state) => [state.setSwitchParams]); - 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 [loading, setLoading] = useProductsStore(state => [state.loading, state.setLoading]); const { travelAgencyId } = usingStorage(); const handleGetAgencyProducts = ({ pick_year, pick_agency, pick_state } = {}) => { - const year = pick_year || use_year || dayjs().year(); + const year = pick_year || use_year || switchParams.use_year || dayjs().year(); const agency = pick_agency || travel_agency_id || travelAgencyId; const state = pick_state ?? audit_state; const param = { travel_agency_id: agency, use_year: year, audit_state: state }; - setSwitchParams(param); // setEditingProduct({}); - getAgencyProducts(param); + getAgencyProducts(param).catch(ex => { + setLoading(false); + notification.error({ + message: 'Notification', + description: ex.message, + placement: 'top', + duration: 4, + }) + }); }; - const isPermitted = useAuthStore((state) => state.isPermitted); - const topPerm = isPermitted(PERM_PRODUCTS_MANAGEMENT); // 高级权限 - const [newActionable, setNewActionable] = useState(false); - useEffect(() => { - const notAudit = activeAgency.audit_state_id < 0 || activeAgency.audit_state_id === 3; - setNewActionable(topPerm || notAudit); - return () => {}; - }, [activeAgency]); - return ( setAddProductVisible(true)} - newActionable={newActionable} /> }> {isEmpty(agencyProducts) ? ( diff --git a/src/views/products/Detail/CopyProducts.jsx b/src/views/products/Detail/CopyProducts.jsx index 641937d..a460e95 100644 --- a/src/views/products/Detail/CopyProducts.jsx +++ b/src/views/products/Detail/CopyProducts.jsx @@ -101,13 +101,20 @@ export const CopyProductsFormModal = ({ source, action = '#' | 'o', open, onSubm const handleCopyAgency = async (param) => { param.target_agency = isEmpty(param.target_agency) ? source.sourceAgency.travel_agency_id : param.target_agency; setCopyLoading(true); - console.log(param); - const toID = param.target_agency; - const success = await copyAgencyDataAction({...param, source_agency: source.sourceAgency.travel_agency_id}); + // console.log(param); + // const toID = param.target_agency; + const success = await copyAgencyDataAction({...param, source_agency: source.sourceAgency.travel_agency_id}).catch(ex => { + notification.error({ + message: 'Notification', + description: ex.message, + placement: 'top', + duration: 4, + }) + }); setCopyLoading(false); success ? message.success(t('Success')) : message.error(t('Failed')); - if (typeof onSubmit === 'function') { + if (success && typeof onSubmit === 'function') { onSubmit(param); } // setCopyModalVisible(false); diff --git a/src/views/products/Detail/Header.jsx b/src/views/products/Detail/Header.jsx index 9a2ba12..84d7bb0 100644 --- a/src/views/products/Detail/Header.jsx +++ b/src/views/products/Detail/Header.jsx @@ -1,10 +1,10 @@ import { useEffect, useState } from 'react'; import { useParams, Link, useNavigate, useLocation } from 'react-router-dom'; -import { App, Button, Divider, Select } from 'antd'; +import { App, Button, Divider, Popconfirm, Select } from 'antd'; import { useProductsAuditStatesMapVal } from '@/hooks/useProductsSets'; import { useTranslation } from 'react-i18next'; import useProductsStore, { postProductsQuoteAuditAction, postAgencyAuditAction } from '@/stores/Products/Index'; -import { isEmpty } from '@/utils/commons'; +import { isEmpty, objectMapper } from '@/utils/commons'; import useAuthStore from '@/stores/Auth'; import RequireAuth from '@/components/RequireAuth'; // import PrintContractPDF from './PrintContractPDF'; @@ -15,19 +15,21 @@ import AuditStateSelector from '@/components/AuditStateSelector'; const Header = ({ refresh, newActionable, ...props }) => { const location = useLocation(); + const isEditPage = location.pathname.includes('edit'); const showEditA = !location.pathname.includes('edit'); const showAuditA = !location.pathname.includes('audit'); 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 [switchParams] = useProductsStore((state) => [state.switchParams]); + const [switchParams, setSwitchParams] = useProductsStore((state) => [state.switchParams, state.setSwitchParams]); + const [activeAgencyState] = useProductsStore((state) => [state.activeAgencyState]); const stateMapVal = useProductsAuditStatesMapVal(); const { message, notification } = App.useApp(); const navigate = useNavigate(); const yearOptions = []; - const currentYear = dayjs().year(); + const currentYear = switchParams.use_year || dayjs().year(); const baseYear = use_year ? Number(use_year === 'all' ? currentYear : use_year) : currentYear; for (let i = currentYear - 5; i <= baseYear + 5; i++) { yearOptions.push({ label: i, value: i }); @@ -38,6 +40,8 @@ const Header = ({ refresh, newActionable, ...props }) => { const [pickAgency, setPickAgency] = useState({ value: activeAgency.travel_agency_id, label: activeAgency.travel_agency_name }); const [pickAuditState, setPickAuditState] = useState(); useEffect(() => { + const _param = objectMapper(param, { pick_year: 'use_year', pick_agency: 'travel_agency_id', pick_state: 'audit_state' }); + setSwitchParams({ ..._param }); refresh(param); return () => {}; @@ -169,19 +173,23 @@ const Header = ({ refresh, newActionable, ...props }) => { {/* 编辑 */} - {newActionable && ( + {isEditPage && ( + + + + )} + {activeAgencyState === 0 && ( <> - - - - + + + )} diff --git a/src/views/products/Detail/NewProductModal.jsx b/src/views/products/Detail/NewProductModal.jsx index 406ea9e..09ed31e 100644 --- a/src/views/products/Detail/NewProductModal.jsx +++ b/src/views/products/Detail/NewProductModal.jsx @@ -58,7 +58,7 @@ export const NewProductModal = ({ source, action = '#' | 'o', open, onSubmit, on const { t } = useTranslation(); const [formInstance, setFormInstance] = useState(); const [setEditingProduct] = useProductsStore((state) => [state.setEditingProduct]); - const [switchParams, setSwitchParams] = useProductsStore((state) => [state.switchParams, state.setSwitchParams]); + const [switchParams] = useProductsStore((state) => [state.switchParams]); const [copyLoading, setCopyLoading] = useState(false); const productsTypesMapVal = useProductsTypesMapVal(); diff --git a/src/views/products/Detail/ProductInfo.jsx b/src/views/products/Detail/ProductInfo.jsx index f35b5c3..56f493a 100644 --- a/src/views/products/Detail/ProductInfo.jsx +++ b/src/views/products/Detail/ProductInfo.jsx @@ -50,17 +50,16 @@ const ProductInfo = ({ ...props }) => { // "lastedit_changed": "", }; /** lgc_details */ - const copyFields = pick(editingProduct.info, ['title', 'product_type_id']); - const readyToSubInfo = { ...copyNewProduct.info, ...values.info, ...copyFields, type: copyFields.product_type_id, ...poster }; + const copyFields = pick(editingProduct.info, ['product_type_id']); // 'title', + const readyToSubInfo = { ...copyNewProduct.info, ...editingProduct.info, ...values.info, ...copyFields, type: copyFields.product_type_id, ...poster }; // console.log('onSave', editingProduct.info, readyToSubInfo); const prevLgcDetailsMapped = editingProduct.lgc_details.reduce((r, c) => ({ ...r, [c.lgc]: { ...c, description: c.descriptions } }), {}); // todo: description字段不一致 const mergedLgc = { ...prevLgcDetailsMapped, ...values.lgc_details_mapped }; /** quotation */ - // todo: 报价不能为空 const prevQuotationMapped = editingProduct.quotation.reduce((r, c) => ({ ...r, [c.id]: { ...c, unit: c.unit_id, audit_state: c.audit_state_id } }), {}); const mergedQ = { ...prevQuotationMapped, ...(values.quotation || []) }; - console.log(values); + // console.log(values); // return false; // debug: 0 /** 提交保存 */ setLoading(true); @@ -69,6 +68,14 @@ const ProductInfo = ({ ...props }) => { info: readyToSubInfo, lgc_details: Object.values(mergedLgc), quotation: Object.values(mergedQ), + }).catch(ex => { + setLoading(false); + notification.error({ + message: 'Notification', + description: ex.message, + placement: 'top', + duration: 4, + }) }); setLoading(false); success ? message.success(t('Success')) : message.error(t('Failed')); diff --git a/src/views/products/Detail/ProductInfoForm.jsx b/src/views/products/Detail/ProductInfoForm.jsx index 7e5e545..2e9fefe 100644 --- a/src/views/products/Detail/ProductInfoForm.jsx +++ b/src/views/products/Detail/ProductInfoForm.jsx @@ -97,7 +97,7 @@ const InfoForm = ({ onSubmit, onReset, onValuesChange, editable: _editable, show )} */} {/* */} - ({ @@ -110,6 +110,7 @@ const InfoForm = ({ onSubmit, onReset, onValuesChange, editable: _editable, show .map((x) => HTLanguageSetsMapVal[x.lgc].label) .join(', '); if (isNotEmpty(invalidLgcName)) { + // Please complete multi -language information return Promise.reject(new Error(`请完善多语种信息: ${invalidLgcName}`)); } return Promise.resolve(); @@ -122,9 +123,9 @@ const InfoForm = ({ onSubmit, onReset, onValuesChange, editable: _editable, show - */} {editable && (
@@ -174,6 +175,14 @@ function getFields(props) { }; let baseChildren = []; baseChildren = [ + item( + 'title', + 99, + + + , + fieldProps?.title?.col || midCol + ), item( 'code', 99, @@ -400,8 +409,8 @@ const formValuesMapper = (values) => { } // omit empty // Object.keys(dest).forEach((key) => (dest[key] == null || dest[key] === '' || dest[key].length === 0) && delete dest[key]); - const { lgc_details, lgc_details_mapped, ...info } = dest; // quotation - return { info, lgc_details, lgc_details_mapped }; // quotation + const { lgc_details, lgc_details_mapped, quotation, ...info } = dest; // quotation + return { info, lgc_details, lgc_details_mapped, quotation }; // quotation }; export default InfoForm; diff --git a/src/views/products/Detail/ProductInfoLgc.jsx b/src/views/products/Detail/ProductInfoLgc.jsx index 3ae3bae..cf937d4 100644 --- a/src/views/products/Detail/ProductInfoLgc.jsx +++ b/src/views/products/Detail/ProductInfoLgc.jsx @@ -18,7 +18,7 @@ const ProductInfoLgc = ({ editable, formInstance, ...props }) => { useEffect(() => { const existsLgc = (editingProduct?.lgc_details || []).map((ele, li) => ({ ...ele, - label: HTLanguageSetsMapVal[ele.lgc].label, + label: HTLanguageSetsMapVal[ele.lgc]?.label || ele.lgc, // key: `${editingProduct.info.id}-${ele.id}`, key: ele.lgc, closable: false, // isPermitted(PERM_PRODUCTS_MANAGEMENT) ? true : false,