Parcourir la source

feat(simple-demo): 重构导航项数据结构并优化编辑功能- 将主地址(mainUrl)改为附加地址(extraUrls)中的第一个元素
- 新增拖拽排序功能,使用 vue.draggable 组件
- 优化编辑对话框布局,调整字段顺序
- 重构数据初始化逻辑,提高代码可读性
- 优化数据保存和加载逻辑,确保兼容性

tianyunperfect il y a 3 mois
Parent
commit
30fd452a88
2 fichiers modifiés avec 125 ajouts et 549 suppressions
  1. 51 45
      simple-demo/nav.html
  2. 74 504
      tmp/tmp.html

+ 51 - 45
simple-demo/nav.html

@@ -306,8 +306,8 @@
                                     <div class="link-section">
                                         <el-link
                                                 type="primary"
-                                                :href="item.mainUrl"
-                                                target="_blank">{{ item.mainUrl }}
+                                                :href="item.extraUrls[0]"
+                                                target="_blank">{{ item.extraUrls[0] }}
                                         </el-link>
                                     </div>
                                     <div slot="content" v-if="item.extraUrls && item.extraUrls.length" class="extra-links">
@@ -346,29 +346,36 @@
             :visible.sync="dialogVisible"
             width="600px">
         <el-form :model="currentItem" label-width="80px">
-            <el-form-item label="标题">
+            <el-form-item label="* 标题">
                 <el-input v-model="currentItem.title"></el-input>
             </el-form-item>
-            <el-form-item label="图标URL">
-                <el-input v-model="currentItem.icon"></el-input>
-            </el-form-item>
-            <el-form-item label="主地址">
-                <el-input v-model="currentItem.mainUrl"></el-input>
-            </el-form-item>
-            <el-form-item label="描述">
-                <el-input v-model="currentItem.description" type="textarea"></el-input>
-            </el-form-item>
-            <el-form-item label="附加地址">
-                <div v-for="(url, index) in currentItem.extraUrls" :key="index">
-                    <el-input v-model="currentItem.extraUrls[index]" style="margin-bottom: 5px;">
-                        <el-button
+
+            <el-form-item label="* 地址">
+                <draggable
+                    v-model="currentItem.extraUrls"
+                    animation="300"
+                    handle=".drag-handle"
+                    class="extra-links">
+                    <div v-for="(url, index) in currentItem.extraUrls" :key="index" style="display: flex; align-items: center; margin-bottom: 5px;">
+
+                        <!-- 地址输入框 -->
+                        <el-input v-model="currentItem.extraUrls[index]" style="flex: 1;">
+                            <el-button
                                 slot="append"
                                 icon="el-icon-remove"
                                 @click="removeExtraUrl(index)"></el-button>
-                    </el-input>
-                </div>
+                        </el-input>
+                        <!-- 拖拽手柄 -->
+                        <el-button class="drag-handle" icon="el-icon-rank" style="margin-left: 8px; cursor: move;"></el-button></div>
+                </draggable>
                 <el-button @click="addExtraUrl">添加附加地址</el-button>
             </el-form-item>
+            <el-form-item label="描述">
+                <el-input v-model="currentItem.description" type="textarea"></el-input>
+            </el-form-item>
+            <el-form-item label="图标URL">
+                <el-input v-model="currentItem.icon"></el-input>
+            </el-form-item>
             <el-form-item label="截图URL">
                 <el-input v-model="currentItem.screenshot"></el-input>
             </el-form-item>
@@ -399,6 +406,15 @@
         _this.all_data.title = title;
     })
 
+    let initItem = {
+        title: '',
+        icon: '',
+        // mainUrl: '',
+        description: '',
+        extraUrls: [''],
+        screenshot: ''
+    }
+
     let _this = new Vue({
         el: '#app',
         data: {
@@ -410,11 +426,10 @@
                     datas: [{
                         title: "GitHub",
                         icon: "https://github.githubassets.com/favicons/favicon.svg",
-                        mainUrl: "https://github.com",
+                        // mainUrl: "https://github.com",
                         description: "代码托管平台",
                         extraUrls: [
                             "https://pages.github.com",
-                            "https://pages1.github.com"
                         ],
                         screenshot: ""
                     }]
@@ -427,14 +442,7 @@
             // 对话框相关
             dialogVisible: false,
             isEditing: false,
-            currentItem: {
-                title: '',
-                icon: '',
-                mainUrl: '',
-                description: '',
-                extraUrls: [],
-                screenshot: ''
-            },
+            currentItem: initItem,
             originalTab: '',
             originalIndex: -1, // 编辑时,记录原始数据在当前标签页中的索引
             targetTabIndex: -1, // 编辑时,记录目标标签页名称
@@ -447,6 +455,18 @@
                     let showContent = res.content;
                     if (pageManager.isBackup) showContent = res.content_back;
                     this.groups = JSON.parse(showContent);
+                    // groups 里的 extraUrls 应该包含 mainUrl,如果没有,添加进去,为了兼容老数据
+                    this.groups.forEach(group => {
+                        group.datas.forEach(item => {
+                            if (!item.extraUrls) item.extraUrls = [];
+                            if (item.mainUrl) {
+                                if (!item.extraUrls.includes(item.mainUrl)) {
+                                    item.extraUrls.unshift(item.mainUrl);
+                                }
+                            }
+
+                        });
+                    });
                 }
                 if (pageManager.isReadOnly()) this.readOnly = true;
             });
@@ -466,14 +486,7 @@
             },
             openAddDialog() {
                 this.isEditing = false;
-                this.currentItem = {
-                    title: '',
-                    icon: '',
-                    mainUrl: '',
-                    description: '',
-                    extraUrls: [],
-                    screenshot: ''
-                };
+                this.currentItem = JSON.parse(JSON.stringify(initItem));
                 this.targetTab = this.activeTab;
                 this.dialogVisible = true;
             },
@@ -510,7 +523,7 @@
 
             // 保存新项目
             saveItem() {
-                if (!this.currentItem.title || !this.currentItem.mainUrl) {
+                if (!this.currentItem.title || !this.currentItem.extraUrls) {
                     this.$message.warning('标题和主地址为必填项');
                     return;
                 }
@@ -667,14 +680,7 @@
             addContentItemAfter(groupName, index) {
                 this.isEditing = false;
                 // 重置当前项
-                this.currentItem = {
-                    title: '',
-                    icon: '',
-                    mainUrl: '',
-                    description: '',
-                    extraUrls: [],
-                    screenshot: ''
-                };
+                this.currentItem = JSON.parse(JSON.stringify(initItem));
 
                 // 存储目标位置信息
                 this.targetTab = groupName;

+ 74 - 504
tmp/tmp.html

@@ -1,505 +1,75 @@
-<!DOCTYPE html>
-<html lang="zh-CN">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>简易链接管理器</title>
-    <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.2/papaparse.min.js"></script>
-    <style>
-        body {
-            font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
-            line-height: 1.6;
-            margin: 0;
-            padding: 20px;
-            color: #333;
-            background-color: #f9f9f9;
-        }
-        .container {
-            max-width: 1200px;
-            margin: 0 auto;
-        }
-        .header {
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-            margin-bottom: 20px;
-        }
-        h1 {
-            color: #2c3e50;
-            margin: 0;
-        }
-        .toolbar {
-            display: flex;
-            gap: 10px;
-            margin-bottom: 20px;
-            flex-wrap: wrap;
-        }
-        .tag-list {
-            display: flex;
-            flex-wrap: wrap;
-            gap: 8px;
-            margin-bottom: 20px;
-        }
-        .tag {
-            background-color: #e0f2fe;
-            color: #0369a1;
-            padding: 5px 10px;
-            border-radius: 15px;
-            cursor: pointer;
-            transition: all 0.2s;
-            user-select: none;
-        }
-        .tag.active {
-            background-color: #0284c7;
-            color: white;
-        }
-        .tag:hover {
-            opacity: 0.8;
-        }
-        input, button, select {
-            padding: 8px 12px;
-            border: 1px solid #ddd;
-            border-radius: 4px;
-            font-size: 14px;
-        }
-        input:focus, select:focus {
-            outline: none;
-            border-color: #0284c7;
-        }
-        button {
-            background-color: #0284c7;
-            color: white;
-            cursor: pointer;
-            border: none;
-        }
-        button:hover {
-            background-color: #0369a1;
-        }
-        table {
-            width: 100%;
-            border-collapse: collapse;
-            margin-top: 20px;
-            background-color: white;
-            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
-            border-radius: 8px;
-            overflow: hidden;
-        }
-        th, td {
-            padding: 12px 15px;
-            text-align: left;
-            border-bottom: 1px solid #eee;
-        }
-        th {
-            background-color: #f1f5f9;
-            font-weight: 600;
-            color: #334155;
-        }
-        tr:hover {
-            background-color: #f1f5f9;
-        }
-        .preview-img {
-            max-width: 100px;
-            max-height: 100px;
-            object-fit: contain;
-            cursor: pointer;
-        }
-        .modal {
-            display: none;
-            position: fixed;
-            top: 0;
-            left: 0;
-            width: 100%;
-            height: 100%;
-            background-color: rgba(0,0,0,0.8);
-            z-index: 1000;
-            justify-content: center;
-            align-items: center;
-        }
-        .modal-content {
-            max-width: 90%;
-            max-height: 90%;
-        }
-        .modal-content img {
-            max-width: 100%;
-            max-height: 90vh;
-            object-fit: contain;
-        }
-        .close {
-            position: absolute;
-            top: 20px;
-            right: 30px;
-            color: white;
-            font-size: 30px;
-            cursor: pointer;
-        }
-        .export-import {
-            margin-top: 20px;
-            display: flex;
-            gap: 10px;
-        }
-        #importInput {
-            display: none;
-        }
-    </style>
-</head>
-<body>
-<div class="container">
-    <div class="header">
-        <h1>简易链接管理器</h1>
-    </div>
-
-    <div class="toolbar">
-        <input type="text" id="searchInput" placeholder="搜索...">
-        <select id="sortSelect">
-            <option value="dateDesc">日期 (新→旧)</option>
-            <option value="dateAsc">日期 (旧→新)</option>
-            <option value="titleAsc">标题 (A→Z)</option>
-            <option value="titleDesc">标题 (Z→A)</option>
-        </select>
-        <button id="addNewBtn">添加新链接</button>
-    </div>
-
-    <div class="tag-list" id="tagList"></div>
-
-    <table id="dataTable">
-        <thead>
-        <tr>
-            <th>标题</th>
-            <th>链接</th>
-            <th>图片</th>
-            <th>标签</th>
-            <th>添加日期</th>
-            <th>操作</th>
-        </tr>
-        </thead>
-        <tbody id="tableBody"></tbody>
-    </table>
-
-    <div class="export-import">
-        <button id="exportBtn">导出数据</button>
-        <button id="importBtn">导入数据</button>
-        <input type="file" id="importInput" accept=".json">
-    </div>
-</div>
-
-<div id="imageModal" class="modal">
-    <span class="close">&times;</span>
-    <div class="modal-content">
-        <img id="modalImage" src="">
-    </div>
-</div>
-
-<script>
-    // 初始数据存储
-    let data = JSON.parse(localStorage.getItem('linkData')) || [];
-    let allTags = new Set();
-    let activeTagFilters = new Set();
-
-    // 初始化应用
-    function init() {
-        renderTags();
-        renderTable();
-        setupEventListeners();
-    }
-
-    // 从数据中提取所有唯一标签
-    function extractAllTags() {
-        allTags = new Set();
-        data.forEach(item => {
-            if (item.tags && Array.isArray(item.tags)) {
-                item.tags.forEach(tag => allTags.add(tag));
-            }
-        });
-        return Array.from(allTags).sort();
-    }
-
-    // 渲染标签筛选器
-    function renderTags() {
-        const tags = extractAllTags();
-        const tagList = document.getElementById('tagList');
-        tagList.innerHTML = '';
-
-        tags.forEach(tag => {
-            const tagElement = document.createElement('span');
-            tagElement.className = `tag ${activeTagFilters.has(tag) ? 'active' : ''}`;
-            tagElement.textContent = tag;
-            tagElement.dataset.tag = tag;
-            tagList.appendChild(tagElement);
-        });
-    }
-
-    // 渲染数据表格
-    function renderTable() {
-        const tableBody = document.getElementById('tableBody');
-        tableBody.innerHTML = '';
-
-        const searchTerm = document.getElementById('searchInput').value.toLowerCase();
-        const sortMethod = document.getElementById('sortSelect').value;
-
-        // 筛选数据
-        let filteredData = data.filter(item => {
-            // 标签筛选
-            if (activeTagFilters.size > 0) {
-                if (!item.tags || !Array.isArray(item.tags)) return false;
-                const hasAllTags = Array.from(activeTagFilters).every(tag =>
-                    item.tags.includes(tag)
-                );
-                if (!hasAllTags) return false;
-            }
-
-            // 搜索筛选
-            if (searchTerm) {
-                const searchFields = [
-                    item.title,
-                    item.url,
-                    (item.tags || []).join(' ')
-                ].join(' ').toLowerCase();
-
-                return searchFields.includes(searchTerm);
-            }
-
-            return true;
-        });
-
-        // 排序数据
-        filteredData.sort((a, b) => {
-            switch (sortMethod) {
-                case 'dateDesc':
-                    return new Date(b.date) - new Date(a.date);
-                case 'dateAsc':
-                    return new Date(a.date) - new Date(b.date);
-                case 'titleAsc':
-                    return a.title.localeCompare(b.title);
-                case 'titleDesc':
-                    return b.title.localeCompare(a.title);
-                default:
-                    return new Date(b.date) - new Date(a.date);
-            }
-        });
-
-        // 渲染数据行
-        filteredData.forEach((item, index) => {
-            const row = document.createElement('tr');
-
-            // 标题列
-            const titleCell = document.createElement('td');
-            titleCell.textContent = item.title;
-            row.appendChild(titleCell);
-
-            // 链接列
-            const urlCell = document.createElement('td');
-            const urlLink = document.createElement('a');
-            urlLink.href = item.url;
-            urlLink.textContent = item.url.length > 30 ? item.url.substring(0, 30) + '...' : item.url;
-            urlLink.target = '_blank';
-            urlCell.appendChild(urlLink);
-            row.appendChild(urlCell);
-
-            // 图片列
-            const imageCell = document.createElement('td');
-            if (item.imageUrl) {
-                const img = document.createElement('img');
-                img.src = item.imageUrl;
-                img.alt = item.title;
-                img.className = 'preview-img';
-                img.dataset.fullImage = item.imageUrl;
-                imageCell.appendChild(img);
-            }
-            row.appendChild(imageCell);
-
-            // 标签列
-            const tagsCell = document.createElement('td');
-            if (item.tags && Array.isArray(item.tags)) {
-                item.tags.forEach(tag => {
-                    const tagSpan = document.createElement('span');
-                    tagSpan.className = 'tag';
-                    tagSpan.textContent = tag;
-                    tagsCell.appendChild(tagSpan);
-                });
-            }
-            row.appendChild(tagsCell);
-
-            // 日期列
-            const dateCell = document.createElement('td');
-            dateCell.textContent = new Date(item.date).toLocaleDateString();
-            row.appendChild(dateCell);
-
-            // 操作列
-            const actionsCell = document.createElement('td');
-            const editBtn = document.createElement('button');
-            editBtn.textContent = '编辑';
-            editBtn.dataset.index = index;
-            editBtn.style.marginRight = '5px';
-
-            const deleteBtn = document.createElement('button');
-            deleteBtn.textContent = '删除';
-            deleteBtn.dataset.index = index;
-            deleteBtn.style.backgroundColor = '#ef4444';
-
-            actionsCell.appendChild(editBtn);
-            actionsCell.appendChild(deleteBtn);
-            row.appendChild(actionsCell);
-
-            tableBody.appendChild(row);
-        });
-    }
-
-    // 设置事件监听器
-    function setupEventListeners() {
-        // 搜索框变化时更新表格
-        document.getElementById('searchInput').addEventListener('input', renderTable);
-
-        // 排序选择变化时更新表格
-        document.getElementById('sortSelect').addEventListener('change', renderTable);
-
-        // 标签点击事件
-        document.getElementById('tagList').addEventListener('click', event => {
-            if (event.target.classList.contains('tag')) {
-                const tag = event.target.dataset.tag;
-                if (activeTagFilters.has(tag)) {
-                    activeTagFilters.delete(tag);
-                } else {
-                    activeTagFilters.add(tag);
-                }
-                renderTags();
-                renderTable();
-            }
-        });
-
-        // 添加新链接按钮
-        document.getElementById('addNewBtn').addEventListener('click', () => {
-            const title = prompt('输入标题:');
-            if (!title) return;
-
-            const url = prompt('输入URL:');
-            if (!url) return;
-
-            const imageUrl = prompt('输入图片URL (可选):');
-
-            const tagsInput = prompt('输入标签 (用逗号分隔):');
-            const tags = tagsInput ? tagsInput.split(',').map(t => t.trim()).filter(t => t) : [];
-
-            const newItem = {
-                title,
-                url,
-                imageUrl,
-                tags,
-                date: new Date().toISOString()
-            };
-
-            data.push(newItem);
-            saveData();
-            renderTags();
-            renderTable();
-        });
-
-        // 表格行操作按钮
-        document.getElementById('tableBody').addEventListener('click', event => {
-            if (event.target.tagName === 'BUTTON') {
-                const index = parseInt(event.target.dataset.index);
-                const item = data[index];
-
-                if (event.target.textContent === '编辑') {
-                    // 编辑操作
-                    const title = prompt('编辑标题:', item.title);
-                    if (!title) return;
-
-                    const url = prompt('编辑URL:', item.url);
-                    if (!url) return;
-
-                    const imageUrl = prompt('编辑图片URL (可选):', item.imageUrl || '');
-
-                    const tagsInput = prompt('编辑标签 (用逗号分隔):', (item.tags || []).join(', '));
-                    const tags = tagsInput ? tagsInput.split(',').map(t => t.trim()).filter(t => t) : [];
-
-                    data[index] = {
-                        ...item,
-                        title,
-                        url,
-                        imageUrl,
-                        tags
-                    };
-
-                    saveData();
-                    renderTags();
-                    renderTable();
-                } else if (event.target.textContent === '删除') {
-                    // 删除操作
-                    if (confirm(`确定要删除 "${item.title}" 吗?`)) {
-                        data.splice(index, 1);
-                        saveData();
-                        renderTags();
-                        renderTable();
-                    }
-                }
-            } else if (event.target.classList.contains('preview-img')) {
-                // 图片预览
-                const modal = document.getElementById('imageModal');
-                const modalImg = document.getElementById('modalImage');
-                modal.style.display = 'flex';
-                modalImg.src = event.target.dataset.fullImage;
-            }
-        });
-
-        // 图片模态框关闭按钮
-        document.querySelector('.close').addEventListener('click', () => {
-            document.getElementById('imageModal').style.display = 'none';
-        });
-
-        // 导出数据
-        document.getElementById('exportBtn').addEventListener('click', () => {
-            const dataStr = JSON.stringify(data, null, 2);
-            const blob = new Blob([dataStr], {type: 'application/json'});
-            const url = URL.createObjectURL(blob);
-            const a = document.createElement('a');
-            a.href = url;
-            a.download = `bookmark-data-${new Date().toISOString().slice(0,10)}.json`;
-            document.body.appendChild(a);
-            a.click();
-            document.body.removeChild(a);
-        });
-
-        // 导入数据点击
-        document.getElementById('importBtn').addEventListener('click', () => {
-            document.getElementById('importInput').click();
-        });
-
-        // 导入数据处理
-        document.getElementById('importInput').addEventListener('change', event => {
-            const file = event.target.files[0];
-            if (!file) return;
-
-            const reader = new FileReader();
-            reader.onload = function(e) {
-                try {
-                    const importedData = JSON.parse(e.target.result);
-                    if (Array.isArray(importedData)) {
-                        if (confirm(`确定要导入 ${importedData.length} 条数据吗? 这将覆盖当前数据。`)) {
-                            data = importedData;
-                            saveData();
-                            renderTags();
-                            renderTable();
-                            alert('数据导入成功!');
-                        }
-                    } else {
-                        alert('导入的文件格式不正确。请提供有效的JSON数组。');
-                    }
-                } catch (err) {
-                    alert('导入失败: ' + err.message);
-                }
-                event.target.value = '';
-            };
-            reader.readAsText(file);
-        });
-    }
-
-    // 保存数据到localStorage
-    function saveData() {
-        localStorage.setItem('linkData', JSON.stringify(data));
-    }
-
-    // 初始化应用
-    init();
-</script>
-</body>
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        /* 隐藏原生 video 控件,用 Canvas 展示动画 */
+        #audioPlayer { display: none; }
+        body { display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #000; }
+    </style>
+</head>
+<body>
+
+<input type="button" onclick="audio.play()" value="播放" />
+<input type="button" onclick="audio.pause()" value="暂停" />
+<canvas id="wrap" height="550" width="800"></canvas>
+<script>
+    var wrap = document.getElementById("wrap");
+    var cxt = wrap.getContext("2d");
+    //获取API
+    var AudioContext = AudioContext || webkitAudioContext;
+    var context = new AudioContext;
+    //加载媒体
+    var audio = new Audio("http://192.168.3.36:5002/play/5e25571cae884d307e0a8f576fbc8632?t=1743822135234");
+    //创建节点
+    var source = context.createMediaElementSource(audio);
+    var analyser = context.createAnalyser();
+    //连接:source → analyser → destination
+    source.connect(analyser);
+    analyser.connect(context.destination);
+    //创建数据
+    var output = new Uint8Array(361);
+    //计算出采样频率44100所需的缓冲区长度
+    var length = analyser.frequencyBinCount * 44100 / context.sampleRate | 0;
+    //创建数据
+    var output2 = new Uint8Array(length);
+    (function drawSpectrum() {
+        analyser.getByteFrequencyData(output);//获取频域数据
+        cxt.clearRect(0, 0, wrap.width, wrap.height);
+        //画线条
+        for (var i = 0; i < output.length; i++) {
+            var value = output[i] / 10;
+            //绘制左半边
+            cxt.beginPath();
+            cxt.lineWidth = 1;
+            cxt.moveTo(300, 300);
+            cxt.lineTo(Math.cos((i * 0.5 + 90) / 180 * Math.PI) * (200 + value) + 300, (- Math.sin((i * 0.5 + 90) / 180 * Math.PI) * (200 + value) + 300));
+            cxt.stroke();
+            //绘制右半边
+            cxt.beginPath();
+            cxt.lineWidth = 1;
+            cxt.moveTo(300, 300);
+            cxt.lineTo((Math.sin((i * 0.5) / 180 * Math.PI) * (200 + value) + 300), -Math.cos((i * 0.5) / 180 * Math.PI) * (200 + value) + 300);
+            cxt.stroke();
+        }
+        //画一个小圆,将线条覆盖
+        cxt.beginPath();
+        cxt.lineWidth = 1;
+        cxt.arc(300, 300, 200, 0, 2 * Math.PI, false);
+        cxt.fillStyle = "#fff";
+        cxt.stroke();
+        cxt.fill();
+        //将缓冲区的数据绘制到Canvas上
+        analyser.getByteTimeDomainData(output2);
+        var height = 100, width = 400;
+        cxt.beginPath();
+        for (var i = 0; i < width; i++) {
+            cxt.lineTo(i + 100, 300 - (height / 2 * (output2[output2.length * i / width | 0] / 256 - 0.5)));
+        }
+        cxt.stroke();
+        //请求下一帧
+        requestAnimationFrame(drawSpectrum);
+    })();
+</script>
+
+</body>
 </html>