// ==UserScript==
// @name 123云盘文件批量重命名助手
// @name:en 123FastRename
// @namespace http://tampermonkey.net/
// @version 1.0.7
// @description 123云盘文件批量重命名助手,支持按序号、追加、查找替换、正则替换、格式替换等多种重命名模式,提供拖拽排序、实时预览、历史记录等功能
// @supportURL https://bbs.tampermonkey.net.cn/thread-10041-1-1.html
// @homepageURL https://github.com/meguoe/123FastLinkPlus
// @author meguoe
// @license Apache-2.0
// @match *://*.123pan.com/*
// @match *://*.123pan.cn/*
// @match *://*.123684.com/*
// @match *://*.123865.com/*
// @icon data:image/x-icon;base64,AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAEAAAMMOAADDDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPt8WAD7fFgC+3xYB/t8WBf7fFhQ+3xYkPt8WMD7fFji+3xY9Px9Wfr8fVn7+3xY+/x9Wfv7fFj7/H1Z+/t8WPv8fVn7/H1Z+/x9Wfv8fVn7/H1Z+/x9Wfv7fVn7+31Z+/t9Wfv7fVn7/H1Z+/x9Wfv8fVn7+3xY+/x9Wfv7fFj7/H1Z+/x9Wfv8fVn7/H1Z+/x9Wfv8fVn7/H1Z+/x9Wfr7fFj1+3xY5Pt8WMP7fFiU+3xYVPt8WBn7fFgI+3xYAvt8WAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+3xYAPt8WAD7fFgF+3xYJft8WIL7fFjV/H1Z8ft9Wff8fVn6+31Z/ft9Wf78fVn//H1Z//x9Wf/8fVn//H1Z//t9Wf/8fVn/+31Z//x9Wf/8fVn//H1Z//t9Wf/7fVn/+31Y//t9Wf/7fVn/+31Z//x9WP/8fVn/+31Z//x9Wf/7fVn//H1Z//t9Wf/8fVn/+31Z//x9Wf/8fVn//H1Z//t9Wf/8fVn/+31Z/vt9Wf38fVn7/H1Z9/x9WfL7fFjZ+3xYift8WCr7fFgG+3xYAft8WAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+3xYAPt8WAH7fFgQ+3xYc/t8WNn8fVn1/H1Z/fx9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z/fx9Wfb7fFjd+3xYfPt8WBP7fFgC+3xYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+3xYAPt8WAL7fFgm+3xYrvt9We/7fVn9+31Z//t8Wf/7fFj/+3xY//t9Wf/7fVn/+31Z//t9Wf/7fFj/+3xY//t8Wf/7fVn/+31Z//t9Wf/7fFj/+3xY//t8Wf/7fVn/+31Z//t9Wf/7fVj/+3xY//t8WP/7fFj/+31Y//t9Wf/7fVn/+31Z//t8Wf/7fFj/+3xY//t9Wf/7fVn/+31Z//t8Wf/7fFj/+3xY//t9Wf/7fVn/+31Z//t9Wf/7fFj/+3xY//t8Wf/7fVn/+31Z/vt9WfD7fFi2+3xYLft8WAP7fFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+3xYAPt8WAL7fFgx+3xYxvt9Wff7fFj/+3xY//t8WP/7fVn/+3xY//t8WP/7fVj/+31Z//t9Wf/7fVj/+3xY//t8WP/7fVn/+3xY//t8WP/7fFj/+31Z//t9WP/7fFj/+3xY//t8WP/7fVj/+31Y//t8WP/7fFj/+3xY//t9WP/7fFj/+3xY//t8WP/7fVj/+31Y//t9Wf/7fFj/+3xY//t8WP/7fVn/+3xY//t8WP/7fVj/+31Z//t9Wf/7fVj/+3xY//t8WP/7fVn/+3xY//t8WP/7fFj/+31Z+ft8WMz7fFg5+3xYA/t8WAAAAAAAAAAAAAAAAAAAAAAA+3xYAPt8WAH7fFgp+3xYxvx9WPn8fVj//H1Y//x9Wf/8fVj//H1Y//x9WP/8fVn//H1Y//x9WP/8fVj//H1Z//x9WP/8fVj//H1Y//x9Wf/8fVj//H1Y//x9WP/8fVn//H1Y//x9WP/8fVj//H1Z//x9WP/8fVj//H1Y//x9WP/8fVn//H1Y//x9WP/8fVj//H1Z//x9WP/8fVj//H1Y//x9Wf/8fVj//H1Y//x9WP/8fVn//H1Y//x9WP/8fVj//H1Z//x9WP/8fVj//H1Y//x9Wf/8fVj//H1Y//x9WP/8fVn7+3xYzft8WDH7fFgC+3xYAAAAAAAAAAAAAAAAAPt8WAH7fFgT+3xYtfx9Wfj7fVj//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//t9WP/8fVn//H1Z//t9Wf/8fVn//H1Z//x9Wf/8fVn/+31Y//x9Wf/8fVn//H1Z//x9Wf/7fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn/+31Z//x9Wf/7fVj//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//t9WP/8fVn//H1Z//t9Wf/8fVn//H1Z//x9Wf/8fVn/+31Y//x9Wf/8fVn//H1Z//x9Wfn7fFi++3xYGft8WAEAAAAAAAAAAPt8WAD7fFgG+3xYgfx9WfD8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z8/t8WI/7fFgG+3xYAAAAAAD7fFgC+3xYMft8WOD8fFn+/HxZ//x8Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fFn//HxZ//x8Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fFn//HxZ//x8Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fFn//HxZ//x8Wf77fFjk+3xYPPt8WAMAAAAA+3xYB/t8WJb7fFj3+3xY//t8WP/7fFj/+3xY//t8Wf/7fFj/+3xY//t8WP/7fFj/+3xZ//t8WP/7fFn/+3xZ//t8Wf/7fFn/+3xY//t8WP/7fFj/+3xY//t8Wf/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8Wf/7fFj/+3xY//t8WP/7fFj/+3xZ//t8WP/7fFn/+3xZ//t8Wf/7fFn/+3xY//t8WP/7fFj/+3xY//t8Wf/7fFj/+3xY//t8WP/7fFj/+3xY+Pt8WKT7fFgI+3xYAft8WCL7fFjg/HxY/vx8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP77fFnm+3xYLft8WAX7fFhj/HxY8/x8Wf/7fFj/+3xY//x8Wf/7fFj/+3xZ//x8WP/8fFj/+3xZ//x8Wf/8fFj//HxZ//x8Wf/8fFn//HxZ//x8WP/8fFn/+3xZ//x8WP/8fFn/+3xZ//t8WP/8fFj//HxY//t8WP/8fFj//HxY//x8WP/8fFj//HxZ//x8WP/8fFj//HxY//x8WP/7fFj//HxY//x8Wf/7fFj/+3xZ//x8WP/8fFj/+3xZ//x8Wf/8fFj//HxZ//x8Wf/8fFn//HxZ//x8WP/8fFn/+3xZ//x8WP/8fFn/+3xZ//t8WP/8fFj/+3xY//t8WP/8fFn//HxY9Pt8WHL7fFgJ+3xYovt8Wfn8fFn//HxZ//t8Wf/8fFn/+3xY//x8Wf/8fVn/+3xY//x8Wf/8fFn//H1Z//x8Wf/7fFn/+3xZ//t8Wf/7fFn//HxZ//x8Wf/7fFj//HxZ//x8Wf/7fFj//H1Y//x8Wf/8fFn//HxZ//x8Wf/8fFn//HxZ//x8Wf/8fFn/+3xZ//x8Wf/8fFn/+3xZ//t8Wf/8fFn/+3xY//x8Wf/8fVn/+3xY//x8Wf/8fFn//H1Z//x8Wf/7fFn/+3xZ//t8Wf/7fFn//HxZ//x8Wf/7fFj//HxZ//x8Wf/7fFj//H1Z//x8Wf/8fFn//HxZ//x9Wfr7fFix+3xYDPt8WND7fVj9+31Z//t9WP/7fVn/+31Y//t9WP/7fVj/+31Y//t9WP/7fVj/+31Y//t9WP/7fVn/+31Y//t9WP/7fVj/+31Y//t9WP/7fVn/+31Y//t9WP/7fVn/+31Y//t9WP/7fVj/+31Y//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Y//t9WP/7fVn/+31Z//t9Wf/7fVn/+31Y//t9WP/7fVj/+31Y//t9WP/7fVj/+31Y//t9WP/7fVn/+31Y//t9WP/7fVj/+31Y//t9WP/7fVn/+31Y//t9WP/7fVn/+31Y//t9WP/7fVj/+31Y//t9Wf/7fVn++3xY3Pt8WBn7fFjo/H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9WO37fFgm+3xY7/t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFjw+3xYLPx8WPD8fVn//H1Z//x9Wf/8fVj++3xZw/t8WK77fFit+3xYrft8WK37fFis/HxY2Px8WPr8fFj/+3xY3/t8WLb7fFis+3xYrft8WK37fFit+3xYrft8WK37fFit+3xYrft8WK37fFit+3xYrft8WK37fFit+3xYrft8WK37fFit+3xYrft8WKv7fFjC/HxY7/x9WP/8fFj//HxY5/t8WLb7fFis+3xYrft8WK37fFit+3xYrft8WK37fFit+3xYr/t8WLP7fFi8+3xYzPt8WOf7fFj//HxY//x8WP/8fVj//H1Y//x8WP/8fVj//H1Z//x9Wf/8fVn//HxY8ft8WCz7fVjw/H1Y//t9WP/7fVj//H1Y/Pt8WV77fFgn+3xYI/t8WCP7fFgj+3xYIvx9WZb8fVjx+31Y//t8WKn7fFg7+3xYIft8WCP7fFgj+3xYI/t8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Wf/7fVn/+3xZ//t8Wf/7fFn/+3xZ//t8Wf/7fFn/+3xZ//t8Wf/7fFn/+3xZ//t8Wf/7fFn/+3xZ//t8Wf/7fFn/+31Z//t9Wf/7fFn/+3xZ//t8Wf/7fFn/+3xZ//t8Wf/7fFn/+3xZ//t8Wf/7fFnk+3xYXvt8WAAAAAAAAAAAAPt8WAD7fFgD+3xYOft8Wfv7fFn/+3xZ//t8Wf/7fFnx+3xYLPx8WfD8fFn//HxZ//x8Wf/8fVn7+3xZRPt8WQX7fFkAAAAAAAAAAAD8fFkA/HxZhfx9We/8fFn/+3xYoft8WB77fFgAAAAAAAAAAAD7fFgA+3xYSft8WdP8fFn//HxZ//x9Wf/8fVn//HxZ//x8Wf/8fFn//HxZ//x8Wf/8fFn//HxZ//x8Wf/8fFn//HxZ//x8Wf/8fVn//H1Z//x8Wf/8fFn//HxZ//x8Wf/8fFn//HxZ//x8Wf/8fFn//HxZ//x8Wf/8fFn//H1Z6vt8WHX7fFgAAAAAAAAAAAD7fFgA+3xYBft8WEL8fVn7/HxZ//x8Wf/8fFn//HxZ8ft8WCz7fFjw+3xY//t8WP/7fFj/+3xY+/t8WUT7fFkF+3xZAAAAAAAAAAAA/H1YAPx9WIX8fFjv+3xY//t8WLD7fFgj+3xYAAAAAAAAAAAA+3xYAPt8WCf7fFjB+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WOD7fFhL+3xYAAAAAAAAAAAA+3xYAPt8WAn7fFhi+3xY/Pt8WP/7fFj/+3xY//t8WPH7fFgs+3xY8Pt8Wf/7fFn/+3xY//t8WPv7fFlE+3xZBft8WQAAAAAAAAAAAPx9WQD8fVmF+3xZ7/t8Wf/7fFjM+3xYLPt8WAAAAAAAAAAAAPt8WAD7fFgF+3xYd/t8WOf7fFj/+3xZ//t8WP/7fFj/+3xY//t8Wf/7fFj/+3xY//t8WP/7fFj/+3xY//t8Wf/7fFj/+3xY//t8WP/7fFn/+3xZ//t8WP/7fFj/+3xY//t8WP/7fFn/+3xZ//t8WP/7fFj/+3xY//t8WOn7fFiG+3xYEft8WAAAAAAAAAAAAPt8WAD7fFgT+3xYoft8WP37fFj/+3xZ//t8Wf/7fFjx+3xYLPx8WPD8fFj//HxY//x8WP/8fFj7+3xYRPt8WAX7fFgAAAAAAAAAAAD7fFgA+3xYhfx8WO/8fFj/+3xY8Pt8WD/7fFgA+3xYAAAAAAAAAAAA+3xYAPt8WBv7fFhn+3xYnPt8Waf7fFmn+3xYp/t8WKr7fFm1+3xYy/t8WO37fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/7fFj++3xY6ft8WMb7fFjI+3xZyPt8Wcj7fFjC+3xYsvt8WJL7fFhc+3xYG/t8WAAAAAAAAAAAAPt8WAD7fFgE+3xYOft8WOT8fFj+/HxY//x8WP/8fFj//HxY8ft8WCz7fFnw/HxZ//x8Wf/7fFn//HxZ+/t8WUT7fFkF+3xZAAAAAAAAAAAA+3xZAPt8WYX8fFnv/HxZ//x8Wf/7fFhy+3xYDvt8WAAAAAAAAAAAAAAAAAD7fFgA+3xYB/t8WBr7fVkl+31ZJft9WSX7fFgn+3xYMPt8WEL7fFhg+3xYlPx8WNz8fFj//HxZ//t8Wf/8fFn//HxZ//x8Wf/8fVn//HxZ/ft8WLP7fFg6+3xYQPt8WED7fFg/+3xYO/t8WC37fFgV+3xYAAAAAAAAAAAAAAAAAPt8WAD7fFYA+3xZHPt8Wab7fFn+/H1Z//t8Wf/8fFn//HxZ//t8WfH7fFgs/H1Z8Px9Wf/8fVn//H1Z//x9Wfv7fFlE+3xYBft8WAAAAAAAAAAAAPx9WQD8fVmF/H1Z7/x8Wf/8fVn/+3xYyft8WDD7fFgA+3xYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+3xYAPt8WBX7fFhc+3xYxPx8Wf/7fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf37fFma/H1ZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPt8WAD7fFgC+3xYJPt8WJD8fVn2/H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVnx+3xYLPx9WfD8fVn//H1Z//x9Wf/8fVn7+3xYRPt8WQX7fFkAAAAAAAAAAAD8fFgA/HxYhfx9We/8fVn//H1Z//x9Wf37fFiQ+3xYGvt8WAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD7fFgA+3xYA/t8WFH7fFjQ/H1Z//x9Wf/8fVn//H1Z//x9WP/8fVn9/H1Zmvx9WQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD7fFgA+3xYC/t8WGf7fFj3/H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z8ft8WCz8fVnw/H1Z//x9Wf/8fVn//H1Z+/t8WET7fFkF+3xZAAAAAAAAAAAA/HxYAPx8WIX8fVnv/H1Z//x9Wf/8fVn/+3xY7vt8WIz7fFgs+3xYBft8WAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPt8WAD7fFgK+3xYe/t8WPj8fVn//H1Z//x9Wf/8fFj//H1Z/fx9WZr8fVkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+3xYAPt8WAj7fFhG+3xYr/x9WP78fVn//H1Z//x9Wf/8fVn//H1Z//x9WfH7fFgs+3xY8Pt8WP/7fFj/+3xY//t8Wfv7fFhE+3xYBft8WAAAAAAAAAAAAPt9WQD8fVmF+3xZ7/t8WP/7fFj/+3xZ//t8WP/7fFjz+3xYs/t8WHX7fFhS+3xYQPx8WDn8fFg3+3xYN/t8WDT7fFgi+3xYCft8WAAAAAAAAAAAAAAAAAAAAAAA+3xYAPt8WEH7fFjK+3xZ//t8Wf/7fFn/+3xZ//t8Wf37fFin+3xYGvt8WCH7fFgh+3xYIft8WB37fFgR+3xYAft8WAAAAAAAAAAAAAAAAAD7fFgA+3xYBPt8WDL7fFjA+3xZ/vt8Wf/7fFn/+3xY//t8WP/7fFjx+3xYLPt8WPD7fFj/+3xY//t8WP/7fFj7+3xZRPt8WQX7fFkAAAAAAAAAAAD7fVgA+31Yhft9WO/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/8fFj3/H1Y3fx9WMj8fVi//H1Yvfx9WL38fFi5/HxYp/t8WHf7fFgp+3xYAPt8WAAAAAAAAAAAAPt8WAD7fFgZ+3xYmPt8WP/8fFj/+3xY//t8WP/7fFj+/H1Y2/x9WKH8fVik/H1YpPx9WKT8fVif/H1Ykvt8WHb7fFhI+3xYD/t8WAAAAAAAAAAAAPt8WAD7fFgH+3xYT/t8WO/7fFj/+3xY//t8WP/7fFj/+3xY8ft8WCz8fVjw/H1Y//x9WP/8fVj//H1Y+/t8WET7fFkF+3xZAAAAAAAAAAAA/H1ZAPx9WYX8fVnv/H1Y//x9WP/8fVj//H1Y//x9WP/8fVj//H1Y//x9WP/8fVj//H1Y//x9WP/8fVj//H1Y//x9WP/7fVj2+3xYkft8WB37fFgAAAAAAAAAAAD7fFgA+ntXAvt8WHn7fFj//H1Y//x9WP/8fVj//H1Y//x9WP/8fVj//H1Y//x9WP/8fVj//H1Y//t9WP/7fFj6+3xY0/t8WHT7fFgM+3xYAAAAAAAAAAAA+3xYAPt8WBb7fFi1/HxY/fx9WP/8fVj//H1Y//x9WPH7fFgs+31Z8Pt9Wf/7fVn/+31Z//t9Wfv7fFlE+3xYBft8WAAAAAAAAAAAAPx9WQD8fVmF+31Z7/t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wdn7fFhO+3xYAAAAAAAAAAAA+XpWAPt8WAD7fFhp+3xY//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fFjd+3xYQPt8WAAAAAAAAAAAAPt8WAD7fFgL+3xYcPt9Wfz7fVn/+31Z//t9Wf/7fVnx+3xYLPx9WfD8fVj//H1Z//x9Wf/8fVn7+3xZRPt8WQX7fFkAAAAAAAAAAAD7fFgA+3xYhfx9WO/8fVn//H1Z//x9Wf/8fVn//H1Z//x9WP/8fVj//H1Z//x9Wf/8fVn//H1Y//x9Wf/8fVn//H1Z//x9Wf/8fVno+3xYXPt8WAAAAAAAAAAAAAAAAAD7fFgA+3xYZPt8WPr8fVn//H1Z//x9WP/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Y6Pt8WGz7fFgAAAAAAAAAAAD7fFgA+3xYBvt8WEv8fVn7/H1Z//x9Wf/8fVj//H1Z8ft8WCz8fVnw/H1Z//x9Wf/8fVn//H1Z+/t8WET7fFgF+3xYAAAAAAAAAAAA/HxZAPx8WYX8fVnv/H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9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WDP7fFg/+3xYY/t8WK77fFjv+3xY//t8WP/7fFj/+3xY//t8WP/7fFjA+3xYQfx9WSf8fVkp/H1ZKfx9WSn8fVkp/H1ZKfx9WSn8fVkp/H1YKft8WCv7fFgv+3xYN/t8WFL7fFiV+3xY6vt8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFjx+3xYK/x8WPD8fFj//HxY//x8WP/8fFj++3xZy/x9Wbn8fVm4/H1ZuPx9Wbj8fVm4/H1Z3fx8WPr8fFj//HxY+/x8WMj8fVm3/H1ZuPx9Wbj8fVm4/H1ZuPx9Wbj8fVm4/H1ZuPx9Wbj8fVi4+3xYuft8WMD7fFjS+3xY7fx8WP78fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY6vx8WMD8fVm3/H1ZuPx9Wbj8fVm4/H1ZuPx9Wbj8fVm4/H1YuPx9WLn7fFi++3xYx/t8WNz7fFj7/HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY8ft8WCX8fFnu/HxY//x8WP/8fFj//HxY//x8Wf/8fFj//HxY//x8WP/8fFj//H1Z//x8WP/8fFj//H1Z//x8WP/8fFj//H1Z//x8WP/8fFj//H1Z//x8WP/8fFj/+3xY//t8WP/8fFn//HxZ//x8Wf/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxZ//x8Wf/8fFj//HxY//x8WP/8fFj//H1Z//x8WP/8fFj//H1Z//x8WP/8fFj//H1Z//x8WP/8fFj//H1Z//x8WP/8fFj/+3xY//x8WP/8fFn//HxY//x8Wf/8fFj//HxY//x8WfD7fFgX+3xY5ft9WP77fVj/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVj/+31Z//t9WP/7fVn/+31Y//t9WP/7fVj/+31Y//t9WP/7fVj/+31Z//t9WP/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9WP/7fVj/+31Y//t9WP/7fVj/+31Y//t9WP/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9WP/7fVn/+31Y//t9WP/7fVj/+31Y//t9WP/7fVj/+31Z//t9WP/7fVn/+31Y//t9Wf/7fVj/+31Z//t9Wf/7fVn/+31Y//t9WP/7fVjr+3xYDPt8WM38fVn8/H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn9+3xY2vt8WAn7fFie+3xY+Pt8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t9WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t9WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY//t8WP/7fFj/+3xY+vt8WK37fFgF+3xYXvt9WPL7fVj/+3xY//t8WP/7fVj/+31Y//t8WP/7fVj/+31Y//t8WP/7fVj/+31Y//t8WP/7fVj/+31Y//t8WP/7fVj/+31Y//t8WP/7fVj/+31Y//t9WP/7fVj/+31Y//t9WP/7fFj/+31Y//t9WP/7fVj/+31Y//t9WP/7fVj/+31Y//t9WP/7fVj/+3xY//t9WP/7fVj/+31Y//t8WP/7fVj/+31Y//t8WP/7fVj/+31Y//t8WP/7fVj/+31Y//t8WP/7fVj/+31Y//t8WP/7fVj/+31Y//t9WP/7fVj/+31Y//t8WP/7fFj/+31Y//t9WPT7fFhs+3xYAft8WB77fFjd/H1Y/vx9WP/8fVj//H1Y//x9WP/8fVj//H1Y//x9WP/8fFj//H1Y//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x9WP/8fVj//H1Y//x9WP/8fVj//H1Y//x9WP/8fVj//H1Y//x9WP/8fVj//H1Y//x9WP/8fVj//H1Y//x9WP/8fVj//H1Y//x9WP/8fVj//H1Y//x9WP/8fVj//H1Y//x9WP/8fFj//H1Y//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x9WP/8fVj//H1Y//x9WP/8fVj//H1Y//x9WP/8fVj//H1Y//x9WP77fFjj+3xYKQAAAAD7fFgH+3xYkPt9Wfb7fVj//H1Y//t9WP/7fVj/+31Z//t9WP/7fVj/+31Y//x9Wf/7fVj/+31Z//t9WP/7fVj/+31Z//t9WP/8fVn/+31Y//t9WP/7fVj/+31Y//t9WP/7fVj//H1Y//t9WP/7fVn/+31Z//t9Wf/7fVn/+31Y//t9Wf/7fVn/+31Z//t9Wf/7fVj//H1Y//t9WP/7fVj/+31Z//t9WP/7fVj/+31Y//x9Wf/7fVj/+31Z//t9WP/7fVj/+31Z//t9WP/8fVn/+31Y//t9WP/7fVj/+31Y//t9WP/7fVj//H1Y//t9WP/7fVn4+3xYnvt8WAcAAAAA+3xYAvt8WCv7fFjc+31Z/ft9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn++3xY4vt8WDb7fFgCAAAAAPt8WAD7fFgF+3xYefx9We78fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z//x9Wf/8fVn//H1Z8ft8WIb7fFgG+3xYAAAAAAAAAAAA+3xYAPt8WBD7fFiu+31Z9vt9Wf/7fVn/+31Z//t9Wf/7fVn/+3xZ//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+3xZ//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t8Wf/7fVn/+3xZ//t8Wf/7fVn/+31Z//t9Wf/7fFn/+3xZ//t9Wf/7fFn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+3xZ//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z//t9Wf/7fVn/+3xZ//t9Wf/7fVn/+31Z//t9Wf/7fVn/+31Z+Pt8WLf7fFgV+3xYAQAAAAAAAAAAAAAAAPt8WAD7fFgB+3xYI/t8WMD8fFj4/HxY//x8Wf/8fFj//HxZ//x8WP/8fFj//HxY//x8WP/8fFn//HxZ//x8WP/8fFj//HxY//x8WP/8fFn//HxY//x8Wf/8fFj//HxY//x8WP/8fFj//HxY//x8Wf/8fFj//HxY//x8WP/8fFj//HxY//x8Wf/8fFn//HxY//x8WP/8fFj//HxY//x8Wf/8fFj//HxZ//x8WP/8fFj//HxY//x8WP/8fFn//HxZ//x8WP/8fFj//HxY//x8WP/8fFn//HxY//x8Wf/8fFj//HxY+vt8WMj7fFgr+3xYAvt8WAAAAAAAAAAAAAAAAAAAAAAA+3xYAPt8WAL7fFgr+3xYvvx8WPX8fFn+/HxY//x8WP/8fFj//HxY//x8WP/8fFn//HxZ//x8Wf/8fFn//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY//x8WP/8fFn//HxY//x8WP/8fFj//HxY//x8WP/8fFn//HxZ//x8Wf/8fFn//HxY//x8WP/8fFj//HxY//x8WP/8fFj//HxY9/t8WMT7fFgx+3xYAvt8WAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD7fFgA+3xYAvt8WCD7fFil+3xZ6/t8Wf37fFn/+3xZ//t8Wf/7fFn/+31Z//t8Wf/7fFn/+3xZ//t8Wf/7fFn/+3xZ//t8Wf/7fFn/+3xZ//t8Wf/7fFn/+3xZ//t8Wf/7fFn/+3xZ//t8Wf/7fFn/+3xZ//t8Wf/7fFn/+3xZ//t8Wf/7fFn/+3xZ//t8Wf/7fFn/+3xZ//t8Wf/7fFn/+3xZ//t8Wf/7fFn/+31Z//t8Wf/7fFn/+3xZ//t8Wf/7fFn/+3xZ//t8Wf/7fFn9+3xZ7ft8WK77fFgm+3xYAvt8WAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPt8WAD7fFgB+3xYC/t8WGf7fFjT+31Y8/t9WPz7fVj/+31Y//t9WP/7fVj/+31Y//t9WP/7fVj/+3xY//t9WP/7fFj/+31Y//t8WP/7fVj/+31Y//t9WP/7fVj/+31Y//t9WP/7fVj/+31Y//t9WP/7fVj/+31Y//t9WP/7fVj/+31Y//t9WP/7fVj/+31Y//t8WP/7fVj/+31Y//t9WP/7fVj/+31Y//t9WP/7fVj/+31Y//t9WP/7fVj/+3xY//t9WP37fFj0+3xY1vt8WHD7fFgO+3xYAft8WAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+nxYAPp8WAD7fFgE+3xYHft8WHb7fFjM/H1Z7fx9WfX8fVn5/H1Z/Px9Wf38fVn+/H1Z/vx9Wf78fVn+/H1Z/vx9Wf78fVn+/H1Z/vx9Wf78fVn+/H1Z/vx9Wf78fVn+/H1Z/vx9Wf78fVn+/H1Z/vx9Wf78fVn+/H1Z/vx9Wf78fVn+/H1Z/vx9Wf78fVn+/H1Z/vx9Wf78fVn+/H1Z/vx9Wf78fVn+/H1Z/fx9Wfz8fVn5/H1Z9vx9We77fFjR+3xYfPt8WCH7fFgF+3xYAPt8WAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+3xYAPt8WAH7fFgF+3xYD/t8WED7fFh7+3xYqPt8WMf7fFjZ/H1Z3/x9WeD8fVnf/H1Z3/x9Wd/8fVnf/H1Z3/x9Wd/8fVnf/H1Z3/x9Wd/8fVnf/H1Z3/x9Wd/8fVnf/H1Z3/x9Wd/8fVnf/H1Z3/x9Wd/8fVnf/H1Z3/x9Wd/8fVnf/H1Z3/x9Wd/8fVnf/H1Z3/x9Wd/8fVnf/H1Z3/t8WNv7fFjK+3xYrPt8WH77fFhF+3xYEvt8WAb7fFgB+3xYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+ntXAPp7VwD7fFgD+3xYB/t8WAr7fFgM+3xYDfx9WQ38fVkN/H1ZDfx9WQ38fVkN/H1ZDfx9WQ38fVkN/H1ZDfx9WQ38fVkN/H1ZDfx9WQ38fVkN/H1ZDfx9WQ38fVkN/H1ZDfx9WQ38fVkN/H1ZDfx9WQ38fVkN/H1ZDfx9WQ38fVkN/H1ZDfx9WQ38fVkN/H1ZDfx9WQ37fFgN+3xYDPt8WAr7fFgH+3xYBPt8WAH7fFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAH/8AAAAAAAAH/gAAAAAAAAP8AAAAAAAAAfgAAAAAAAAA8AAAAAAAAABwAAAAAAAAAGAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPB//+B/+AAA8H//4H/+AADwf//gf/8AAPB4AAAAH4AA8HgAAAAPgADweAAAAAeAAPB4AAAAB4AA8HgAAAAHgADweAAAAAeAAPB4AAAAB4AA8HwAAAAPAADwPgAAAD4AAPA//wAf/AAA8B//gB/8AADwB//AH/wAAPAAB+AAPgAA8AAD4AAPAADwAAHgAAeAAPAAAfAAB4AA8AAB8AAHgADwAAHwAAeAAPAAAeAAB4AA8AAD4AAPgADwAA/gAB8AAPB//8B//wAA8H//gH/+AADwf/4Af/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAACAAAAAAAAAAMAAAAAAAAABwAAAAAAAAAHgAAAAAAAAA/AAAAAAAAAH+AAAAAAAAA/8AAAAAAAAH/8AAAAAAAB//+AAAAAAA/8=
// @grant none
// @tag 123 123云盘 云盘 批量重命名助手 123FastRename
// ==/UserScript==
(function () {
'use strict';
const CONSTANTS = {
API_DELAY: 100,
PAGE_SIZE: 100,
PRIMARY_COLOR: '#2961D9',
BORDER_COLOR: '#d9d9d9',
TEXT_COLOR: '#919191',
TEXT_COLOR_DARK: '#333',
TEXT_COLOR_LIGHT: '#999',
DELETE_BTN_COLOR: '#ff4d4f',
DELETE_BTN_HOVER: '#f44336',
MODAL_Z_INDEX: 9999,
FILE_TYPE_FILE: 0,
FILE_TYPE_FOLDER: 1,
CATEGORY_VIDEO: '2',
DEBUG_MODE: false
};
const logger = {
log: (...args) => {
if (CONSTANTS.DEBUG_MODE) {
console.log('[123FASTRENAME]', ...args);
}
},
error: (...args) => {
if (CONSTANTS.DEBUG_MODE) {
console.error('[123FASTRENAME]', ...args);
}
},
warn: (...args) => {
if (CONSTANTS.DEBUG_MODE) {
console.warn('[123FASTRENAME]', ...args);
}
}
};
const CSS_STYLES = `
.separator {
height: 1px;
margin: 12px 0;
border-bottom: 1px dashed #d9d9d9;
}
.sfr-button-container {
position: relative;
display: none;
}
.sfr-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.sfr-modal-content {
background: #fff;
border-radius: 20px;
width: 70vw;
height: 80vh;
display: flex;
font-size: 14px;
flex-direction: column;
box-shadow: 0px 4px 60px 0px rgba(0, 0, 0, 0.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.sfr-modal-header {
padding: 16px 20px;
border-bottom: 1px solid #e8e8e8;
display: flex;
justify-content: space-between;
align-items: center;
}
.sfr-modal-title-container {
display: flex;
flex-direction: row;
align-items: baseline;
gap: 12px;
}
.sfr-modal-title {
margin: 0;
font-size: 16px;
font-weight: 500;
color: #333;
}
.sfr-modal-subtitle {
margin: 0;
font-size: 12px;
color: #999;
}
.sfr-button-container-inner {
display: flex;
gap: 12px;
align-items: center;
}
.sfr-modal-body {
padding: 20px;
overflow-y: auto;
flex: 1;
}
.sfr-file-list {
display: flex;
flex-direction: column;
gap: 8px;
max-width: 100%;
width: 100%;
overflow-x: hidden;
}
.sfr-file-item {
display: flex;
align-items: center;
padding: 12px;
background: #f5f5f5;
border-radius: 8px;
transition: background 0.2s, transform 0.2s;
cursor: move;
gap: 12px;
border: 1px solid #d7d7d7;
}
.sfr-file-item:hover {
background: #e8e8e8;
}
.sfr-file-item.dragging {
opacity: 0.5;
transform: scale(0.98);
}
.sfr-file-index {
color: rgb(51, 51, 51);
min-width: 30px;
font-size: 14px;
}
.sfr-file-name {
flex: 1;
color: #333;
word-break: break-all;
}
.sfr-file-size {
color: #999;
font-size: 12px;
}
.sfr-file-delete-btn {
padding: 2px !important;
width: 16px !important;
height: 16px !important;
border: none;
background: #ff4d4f;
color: #fff;
cursor: pointer;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.sfr-file-delete-btn:hover {
background: #f44336;
}
.sfr-file-item-rename {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
max-width: 100%;
width: 100%;
}
.sfr-file-item-rename .sfr-file-name-original {
flex: 1;
color: #999;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
background: #f5f5f5;
border-radius: 8px;
overflow: hidden;
border: 1px solid #d7d7d7;
}
.sfr-file-item-rename .sfr-file-name-original span:last-child {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sfr-file-item-rename .sfr-file-index {
color: #666;
min-width: 30px;
font-size: 14px;
flex-shrink: 0;
}
.sfr-file-item-rename .sfr-arrow-icon {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
color: #f44336;
font-size: 18px;
font-weight: 500;
}
.sfr-file-item-rename .sfr-file-name-new {
flex: 1;
color: #333;
font-size: 14px;
padding: 12px;
background: #f5f5f5;
border-radius: 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border: 1px solid #d7d7d7;
}
.sfr-modal-header-right {
margin-left: auto;
display: flex;
align-items: center;
}
.sfr-tab-container {
display: flex;
gap: 4px;
background: #f5f5f5;
padding: 4px;
border-radius: 8px;
}
.sfr-tab-item {
padding: 6px 12px;
font-size: 12px;
color: #666;
cursor: pointer;
border-radius: 6px;
transition: all 0.2s;
user-select: none;
}
.sfr-tab-item:hover {
color: #2961D9;
}
.sfr-tab-item.active {
background: #fff;
color: #2961D9;
font-weight: 500;
}
.sfr-modal-footer {
padding: 16px 20px;
border-top: 1px solid #e8e8e8;
display: flex;
justify-content: flex-end;
gap: 12px;
}
.sfr-rename-config {
padding: 16px;
background: #f5f5f5;
border-radius: 8px;
margin-bottom: 12px;
border: 1px solid #d7d7d7;
}
.sfr-rename-inputs-container {
display: flex;
gap: 12px;
}
.sfr-rename-config-input {
flex: 1;
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 8px;
font-size: 14px;
outline: none;
transition: border-color 0.2s;
}
.sfr-rename-config-input:focus {
border-color: #2961D9;
}
.sfr-checkbox-button {
padding: 6px 10px;
background: #fff;
border: 1px solid #d9d9d9;
border-radius: 8px;
cursor: pointer;
font-size: 12px;
color: #666;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 8px;
}
.sfr-checkbox-button:hover {
border-color: #2961D9;
color: #2961D9;
}
.sfr-checkbox-button[data-checked="true"] {
border-color: #2961D9;
color: #2961D9;
}
.sfr-checkbox-label {
display: flex;
align-items: center;
cursor: pointer;
pointer-events: none;
}
.sfr-checkbox-span {
display: inline-flex;
align-items: center;
justify-content: center;
}
.sfr-checkbox-input {
cursor: pointer;
pointer-events: none;
}
.sfr-checkbox-text {
padding: 0;
font-size: 13px;
font-weight: 500;
margin-left: 6px;
color: #919191;
}
.sfr-checkbox-button[data-checked="true"] .sfr-checkbox-text {
color: #2961D9;
}
.sfr-drag-handle {
color: #999;
cursor: move;
font-size: 16px;
user-select: none;
}
.sfr-stats-container {
display: flex;
align-items: center;
gap: 16px;
padding: 0;
font-size: 13px;
color: #666;
}
.sfr-stats-item {
display: flex;
align-items: center;
}
.sfr-stats-item strong {
color: #2961D9;
margin: 0 2px;
}
.sfr-modal-footer-content {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
gap: 16px;
}
.sfr-footer-buttons-container {
display: flex;
align-items: center;
gap: 8px;
}
`;
const styleElement = document.createElement('style');
styleElement.textContent = CSS_STYLES;
document.head.appendChild(styleElement);
// 1. 123云盘API通信类
class PanApiClient {
constructor() {
this.host = 'https://' + window.location.host;
this.authToken = localStorage['authorToken'] || '';
this.loginUuid = localStorage['LoginUuid'] || '';
this.appVersion = '3';
this.referer = document.location.href;
this._validateCredentials();
}
_validateCredentials() {
if (!this.authToken || !this.loginUuid) {
logger.warn('[PanApiClient]', '缺少认证信息,请先登录');
}
}
buildURL(path, queryParams) {
const queryString = new URLSearchParams(queryParams || {}).toString();
return `${this.host}${path}?${queryString}`;
}
async sendRequest(method, path, queryParams, body) {
const headers = {
'Content-Type': 'application/json;charset=UTF-8',
'Authorization': 'Bearer ' + this.authToken,
'platform': 'web',
'App-Version': this.appVersion,
'LoginUuid': this.loginUuid,
'Origin': this.host,
'Referer': this.referer,
};
try {
const response = await fetch(this.buildURL(path, queryParams), {
method, headers, body, credentials: 'include'
});
const data = await response.json();
if (data.code !== 0) {
throw new Error(data.message || 'API请求失败');
}
await new Promise(resolve => setTimeout(resolve, CONSTANTS.API_DELAY));
return data;
} catch (e) {
logger.error('[PanApiClient]', 'API请求失败:', e);
throw e;
}
}
async getOnePageFileList(parentFileId, page) {
const urlParams = {
driveId: '0',
limit: '100',
next: '0',
orderBy: 'file_name',
orderDirection: 'asc',
parentFileId: parentFileId.toString(),
trashed: 'false',
SearchData: '',
Page: page.toString(),
OnlyLookAbnormalFile: '0',
event: 'homeListFile',
operateType: '1',
inDirectSpace: 'false'
};
const data = await this.sendRequest("GET", "/b/api/file/list/new", urlParams);
return { data: { InfoList: data.data.InfoList }, total: data.data.Total };
}
async getFileList(parentFileId) {
let InfoList = [];
const info = await this.getOnePageFileList(parentFileId, 1);
InfoList.push(...info.data.InfoList);
const total = info.total;
if (total > 100) {
const times = Math.ceil(total / 100);
for (let i = 2; i < times + 1; i++) {
const info = await this.getOnePageFileList(parentFileId, i);
InfoList.push(...info.data.InfoList);
}
}
return { data: { InfoList }, total: total };
}
async getFileInfo(idList) {
const fileIdList = idList.map(fileId => ({ fileId }));
const data = await this.sendRequest("POST", "/b/api/file/info", {}, JSON.stringify({ fileIdList }));
return { data: { InfoList: data.data.infoList } };
}
async fileRename(fileInfo) {
if (fileInfo.OriginalFileName === fileInfo.NewFileName) {
return true;
}
const data = await this.sendRequest("POST", "/b/api/file/rename", {}, JSON.stringify({
driveId: '0',
duplicate: '1',
fileId: Number(fileInfo.FileId),
fileName: fileInfo.NewFileName,
}));
return data.code === 0;
}
}
// 2. 选中文件管理类
class TableRowSelector {
constructor() {
this.selectedRowKeys = [];
this.unselectedRowKeys = [];
this.isSelectAll = false;
this._inited = false;
this._callbacks = [];
this._observers = [];
this._breadcrumbObserver = null;
}
init() {
if (this._inited) return;
this._inited = true;
this._observeBreadcrumb();
const originalCreateElement = document.createElement;
const self = this;
document.createElement = function (tagName, options) {
const element = originalCreateElement.call(document, tagName, options);
if (!(tagName.toLowerCase() === 'input')) {
return element;
}
const observer = new MutationObserver(() => {
if (element.classList.contains('ant-checkbox-input')) {
const isSelectAll = element.getAttribute('aria-label') === 'Select all';
const tableRow = element.closest('.ant-table-row');
if (!isSelectAll && !tableRow) {
observer.disconnect();
return;
}
if (isSelectAll) {
self.unselectedRowKeys = [];
self.selectedRowKeys = [];
self.isSelectAll = false;
self._bindSelectAllEvent(element);
} else {
const input = element;
input.addEventListener('click', function () {
const rowKey = tableRow.getAttribute('data-row-key');
if (self.isSelectAll) {
if (!this.checked) {
if (!self.unselectedRowKeys.includes(rowKey)) {
self.unselectedRowKeys.push(rowKey);
}
} else {
const idx = self.unselectedRowKeys.indexOf(rowKey);
if (idx > -1) {
self.unselectedRowKeys.splice(idx, 1);
}
}
} else {
if (this.checked) {
if (!self.selectedRowKeys.includes(rowKey)) {
self.selectedRowKeys.push(rowKey);
}
} else {
const idx = self.selectedRowKeys.indexOf(rowKey);
if (idx > -1) {
self.selectedRowKeys.splice(idx, 1);
}
}
}
self._outputSelection();
self._notifyCallbacks();
});
}
}
observer.disconnect();
});
observer.observe(element, {
attributes: true,
attributeFilter: ['class', 'aria-label']
});
self._observers.push(observer);
return element;
};
}
_bindSelectAllEvent(checkbox) {
if (checkbox.dataset.selectAllBound) return;
checkbox.dataset.selectAllBound = 'true';
checkbox.addEventListener('click', () => {
if (checkbox.checked) {
this.isSelectAll = true;
this.unselectedRowKeys = [];
this.selectedRowKeys = [];
} else {
this.isSelectAll = false;
this.selectedRowKeys = [];
this.unselectedRowKeys = [];
}
this._outputSelection();
this._notifyCallbacks();
});
}
_outputSelection() {
if (this.isSelectAll) {
if (this.unselectedRowKeys.length === 0) {
} else {
}
}
}
_notifyCallbacks() {
this._callbacks.forEach(callback => callback());
}
onSelectionChange(callback) {
this._callbacks.push(callback);
}
getSelection() {
return {
isSelectAll: this.isSelectAll,
selectedRowKeys: [...this.selectedRowKeys],
unselectedRowKeys: [...this.unselectedRowKeys]
};
}
destroy() {
this._observers.forEach(observer => {
observer.disconnect();
});
this._observers = [];
if (this._breadcrumbObserver) {
this._breadcrumbObserver.disconnect();
this._breadcrumbObserver = null;
}
}
_observeBreadcrumb() {
const breadcrumb = document.querySelector('.home-breadcrumb');
if (!breadcrumb) {
logger.log('面包屑元素不存在,延迟重试');
setTimeout(() => this._observeBreadcrumb(), 100);
return;
}
this._breadcrumbObserver = new MutationObserver(() => {
logger.log('检测到面包屑变化,清空所有选择');
this.selectedRowKeys = [];
this.unselectedRowKeys = [];
this.isSelectAll = false;
this._notifyCallbacks();
});
this._breadcrumbObserver.observe(breadcrumb, {
childList: true,
subtree: true,
attributes: true,
characterData: true
});
logger.log('面包屑监听已启动');
}
}
// 3. 选中文件管理类
class SelectedFilesManager {
constructor(apiClient, selector) {
this.apiClient = apiClient;
this.selector = selector;
this.selectedFiles = [];
this._callbacks = [];
this._debounceTimer = null;
this._cachedFileList = null;
this._cachedParentFileId = null;
this._isUpdating = false;
}
init() {
this.selector.onSelectionChange(() => {
logger.log('文件选择变化,开始防抖更新');
this._debounceUpdate();
});
}
_debounceUpdate() {
if (this._debounceTimer) {
clearTimeout(this._debounceTimer);
}
this._debounceTimer = setTimeout(() => {
logger.log('防抖结束,开始更新选中文件');
this._updateSelectedFiles();
}, 300);
}
async _updateSelectedFiles() {
logger.log('_updateSelectedFiles 开始执行,_isUpdating:', this._isUpdating);
this._isUpdating = true;
const selection = this.selector.getSelection();
logger.log('当前选择状态:', selection);
this.selectedFiles = [];
const extractFileFields = (file) => ({
FileId: file.FileId,
FileName: file.FileName,
Size: file.Size,
Trashed: file.Trashed,
Category: file.Category,
Type: file.Type
});
if (selection.isSelectAll) {
logger.log('全选模式');
const parentFileId = await this._getParentFileId();
logger.log('父级文件ID:', parentFileId);
let allFiles;
if (this._cachedFileList && this._cachedParentFileId === parentFileId) {
allFiles = this._cachedFileList;
logger.log('_updateSelectedFiles using cached file list');
} else {
const fileList = await this.apiClient.getFileList(parentFileId);
allFiles = fileList.data.InfoList;
this._cachedFileList = allFiles;
this._cachedParentFileId = parentFileId;
logger.log('_updateSelectedFiles allFiles:', allFiles.length);
}
if (selection.unselectedRowKeys.length === 0) {
// 全选且没有取消的文件,直接使用所有文件
logger.log('全选且没有取消的文件');
this.selectedFiles = allFiles
.filter(file => file.Type !== CONSTANTS.FILE_TYPE_FOLDER)
.map(extractFileFields);
logger.log('_updateSelectedFiles selectedFiles:', this.selectedFiles.length);
} else {
// 全选但有取消的文件,过滤掉取消的文件
logger.log('全选但有取消的文件,取消数量:', selection.unselectedRowKeys.length);
this.selectedFiles = allFiles
.filter(file => {
const fileIdStr = String(file.FileId);
const isUnselected = selection.unselectedRowKeys.some(key => String(key) === fileIdStr);
const isFile = file.Type !== CONSTANTS.FILE_TYPE_FOLDER;
return !isUnselected && isFile;
})
.map(extractFileFields);
logger.log('_updateSelectedFiles selectedFiles:', this.selectedFiles.length);
}
} else {
// 非全选模式,清除缓存
logger.log('非全选模式');
this._cachedFileList = null;
this._cachedParentFileId = null;
// 非全选模式,使用 getFileInfo 获取选中的文件
const fileIds = selection.selectedRowKeys;
logger.log('_updateSelectedFiles fileIds:', fileIds.length, fileIds);
if (fileIds.length > 0) {
try {
const fileInfoList = await this.apiClient.getFileInfo(fileIds);
this.selectedFiles = fileInfoList.data.InfoList
.filter(file => file.Type !== CONSTANTS.FILE_TYPE_FOLDER)
.map(extractFileFields);
logger.log('_updateSelectedFiles selectedFiles:', this.selectedFiles.length);
} catch (e) {
logger.error('[SelectedFilesManager]', '获取文件信息失败:', e);
}
}
}
this._notifyCallbacks();
this._isUpdating = false;
logger.log('_updateSelectedFiles 执行完成,最终选中文件数:', this.selectedFiles.length);
return this.selectedFiles;
}
async _getParentFileId() {
try {
const homeFilePath = JSON.parse(sessionStorage['filePath'])['homeFilePath'];
const parentFileId = (homeFilePath[homeFilePath.length - 1] || 0);
return parentFileId.toString();
} catch (e) {
logger.error('[SelectedFilesManager]', '获取父级文件ID失败:', e);
return '0';
}
}
_notifyCallbacks() {
this._callbacks.forEach(callback => callback());
}
onFilesChange(callback) {
this._callbacks.push(callback);
}
isUpdating() {
return this._isUpdating;
}
getSelectedFiles() {
return [...this.selectedFiles];
}
hasSelectedFiles() {
return this.selectedFiles.length > 0;
}
}
// 4. 模态框组件类
class Modal {
constructor(options = {}) {
this.title = options.title || '';
this.subtitle = options.subtitle || '';
this.bodyContent = options.bodyContent || null;
this.headerButtons = options.headerButtons || null;
this.headerRight = options.headerRight || null;
this.footerButtons = options.footerButtons || [];
this.footerContent = options.footerContent || null;
this.onClose = options.onClose || null;
this.modal = null;
}
create() {
const modal = document.createElement('div');
modal.className = 'sfr-modal-overlay';
const modalContent = document.createElement('div');
modalContent.className = 'sfr-modal-content';
const modalHeader = document.createElement('div');
modalHeader.className = 'sfr-modal-header';
const titleContainer = document.createElement('div');
titleContainer.className = 'sfr-modal-title-container';
const modalTitle = document.createElement('h3');
modalTitle.textContent = this.title;
modalTitle.className = 'sfr-modal-title';
titleContainer.appendChild(modalTitle);
if (this.subtitle) {
const modalSubtitle = document.createElement('p');
modalSubtitle.textContent = this.subtitle;
modalSubtitle.className = 'sfr-modal-subtitle';
titleContainer.appendChild(modalSubtitle);
}
modalHeader.appendChild(titleContainer);
if (this.headerButtons) {
modalHeader.appendChild(this.headerButtons);
}
if (this.headerRight) {
modalHeader.appendChild(this.headerRight);
}
const modalBody = document.createElement('div');
modalBody.className = 'sfr-modal-body';
if (this.bodyContent) {
modalBody.appendChild(this.bodyContent);
}
const modalFooter = document.createElement('div');
modalFooter.className = 'sfr-modal-footer';
if (this.footerContent) {
modalFooter.appendChild(this.footerContent);
} else {
this.footerButtons.forEach(btn => {
modalFooter.appendChild(btn);
});
}
modalContent.appendChild(modalHeader);
modalContent.appendChild(modalBody);
modalContent.appendChild(modalFooter);
modal.appendChild(modalContent);
modal.onclick = (e) => {
if (e.target === modal) {
this.close();
}
};
this.modal = modal;
return modal;
}
show() {
if (!this.modal) {
this.create();
}
document.body.appendChild(this.modal);
}
close() {
if (this.modal) {
this.modal.remove();
this.modal = null;
}
if (this.onClose) {
this.onClose();
}
}
}
// 5. UI管理类
class UiManager {
constructor(selectedFilesManager, selector, apiClient) {
this.selectedFilesManager = selectedFilesManager;
this.selector = selector;
this.apiClient = apiClient;
this.actionButton = null;
this._observer = null;
this._sortModal = null;
this._renameModal = null;
this._statsContainer = null;
}
init() {
this.selector.init();
this.selectedFilesManager.init();
this.selectedFilesManager.onFilesChange(() => {
this._updateActionButton();
});
this._waitForContainerAndCreateButton();
}
_waitForContainerAndCreateButton() {
const checkAndCreate = () => {
const container = document.querySelector('.home-operator-button-group');
if (container) {
this._createActionButton();
if (this._observer) {
this._observer.disconnect();
this._observer = null;
}
}
};
checkAndCreate();
if (!this.actionButton) {
this._observer = new MutationObserver((mutations) => {
checkAndCreate();
});
this._observer.observe(document.body, {
childList: true,
subtree: true
});
}
}
_createActionButton() {
logger.log('开始创建批量重命名按钮');
const buttonExist = document.querySelector('.sfr-button-container');
if (buttonExist) {
logger.log('按钮已存在,复用现有按钮');
this.actionButton = buttonExist;
return;
}
const container = document.querySelector('.home-operator-button-group');
if (!container) {
logger.log('未找到按钮容器,延迟重试');
return;
}
const firstButton = container.querySelector('button');
const buttonClass = firstButton ? firstButton.className : 'ant-btn css-1doczi2 css-var-_r_0_ ant-btn-primary ant-btn-color-primary ant-btn-variant-solid ant-dropdown-trigger mfy-button upload-button mfy-button';
const btnContainer = document.createElement('div');
btnContainer.className = 'sfr-button-container';
const btn = document.createElement('button');
btn.className = buttonClass;
btn.innerHTML = `批量重命名`;
btn.addEventListener('click', () => {
this._handleButtonClick();
});
btnContainer.appendChild(btn);
container.insertBefore(btnContainer, container.firstChild);
this.actionButton = btnContainer;
logger.log('批量重命名按钮创建完成');
}
_updateActionButton() {
if (!this.actionButton) {
return;
}
const hasFiles = this.selectedFilesManager.hasSelectedFiles();
if (hasFiles) {
this.actionButton.style.display = 'inline-block';
} else {
this.actionButton.style.display = 'none';
}
}
_handleButtonClick() {
if (this.selectedFilesManager.isUpdating()) {
return;
}
this.selectedFilesManager._updateSelectedFiles().then(() => {
const selectedFiles = this.selectedFilesManager.getSelectedFiles();
this._showFileListModal(selectedFiles);
});
}
_createCheckboxButton(text, defaultChecked = false, onChange = null) {
const button = document.createElement('button');
button.className = 'sfr-checkbox-button';
button.dataset.checked = defaultChecked.toString();
const checkboxLabel = document.createElement('label');
checkboxLabel.className = 'ant-checkbox-wrapper css-var-_r_0_ ant-checkbox-css-var css-1doczi2 sfr-checkbox-label';
const checkboxSpan = document.createElement('span');
checkboxSpan.className = 'ant-checkbox ant-wave-target css-1doczi2 sfr-checkbox-span';
const checkboxInput = document.createElement('input');
checkboxInput.type = 'checkbox';
checkboxInput.className = 'ant-checkbox-input sfr-checkbox-input';
checkboxInput.checked = defaultChecked;
const checkboxLabelText = document.createElement('span');
checkboxLabelText.textContent = text;
checkboxLabelText.className = 'sfr-checkbox-text';
checkboxSpan.appendChild(checkboxInput);
checkboxLabel.appendChild(checkboxSpan);
checkboxLabel.appendChild(checkboxLabelText);
button.appendChild(checkboxLabel);
if (defaultChecked) {
checkboxLabel.classList.add('ant-checkbox-wrapper-checked');
checkboxSpan.classList.add('ant-checkbox-checked');
}
button.onmouseover = () => {
if (button.dataset.checked === 'false') {
button.style.borderColor = CONSTANTS.PRIMARY_COLOR;
button.style.color = CONSTANTS.PRIMARY_COLOR;
}
};
button.onmouseout = () => {
if (button.dataset.checked === 'false') {
button.style.borderColor = CONSTANTS.BORDER_COLOR;
button.style.color = CONSTANTS.TEXT_COLOR;
}
};
button.onclick = () => {
const isChecked = button.dataset.checked === 'true';
const newState = !isChecked;
button.dataset.checked = newState.toString();
checkboxInput.checked = newState;
if (newState) {
checkboxLabel.classList.add('ant-checkbox-wrapper-checked');
checkboxSpan.classList.add('ant-checkbox-checked');
} else {
checkboxLabel.classList.remove('ant-checkbox-wrapper-checked');
checkboxSpan.classList.remove('ant-checkbox-checked');
}
if (onChange && typeof onChange === 'function') {
onChange(newState);
}
};
return {
button,
checkboxLabel,
checkboxSpan,
checkboxInput,
checkboxLabelText,
setChecked: (checked) => {
button.dataset.checked = checked.toString();
checkboxInput.checked = checked;
if (checked) {
checkboxLabel.classList.add('ant-checkbox-wrapper-checked');
checkboxSpan.classList.add('ant-checkbox-checked');
} else {
checkboxLabel.classList.remove('ant-checkbox-wrapper-checked');
checkboxSpan.classList.remove('ant-checkbox-checked');
}
},
isChecked: () => button.dataset.checked === 'true'
};
}
_showFileListModal(files) {
const fileList = document.createElement('div');
fileList.className = 'sfr-file-list';
let draggedItem = null;
const fragment = document.createDocumentFragment();
files.forEach((file, index) => {
const fileItem = document.createElement('div');
fileItem.className = 'sfr-file-item';
fileItem.dataset.fileId = file.FileId;
fileItem.dataset.category = file.Category || '0';
fileItem.draggable = true;
fileItem.addEventListener('dragstart', (e) => {
draggedItem = fileItem;
fileItem.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', file.FileId);
});
fileItem.addEventListener('dragend', () => {
draggedItem = null;
fileItem.classList.remove('dragging');
const allItems = fileList.querySelectorAll('.sfr-file-item');
allItems.forEach(item => {
item.style.transform = '';
});
this._updateFileIndices(fileList);
});
fileItem.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
if (draggedItem && draggedItem !== fileItem) {
const rect = fileItem.getBoundingClientRect();
const midY = rect.top + rect.height / 2;
if (e.clientY < midY) {
fileList.insertBefore(draggedItem, fileItem);
} else {
fileList.insertBefore(draggedItem, fileItem.nextSibling);
}
}
});
fileItem.addEventListener('dragenter', (e) => {
e.preventDefault();
if (draggedItem && draggedItem !== fileItem) {
fileItem.style.transform = 'translateY(4px)';
}
});
fileItem.addEventListener('dragleave', (e) => {
if (draggedItem && draggedItem !== fileItem) {
fileItem.style.transform = '';
}
});
fileItem.addEventListener('drop', (e) => {
e.preventDefault();
fileItem.style.transform = '';
});
const dragHandle = document.createElement('span');
dragHandle.innerHTML = '⋮⋮';
dragHandle.className = 'sfr-drag-handle';
const fileIndex = document.createElement('span');
fileIndex.textContent = `${index + 1}.`;
fileIndex.className = 'sfr-file-index';
const fileName = document.createElement('span');
fileName.textContent = file.FileName;
fileName.className = 'sfr-file-name';
const fileSize = document.createElement('span');
fileSize.textContent = this._formatFileSize(file.Size);
fileSize.className = 'sfr-file-size';
const deleteBtn = document.createElement('button');
deleteBtn.innerHTML = '×';
deleteBtn.className = 'sfr-file-delete-btn ant-btn css-dev-only-do-not-override-168k93g ant-btn-default ant-btn-color-default ant-btn-variant-outlined';
deleteBtn.onmousedown = (e) => {
e.stopPropagation();
};
deleteBtn.onclick = (e) => {
e.stopPropagation();
fileItem.remove();
this._updateFileIndices(fileList);
if (this._statsContainer) {
this._updateStats(fileList, files, this._statsContainer);
}
};
fileItem.appendChild(dragHandle);
fileItem.appendChild(fileIndex);
fileItem.appendChild(fileName);
fileItem.appendChild(fileSize);
fileItem.appendChild(deleteBtn);
fragment.appendChild(fileItem);
});
fileList.appendChild(fragment);
const sortButtonObj = this._createCheckboxButton('文件名降序', false, (isChecked) => {
const fileItems = Array.from(fileList.querySelectorAll('.sfr-file-item'));
fileItems.sort((a, b) => {
const nameA = a.querySelector('span:nth-child(3)').textContent;
const nameB = b.querySelector('span:nth-child(3)').textContent;
if (isChecked) {
return nameB.localeCompare(nameA, 'zh-CN');
} else {
return nameA.localeCompare(nameB, 'zh-CN');
}
});
fileItems.forEach(item => {
fileList.appendChild(item);
});
this._updateFileIndices(fileList);
});
const filterButtonObj = this._createCheckboxButton('过滤视频文件', true, (isChecked) => {
const fileItems = fileList.querySelectorAll('.sfr-file-item');
fileItems.forEach(item => {
const category = item.dataset.category;
if (isChecked && category !== CONSTANTS.CATEGORY_VIDEO) {
item.style.display = 'none';
} else {
item.style.display = 'flex';
}
});
this._updateFileIndices(fileList);
this._updateStats(fileList, files, statsContainer);
});
const headerButtonsContainer = document.createElement('div');
headerButtonsContainer.className = 'sfr-button-container-inner';
headerButtonsContainer.appendChild(sortButtonObj.button);
headerButtonsContainer.appendChild(filterButtonObj.button);
const fileItems = fileList.querySelectorAll('.sfr-file-item');
fileItems.forEach(item => {
const category = item.dataset.category;
if (category !== CONSTANTS.CATEGORY_VIDEO) {
item.style.display = 'none';
}
});
this._updateFileIndices(fileList);
const statsContainer = document.createElement('div');
statsContainer.className = 'sfr-stats-container';
this._statsContainer = statsContainer;
this._updateStats(fileList, files, statsContainer);
const nextBtn = document.createElement('button');
nextBtn.textContent = '下一步';
nextBtn.className = 'ant-btn css-1doczi2 css-1oxmu1t css-var-_r_0_ ant-btn-primary ant-btn-color-primary ant-btn-variant-solid';
nextBtn.onclick = () => {
const orderedFiles = this._getOrderedFiles(fileList, files);
logger.log('排序后的文件列表:', orderedFiles);
this._sortModal.close();
this._showRenameModal(orderedFiles);
};
const closeBtn = document.createElement('button');
closeBtn.textContent = '取消';
closeBtn.className = 'ant-btn css-1doczi2 css-var-_r_0_ ant-btn-default ant-btn-color-default ant-btn-variant-outlined';
closeBtn.onclick = () => this._sortModal.close();
const footerButtonsContainer = document.createElement('div');
footerButtonsContainer.className = 'sfr-footer-buttons-container';
footerButtonsContainer.appendChild(closeBtn);
footerButtonsContainer.appendChild(nextBtn);
const footerContent = document.createElement('div');
footerContent.className = 'sfr-modal-footer-content';
footerContent.appendChild(statsContainer);
footerContent.appendChild(footerButtonsContainer);
this._sortModal = new Modal({
title: '文件排序',
subtitle: '文件已自动排序,如顺序不对,请手动拖动排序,然后点击下一步',
bodyContent: fileList,
headerButtons: headerButtonsContainer,
footerContent: footerContent
});
this._sortModal.show();
}
_showRenameModal(files) {
const bodyContainer = document.createElement('div');
bodyContainer.className = 'sfr-modal-body-container';
const configArea = document.createElement('div');
configArea.className = 'sfr-rename-config';
const separator = document.createElement('div');
separator.className = 'separator';
const fileList = document.createElement('div');
fileList.className = 'sfr-file-list';
const fragment = document.createDocumentFragment();
files.forEach((file, index) => {
const fileItem = document.createElement('div');
fileItem.className = 'sfr-file-item-rename';
fileItem.dataset.fileId = file.FileId;
fileItem.dataset.originalIndex = index;
fileItem.dataset.originalFileName = file.FileName;
const originalName = document.createElement('div');
originalName.className = 'sfr-file-name-original';
const fileIndex = document.createElement('span');
fileIndex.textContent = `${index + 1}.`;
fileIndex.className = 'sfr-file-index';
const fileName = document.createElement('span');
fileName.textContent = file.FileName;
originalName.appendChild(fileIndex);
originalName.appendChild(fileName);
const arrowIcon = document.createElement('div');
arrowIcon.className = 'sfr-arrow-icon';
arrowIcon.innerHTML = '→';
const newName = document.createElement('div');
newName.className = 'sfr-file-name-new';
newName.textContent = file.FileName;
fileItem.appendChild(originalName);
fileItem.appendChild(arrowIcon);
fileItem.appendChild(newName);
fragment.appendChild(fileItem);
});
fileList.appendChild(fragment);
bodyContainer.appendChild(configArea);
bodyContainer.appendChild(separator);
bodyContainer.appendChild(fileList);
const tabContainer = document.createElement('div');
tabContainer.className = 'sfr-tab-container';
const tabs = [
{ id: 'sequence', name: '按序号重命名' },
{ id: 'append', name: '追加重命名' },
{ id: 'findReplace', name: '查找替换' },
{ id: 'regex', name: '正则替换' },
{ id: 'format', name: '格式替换' }
];
tabs.forEach((tab, index) => {
const tabItem = document.createElement('div');
tabItem.className = 'sfr-tab-item' + (index === 0 ? ' active' : '');
tabItem.dataset.tabId = tab.id;
tabItem.textContent = tab.name;
tabItem.onclick = () => {
tabContainer.querySelectorAll('.sfr-tab-item').forEach(item => {
item.classList.remove('active');
});
tabItem.classList.add('active');
logger.log('切换到模式:', tab.id);
this._updateConfigArea(tab.id, configArea, fileList);
};
tabContainer.appendChild(tabItem);
});
const headerRight = document.createElement('div');
headerRight.className = 'sfr-modal-header-right';
headerRight.appendChild(tabContainer);
const confirmBtn = document.createElement('button');
confirmBtn.textContent = '确定';
confirmBtn.className = 'ant-btn css-1doczi2 css-1oxmu1t css-var-_r_0_ ant-btn-primary ant-btn-color-primary ant-btn-variant-solid';
let hasExecuted = false;
confirmBtn.onclick = async () => {
if (hasExecuted) {
logger.log('重命名已完成,关闭弹窗并刷新页面');
this._renameModal.close();
window.location.reload();
return;
}
logger.log('开始执行重命名');
prevBtn.style.display = 'none';
const inputs = configArea.querySelectorAll('input');
inputs.forEach(input => {
input.disabled = true;
});
const renamedFiles = [];
const fileItems = fileList.querySelectorAll('.sfr-file-item-rename');
logger.log('待重命名文件数量:', fileItems.length);
fileItems.forEach(item => {
const fileId = item.dataset.fileId;
const originalFileName = item.dataset.originalFileName;
const newNameElement = item.querySelector('.sfr-file-name-new');
const newFileName = newNameElement ? newNameElement.textContent : originalFileName;
renamedFiles.push({
FileId: fileId,
OriginalFileName: originalFileName,
NewFileName: newFileName
});
});
logger.log('待重命名文件列表:', renamedFiles);
try {
confirmBtn.disabled = true;
confirmBtn.textContent = '重命名中...';
let successCount = 0;
let failCount = 0;
for (const fileInfo of renamedFiles) {
logger.log('正在重命名文件:', fileInfo.FileId, fileInfo.OriginalFileName, '->', fileInfo.NewFileName);
const result = await this.apiClient.fileRename(fileInfo);
logger.log('重命名结果:', fileInfo.FileId, result);
const fileItem = fileList.querySelector(`.sfr-file-item-rename[data-file-id="${fileInfo.FileId}"]`);
if (fileItem) {
const newNameElement = fileItem.querySelector('.sfr-file-name-new');
if (newNameElement) {
if (result === true) {
newNameElement.style.backgroundColor = '#dfffcc';
newNameElement.style.border = '1px solid #84d75b';
if (fileInfo.OriginalFileName !== fileInfo.NewFileName) {
successCount++;
}
} else {
newNameElement.style.backgroundColor = '#ffc4c4';
newNameElement.style.border = '1px solid #d75b5b';
failCount++;
}
}
fileItem.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
logger.log('重命名完成,成功:', successCount, '失败:', failCount);
this._updateRenameStats(statsContainer, renamedFiles.length, successCount, failCount);
hasExecuted = true;
confirmBtn.disabled = false;
confirmBtn.textContent = '关闭';
} catch (error) {
logger.error('重命名失败:', error);
confirmBtn.disabled = false;
confirmBtn.textContent = '确定';
const inputs = configArea.querySelectorAll('input');
inputs.forEach(input => {
input.disabled = false;
});
}
};
const prevBtn = document.createElement('button');
prevBtn.textContent = '上一步';
prevBtn.className = 'ant-btn css-1doczi2 css-var-_r_0_ ant-btn-default ant-btn-color-default ant-btn-variant-outlined';
prevBtn.onclick = () => {
this._renameModal.close();
this._sortModal.show();
};
const statsContainer = document.createElement('div');
statsContainer.className = 'sfr-stats-container';
this._updateStats(fileList, files, statsContainer);
const footerButtonsContainer = document.createElement('div');
footerButtonsContainer.className = 'sfr-footer-buttons-container';
footerButtonsContainer.appendChild(prevBtn);
footerButtonsContainer.appendChild(confirmBtn);
const footerContent = document.createElement('div');
footerContent.className = 'sfr-modal-footer-content';
footerContent.appendChild(statsContainer);
footerContent.appendChild(footerButtonsContainer);
this._renameModal = new Modal({
title: '批量重命名',
bodyContent: bodyContainer,
headerRight: headerRight,
footerContent: footerContent
});
this._renameModal.show();
this._updateConfigArea('sequence', configArea, fileList);
}
_updateConfigArea(tabId, configArea, fileList) {
configArea.innerHTML = '';
if (tabId === 'sequence') {
const inputsContainer = document.createElement('div');
inputsContainer.className = 'sfr-rename-inputs-container';
const prefixInput = document.createElement('input');
prefixInput.type = 'text';
prefixInput.className = 'sfr-rename-config-input';
prefixInput.placeholder = '追加前缀';
prefixInput.id = 'sfr-sequence-prefix';
const numberInput = document.createElement('input');
numberInput.type = 'number';
numberInput.className = 'sfr-rename-config-input';
numberInput.placeholder = '默认序号';
numberInput.id = 'sfr-sequence-number';
const suffixInput = document.createElement('input');
suffixInput.type = 'text';
suffixInput.className = 'sfr-rename-config-input';
suffixInput.placeholder = '追加后缀';
suffixInput.id = 'sfr-sequence-suffix';
inputsContainer.appendChild(prefixInput);
inputsContainer.appendChild(numberInput);
inputsContainer.appendChild(suffixInput);
configArea.appendChild(inputsContainer);
const updateFileNames = () => {
const prefix = prefixInput.value || '';
const startNumberStr = numberInput.value;
const suffix = suffixInput.value || '';
let startNumber = 1;
let paddingLength = 0;
if (startNumberStr) {
const parsedNumber = parseInt(startNumberStr);
if (parsedNumber === 0) {
paddingLength = startNumberStr.length;
startNumber = 1;
} else {
startNumber = parsedNumber;
paddingLength = startNumberStr.length;
}
}
const fileItems = fileList.querySelectorAll('.sfr-file-item-rename');
fileItems.forEach(item => {
const originalIndex = parseInt(item.dataset.originalIndex);
const originalFileName = item.dataset.originalFileName;
const ext = originalFileName.includes('.') ? '.' + originalFileName.split('.').pop() : '';
const sequenceNumber = startNumber + originalIndex;
let numberPart;
if (paddingLength > 0) {
numberPart = sequenceNumber.toString().padStart(paddingLength, '0');
} else {
numberPart = sequenceNumber.toString();
}
const newName = prefix + numberPart + suffix + ext;
const newNameElement = item.querySelector('.sfr-file-name-new');
if (newNameElement) {
newNameElement.textContent = newName;
}
});
};
prefixInput.addEventListener('input', updateFileNames);
numberInput.addEventListener('input', updateFileNames);
suffixInput.addEventListener('input', updateFileNames);
setTimeout(updateFileNames, 0);
} else if (tabId === 'append') {
const inputsContainer = document.createElement('div');
inputsContainer.className = 'sfr-rename-inputs-container';
const prefixInput = document.createElement('input');
prefixInput.type = 'text';
prefixInput.className = 'sfr-rename-config-input';
prefixInput.placeholder = '追加前缀';
prefixInput.id = 'sfr-append-prefix';
const suffixInput = document.createElement('input');
suffixInput.type = 'text';
suffixInput.className = 'sfr-rename-config-input';
suffixInput.placeholder = '追加后缀';
suffixInput.id = 'sfr-append-suffix';
inputsContainer.appendChild(prefixInput);
inputsContainer.appendChild(suffixInput);
configArea.appendChild(inputsContainer);
const updateFileNames = () => {
const prefix = prefixInput.value || '';
const suffix = suffixInput.value || '';
const fileItems = fileList.querySelectorAll('.sfr-file-item-rename');
fileItems.forEach(item => {
const originalFileName = item.dataset.originalFileName;
const ext = originalFileName.includes('.') ? '.' + originalFileName.split('.').pop() : '';
const nameWithoutExt = originalFileName.includes('.') ? originalFileName.substring(0, originalFileName.lastIndexOf('.')) : originalFileName;
const newName = prefix + nameWithoutExt + suffix + ext;
const newNameElement = item.querySelector('.sfr-file-name-new');
if (newNameElement) {
newNameElement.textContent = newName;
}
});
};
prefixInput.addEventListener('input', updateFileNames);
suffixInput.addEventListener('input', updateFileNames);
setTimeout(updateFileNames, 0);
} else if (tabId === 'findReplace') {
const inputsContainer = document.createElement('div');
inputsContainer.className = 'sfr-rename-inputs-container';
const findInput = document.createElement('input');
findInput.type = 'text';
findInput.className = 'sfr-rename-config-input';
findInput.placeholder = '查找内容';
findInput.id = 'sfr-find-content';
const replaceInput = document.createElement('input');
replaceInput.type = 'text';
replaceInput.className = 'sfr-rename-config-input';
replaceInput.placeholder = '替换内容';
replaceInput.id = 'sfr-replace-content';
const ignoreCaseLabel = document.createElement('label');
ignoreCaseLabel.className = 'sfr-checkbox-button';
const ignoreCaseCheckbox = document.createElement('input');
ignoreCaseCheckbox.type = 'checkbox';
ignoreCaseCheckbox.id = 'sfr-ignore-case';
const ignoreCaseText = document.createElement('span');
ignoreCaseText.textContent = '忽略大小写';
ignoreCaseLabel.appendChild(ignoreCaseCheckbox);
ignoreCaseLabel.appendChild(ignoreCaseText);
inputsContainer.appendChild(findInput);
inputsContainer.appendChild(replaceInput);
inputsContainer.appendChild(ignoreCaseLabel);
configArea.appendChild(inputsContainer);
const updateFileNames = () => {
const findText = findInput.value || '';
const replaceText = replaceInput.value || '';
const ignoreCase = ignoreCaseCheckbox.checked;
const fileItems = fileList.querySelectorAll('.sfr-file-item-rename');
fileItems.forEach(item => {
const originalFileName = item.dataset.originalFileName;
let newName = originalFileName;
if (findText) {
if (ignoreCase) {
const regex = new RegExp(findText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
newName = originalFileName.replace(regex, replaceText);
} else {
newName = originalFileName.replace(new RegExp(findText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), replaceText);
}
}
const newNameElement = item.querySelector('.sfr-file-name-new');
if (newNameElement) {
newNameElement.textContent = newName;
}
});
};
findInput.addEventListener('input', updateFileNames);
replaceInput.addEventListener('input', updateFileNames);
ignoreCaseCheckbox.addEventListener('change', updateFileNames);
setTimeout(updateFileNames, 0);
} else if (tabId === 'regex') {
const inputsContainer = document.createElement('div');
inputsContainer.className = 'sfr-rename-inputs-container';
const regexInput = document.createElement('input');
regexInput.type = 'text';
regexInput.className = 'sfr-rename-config-input';
regexInput.placeholder = '正则表达式';
regexInput.id = 'sfr-regex-pattern';
const replaceInput = document.createElement('input');
replaceInput.type = 'text';
replaceInput.className = 'sfr-rename-config-input';
replaceInput.placeholder = '替换内容';
replaceInput.id = 'sfr-regex-replace';
inputsContainer.appendChild(regexInput);
inputsContainer.appendChild(replaceInput);
configArea.appendChild(inputsContainer);
const updateFileNames = () => {
const regexPattern = regexInput.value || '';
const replaceText = replaceInput.value || '';
const fileItems = fileList.querySelectorAll('.sfr-file-item-rename');
fileItems.forEach(item => {
const originalFileName = item.dataset.originalFileName;
let newName = originalFileName;
if (regexPattern) {
try {
const regex = new RegExp(regexPattern, 'g');
newName = originalFileName.replace(regex, replaceText);
} catch (e) {
newName = originalFileName;
}
}
const newNameElement = item.querySelector('.sfr-file-name-new');
if (newNameElement) {
newNameElement.textContent = newName;
}
});
};
regexInput.addEventListener('input', updateFileNames);
replaceInput.addEventListener('input', updateFileNames);
setTimeout(updateFileNames, 0);
} else if (tabId === 'format') {
const inputsContainer = document.createElement('div');
inputsContainer.className = 'sfr-rename-inputs-container';
const formatInput = document.createElement('input');
formatInput.type = 'text';
formatInput.className = 'sfr-rename-config-input';
formatInput.placeholder = '新格式名';
formatInput.id = 'sfr-format-ext';
inputsContainer.appendChild(formatInput);
configArea.appendChild(inputsContainer);
const updateFileNames = () => {
const newExt = formatInput.value || '';
const fileItems = fileList.querySelectorAll('.sfr-file-item-rename');
fileItems.forEach(item => {
const originalFileName = item.dataset.originalFileName;
let newName = originalFileName;
if (newExt) {
const ext = originalFileName.includes('.') ? '.' + originalFileName.split('.').pop() : '';
const nameWithoutExt = originalFileName.includes('.') ? originalFileName.substring(0, originalFileName.lastIndexOf('.')) : originalFileName;
let finalExt = newExt;
if (!finalExt.startsWith('.')) {
finalExt = '.' + finalExt;
}
newName = nameWithoutExt + finalExt;
}
const newNameElement = item.querySelector('.sfr-file-name-new');
if (newNameElement) {
newNameElement.textContent = newName;
}
});
};
formatInput.addEventListener('input', updateFileNames);
setTimeout(updateFileNames, 0);
}
}
_updateFileIndices(fileList) {
const fileItems = fileList.querySelectorAll('.sfr-file-item');
let visibleIndex = 1;
fileItems.forEach(item => {
if (item.style.display !== 'none') {
const indexSpan = item.querySelector('.sfr-file-index');
if (indexSpan) {
indexSpan.textContent = `${visibleIndex}.`;
}
visibleIndex++;
}
});
}
_updateStats(fileList, files, statsContainer) {
const fileItems = fileList.querySelectorAll('.sfr-file-item');
const renameFileItems = fileList.querySelectorAll('.sfr-file-item-rename');
const visibleFileItems = fileItems.length > 0
? Array.from(fileItems).filter(item => item.style.display !== 'none')
: Array.from(renameFileItems);
const totalFiles = visibleFileItems.length;
const fileCountSpan = document.createElement('span');
fileCountSpan.className = 'sfr-stats-item';
fileCountSpan.innerHTML = `共 ${totalFiles} 个文件`;
statsContainer.innerHTML = '';
statsContainer.appendChild(fileCountSpan);
}
_updateRenameStats(statsContainer, totalFiles, successCount, failCount) {
const skippedCount = totalFiles - successCount - failCount;
const totalCountSpan = document.createElement('span');
totalCountSpan.className = 'sfr-stats-item';
totalCountSpan.innerHTML = `共 ${totalFiles} 个文件`;
const successSpan = document.createElement('span');
successSpan.className = 'sfr-stats-item';
successSpan.innerHTML = `成功 ${successCount}`;
const failSpan = document.createElement('span');
failSpan.className = 'sfr-stats-item';
failSpan.innerHTML = `失败 ${failCount}`;
if (skippedCount > 0) {
const skippedSpan = document.createElement('span');
skippedSpan.className = 'sfr-stats-item';
skippedSpan.innerHTML = `跳过 ${skippedCount}`;
statsContainer.innerHTML = '';
statsContainer.appendChild(totalCountSpan);
statsContainer.appendChild(successSpan);
statsContainer.appendChild(failSpan);
statsContainer.appendChild(skippedSpan);
} else {
statsContainer.innerHTML = '';
statsContainer.appendChild(totalCountSpan);
statsContainer.appendChild(successSpan);
statsContainer.appendChild(failSpan);
}
}
_getOrderedFiles(fileList, originalFiles) {
const fileItems = fileList.querySelectorAll('.sfr-file-item');
const orderedFiles = [];
const fileMap = new Map(originalFiles.map(f => [String(f.FileId), f]));
fileItems.forEach((item, index) => {
const fileId = item.dataset.fileId;
const isVisible = item.style.display !== 'none';
if (isVisible) {
const file = fileMap.get(String(fileId));
if (file) {
orderedFiles.push(file);
} else {
logger.warn('未找到文件信息:', fileId);
}
}
});
return orderedFiles;
}
_formatFileSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
}
}
const apiClient = new PanApiClient();
const selector = new TableRowSelector();
const selectedFilesManager = new SelectedFilesManager(apiClient, selector);
const uiManager = new UiManager(selectedFilesManager, selector, apiClient);
uiManager.init();
})();