diff --git a/packages/app-operation/src/app.tsx b/packages/app-operation/src/app.tsx index 71aca24..70182e4 100644 --- a/packages/app-operation/src/app.tsx +++ b/packages/app-operation/src/app.tsx @@ -2,17 +2,18 @@ // 全局初始化数据配置,用于 Layout 用户信息和权限初始化 // 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate -import { LeftMenu, SearchMenu, VersionChecker } from '@/components'; +import { + LeftMenu, + NotificationMessage, + SearchMenu, + VersionChecker, +} from '@/components'; import Avatar from '@/layout/guide/Avatar'; import { auth } from '@/services'; import { Navigate } from '@@/exports'; import { RunTimeLayoutConfig } from '@@/plugin-layout/types'; import { RequestConfig } from '@@/plugin-request/request'; -import { - InfoCircleFilled, - NotificationFilled, - QuestionCircleFilled, -} from '@ant-design/icons'; +import { InfoCircleFilled, QuestionCircleFilled } from '@ant-design/icons'; import '@nutui/nutui-react/dist/style.scss'; import { history } from '@umijs/max'; import { Alert, Button, message, notification, Spin } from 'antd'; @@ -302,13 +303,18 @@ export const layout: RunTimeLayoutConfig = ({ initialState }) => { actionsRender: (props) => { if (props.isMobile) return []; if (typeof window === 'undefined') return []; + + // 获取当前管理员ID + const adminVO = JSON.parse(window.localStorage.getItem('admin') || '{}'); + const adminId = adminVO?.adminId; + return [ props.layout !== 'side' && document.body.clientWidth > 1400 ? ( ) : undefined, , , - , + , ]; }, headerRender: (props, defaultDom) => { diff --git a/packages/app-operation/src/components/BasicData/BoxProductList.tsx b/packages/app-operation/src/components/BasicData/BoxProductList.tsx index f81dd65..68c9313 100644 --- a/packages/app-operation/src/components/BasicData/BoxProductList.tsx +++ b/packages/app-operation/src/components/BasicData/BoxProductList.tsx @@ -268,16 +268,8 @@ export default function BoxProductList(props: IBoxProductListProps) { {(value: RouteContextType) => { const { isMobile } = value; return ( - - + + {/* 品牌列表 */}
- {boxBrand ? ( - - -
- {boxBrand?.name || '请从左侧选择一个品牌'} -
-
- {defaultDom} - - ) : ( - + + {boxBrand ? ( + <> + +
+ {boxBrand?.name || '请从左侧选择一个品牌'} +
+
+ {defaultDom} + + ) : (
@@ -455,8 +445,8 @@ export default function BoxProductList(props: IBoxProductListProps) { />
- - )} + )} + ); }} diff --git a/packages/app-operation/src/components/NotifyMessage/NotificationMessage.module.less b/packages/app-operation/src/components/NotifyMessage/NotificationMessage.module.less new file mode 100644 index 0000000..db62086 --- /dev/null +++ b/packages/app-operation/src/components/NotifyMessage/NotificationMessage.module.less @@ -0,0 +1,157 @@ +.bellIcon { + font-size: 20px; + cursor: pointer; + padding: 8px; + transition: all 0.3s; + color: rgba(0, 0, 0, 65%); + + &:hover { + color: #1890ff; + } +} + +// 确保红点样式明显 +:global { + .ant-badge-count, + .ant-badge-dot { + box-shadow: 0 0 0 1px #fff; + } +} + +.dropdownContent { + width: 380px; + max-height: 500px; + background: #fff; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 15%); + display: flex; + flex-direction: column; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px; + border-bottom: 1px solid #f0f0f0; + + .title { + font-size: 16px; + font-weight: 500; + color: #262626; + } + + .count { + font-size: 14px; + color: #ff4d4f; + } +} + +.messageList { + flex: 1; + overflow-y: auto; + max-height: 400px; + padding: 8px; + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 20%); + border-radius: 3px; + + &:hover { + background-color: rgba(0, 0, 0, 30%); + } + } +} + +.messageItem { + cursor: pointer; + padding: 12px 16px; + transition: background-color 0.3s; + border-bottom: none; + + &:hover { + background-color: #f5f5f5; + } + + &.unread { + background-color: #f6ffed; + } +} + +.messageContent { + flex: 1; +} + +.messageTitle { + display: flex; + align-items: center; + font-size: 14px; + font-weight: 500; + color: #262626; + margin-bottom: 4px; + position: relative; +} + +.unreadDot { + display: inline-block; + width: 6px; + height: 6px; + background-color: #ff4d4f; + border-radius: 50%; + margin-left: 8px; +} + +.messageDesc { + font-size: 13px; + color: #595959; + line-height: 1.5; + margin-bottom: 4px; + display: box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; +} + +.messageTime { + font-size: 12px; + color: #8c8c8c; +} + +.loadingWrapper { + display: flex; + justify-content: center; + align-items: center; + padding: 40px 0; +} + +.emptyWrapper { + display: flex; + justify-content: center; + align-items: center; + padding: 40px 0; +} + +.footer { + display: flex; + justify-content: center; + align-items: center; + padding: 12px 16px; + border-top: 1px solid #f0f0f0; + cursor: pointer; + transition: background-color 0.3s; + font-size: 14px; + color: #1890ff; + + &:hover { + background-color: #f5f5f5; + } + + span { + margin-right: 4px; + } +} diff --git a/packages/app-operation/src/components/NotifyMessage/NotificationMessage.tsx b/packages/app-operation/src/components/NotifyMessage/NotificationMessage.tsx new file mode 100644 index 0000000..78b3b67 --- /dev/null +++ b/packages/app-operation/src/components/NotifyMessage/NotificationMessage.tsx @@ -0,0 +1,189 @@ +import { business } from '@/services'; +import { BellOutlined, LoadingOutlined, MoreOutlined } from '@ant-design/icons'; +import { history, useIntl } from '@umijs/max'; +import { useRequest } from 'ahooks'; +import { Badge, Dropdown, Empty, List, message, Spin } from 'antd'; +import React, { useMemo, useState } from 'react'; +import styles from './NotificationMessage.module.less'; + +interface NotificationMessageProps { + adminId?: string; +} + +const NotificationMessage: React.FC = ({ + adminId, +}) => { + const intl = useIntl(); + const [dropdownOpen, setDropdownOpen] = useState(false); + + // 获取未读消息数量 + const { data: unreadCountData, refresh: refreshUnreadCount } = useRequest( + () => + business.messageReceiver.getUnreadCount({ + messageReceiverUnreadCountQry: { + adminId: adminId || '', + }, + }), + { + ready: !!adminId, + refreshDeps: [adminId], + }, + ); + + const unreadCount = unreadCountData?.data || 0; + + // 获取消息列表(只获取前5条未读消息) + const { + data: messageListData, + loading: messageListLoading, + refresh: refreshMessageList, + } = useRequest( + () => + business.messageReceiver.pageMessageReceiver({ + messageReceiverPageQry: { + pageIndex: 1, + pageSize: 5, + }, + }), + { + ready: dropdownOpen && !!adminId, + refreshDeps: [dropdownOpen, adminId], + }, + ); + + const messageList = messageListData?.data || []; + + // 标记已读 + const { run: markAsRead } = useRequest( + (messageReceiverId: string) => + business.messageReceiver.markReadMessageReceiver({ + messageReceiverId: messageReceiverId, + }), + { + manual: true, + onSuccess: () => { + refreshUnreadCount(); + refreshMessageList(); + }, + onError: (error) => { + message.error( + intl.formatMessage({ id: 'notification.markReadFailed' }), + ); + console.error('标记已读失败:', error); + }, + }, + ); + + // 点击消息项 + const handleMessageClick = (item: BusinessAPI.MessageReceiverVO) => { + if (!item.isRead) { + markAsRead(item.messageReceiverId); + } + // 可以跳转到消息详情页面 + // history.push(`/message/detail/${item.messageReceiverId}`); + }; + + // 查看更多消息 + const handleViewMore = () => { + setDropdownOpen(false); + history.push('/message'); + }; + + // 下拉菜单内容 + const dropdownContent = useMemo(() => { + return ( +
+
+ + {intl.formatMessage({ id: 'notification.title' })} + + {unreadCount > 0 && ( + + {intl.formatMessage( + { id: 'notification.unreadCount' }, + { count: unreadCount }, + )} + + )} +
+ +
+ {messageListLoading ? ( +
+ } /> +
+ ) : messageList.length > 0 ? ( + ( + handleMessageClick(item)} + > +
+
+ {item.messageVO?.title || '消息通知'} + {!item.isRead && } +
+
+ {item.messageVO?.content || ''} +
+
{item.createdAt}
+
+
+ )} + /> + ) : ( +
+ +
+ )} +
+ + {messageList.length > 0 && ( +
+ {intl.formatMessage({ id: 'notification.viewMore' })} + +
+ )} +
+ ); + }, [ + messageList, + messageListLoading, + unreadCount, + intl, + markAsRead, + handleMessageClick, + handleViewMore, + ]); + + return ( + dropdownContent} + > + + + + + ); +}; + +export default NotificationMessage; diff --git a/packages/app-operation/src/components/NotifyMessage/NotifyMessageTemplateList.tsx b/packages/app-operation/src/components/NotifyMessage/NotifyMessageTemplateList.tsx new file mode 100644 index 0000000..b01df59 --- /dev/null +++ b/packages/app-operation/src/components/NotifyMessage/NotifyMessageTemplateList.tsx @@ -0,0 +1,433 @@ +import { BizContainer, BizValueType, ModeType } from '@/components'; +import { business } from '@/services'; +import { useIntl } from '@@/exports'; +import { + ProColumns, + ProFormDependency, + ProFormItem, + ProFormSelect, + ProFormText, + ProFormTextArea, +} from '@ant-design/pro-components'; +import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions'; +import { Button, Space, Tag, Typography } from 'antd'; +import React from 'react'; + +interface INotifyMessageTemplateListProps { + ghost?: boolean; + search?: boolean; + onValueChange?: () => void; + mode?: ModeType; + trigger?: () => React.ReactNode; +} + +const variables = [ + // 采购单 + '经销商简称', + '车次号', + '车牌号', + '目的地', + '利润金额', + '瓜农姓名', + '瓜农货款', + '录入员姓名', + '录入员提审时间', + // 采购单审核 + '审核员姓名', + '审核员审核时间', + '审核员驳回原因', + // 采购单审批 + '审批员姓名', + '审批员审批时间', + '审批员驳回原因', +]; + +export default function NotifyMessageTemplateList( + props: INotifyMessageTemplateListProps, +) { + const { + ghost = false, + search = true, + mode = 'page', + trigger, + onValueChange, + } = props; + const intl = useIntl(); + const intlPrefix = 'notifyMessageTemplate'; + + // 模板分类映射 + const templateCategoryMap: Record = { + PURCHASE_ORDER_MESSAGE_TEMPLATE: { + label: intl.formatMessage({ + id: intlPrefix + '.templateCategory.purchaseOrderMessageTemplate', + }), + }, + }; + // 模板事件映射 + const templateSceneMap: Record = { + WAIT_AUDIT: { + label: intl.formatMessage({ + id: intlPrefix + '.templateScene.waitAudit', + }), + }, + WAIT_APPROVE: { + label: intl.formatMessage({ + id: intlPrefix + '.templateScene.waitApprove', + }), + }, + APPROVE_PASS: { + label: intl.formatMessage({ + id: intlPrefix + '.templateScene.approvePass', + }), + }, + AUDIT_REJECT: { + label: intl.formatMessage({ + id: intlPrefix + '.templateScene.auditReject', + }), + }, + APPROVE_REJECT: { + label: intl.formatMessage({ + id: intlPrefix + '.templateScene.approveReject', + }), + }, + }; + + // 表单上下文 + const formContext = [ +