From 3422b7c080e1f6a28da1978e7ba55462eab23306 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 23 Jul 2025 09:28:57 +0800 Subject: [PATCH] =?UTF-8?q?=E2=8F=B3feat:=20=E5=AE=A1=E6=A0=B8:=20?= =?UTF-8?q?=E6=9F=A5=E7=9C=8B=E4=BB=B7=E6=A0=BC=E5=8E=86=E5=8F=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/Products/Index.js | 34 +++++++- src/views/products/Audit.jsx | 157 +++++++++++++++++++++++++++++------ 2 files changed, 166 insertions(+), 25 deletions(-) diff --git a/src/stores/Products/Index.js b/src/stores/Products/Index.js index 850e8c4..cb90470 100644 --- a/src/stores/Products/Index.js +++ b/src/stores/Products/Index.js @@ -146,13 +146,45 @@ export const fetchRemarkList = async (params) => { } /** - * 获取合同备注 + * 保存合同备注 */ export const postRemarkList = async (params) => { const { errcode, result } = await postJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_memo_add`, params) return { errcode, result, success: errcode === 0 } } +/** + * 产品价格快照 + */ +export const getPPSnapshotAction = async (params) => { + const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_price_snapshot`, params) + return errcode !== 0 ? [] : result; +} + +/** + * 产品价格日志 + */ +export const getPPLogAction = async (params) => { + const { errcode, result } = await fetchJSON(`http://127.0.0.1:4523/m1/2602949-1933890-default/agency_product_price_log`, params) + return errcode !== 0 ? [] : result; +}; + +/** + * 产品价格: 已发布的 + */ +export const getPPRunningAction = async (params) => { + const { errcode, result } = await fetchJSON(`http://127.0.0.1:4523/m1/2602949-1933890-default/agency_product_price_running`, params) + return errcode !== 0 ? [] : result; +}; + +/** + * 修改产品的类型 + */ +export const moveProductTypeAction = async (params) => { + const { errcode, result } = await postJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_product_move`, params) + return errcode !== 0 ? [] : result; +}; + const defaultRemarkList = [ {id: 0, "product_type_id": "6","Memo": ""}, {id: 0, "product_type_id": "B","Memo": ""}, diff --git a/src/views/products/Audit.jsx b/src/views/products/Audit.jsx index 223ce22..43cadb0 100644 --- a/src/views/products/Audit.jsx +++ b/src/views/products/Audit.jsx @@ -1,10 +1,10 @@ import { useEffect, useState } from 'react'; import { useParams, Link } from 'react-router-dom'; -import { App, Empty, Button, Collapse, Table, Space, Alert } from 'antd'; -import { useProductsTypes, useProductsAuditStatesMapVal } from '@/hooks/useProductsSets'; +import { App, Empty, Button, Collapse, Table, Space, Alert, Tooltip, Popover, Typography } from 'antd'; +import { useProductsTypes, useProductsAuditStatesMapVal, formatGroupSize } from '@/hooks/useProductsSets'; import SecondHeaderWrapper from '@/components/SecondHeaderWrapper'; import { useTranslation } from 'react-i18next'; -import useProductsStore, { postProductsQuoteAuditAction, } from '@/stores/Products/Index'; +import useProductsStore, { getPPLogAction, getPPRunningAction, postProductsQuoteAuditAction, } from '@/stores/Products/Index'; import { cloneDeep, groupBy, isEmpty, isNotEmpty } from '@/utils/commons'; import useAuthStore from '@/stores/Auth'; import RequireAuth from '@/components/RequireAuth'; @@ -14,6 +14,88 @@ import dayjs from 'dayjs'; import { usingStorage } from '@/hooks/usingStorage'; import { ClockCircleFilled, ClockCircleOutlined, PlusCircleFilled, PlusCircleOutlined } from '@ant-design/icons'; +const parseJson = (str) => { + let result; + if (str === null || str === undefined || str === '') { + return []; + } + try { + result = JSON.parse(str); + return result; + } catch (e) { + return []; + } +}; + + const columnsSets = t => [ + { key: 'adult', title: t('AgeType.Adult'), width: '12rem', render: (_, { adult_cost, currency, unit_id, unit_name, lastedit_changed }) => { + const _changed = parseJson(lastedit_changed)?.reduce((acc, cur) => ({...acc, ...cur}), {}); + const preValue = isNotEmpty(_changed.adult_cost) ?
{_changed.adult_cost}
: null; + return (
{preValue}{`${adult_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}`}
)} }, + { key: 'child', title: t('AgeType.Child'), width: '12rem', render: (_, { child_cost, currency, unit_id, unit_name, lastedit_changed }) => { + const _changed = parseJson(lastedit_changed)?.reduce((acc, cur) => ({...acc, ...cur}), {}); + const preValue = isNotEmpty(_changed.child_cost) ?
{_changed.child_cost}
: null; + return
{preValue}{`${child_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}`}
; + } }, + // {key: 'unit', title: t('Unit'), }, + { + key: 'groupSize', + dataIndex: ['group_size_min'], + title: t('group_size'), width: '6rem', + render: (_, { audit_state_id, group_size_min, group_size_max, lastedit_changed }) => { + const _changed = parseJson(lastedit_changed)?.reduce((acc, cur) => ({...acc, ...cur}), {}); + const preValue = ![-1, 1, 2].includes(audit_state_id) && (isNotEmpty(_changed.group_size_min) || isNotEmpty(_changed.group_size_max)) ?
{`${_changed.group_size_min ?? group_size_min} - ${_changed.group_size_max ?? group_size_max}`}
: null; + return
{preValue}{formatGroupSize(group_size_min, group_size_max)}
; + } + }, + { + key: 'useDates', + dataIndex: ['use_dates_start'], + title: t('use_dates'), width: '12rem', + render: (_, { use_dates_start, use_dates_end, weekdays }) => `${use_dates_start} ~ ${use_dates_end}`, // + (weekdays ? `, ${t('OnWeekdays')}${weekdays}` : ''), + }, + { key: 'weekdays', dataIndex: ['weekdays'], title: t('Weekdays'), width: '6rem', render: (text, r) => text || t('Unlimited') }, + ]; + +const PriceLogPopover = ({ title, fetchData, ...props}) => { + const { t } = useTranslation('products'); + const [open, setOpen] = useState(false); + const [logData, setLogData] = useState([]); + const getData = async () => { + const data = await fetchData(); + setLogData(data); + }; + const columns = [...columnsSets(t), { title: '时间', dataIndex: 'updatetime', key: 'updatetime'}]; + return ( + + {title} + + + } + content={ + <> + + + } + trigger={['click']} + placement='bottom' + className='' + rootClassName='w-5/6' + open={open} + onOpenChange={(v) => { + setOpen(v); + }}> + + + ); +}; + const PriceTable = ({ productType, dataSource, refresh }) => { const { t } = useTranslation('products'); const { travel_agency_id, use_year, audit_state } = useParams(); @@ -52,6 +134,16 @@ const PriceTable = ({ productType, dataSource, refresh }) => { }); }; + const getLog = async ({ product_id, price_id }) => { + const data = await getPPLogAction({ travel_agency_id, product_id, price_id }); + return data; + }; + + const getRunning = async ({ product_id }) => { + const data = await getPPRunningAction({ travel_agency_id, product_id_list: product_id }); + return data?.[0]?.quotation || []; + }; + const rowStyle = (r, tri) => { const trCls = tri%2 !== 0 ? ' bg-stone-50' : ''; // 奇偶行 const [infoI, quoteI] = r.rowSpanI; @@ -67,25 +159,9 @@ const PriceTable = ({ productType, dataSource, refresh }) => { { key: 'title', dataIndex: ['info', 'title'], width: '16rem', title: t('Title'), onCell: (r, index) => ({ rowSpan: r.rowSpan, }), className: 'bg-white', render: (text, r) => { const title = text || r.lgc_details?.['2']?.title || r.lgc_details?.['1']?.title || ''; const itemLink = isPermitted(PERM_PRODUCTS_OFFER_AUDIT) ? `/products/${travel_agency_id}/${use_year}/${audit_state}/edit` : isPermitted(PERM_PRODUCTS_OFFER_PUT) ? `/products/edit` : ''; - return isNotEmpty(itemLink) ? setEditingProduct({info: r.info})}>{title} : title; + return
{isNotEmpty(itemLink) ?
setEditingProduct({info: r.info})}>{title}
: title}
; } }, - // ...(productType === 'B' ? [{ key: 'km', dataIndex: ['info', 'km'], title: t('KM')}] : []), - { key: 'adult', title: t('AgeType.Adult'), render: (_, { adult_cost, currency, unit_id, unit_name }) => `${adult_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}` }, - { key: 'child', title: t('AgeType.Child'), render: (_, { child_cost, currency, unit_id, unit_name }) => `${child_cost} ${currency} / ${t(`PriceUnit.${unit_id}`)}` }, - // {key: 'unit', title: t('Unit'), }, - { - key: 'groupSize', - dataIndex: ['group_size_min'], - title: t('group_size'), - render: (_, { group_size_min, group_size_max }) => `${group_size_min} - ${group_size_max}`, - }, - { - key: 'useDates', - dataIndex: ['use_dates_start'], - title: t('use_dates'), - render: (_, { use_dates_start, use_dates_end, weekdays }) => `${use_dates_start} ~ ${use_dates_end}`, // + (weekdays ? `, ${t('OnWeekdays')}${weekdays}` : ''), - }, - { key: 'weekdays', dataIndex: ['weekdays'], title: t('Weekdays'), render: (text, r) => text || t('Unlimited') }, + ...columnsSets(t), { key: 'state', title: t('State'), @@ -103,12 +179,42 @@ const PriceTable = ({ productType, dataSource, refresh }) => { + getLog({ travel_agency_id, product_id: r.info.id, price_id: r.id })} {...{ travel_agency_id, product_id: r.info.id, price_id: r.id }} /> ) : null, }, + { + title: '', + key: 'action2', width: '6rem', + onCell: (r, index) => ({ rowSpan: r.rowSpan, }), + render: (_, r) => { + const showPublicBtn = null; // r.pendingQuotation ? : null; + const btn2 = ( + getRunning({ travel_agency_id, product_id: r.info.id, price_id: r.id })} + {...{ travel_agency_id, product_id: r.info.id, price_id: r.id }} + /> + ); + return
{btn2}
; + }, + } ]; - return
r.id} />; + return ( +
r.id} + /> + ); }; /** @@ -124,6 +230,7 @@ const TypesPanels = (props) => { useEffect(() => { // 只显示有产品的类型; 展开产品的价格表, 合并名称列; 转化为价格主表, 携带产品属性信息 const hasDataTypes = Object.keys(agencyProducts); + let tempKey = ''; const _show = productsTypes .filter((kk) => hasDataTypes.includes(kk.value)) .map((ele) => { @@ -141,12 +248,14 @@ const TypesPanels = (props) => { lgc_details: c.lgc_details.reduce((rlgc, clgc) => ({...rlgc, [clgc.lgc]: clgc}), {}), rowSpan: i === 0 ? c.quotation.length : 0, rowSpanI: [ri, i], + pendingQuotation: c.quotation.findIndex(q2 => q2.audit_state_id===0) !== -1 , })) ), [] ); + tempKey = _children.length > 0 && tempKey==='' ? ele.key : tempKey; const _childrenByState = groupBy(_children, 'audit_state_id'); - // console.log(_childrenByState); + // if (_children.length > 0) console.log('PriceTable\n'+ele.value+'\n', _children) return { ...ele, extra: @@ -168,7 +277,7 @@ const TypesPanels = (props) => { }}); setShowTypes(_show); - setActiveKey(isEmpty(_show) ? [] : [_show[0].key]); + setActiveKey(isEmpty(_show) ? [] : [tempKey]); return () => {}; }, [productsTypes, agencyProducts]);