From 8012c1e47a805ae4eb867c1c0bc776e6eb07662b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8C=97=E6=9E=B3?= <7854742+wang_rumeng@user.noreply.gitee.com> Date: Mon, 1 Sep 2025 21:25:33 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=86=E9=A2=91=E7=94=9F=E6=88=90=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=20=E9=87=8D=E6=96=B0=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 1 + api/DTO/movieEdit.ts | 4 + api/video_flow.ts | 6 +- app/service/Interaction/RoleShotService.ts | 2 +- app/service/usecase/RoleEditUseCase.ts | 2 +- components/pages/work-flow.tsx | 4 +- components/pages/work-flow/media-viewer.tsx | 51 ++++++------ components/pages/work-flow/thumbnail-grid.tsx | 4 +- components/pages/work-flow/use-edit-data.tsx | 3 +- .../pages/work-flow/use-workflow-data.tsx | 32 +++++++- components/script-renderer/ScriptRenderer.tsx | 17 ++-- components/ui/character-editor.tsx | 6 +- components/ui/character-tab-content.tsx | 12 ++- components/ui/edit-modal.tsx | 7 ++ components/ui/main-editor/MainEditor.tsx | 7 +- components/ui/shot-tab-content.tsx | 13 ++- package.json | 6 +- utils/message.tsx | 80 +++++++++++++++++++ 18 files changed, 202 insertions(+), 55 deletions(-) create mode 100644 utils/message.tsx diff --git a/.env.development b/.env.development index e9c8845..58dc325 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1,4 @@ NEXT_PUBLIC_JAVA_URL = https://77.app.java.auth.qikongjian.com NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com +# NEXT_PUBLIC_BASE_URL = https://pre.movieflow.api.huiying.video NEXT_PUBLIC_API_BASE_URL = https://77.api.qikongjian.com diff --git a/api/DTO/movieEdit.ts b/api/DTO/movieEdit.ts index 0d92cca..28745c0 100644 --- a/api/DTO/movieEdit.ts +++ b/api/DTO/movieEdit.ts @@ -619,6 +619,10 @@ export interface RoleResponse { /**缓存 */ character_draft: string; } +export interface RealRoleResponse { + system_characters: []; + project_characters: RoleResponse[]; +} export interface Role { diff --git a/api/video_flow.ts b/api/video_flow.ts index ff82f46..beccef3 100644 --- a/api/video_flow.ts +++ b/api/video_flow.ts @@ -16,7 +16,7 @@ import { } from "@/app/service/domain/valueObject"; import { task_item, VideoSegmentEntityAdapter } from "@/app/service/adapter/oldErrAdapter"; import { VideoFlowProjectResponse, NewCharacterItem, NewCharacterListResponse, CharacterListByProjectWithHighlightResponse, CharacterUpdateAndRegenerateRequest, CharacterUpdateAndRegenerateResponse } from "./DTO/movieEdit"; -import { RoleResponse } from "./DTO/movieEdit"; +import { RealRoleResponse } from "./DTO/movieEdit"; import { RoleRecognitionResponse } from "./DTO/movieEdit"; /** @@ -1165,14 +1165,14 @@ export const getSimilarCharacters = async (request: { /** * 获取项目角色列表(含高亮关键词)接口 * @param request - 项目角色列表请求参数 - * @returns Promise> 项目角色列表 + * @returns Promise> 项目角色列表 */ export const getCharacterListByProjectWithHighlight = async (request: { /** 项目ID */ project_id: string; /** 每个角色最多提取的高亮关键词数量 */ max_keywords?: number; -}): Promise> => { +}): Promise> => { return post("/character/list_by_project_with_highlight", request); }; diff --git a/app/service/Interaction/RoleShotService.ts b/app/service/Interaction/RoleShotService.ts index de33edf..890e2ce 100644 --- a/app/service/Interaction/RoleShotService.ts +++ b/app/service/Interaction/RoleShotService.ts @@ -188,7 +188,7 @@ export const useRoleShotServiceHook = (projectId: string,selectRole?:RoleEntity, shot_id: shot.id, // 单个分镜ID character_replacements: characterReplacements, wait_for_completion: false, // 不等待完成,异步处理 - character_draft: JSON.stringify(newDraftRoleList) + character_draft: newDraftRoleList ? JSON.stringify(newDraftRoleList) : "" }) })) diff --git a/app/service/usecase/RoleEditUseCase.ts b/app/service/usecase/RoleEditUseCase.ts index 2a05478..1989f93 100644 --- a/app/service/usecase/RoleEditUseCase.ts +++ b/app/service/usecase/RoleEditUseCase.ts @@ -44,7 +44,7 @@ export class RoleEditUseCase { }); if (response.successful) { - const roleList = this.parseProjectRoleList(response.data); + const roleList = this.parseProjectRoleList(response.data.project_characters); console.log('roleList', roleList) return roleList; } else { diff --git a/components/pages/work-flow.tsx b/components/pages/work-flow.tsx index 608506b..52e3846 100644 --- a/components/pages/work-flow.tsx +++ b/components/pages/work-flow.tsx @@ -54,7 +54,8 @@ const WorkFlow = React.memo(function WorkFlow() { fallbackToStep, originalText, showGotoCutButton, - generateEditPlan + generateEditPlan, + handleRetryVideo } = useWorkflowData(); const { @@ -126,6 +127,7 @@ const WorkFlow = React.memo(function WorkFlow() { showGotoCutButton={showGotoCutButton} onGotoCut={generateEditPlan} isSmartChatBoxOpen={isSmartChatBoxOpen} + onRetryVideo={(video_id) => handleRetryVideo(video_id)} /> )} diff --git a/components/pages/work-flow/media-viewer.tsx b/components/pages/work-flow/media-viewer.tsx index 89bbd89..bde8956 100644 --- a/components/pages/work-flow/media-viewer.tsx +++ b/components/pages/work-flow/media-viewer.tsx @@ -2,7 +2,7 @@ import React, { useRef, useEffect, useState, SetStateAction, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; -import { Edit3, Play, Pause, Volume2, VolumeX, Maximize, Minimize, Loader2, X, Scissors } from 'lucide-react'; +import { Edit3, Play, Pause, Volume2, VolumeX, Maximize, Minimize, Loader2, X, Scissors, RotateCcw, MessageCircleMore } from 'lucide-react'; import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal'; import { GlassIconButton } from '@/components/ui/glass-icon-button'; import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer'; @@ -29,6 +29,7 @@ interface MediaViewerProps { showGotoCutButton?: boolean; onGotoCut: () => void; isSmartChatBoxOpen: boolean; + onRetryVideo?: (video_id: string) => void; } export const MediaViewer = React.memo(function MediaViewer({ @@ -47,7 +48,8 @@ export const MediaViewer = React.memo(function MediaViewer({ setVideoPreview, showGotoCutButton, onGotoCut, - isSmartChatBoxOpen + isSmartChatBoxOpen, + onRetryVideo }: MediaViewerProps) { const mainVideoRef = useRef(null); const finalVideoRef = useRef(null); @@ -354,6 +356,13 @@ export const MediaViewer = React.memo(function MediaViewer({
+ + handleEditClick('3', 'final')} + /> + {showGotoCutButton && ( @@ -361,23 +370,6 @@ export const MediaViewer = React.memo(function MediaViewer({ )}
- {/* 操作按钮组 */} - {/* - - handleEditClick('3', 'final')} - /> - - */} - {/* 底部控制区域 */} -
- - Failed +
+ + Retry
)} @@ -477,9 +471,20 @@ export const MediaViewer = React.memo(function MediaViewer({
+ {/* 重试按钮 */} + {taskObject.videos.data[currentSketchIndex].video_status === 2 && ( + + { + const video = taskObject.videos.data[currentSketchIndex]; + if (onRetryVideo && video?.video_id) { + onRetryVideo(video.video_id); + } + }} /> + + )} {/* 添加到chat去编辑 按钮 */} - { + { const currentVideo = taskObject.videos.data[currentSketchIndex]; if (currentVideo && currentVideo.urls && currentVideo.urls.length > 0 && setVideoPreview) { setVideoPreview(currentVideo.urls[0], currentVideo.video_id); diff --git a/components/pages/work-flow/thumbnail-grid.tsx b/components/pages/work-flow/thumbnail-grid.tsx index 9ca6472..106c43d 100644 --- a/components/pages/work-flow/thumbnail-grid.tsx +++ b/components/pages/work-flow/thumbnail-grid.tsx @@ -4,7 +4,7 @@ import React, { useRef, useEffect, useState, useCallback } from 'react'; import { motion } from 'framer-motion'; import { Skeleton } from '@/components/ui/skeleton'; import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal'; -import { Loader2, X, SquareUserRound, MapPinHouse, Clapperboard, Video } from 'lucide-react'; +import { Loader2, X, SquareUserRound, MapPinHouse, Clapperboard, Video, RotateCcw } from 'lucide-react'; import { TaskObject } from '@/api/DTO/movieEdit'; interface ThumbnailGridProps { @@ -187,7 +187,7 @@ export function ThumbnailGrid({ {taskObject.videos.data[index].video_status === 2 && (
- +
)} diff --git a/components/pages/work-flow/use-edit-data.tsx b/components/pages/work-flow/use-edit-data.tsx index 33a81c4..dd7e5dd 100644 --- a/components/pages/work-flow/use-edit-data.tsx +++ b/components/pages/work-flow/use-edit-data.tsx @@ -109,10 +109,11 @@ export const useEditData = (tabType: string, originalText?: string) => { useEffect(() => { console.log('useEditData-----videoSegments', videoSegments, scriptRoles); setShotData(videoSegments); - setRoleData(scriptRoles); + // setRoleData(scriptRoles); }, [videoSegments, scriptRoles]); useEffect(() => { + console.log('useEditData-----roleList', roleList); setRoleData(roleList); // setRoleData(mockRoleData); }, [roleList]); diff --git a/components/pages/work-flow/use-workflow-data.tsx b/components/pages/work-flow/use-workflow-data.tsx index 2890c61..0762255 100644 --- a/components/pages/work-flow/use-workflow-data.tsx +++ b/components/pages/work-flow/use-workflow-data.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { useSearchParams } from 'next/navigation'; -import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan, getGenerateEditPlan } from '@/api/video_flow'; +import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan, getGenerateEditPlan, regenerateShot } from '@/api/video_flow'; import { useScriptService } from "@/app/service/Interaction/ScriptService"; import { useUpdateEffect } from '@/app/hooks/useUpdateEffect'; import { LOADING_TEXT_MAP, TaskObject, Status, Stage } from '@/api/DTO/movieEdit'; @@ -200,9 +200,9 @@ export function useWorkflowData() { // 收集所有需要更新的状态 let stateUpdates = JSON.stringify(taskCurrent); // 视频分析 - let analyze_video_completed_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video' && item.task_status !== 'IN_PROCESS').length; + let analyze_video_completed_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video' && item.task_status !== 'INIT').length; let analyze_video_total_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video').length; - if (analyze_video_completed_count === analyze_video_total_count) { + if (analyze_video_total_count && analyze_video_completed_count === analyze_video_total_count) { setCanGoToCut(true); } @@ -524,6 +524,29 @@ export function useWorkflowData() { } }; + // 重试生成视频 + const handleRetryVideo = async (video_id: string) => { + try { + // 重置视频状态为生成中 + setTaskObject(prev => { + const newState = JSON.parse(JSON.stringify(prev)); + const videoIndex = newState.videos.data.findIndex((v: any) => v.video_id === video_id); + if (videoIndex !== -1) { + newState.videos.data[videoIndex].video_status = 0; + } + return newState; + }); + + // 调用重新生成接口 + await regenerateShot({ project_id: episodeId, shot_id: video_id }); + + // 重新开启轮询 如果轮询结束的话 + if (!needStreamData) setNeedStreamData(true); + } catch (error) { + console.error('重试生成视频失败:', error); + } + }; + // 回退到 指定状态 重新获取数据 const fallbackToStep = (step: string) => { console.log('fallbackToStep', step); @@ -564,6 +587,7 @@ export function useWorkflowData() { originalText: state.originalText, // showGotoCutButton: from && currentLoadingText.includes('Post-production') ? true : false, showGotoCutButton: canGoToCut ? true : false, - generateEditPlan + generateEditPlan, + handleRetryVideo }; } diff --git a/components/script-renderer/ScriptRenderer.tsx b/components/script-renderer/ScriptRenderer.tsx index ad9b6a0..60ef418 100644 --- a/components/script-renderer/ScriptRenderer.tsx +++ b/components/script-renderer/ScriptRenderer.tsx @@ -3,9 +3,9 @@ import { motion, AnimatePresence } from 'framer-motion'; import { SquarePen, Lightbulb, Navigation, Globe, Copy, SendHorizontal, X, Plus } from 'lucide-react'; import { ScriptData, ScriptBlock, ScriptContent, ThemeTagBgColor, ThemeType } from './types'; import ContentEditable, { ContentEditableEvent } from 'react-contenteditable'; -import { toast } from 'sonner'; import { SelectDropdown } from '@/components/ui/select-dropdown'; import { TypewriterText } from '@/components/workflow/work-office/common/TypewriterText'; +import { msg } from '@/utils/message'; interface ScriptRendererProps { data: any[]; @@ -126,11 +126,7 @@ export const ScriptRenderer: React.FC = ({ data, setIsPause const handleThemeTagChange = (value: string[]) => { console.log('主题标签更改', value); if (value.length > 5) { - toast.error('最多可选择5个主题标签', { - duration: 3000, - position: 'top-center', - richColors: true, - }); + msg.error('最多可选择5个主题标签', 3000); return; } setAddThemeTag(value); @@ -208,6 +204,7 @@ export const ScriptRenderer: React.FC = ({ data, setIsPause default: return ( <> + {/* 需要权限控制 */} {(isHovered || isActive) && ( = ({ data, setIsPause { + // 提示权限不够 + msg.error('No permission!'); + return; handleEditBlock(block); }} /> @@ -226,7 +226,7 @@ export const ScriptRenderer: React.FC = ({ data, setIsPause className="w-6 h-6 p-1 cursor-pointer text-gray-600 hover:text-blue-500 transition-colors" onClick={() => { navigator.clipboard.writeText(block.content.map(item => item.text).join('\n')); - toast.success('Copied!'); + msg.success('Copied!'); }} /> @@ -237,7 +237,8 @@ export const ScriptRenderer: React.FC = ({ data, setIsPause renderEditBlock(block) ) : ( block.content.map((item, index) => ( -
handleEditBlock(block)}>{renderContent(item)}
+ //
handleEditBlock(block)}>{renderContent(item)}
+
{renderContent(item)}
)) )}
diff --git a/components/ui/character-editor.tsx b/components/ui/character-editor.tsx index 08b10af..c8066fd 100644 --- a/components/ui/character-editor.tsx +++ b/components/ui/character-editor.tsx @@ -12,6 +12,7 @@ interface CharacterEditorProps { highlight: TagValueObject[]; onSmartPolish: (text: string) => void; onUpdateText: (text: string) => void; + disabled?: boolean; } export const CharacterEditor = forwardRef(({ @@ -19,7 +20,8 @@ export const CharacterEditor = forwardRef(({ description, highlight, onSmartPolish, - onUpdateText + onUpdateText, + disabled }, ref) => { const [isOptimizing, setIsOptimizing] = useState(false); const [content, setContent] = useState([]); @@ -70,7 +72,7 @@ export const CharacterEditor = forwardRef(({
{/* 自由输入区域 */} { - !isInit && + !isInit && } {/* 智能润色按钮 */} diff --git a/components/ui/character-tab-content.tsx b/components/ui/character-tab-content.tsx index a8a859b..a725d2f 100644 --- a/components/ui/character-tab-content.tsx +++ b/components/ui/character-tab-content.tsx @@ -12,6 +12,7 @@ import { useEditData } from '@/components/pages/work-flow/use-edit-data'; import { useSearchParams } from 'next/navigation'; import { RoleEntity } from '@/app/service/domain/Entities'; import { Role } from '@/api/DTO/movieEdit'; +import { msg } from '@/utils/message'; interface CharacterTabContentProps { originalRoles: Role[]; @@ -112,6 +113,8 @@ CharacterTabContentProps const handleSmartPolish = (text: string) => { // 然后调用优化角色文本 + msg.error('No permission!'); + return; optimizeRoleText(text); }; @@ -209,6 +212,8 @@ CharacterTabContentProps }; const handleOpenReplaceLibrary = async () => { + msg.error('No permission!'); + return; setIsLoadingLibrary(true); setIsReplaceLibraryOpen(true); setShowAddToLibrary(true); @@ -217,6 +222,8 @@ CharacterTabContentProps }; const handleRegenerate = async () => { + msg.error('No permission!'); + return; console.log('Regenerate'); setIsRegenerate(true); // const text = characterEditorRef.current.getRoleText(); @@ -230,6 +237,8 @@ CharacterTabContentProps }; const handleUploadClick = () => { + msg.error('No permission!'); + return; fileInputRef.current?.click(); }; @@ -339,7 +348,7 @@ CharacterTabContentProps height='100%' enableAnimation={enableAnimation} /> - {/* 应用角色按钮 */} + {/* 应用角色按钮 暂时注释需要权限控制 */}
diff --git a/components/ui/edit-modal.tsx b/components/ui/edit-modal.tsx index cc107e9..0514a4c 100644 --- a/components/ui/edit-modal.tsx +++ b/components/ui/edit-modal.tsx @@ -13,6 +13,7 @@ import { MusicTabContent } from './music-tab-content'; import FloatingGlassPanel from './FloatingGlassPanel'; import { SaveEditUseCase } from '@/app/service/usecase/SaveEditUseCase'; import { TaskObject } from '@/api/DTO/movieEdit'; +import { msg } from '@/utils/message'; interface EditModalProps { isOpen: boolean; @@ -123,6 +124,8 @@ export function EditModal({ } const handleSave = () => { + msg.error('No permission!'); + return; console.log('handleSave'); // setIsRemindFallbackOpen(true); if (activeTab === '0') { @@ -140,6 +143,8 @@ export function EditModal({ } const handleConfirmGotoFallback = () => { + msg.error('No permission!'); + return; setDisabledBtn(true); console.log('handleConfirmGotoFallback'); SaveEditUseCase.saveData(); @@ -165,6 +170,8 @@ export function EditModal({ } const handleReset = () => { + msg.error('No permission!'); + return; console.log('handleReset'); // 重置当前tab修改的数据 setIsRemindResetOpen(true); diff --git a/components/ui/main-editor/MainEditor.tsx b/components/ui/main-editor/MainEditor.tsx index a518903..9ea2bdd 100644 --- a/components/ui/main-editor/MainEditor.tsx +++ b/components/ui/main-editor/MainEditor.tsx @@ -7,9 +7,10 @@ import { HighlightTextExtension } from './HighlightText'; interface MainEditorProps { content: any[]; onChangeContent?: (content: any[]) => void; + disabled?: boolean; } -export default function MainEditor({ content, onChangeContent }: MainEditorProps) { +export default function MainEditor({ content, onChangeContent, disabled }: MainEditorProps) { const [renderContent, setRenderContent] = useState(content); useEffect(() => { @@ -33,7 +34,7 @@ export default function MainEditor({ content, onChangeContent }: MainEditorProps }, editorProps: { attributes: { - class: 'prose prose-invert max-w-none focus:outline-none' + class: `prose prose-invert max-w-none focus:outline-none ${disabled ? 'cursor-not-allowed' : ''}` }, handleDOMEvents: { keydown: (view, event) => { @@ -50,7 +51,7 @@ export default function MainEditor({ content, onChangeContent }: MainEditorProps } }, onCreate: ({ editor }) => { - editor.setOptions({ editable: true }); + editor.setOptions({ editable: !disabled }); }, onUpdate: ({ editor }) => { try { diff --git a/components/ui/shot-tab-content.tsx b/components/ui/shot-tab-content.tsx index 7a1095e..2df4dff 100644 --- a/components/ui/shot-tab-content.tsx +++ b/components/ui/shot-tab-content.tsx @@ -13,6 +13,7 @@ import HorizontalScroller from './HorizontalScroller'; import { useEditData } from '@/components/pages/work-flow/use-edit-data'; import { RoleEntity, VideoSegmentEntity } from '@/app/service/domain/Entities'; import { ShotVideo } from '@/api/DTO/movieEdit'; +import { msg } from '@/utils/message'; interface ShotTabContentProps { currentSketchIndex: number; @@ -444,7 +445,11 @@ export const ShotTabContent = forwardRef< {/* 人物替换按钮 */} handleScan()} + onClick={() => { + msg.error('No permission!'); + return; + handleScan() + }} className={`p-2 backdrop-blur-sm transition-colors z-10 rounded-full ${scanState === 'detected' ? 'bg-cyan-500/50 hover:bg-cyan-500/70 text-white' @@ -496,7 +501,11 @@ export const ShotTabContent = forwardRef< Add Shot */} handleRegenerate()} + onClick={() => { + msg.error('No permission!'); + return; + handleRegenerate(); + }} className="flex items-center justify-center gap-2 px-4 py-3 bg-blue-500/10 hover:bg-blue-500/20 text-blue-500 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed" whileHover={{ scale: 1.02 }} diff --git a/package.json b/package.json index f35775f..ddd11f2 100644 --- a/package.json +++ b/package.json @@ -116,10 +116,10 @@ "zod": "^3.23.8" }, "devDependencies": { - "@types/jest": "^30.0.0", + "@types/jest": "^29.5.12", "@types/lodash": "^4.17.19", "@types/react-grid-layout": "^1.3.5", - "jest": "^30.0.5", - "ts-jest": "^29.4.0" + "jest": "^29.7.0", + "ts-jest": "^29.1.2" } } diff --git a/utils/message.tsx b/utils/message.tsx new file mode 100644 index 0000000..6ba9e86 --- /dev/null +++ b/utils/message.tsx @@ -0,0 +1,80 @@ +import { message } from 'antd'; +import type { MessageArgsProps } from 'antd/es/message'; + +/** + * 全局消息提示工具 + * 对 antd message 进行封装,提供更简洁的 API + */ +class MessageUtil { + /** + * 成功提示 + * @param content - 提示内容 + * @param duration - 显示时长(秒),默认 3 秒 + * @param options - 其他配置项 + */ + success(content: string, duration = 3, options?: Omit) { + message.success({ content, duration, ...options }); + } + + /** + * 错误提示 + * @param content - 提示内容 + * @param duration - 显示时长(秒),默认 3 秒 + * @param options - 其他配置项 + */ + error(content: string, duration = 3, options?: Omit) { + message.error({ content, duration, ...options }); + } + + /** + * 警告提示 + * @param content - 提示内容 + * @param duration - 显示时长(秒),默认 3 秒 + * @param options - 其他配置项 + */ + warning(content: string, duration = 3, options?: Omit) { + message.warning({ content, duration, ...options }); + } + + /** + * 普通提示 + * @param content - 提示内容 + * @param duration - 显示时长(秒),默认 3 秒 + * @param options - 其他配置项 + */ + info(content: string, duration = 3, options?: Omit) { + message.info({ content, duration, ...options }); + } + + /** + * 加载提示 + * @param content - 提示内容 + * @param duration - 显示时长(秒),默认 0 表示不自动关闭 + * @param options - 其他配置项 + * @returns - 返回一个函数,调用该函数可以手动关闭提示 + */ + loading(content: string, duration = 0, options?: Omit) { + return message.loading({ content, duration, ...options }); + } + + /** + * 销毁所有消息提示 + */ + destroy() { + message.destroy(); + } + + /** + * 配置全局默认值 + * @param options - 全局配置项 + */ + config(options: MessageArgsProps) { + message.config(options); + } +} + +// 导出单例实例 +export const msg = new MessageUtil(); + +// 为了方便使用,也导出单独的方法 +export const { success, error, warning, info, loading, destroy, config } = msg;