SOURCE

console 命令行工具 X clear

                    
>
console
<!DOCTYPE html>
<html>
<head>
    <title>全功能导航站</title>
    <script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            font-family: Arial, sans-serif;
            background: #f5f5f5;
        }

        /* 左侧目录 */
        .directory {
            position: fixed;
            left: 0;
            top: 0;
            width: 200px;
            height: 100vh;
            padding: 20px;
            background: #fff;
            box-shadow: 2px 0 5px rgba(0,0,0,0.1);
            overflow-y: auto;
            z-index: 100;
        }
        .directory-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
        }
        .add-category-btn {
            padding: 5px 10px;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }

        /* 分类列表 */
        #categoryList {
            list-style: none;
        }
        #categoryList li {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin: 8px 0;
            padding: 6px;
            border-radius: 4px;
            transition: background 0.2s;
        }
        #categoryList li:hover {
            background: #f8f9fa;
        }
        .category-name {
            cursor: pointer;
            flex-grow: 1;
            margin-right: 8px;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .edit-category-btn {
            opacity: 0;
            padding: 2px 6px;
            background: none;
            border: none;
            color: #666;
            cursor: pointer;
            transition: opacity 0.2s;
        }
        #categoryList li:hover .edit-category-btn {
            opacity: 1;
        }

        /* 右侧内容区 */
        .content {
            margin-left: 220px;
            padding: 30px;
            min-height: 100vh;
        }

        /* 分类行 */
        .category-row {
            margin-bottom: 30px;
        }
        .category-header {
            display: flex;
            align-items: center;
            margin-bottom: 15px;
        }
        .category-header h2 {
            margin-right: 15px;
            padding-left: 10px;
            border-left: 4px solid #007bff;
            font-size: 18px;
            color: #333;
        }
        .add-bookmark-btn {
            padding: 5px 10px;
            background: #28a745;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }

        /* 网站卡片 */
        .website-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
            gap: 15px;
        }
        .website-card {
            width: 260px;
            height: 120px;
            background: white;
            padding: 15px;
            border-radius: 10px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.08);
            display: flex;
            align-items: center;
            position: relative;
            transition: transform 0.2s, box-shadow 0.2s;
            cursor: pointer;
        }
        .website-card:hover {
            transform: translateY(-3px);
            box-shadow: 0 4px 12px rgba(0,0,0,0.12);
        }
        .website-card a {
            display: flex;
            align-items: center;
            color: #333;
            text-decoration: none;
            width: 100%;
            height: 100%;
        }
        .website-card img {
            width: 40px;
            height: 40px;
            margin-right: 15px;
            border-radius: 8px;
            object-fit: cover;
            border: 1px solid #eee;
            background: #fff;
            padding: 3px;
        }
        .website-card span {
            font-size: 14px;
            max-width: 160px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        /* 右键菜单 */
        .context-menu {
            display: none;
            position: fixed;
            background: white;
            box-shadow: 0 2px 8px rgba(0,0,0,0.15);
            border-radius: 6px;
            z-index: 1000;
        }
        .menu-item {
            padding: 8px 16px;
            cursor: pointer;
            transition: background 0.2s;
        }
        .menu-item:hover {
            background: #f8f9fa;
        }

        /* 工具条 */
        .toolbar {
            position: fixed;
            top: 20px;
            right: 20px;
            display: flex;
            gap: 10px;
            z-index: 100;
        }
        .toolbar button {
            padding: 8px 12px;
            background: #17a2b8;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }

        /* 分类选择器 */
        .category-selector {
            display: none;
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 0 15px rgba(0,0,0,0.2);
            z-index: 1001;
        }

        /* 拖拽样式 */
        .website-card.dragging {
            opacity: 0.5;
            transform: scale(0.95);
            transition: all 0.2s ease;
        }
        .website-card.ghost {
            background: #f8f9fa;
            border: 2px dashed #007bff !important;
            opacity: 0.8;
        }
        /* 新增分类拖拽样式 */
        #categoryList li.dragging {
        background: #f8f9fa;
        box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        #categoryList li.ghost {
        opacity: 0.5;
        border-left: 3px solid #007bff;
        }
    </style>
</head>
<body>
    <div class="directory">
        <div class="directory-header">
            <h2>网站分类</h2>
            <button class="add-category-btn" onclick="addCategory()">+ 类目</button>
        </div>
        <ul id="categoryList"></ul>
    </div>

    <div class="content" id="content"></div>

    <div class="toolbar">
        <button onclick="exportData()">导出数据</button>
        <button onclick="document.getElementById('fileInput').click()">导入数据</button>
        <input type="file" id="fileInput" hidden accept=".json" onchange="importData(event)">
    </div>

    <div id="contextMenu" class="context-menu">
        <div class="menu-item" onclick="handleMenu('edit')">修改图标</div>
        <div class="menu-item" onclick="handleMenu('move')">调整分类</div>
        <div class="menu-item" onclick="handleMenu('delete')">删除书签</div>
    </div>

    <div id="categorySelector" class="category-selector">
        <h3>移动到分类:</h3>
        <div id="targetCategoryList"></div>
        <button onclick="closeCategorySelector()" style="margin-top:10px;">取消</button>
    </div>

    <script>
        // 初始化数据
        let data = JSON.parse(localStorage.getItem('navData')) || {
            categories: [
                {
                    id: 'default',
                    name: '默认分类',
                    bookmarks: []
                }
            ]
        };

        let currentContext = null;
        let sortableInstances = [];
        let categorySortable = null;

        // 核心渲染函数
        function render() {
             renderDirectory();
            renderContent();
            initSortable();
        }
            // 渲染目录
        function renderDirectory() {
            const categoryList = document.getElementById('categoryList');
            categoryList.innerHTML = data.categories.map(cat => `
                <li data-category-id="${cat.id}">
                    <span class="category-name">${cat.name}</span>
                    <button class="edit-category-btn" onclick="editCategoryName('${cat.id}')">✎</button>
                </li>
            `).join('');
        }

        // 渲染内容区
        function renderContent() {
            const content = document.getElementById('content');
            content.innerHTML = data.categories.map(cat => `
                <div class="category-row" id="${cat.id}">
                    <div class="category-header">
                        <h2>${cat.name}</h2>
                        <button class="add-bookmark-btn" onclick="addBookmark('${cat.id}')">+ 书签</button>
                    </div>
                    <div class="website-grid">
                        ${cat.bookmarks.map(bookmark => `
                            <div class="website-card" 
                                 data-category="${cat.id}" 
                                 data-url="${bookmark.url}"
                                 oncontextmenu="showContextMenu(event, '${cat.id}', '${bookmark.url}')">
                                <a href="${bookmark.url}" target="_blank">
                                    <img src="${getIconUrl(bookmark)}" 
                                         onerror="this.onerror=null;this.src='${generateLetterIcon(bookmark.name)}'">
                                    <span>${bookmark.name}</span>
                                </a>
                            </div>
                        `).join('')}
                    </div>
                </div>
            `).join('');

            localStorage.setItem('navData', JSON.stringify(data));
        }

        // 初始化拖拽功能
        function initSortable() {
            // 销毁旧实例
            if(categorySortable) categorySortable.destroy();
            sortableInstances.forEach(instance => instance.destroy());
            sortableInstances = [];

            // 初始化分类拖拽
            categorySortable = new Sortable(document.getElementById('categoryList'), {
                animation: 150,
                handle: '.category-name',
                ghostClass: 'ghost',
                chosenClass: 'dragging',
                delay: 300,
                delayOnTouchOnly: true,
                onEnd: (evt) => {
                    const newOrder = Array.from(evt.from.children).map(li => 
                        data.categories.find(c => c.id === li.dataset.categoryId)
                    );
                    data.categories = newOrder.filter(Boolean);
                    renderContent();
                }
            });

            // 初始化书签拖拽
            document.querySelectorAll('.website-grid').forEach(container => {
                const sortable = new Sortable(container, {
                    animation: 150,
                    ghostClass: 'ghost',
                    chosenClass: 'dragging',
                    delay: 300,
                    delayOnTouchOnly: true,
                    onStart: () => {
                        document.getElementById('contextMenu').style.display = 'none';
                    },
                    onEnd: (evt) => {
                        const categoryId = evt.from.closest('.category-row').id;
                        const category = data.categories.find(c => c.id === categoryId);
                        
                        const newOrder = Array.from(evt.from.children).map(item => 
                            category.bookmarks.find(b => b.url === item.dataset.url)
                        ).filter(Boolean);
                        
                        category.bookmarks = newOrder;
                        localStorage.setItem('navData', JSON.stringify(data));
                    }
                });
                sortableInstances.push(sortable);
            });
        }

        // 分类管理
        function addCategory() {
            const name = prompt('请输入新分类名称');
            if (name) {
                data.categories.push({
                    id: `category-${Date.now()}`,
                    name: name,
                    bookmarks: []
                });
                render();
            }
        }

        function editCategoryName(categoryId) {
            const category = data.categories.find(cat => cat.id === categoryId);
            if (!category) return;

            const newName = prompt('请输入新分类名称', category.name);
            if (newName) {
                category.name = newName;
                render();
            }
        }

        // 书签管理
        function addBookmark(categoryId) {
            const name = prompt('请输入网站名称');
            const url = prompt('请输入网站地址');
            if (name && url) {
                try {
                    new URL(url);
                    const category = data.categories.find(cat => cat.id === categoryId);
                    if (category) {
                        category.bookmarks.push({
                            name,
                            url,
                            customIcon: ''
                        });
                        render();
                    }
                } catch {
                    alert('请输入有效的网址!');
                }
            }
        }

        // 右键菜单功能
        function showContextMenu(e, categoryId, url) {
            e.preventDefault();
            e.stopPropagation();
            currentContext = { categoryId, url };
            
            const menu = document.getElementById('contextMenu');
            menu.style.display = 'block';
            menu.style.left = `${e.pageX}px`;
            menu.style.top = `${e.pageY}px`;
        }

        document.addEventListener('click', () => {
            document.getElementById('contextMenu').style.display = 'none';
        });

        function handleMenu(action) {
            switch(action) {
                case 'edit':
                    editIcon();
                    break;
                case 'move':
                    showMoveSelector();
                    break;
                case 'delete':
                    deleteBookmark();
                    break;
            }
        }

        // 删除功能(已修复)
        function deleteBookmark() {
            if (!currentContext) return;
            
            if (confirm('确定要删除这个书签吗?')) {
                const category = data.categories.find(cat => cat.id === currentContext.categoryId);
                if (category) {
                    category.bookmarks = category.bookmarks.filter(b => b.url !== currentContext.url);
                    render();
                }
            }
        }

        // 图标管理
        function getIconUrl(bookmark) {
            return bookmark.customIcon || 
                   `https://www.google.com/s2/favicons?domain=${new URL(bookmark.url).hostname}&sz=64`;
        }

        function editIcon() {
            const bookmark = getCurrentBookmark();
            const input = document.createElement('input');
            input.type = 'file';
            input.accept = 'image/*';
            input.onchange = (e) => {
                const file = e.target.files[0];
                const reader = new FileReader();
                reader.onload = () => {
                    bookmark.customIcon = reader.result;
                    render();
                };
                reader.readAsDataURL(file);
            };
            input.click();
        }

        // 分类移动
        function showMoveSelector() {
            const selector = document.getElementById('categorySelector');
            selector.style.display = 'block';
            document.getElementById('targetCategoryList').innerHTML = data.categories
                .filter(cat => cat.id !== currentContext.categoryId)
                .map(cat => `
                    <div class="menu-item" onclick="moveBookmark('${cat.id}')">${cat.name}</div>
                `).join('');
        }

        function moveBookmark(targetCategoryId) {
            const sourceCat = data.categories.find(c => c.id === currentContext.categoryId);
            const targetCat = data.categories.find(c => c.id === targetCategoryId);
            const bookmark = sourceCat.bookmarks.find(b => b.url === currentContext.url);

            sourceCat.bookmarks = sourceCat.bookmarks.filter(b => b.url !== currentContext.url);
            targetCat.bookmarks.push(bookmark);
            render();
            closeCategorySelector();
        }

        // 数据管理
        function exportData() {
            const json = JSON.stringify(data);
            const blob = new Blob([json], {type: 'application/json'});
            const url = URL.createObjectURL(blob);
            
            const a = document.createElement('a');
            a.href = url;
            a.download = 'nav-data.json';
            a.click();
            URL.revokeObjectURL(url);
        }

        function importData(event) {
            const file = event.target.files[0];
            const reader = new FileReader();
            
            reader.onload = (e) => {
                try {
                    data = JSON.parse(e.target.result);
                    localStorage.setItem('navData', JSON.stringify(data));
                    render();
                } catch {
                    alert('无效的数据文件!');
                }
            };
            reader.readAsText(file);
        }
        
        // 辅助函数
        function scrollToCategory(categoryId) {
            const element = document.getElementById(categoryId);
            if (element) {
                element.scrollIntoView({ behavior: 'smooth' });
            }
        }

        function getCurrentBookmark() {
            return data.categories
                .find(c => c.id === currentContext.categoryId)
                .bookmarks.find(b => b.url === currentContext.url);
        }

        function generateLetterIcon(name) {
            const letter = (name || '?').charAt(0).toUpperCase();
            const hue = stringToHash(name) % 360;
            return `data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'>
                <rect width='100%' height='100%' fill='hsl(${hue},70%,85%)' rx='15'/>
                <text x='50%' y='55%' font-size='50' dominant-baseline='middle' text-anchor='middle'
                      fill='hsl(${hue},60%,30%)' font-family='Arial'>${letter}</text>
            </svg>`;
        }

        function stringToHash(str) {
            let hash = 0;
            for (let i = 0; i < str.length; i++) {
                hash = str.charCodeAt(i) + ((hash << 5) - hash);
            }
            return Math.abs(hash);
        }

        function closeCategorySelector() {
            document.getElementById('categorySelector').style.display = 'none';
        }

        // 初始化
        render();
    </script>
</body>
</html>