Vue2实战教程】零基础也能学会的收藏夹管理系统,新手前端开发必看!

效果图

在这里插入图片描述


🌟【定制化开发服务,让您的项目领先一步】🌟

如有需求,直接私信留下您的联系方式。谢谢。
我的邮箱:2351598671@qq.com


完整代码

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>智能收藏夹管理系统</title>
  <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
  <script src="https://unpkg.com/element-ui/lib/index.js"></script>
</head>
<body>
  <div id="app">
    <el-container class="bookmark-manager">
      <!-- 顶部导航 -->
      <el-header>
        <div class="header-content">
          <div class="logo">
            <i class="el-icon-collection"></i>
            智能收藏夹管理系统
          </div>
          <div class="header-actions">
            <el-button type="primary" size="small" @click="importBookmarks">
              <i class="el-icon-upload2"></i> 导入收藏夹
            </el-button>
            <el-button type="success" size="small" @click="exportBookmarks">
              <i class="el-icon-download"></i> 导出收藏夹
            </el-button>
          </div>
        </div>
      </el-header>

      <el-container>
        <!-- 左侧分类栏 -->
        <el-aside width="250px">
          <div class="category-header">
            <span>分类管理</span>
            <el-button type="text" @click="addCategory">
              <i class="el-icon-plus"></i>
            </el-button>
          </div>
          
          <el-menu
            :default-active="activeCategory"
            class="category-menu"
            @select="handleCategorySelect">
            <el-menu-item index="all">
              <i class="el-icon-files"></i>
              <span>全部收藏</span>
              <span class="item-count">{{ totalBookmarks }}</span>
            </el-menu-item>
            <el-menu-item index="uncategorized">
              <i class="el-icon-folder"></i>
              <span>未分类</span>
              <span class="item-count">{{ uncategorizedCount }}</span>
            </el-menu-item>
            <el-menu-item 
              v-for="category in categories" 
              :key="category.id" 
              :index="category.id">
              <i class="el-icon-folder"></i>
              <span>{{ category.name }}</span>
              <span class="item-count">{{ category.count }}</span>
            </el-menu-item>
          </el-menu>

          <div class="tag-section">
            <div class="section-header">
              <span>常用标签</span>
            </div>
            <div class="tag-cloud">
              <el-tag
                v-for="tag in popularTags"
                :key="tag.name"
                :type="tag.type"
                size="small"
                @click="filterByTag(tag.name)">
                {{ tag.name }} ({{ tag.count }})
              </el-tag>
            </div>
          </div>
        </el-aside>

        <!-- 主要内容区 -->
        <el-main>
          <!-- 搜索和工具栏 -->
          <div class="toolbar">
            <el-input
              placeholder="搜索收藏..."
              v-model="searchQuery"
              class="search-input"
              prefix-icon="el-icon-search"
              clearable>
            </el-input>
            
            <div class="tool-actions">
              <el-select v-model="sortBy" size="small" placeholder="排序方式">
                <el-option label="添加时间" value="date"></el-option>
                <el-option label="名称" value="name"></el-option>
                <el-option label="访问次数" value="visits"></el-option>
              </el-select>

              <el-button-group>
                <el-button 
                  size="small" 
                  :type="viewMode === 'list' ? 'primary' : ''"
                  @click="viewMode = 'list'">
                  <i class="el-icon-menu"></i>
                </el-button>
                <el-button 
                  size="small"
                  :type="viewMode === 'grid' ? 'primary' : ''"
                  @click="viewMode = 'grid'">
                  <i class="el-icon-s-grid"></i>
                </el-button>
              </el-button-group>
            </div>
          </div>

          <!-- 批量操作工具栏 -->
          <div class="batch-toolbar" v-show="selected.length > 0">
            <span class="selected-count">已选择 {{ selected.length }} 项</span>
            <el-button-group>
              <el-button size="small" @click="batchMove">
                <i class="el-icon-folder"></i> 移动到
              </el-button>
              <el-button size="small" @click="batchAddTags">
                <i class="el-icon-price-tag"></i> 添加标签
              </el-button>
              <el-button size="small" type="danger" @click="batchDelete">
                <i class="el-icon-delete"></i> 删除
              </el-button>
            </el-button-group>
          </div>

          <!-- 书签列表/网格视图 -->
          <div :class="['bookmark-container', viewMode]">
            <template v-if="viewMode === 'list'">
              <el-table
                :data="filteredBookmarks"
                @selection-change="handleSelectionChange"
                style="width: 100%">
                <el-table-column
                  type="selection"
                  width="55">
                </el-table-column>
                <el-table-column width="40">
                  <template slot-scope="scope">
                    <img :src="getFavicon(scope.row.url)" class="favicon"/>
                  </template>
                </el-table-column>
                <el-table-column prop="title" label="标题">
                  <template slot-scope="scope">
                    <a :href="scope.row.url" target="_blank">{{ scope.row.title }}</a>
                    <div class="bookmark-tags">
                      <el-tag 
                        v-for="tag in scope.row.tags"
                        :key="tag"
                        size="mini">
                        {{ tag }}
                      </el-tag>
                    </div>
                  </template>
                </el-table-column>
                <el-table-column prop="category" label="分类" width="120">
                </el-table-column>
                <el-table-column prop="addDate" label="添加时间" width="180">
                </el-table-column>
                <el-table-column width="100">
                  <template slot-scope="scope">
                    <el-dropdown trigger="click">
                      <el-button type="text">
                        <i class="el-icon-more"></i>
                      </el-button>
                      <el-dropdown-menu slot="dropdown">
                        <el-dropdown-item @click.native="editBookmark(scope.row)">
                          编辑
                        </el-dropdown-item>
                        <el-dropdown-item @click.native="moveBookmark(scope.row)">
                          移动到
                        </el-dropdown-item>
                        <el-dropdown-item @click.native="deleteBookmark(scope.row)">
                          删除
                        </el-dropdown-item>
                      </el-dropdown-menu>
                    </el-dropdown>
                  </template>
                </el-table-column>
              </el-table>
            </template>

            <template v-else>
              <el-checkbox-group v-model="selected" class="grid-view">
                <div 
                  v-for="item in filteredBookmarks"
                  :key="item.id"
                  class="grid-item">
                  <el-checkbox :label="item.id" class="grid-checkbox"></el-checkbox>
                  <div class="grid-content">
                    <img :src="getFavicon(item.url)" class="grid-favicon"/>
                    <div class="grid-title">{{ item.title }}</div>
                    <div class="grid-tags">
                      <el-tag 
                        v-for="tag in item.tags"
                        :key="tag"
                        size="mini">
                        {{ tag }}
                      </el-tag>
                    </div>
                  </div>
                </div>
              </el-checkbox-group>
            </template>
          </div>
        </el-main>
      </el-container>
    </el-container>

    <!-- 添加/编辑书签对话框 -->
    <el-dialog
      :title="dialogType === 'add' ? '添加书签' : '编辑书签'"
      :visible.sync="bookmarkDialogVisible"
      width="500px">
      <el-form :model="bookmarkForm" label-width="80px">
        <el-form-item label="标题">
          <el-input v-model="bookmarkForm.title"></el-input>
        </el-form-item>
        <el-form-item label="URL">
          <el-input v-model="bookmarkForm.url"></el-input>
        </el-form-item>
        <el-form-item label="分类">
          <el-select v-model="bookmarkForm.category" style="width: 100%">
            <el-option
              v-for="category in categories"
              :key="category.id"
              :label="category.name"
              :value="category.id">
            </el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="标签">
          <el-select
            v-model="bookmarkForm.tags"
            multiple
            filterable
            allow-create
            default-first-option
            style="width: 100%">
            <el-option
              v-for="tag in allTags"
              :key="tag"
              :label="tag"
              :value="tag">
            </el-option>
          </el-select>
        </el-form-item>
      </el-form>
      <span slot="footer">
        <el-button @click="bookmarkDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="saveBookmark">确定</el-button>
      </span>
    </el-dialog>
  </div>

  <script>
    new Vue({
      el: '#app',
      data() {
        return {
          activeCategory: 'all',
          searchQuery: '',
          sortBy: 'date',
          viewMode: 'list',
          selected: [],
          bookmarkDialogVisible: false,
          dialogType: 'add',
          bookmarkForm: {
            title: '',
            url: '',
            category: '',
            tags: []
          },
          categories: [
            { id: '1', name: '工作', count: 15 },
            { id: '2', name: '学习', count: 23 },
            { id: '3', name: '娱乐', count: 8 },
            { id: '4', name: '生活', count: 12 }
          ],
          bookmarks: [
            {
              id: '1',
              title: 'Google',
              url: 'https://www.google.com',
              category: '1',
              tags: ['搜索', '工具'],
              addDate: '2024-02-25 10:30'
            },
            {
              id: '2',
              title: 'GitHub',
              url: 'https://github.com',
              category: '2',
              tags: ['开发', '代码'],
              addDate: '2024-02-24 15:20'
            }
            // 更多书签数据...
          ],
          popularTags: [
            { name: '工具', count: 15, type: '' },
            { name: '教程', count: 12, type: 'success' },
            { name: '参考', count: 8, type: 'info' },
            { name: '娱乐', count: 6, type: 'warning' }
          ]
        }
      },
      computed: {
        totalBookmarks() {
          return this.bookmarks.length
        },
        uncategorizedCount() {
          return this.bookmarks.filter(b => !b.category).length
        },
        filteredBookmarks() {
          let result = [...this.bookmarks]
          
          // 分类过滤
          if (this.activeCategory !== 'all') {
            if (this.activeCategory === 'uncategorized') {
              result = result.filter(b => !b.category)
            } else {
              result = result.filter(b => b.category === this.activeCategory)
            }
          }

          // 搜索过滤
          if (this.searchQuery) {
            const query = this.searchQuery.toLowerCase()
            result = result.filter(b => 
              b.title.toLowerCase().includes(query) ||
              b.url.toLowerCase().includes(query) ||
              b.tags.some(tag => tag.toLowerCase().includes(query))
            )
          }

          // 排序
          result.sort((a, b) => {
            switch (this.sortBy) {
              case 'name':
                return a.title.localeCompare(b.title)
              case 'date':
                return new Date(b.addDate) - new Date(a.addDate)
              case 'visits':
                return (b.visits || 0) - (a.visits || 0)
              default:
                return 0
            }
          })

          return result
        },
        allTags() {
          const tags = new Set()
          this.bookmarks.forEach(bookmark => {
            bookmark.tags.forEach(tag => tags.add(tag))
          })
          return Array.from(tags)
        }
      },
      methods: {
        handleCategorySelect(index) {
          this.activeCategory = index
        },
        handleSelectionChange(val) {
          this.selected = val.map(item => item.id)
        },
        getFavicon(url) {
          try {
            const domain = new URL(url).hostname
            return `https://www.google.com/s2/favicons?domain=${domain}`
          } catch {
            return 'https://via.placeholder.com/16'
          }
        },
        importBookmarks() {
          // 实现导入功能
          this.$message({
            message: '导入功能开发中...',
            type: 'info'
          })
        },
        exportBookmarks() {
          // 实现导出功能
          this.$message({
            message: '导出功能开发中...',
            type: 'info'
          })
        },
        addCategory() {
          this.$prompt('请输入分类名称', '新建分类', {
            confirmButtonText: '确定',
            cancelButtonText: '取消'
          }).then(({ value }) => {
            if (value) {
              this.categories.push({
                id: Date.now().toString(),
                name: value,
                count: 0
              })
            }
          })
        },
        filterByTag(tag) {
          this.searchQuery = tag
        },
        editBookmark(bookmark) {
          this.dialogType = 'edit'
          this.bookmarkForm = { ...bookmark }
          this.bookmarkDialogVisible = true
        },
        moveBookmark(bookmark) {
          // 实现移动功能
        },
        deleteBookmark(bookmark) {
          this.$confirm('确定要删除这个书签吗?', '提示', {
            type: 'warning'
          }).then(() => {
            const index = this.bookmarks.findIndex(b => b.id === bookmark.id)
            if (index > -1) {
              this.bookmarks.splice(index, 1)
              this.$message({
                type: 'success',
                message: '删除成功'
              })
            }
          })
        },
        batchMove() {
          // 实现批量移动功能
        },
        batchAddTags() {
          // 实现批量添加标签功能
        },
        batchDelete() {
          this.$confirm(`确定要删除选中的 ${this.selected.length} 个书签吗?`, '提示', {
            type: 'warning'
          }).then(() => {
            this.bookmarks = this.bookmarks.filter(b => !this.selected.includes(b.id))
            this.selected = []
            this.$message({
              type: 'success',
              message: '批量删除成功'
            })
          })
        },
        saveBookmark() {
          if (this.dialogType === 'add') {
            this.bookmarks.push({
              id: Date.now().toString(),
              ...this.bookmarkForm,
              addDate: new Date().toLocaleString()
            })
          } else {
            const index = this.bookmarks.findIndex(b => b.id === this.bookmarkForm.id)
            if (index > -1) {
              this.bookmarks[index] = { ...this.bookmarkForm }
            }
          }
          this.bookmarkDialogVisible = false
          this.$message({
            type: 'success',
            message: this.dialogType === 'add' ? '添加成功' : '更新成功'
          })
        }
      }
    })
  </script>

  <style>
    body {
      margin: 0;
      font-family: Arial, sans-serif;
      background-color: #f5f7fa;
    }

    .bookmark-manager {
      height: 100vh;
    }

    .el-header {
      background-color: #fff;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12);
      position: relative;
      z-index: 1;
    }

    .header-content {
      display: flex;
      justify-content: space-between;
      align-items: center;
      height: 100%;
    }

    .logo {
      font-size: 18px;
      font-weight: bold;
      color: #409EFF;
    }

    .logo i {
      margin-right: 8px;
    }

    .el-aside {
      background-color: #fff;
      border-right: 1px solid #e6e6e6;
    }

    .category-header {
      padding: 15px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      border-bottom: 1px solid #e6e6e6;
    }

    .category-menu {
      border-right: none;
    }

    .item-count {
      float: right;
      color: #909399;
      font-size: 12px;
    }

    .tag-section {
      padding: 15px;
    }

    .section-header {
      margin-bottom: 10px;
      color: #606266;
    }

    .tag-cloud {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;
    }

    .toolbar {
      margin-bottom: 20px;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    .search-input {
      width: 300px;
    }

    .tool-actions {
      display: flex;
      gap: 10px;
    }

    .batch-toolbar {
      background-color: #f0f9ff;
      padding: 10px;
      border-radius: 4px;
      margin-bottom: 20px;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    .selected-count {
      color: #409EFF;
    }

    .bookmark-container {
      background-color: #fff;
      border-radius: 4px;
      padding: 20px;
    }

    .bookmark-container.grid {
      padding: 10px;
    }

    .favicon {
      width: 16px;
      height: 16px;
      vertical-align: middle;
    }

    .bookmark-tags {
      margin-top: 5px;
    }

    .grid-view {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
      gap: 20px;
    }

    .grid-item {
      position: relative;
      border: 1px solid #e6e6e6;
      border-radius: 4px;
      padding: 15px;
      transition: all 0.3s;
    }

    .grid-item:hover {
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    }

    .grid-checkbox {
      position: absolute;
      top: 10px;
      right: 10px;
    }

    .grid-content {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 10px;
    }

    .grid-favicon {
      width: 32px;
      height: 32px;
    }

    .grid-title {
      text-align: center;
      font-size: 14px;
      color: #303133;
    }

    .grid-tags {
      display: flex;
      flex-wrap: wrap;
      gap: 5px;
      justify-content: center;
    }

    /* 响应式布局 */
    @media screen and (max-width: 768px) {
      .el-aside {
        width: 100% !important;
        position: fixed;
        top: 60px;
        bottom: 0;
        z-index: 2;
        transform: translateX(-100%);
        transition: transform 0.3s;
      }

      .el-aside.show {
        transform: translateX(0);
      }

      .search-input {
        width: 100%;
      }

      .tool-actions {
        margin-top: 10px;
      }

      .toolbar {
        flex-direction: column;
      }

      .grid-view {
        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
      }
    }
  </style>
</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南北极之间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值