Преглед изворни кода

更新 disk 模块的 ui, 向 pikpak 看齐

reghao пре 1 месец
родитељ
комит
392f2dee9c
8 измењених фајлова са 1432 додато и 422 уклоњено
  1. 1 1
      src/api/admin.js
  2. 5 0
      src/api/disk.js
  3. 35 20
      src/router/disk.js
  4. 236 90
      src/views/disk/Disk.vue
  5. 565 310
      src/views/disk/DiskFile.vue
  6. 271 0
      src/views/disk/DiskMe.vue
  7. 319 0
      src/views/disk/DiskPhoto.vue
  8. 0 1
      src/views/disk/DiskShare.vue

+ 1 - 1
src/api/admin.js

@@ -14,7 +14,7 @@ const adminApi = {
   getNotifyConfigApi: '/api/admin/message/notify/webhook',
   getNotifyConfigApi: '/api/admin/message/notify/webhook',
   getAlbumsApi: '/api/admin/content/video/all',
   getAlbumsApi: '/api/admin/content/video/all',
   getArticlesApi: '/api/admin/content/video/all',
   getArticlesApi: '/api/admin/content/video/all',
-  getVideosApi: '/api/admin/content/list',
+  getVideosApi: '/api/admin/content/vod/list',
   getSiteNoticeApi: '/api/admin/content/site_notice'
   getSiteNoticeApi: '/api/admin/content/site_notice'
 }
 }
 
 

+ 5 - 0
src/api/disk.js

@@ -23,6 +23,7 @@ const diskApi = {
   submitUserActivityApi: '/api/disk/cam/activity',
   submitUserActivityApi: '/api/disk/cam/activity',
   getCamRecordByMonthAPi: '/api/disk/cam/record/month',
   getCamRecordByMonthAPi: '/api/disk/cam/record/month',
   getRecordUrlAPi: '/api/disk/cam/record/url',
   getRecordUrlAPi: '/api/disk/cam/record/url',
+  getAlbumApi: '/api/disk/album',
   getDiskChannelInfoApi: '/api/file/oss/serverinfo/file'
   getDiskChannelInfoApi: '/api/file/oss/serverinfo/file'
 }
 }
 
 
@@ -54,6 +55,10 @@ export function getDiskFile(query) {
   return get(diskApi.diskFileApi, query)
   return get(diskApi.diskFileApi, query)
 }
 }
 
 
+export function getPhotoItems(queryParams) {
+  return get(diskApi.getAlbumApi + '/items', queryParams)
+}
+
 export function getFileDetail(fileId) {
 export function getFileDetail(fileId) {
   return get(diskApi.filePreviewApi + '?fileId=' + fileId)
   return get(diskApi.filePreviewApi + '?fileId=' + fileId)
 }
 }

+ 35 - 20
src/router/disk.js

@@ -1,5 +1,7 @@
 const Disk = () => import('views/disk/Disk')
 const Disk = () => import('views/disk/Disk')
 const DiskAlbumIndex = () => import('views/disk/DiskAlbumIndex')
 const DiskAlbumIndex = () => import('views/disk/DiskAlbumIndex')
+const DiskPhoto = () => import('views/disk/DiskPhoto')
+const DiskMe = () => import('views/disk/DiskMe')
 const DiskAlbumView = () => import('views/disk/DiskAlbumView')
 const DiskAlbumView = () => import('views/disk/DiskAlbumView')
 const DiskAlbumEdit = () => import('views/disk/DiskAlbumEdit')
 const DiskAlbumEdit = () => import('views/disk/DiskAlbumEdit')
 const DiskFile = () => import('views/disk/DiskFile')
 const DiskFile = () => import('views/disk/DiskFile')
@@ -12,48 +14,61 @@ export default {
   path: '/disk',
   path: '/disk',
   name: 'Disk',
   name: 'Disk',
   component: Disk,
   component: Disk,
+  redirect: '/disk/file',
   meta: { needAuth: true },
   meta: { needAuth: true },
   children: [
   children: [
     {
     {
-      path: '',
+      path: '/disk/file',
       name: 'DiskFile',
       name: 'DiskFile',
       component: DiskFile,
       component: DiskFile,
-      meta: { needAuth: true }
+      meta: { title: '我的文件', needAuth: true }
     },
     },
     {
     {
-      path: '/disk/album',
-      name: 'DiskAlbumIndex',
-      component: DiskAlbumIndex,
-      meta: { needAuth: true }
+      path: '/disk/photo',
+      name: 'DiskPhoto',
+      component: DiskPhoto,
+      meta: { title: '我的相册', needAuth: true }
     },
     },
     {
     {
-      path: '/disk/album/edit/:albumId',
-      name: 'DiskAlbumEdit',
-      component: DiskAlbumEdit,
-      meta: { needAuth: true }
-    },
-    {
-      path: '/disk/album/:albumId',
-      name: 'DiskAlbumView',
-      component: DiskAlbumView,
-      meta: { needAuth: true }
+      path: '/disk/cam',
+      name: 'CamList',
+      component: CamList,
+      meta: { title: '我的监控', needAuth: true }
     },
     },
     {
     {
       path: '/disk/share',
       path: '/disk/share',
       name: 'DiskShare',
       name: 'DiskShare',
       component: DiskShare,
       component: DiskShare,
-      meta: { needAuth: true }
+      meta: { title: '我的分享', needAuth: true }
+    },
+    {
+      path: '/disk/me',
+      name: 'DiskMe',
+      component: DiskMe,
+      meta: { title: '我的', needAuth: true }
     },
     },
     {
     {
       path: '/disk/recycle',
       path: '/disk/recycle',
       name: 'DiskRecycle',
       name: 'DiskRecycle',
       component: DiskRecycle,
       component: DiskRecycle,
+      meta: { title: '回收站', needAuth: true }
+    },
+    {
+      path: '/disk/album',
+      name: 'DiskAlbumIndex',
+      component: DiskAlbumIndex,
       meta: { needAuth: true }
       meta: { needAuth: true }
     },
     },
     {
     {
-      path: '/disk/cam',
-      name: 'CamList',
-      component: CamList,
+      path: '/disk/album/edit/:albumId',
+      name: 'DiskAlbumEdit',
+      component: DiskAlbumEdit,
+      meta: { needAuth: true }
+    },
+    {
+      path: '/disk/album/:albumId',
+      name: 'DiskAlbumView',
+      component: DiskAlbumView,
       meta: { needAuth: true }
       meta: { needAuth: true }
     },
     },
     {
     {

+ 236 - 90
src/views/disk/Disk.vue

@@ -1,48 +1,103 @@
 <template>
 <template>
-  <el-container class="disk-container">
-    <el-header height="auto" class="header-wrapper">
-      <el-row type="flex" align="middle" class="nav-row">
-        <el-col :xs="6" :sm="4" :md="2">
-          <div class="logo-box">
-            <router-link to="/disk" class="logo-link">
-              <img src="@/assets/img/logo.png" class="logo" alt="logo">
-              <span class="logo-text">Disk</span>
-            </router-link>
+  <el-container class="pikpak-layout">
+    <el-aside width="240px" class="aside-wrapper">
+      <div class="aside-inner">
+        <div class="logo-box">
+          <router-link to="/disk" class="logo-link">
+            <img src="@/assets/img/logo.png" class="logo" alt="logo">
+            <span class="logo-text">disk</span>
+          </router-link>
+        </div>
+
+        <el-menu
+          :default-active="$route.path"
+          router
+          class="side-menu"
+        >
+          <el-menu-item index="/disk/file">
+            <i class="el-icon-folder" />
+            <span slot="title">我的文件</span>
+          </el-menu-item>
+          <el-menu-item index="/disk/photo">
+            <i class="el-icon-picture-outline" />
+            <span slot="title">我的相册</span>
+          </el-menu-item>
+          <el-menu-item index="/disk/cam">
+            <i class="el-icon-video-camera" />
+            <span slot="title">我的监控</span>
+          </el-menu-item>
+          <el-menu-item index="/disk/share">
+            <i class="el-icon-share" />
+            <span slot="title">我的分享</span>
+          </el-menu-item>
+          <el-menu-item index="/disk/me">
+            <i class="el-icon-user" />
+            <span slot="title">我的</span>
+          </el-menu-item>
+        </el-menu>
+
+        <div class="aside-footer">
+          <div class="quota-info">
+            <div class="quota-text">已用 1.2GB / 6TB</div>
+            <el-progress :percentage="20" :show-text="false" :stroke-width="4" />
           </div>
           </div>
-        </el-col>
-
-        <el-col :xs="14" :sm="16" :md="20">
-          <el-menu
-            :default-active="$route.path"
-            router
-            mode="horizontal"
-            class="top-menu"
-          >
-            <el-menu-item index="/disk/album">相册</el-menu-item>
-            <el-menu-item index="/disk/share">分享</el-menu-item>
-            <el-menu-item index="/disk/cam">摄像头</el-menu-item>
-          </el-menu>
-        </el-col>
-
-        <el-col :xs="4" :sm="4" :md="2" class="user-col">
-          <el-dropdown v-if="user" trigger="click">
+          <el-dropdown trigger="click" class="user-dropdown">
             <div class="avatar-wrapper">
             <div class="avatar-wrapper">
-              <img :src="user.avatarUrl" class="user-avatar" alt="avatar">
+              <img :src="user ? user.avatarUrl : ''" class="user-avatar" alt="">
+              <span class="username">{{ user ? user.name : '未登录' }}</span>
+              <i class="el-icon-more" />
             </div>
             </div>
             <el-dropdown-menu slot="dropdown">
             <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item icon="el-icon-setting" @click.native="showSettingDialog = true">设置</el-dropdown-item>
               <el-dropdown-item icon="el-icon-back" @click.native="goToLogout">登出</el-dropdown-item>
               <el-dropdown-item icon="el-icon-back" @click.native="goToLogout">登出</el-dropdown-item>
             </el-dropdown-menu>
             </el-dropdown-menu>
           </el-dropdown>
           </el-dropdown>
-          <span v-else class="login-btn">登录</span>
-        </el-col>
-      </el-row>
-    </el-header>
+        </div>
+      </div>
+    </el-aside>
 
 
     <el-main class="main-content">
     <el-main class="main-content">
-      <transition name="fade-transform" mode="out-in">
-        <router-view :key="$route.fullPath" />
-      </transition>
+      <div class="content-card">
+        <transition name="page-fade" mode="out-in">
+          <router-view :key="$route.fullPath" />
+        </transition>
+      </div>
     </el-main>
     </el-main>
+
+    <el-dialog
+      title="系统设置"
+      :visible.sync="showSettingDialog"
+      width="500px"
+      append-to-body
+      custom-class="setting-dialog"
+    >
+      <div class="setting-content">
+        <el-form label-position="top">
+          <el-form-item label="外观设置">
+            <el-radio-group v-model="settingForm.theme" size="small">
+              <el-radio-button label="light">浅色模式</el-radio-button>
+              <el-radio-button label="dark">深色模式</el-radio-button>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="默认视图">
+            <el-select v-model="settingForm.defaultView" placeholder="请选择" style="width: 100%">
+              <el-option label="列表视图" value="list" />
+              <el-option label="网格视图" value="grid" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="语言">
+            <el-select v-model="settingForm.lang" style="width: 100%">
+              <el-option label="简体中文" value="zh" />
+              <el-option label="English" value="en" />
+            </el-select>
+          </el-form-item>
+        </el-form>
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="showSettingDialog = false">取 消</el-button>
+        <el-button type="primary" @click="saveSettings">保 存</el-button>
+      </span>
+    </el-dialog>
   </el-container>
   </el-container>
 </template>
 </template>
 
 
@@ -55,112 +110,203 @@ export default {
   mixins: [userMixin],
   mixins: [userMixin],
   data() {
   data() {
     return {
     return {
-      user: null
+      user: null,
+      showSettingDialog: false,
+      settingForm: {
+        theme: 'light',
+        defaultView: 'list',
+        lang: 'zh'
+      }
     }
     }
   },
   },
-  // 注意:不再建议监听 $route 执行 router.go(),这会强制刷新整个页面。
-  // 通过在 router-view 绑定 :key="$route.fullPath" 即可实现组件更新。
   created() {
   created() {
-    document.title = '我的云端'
     this.user = getAuthedUser()
     this.user = getAuthedUser()
+  },
+  methods: {
+    saveSettings() {
+      this.$message.success('设置已保存')
+      this.showSettingDialog = false
+    }
   }
   }
 }
 }
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
-.disk-container {
-  min-height: 100vh;
-  background-color: #f5f7fa;
+/* 整体布局背景 */
+.pikpak-layout {
+  height: 100vh;
+  background-color: #f8f9fa; /* 极浅灰背景 */
 }
 }
 
 
-.header-wrapper {
+/* 侧边栏样式 */
+.aside-wrapper {
   background-color: #fff;
   background-color: #fff;
-  box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05);
-  padding: 0 15px;
-  z-index: 100;
+  border-right: 1px solid #f0f0f0;
+  padding: 20px 0;
 }
 }
 
 
-.nav-row {
-  height: 60px;
+.aside-inner {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
 }
 }
 
 
-.logo-link {
+.logo-box {
+  padding: 0 24px 30px;
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
-  text-decoration: none;
-  color: #007bff;
-  font-weight: bold;
 }
 }
 
 
 .logo {
 .logo {
-  width: 28px;
-  height: 28px;
-  margin-right: 8px;
+  width: 32px;
+  height: 32px;
+  border-radius: 8px;
 }
 }
 
 
-.top-menu {
-  border-bottom: none !important;
+.logo-text {
+  margin-left: 12px;
+  font-size: 20px;
+  font-weight: 800;
+  color: #1a1a1a;
+  letter-spacing: -0.5px;
 }
 }
 
 
-/* 兼容移动端菜单横向滑动 */
-.el-menu--horizontal {
-  display: flex;
-  overflow-x: auto;
-  scrollbar-width: none; /* Firefox */
+.add-btn {
+  width: 100%;
+  border-radius: 12px; /* 大圆角 */
+  padding: 12px;
+  font-weight: bold;
+  font-size: 15px;
+  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
 }
 }
-.el-menu--horizontal::-webkit-scrollbar {
-  display: none; /* Chrome/Safari */
+
+/* 菜单样式微调 */
+.side-menu {
+  border-right: none !important;
+  flex-grow: 1;
 }
 }
 
 
-.top-menu .el-menu-item {
-  font-weight: 500;
-  padding: 0 15px;
+.side-menu .el-menu-item {
+  height: 48px;
+  line-height: 48px;
+  margin: 4px 12px;
+  border-radius: 10px;
+  color: #5f6368;
 }
 }
 
 
-.user-col {
-  display: flex;
-  justify-content: flex-end;
-  align-items: center;
+.side-menu .el-menu-item.is-active {
+  background-color: #e8f0fe !important;
+  color: #1a73e8;
+  font-weight: bold;
+}
+
+.side-menu .el-menu-item i {
+  font-size: 20px;
+  margin-right: 12px;
+}
+
+/* 侧边栏底部 */
+.aside-footer {
+  padding: 20px 16px;
+  border-top: 1px solid #f0f0f0;
+}
+
+.quota-info {
+  margin-bottom: 20px;
+  padding: 0 8px;
+}
+
+.quota-text {
+  font-size: 12px;
+  color: #909399;
+  margin-bottom: 8px;
 }
 }
 
 
 .avatar-wrapper {
 .avatar-wrapper {
-  cursor: pointer;
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
+  padding: 8px;
+  border-radius: 8px;
+  cursor: pointer;
+  transition: background 0.2s;
+}
+
+.avatar-wrapper:hover {
+  background: #f5f5f5;
 }
 }
 
 
 .user-avatar {
 .user-avatar {
-  width: 35px;
-  height: 35px;
+  width: 32px;
+  height: 32px;
   border-radius: 50%;
   border-radius: 50%;
-  border: 1px solid #ebeef5;
+  margin-right: 10px;
+}
+
+.username {
+  flex-grow: 1;
+  font-size: 14px;
+  color: #333;
 }
 }
 
 
+/* 内容区卡片化 */
 .main-content {
 .main-content {
-  padding: 10px; /* 移动端减少边距 */
+  padding: 20px;
+  display: flex;
 }
 }
 
 
-/* 页面切换动画 */
-.fade-transform-enter-active,
-.fade-transform-leave-active {
-  transition: all 0.3s;
+.content-card {
+  background: #fff;
+  width: 100%;
+  border-radius: 20px; /* 大圆角内容区 */
+  box-shadow: 0 2px 10px rgba(0,0,0,0.02);
+  padding: 24px;
+  overflow-y: auto;
 }
 }
-.fade-transform-enter {
-  opacity: 0;
-  transform: translateX(-10px);
+
+/* 动画效果 */
+.page-fade-enter-active, .page-fade-leave-active {
+  transition: opacity 0.2s ease;
 }
 }
-.fade-transform-leave-to {
+.page-fade-enter, .page-fade-leave-to {
   opacity: 0;
   opacity: 0;
-  transform: translateX(10px);
 }
 }
 
 
+/* 移动端适配 */
 @media screen and (max-width: 768px) {
 @media screen and (max-width: 768px) {
-  .logo-text {
-    display: none; /* 手机端隐藏 Disk 文字,只留图标 */
-  }
-  .el-menu-item {
-    padding: 0 10px !important;
-    font-size: 13px;
+  .aside-wrapper {
+    display: none; /* 移动端通常改为抽屉或底部导航 */
   }
   }
 }
 }
+
+.logo-link {
+  display: flex;
+  align-items: center;
+  text-decoration: none; /* 移除 router-link 默认的下划线 */
+  color: inherit;       /* 继承文字颜色 */
+  cursor: pointer;
+}
+
+/* 弹窗圆角美化 */
+::v-deep .setting-dialog {
+  border-radius: 16px;
+}
+
+::v-deep .setting-dialog .el-dialog__header {
+  padding: 20px 24px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+::v-deep .setting-dialog .el-dialog__title {
+  font-weight: bold;
+  font-size: 16px;
+}
+
+.setting-content {
+  padding: 10px 0;
+}
+
+/* 调整表单间距 */
+.setting-content .el-form-item {
+  margin-bottom: 15px;
+}
 </style>
 </style>

+ 565 - 310
src/views/disk/DiskFile.vue

@@ -1,282 +1,205 @@
 <template>
 <template>
-  <div style="padding-right: 5px">
-    <el-row style="padding: 5px">
-      <el-button size="small" type="primary" icon="el-icon-upload" round @click="onClickUpload">上传</el-button>
-      <el-button size="small" type="primary" icon="el-icon-folder-add" round @click="onCreateFolder">新建文件夹</el-button>
-      <el-input
-        v-model="inputData"
-        placeholder="搜索我的文件"
-        size="small"
-        style="width: 30%; padding-left: 5px"
-        clearable
-        @keyup.enter.native="onSearchFile"
-      >
-        <el-button slot="append" icon="el-icon-search" @click="onSearchFile" />
-      </el-input>
-      <el-divider />
-      <el-breadcrumb separator-class="el-icon-arrow-right">
-        <el-breadcrumb-item
-          v-for="(item, index) in pathList"
-          :key="index"
-          style="padding: 1px"
-          :replace="true"
-          :to="{ path: item.path }"
-        >
-          <span style="color: #0a84ff">
+  <div class="file-list-container">
+    <div class="toolbar">
+      <div class="toolbar-left">
+        <el-breadcrumb separator-class="el-icon-arrow-right">
+          <el-breadcrumb-item
+            v-for="(item, index) in pathList"
+            :key="index"
+            :to="{ path: item.path }"
+          >
             {{ item.name }}
             {{ item.name }}
-          </span>
-        </el-breadcrumb-item>
-      </el-breadcrumb>
-      <el-divider />
+          </el-breadcrumb-item>
+        </el-breadcrumb>
+      </div>
+
+      <div class="toolbar-right">
+        <el-input
+          v-model="inputData"
+          placeholder="搜索我的文件"
+          prefix-icon="el-icon-search"
+          size="medium"
+          class="search-input"
+          clearable
+          @keyup.enter.native="onSearchFile"
+        />
+        <el-button type="primary" icon="el-icon-upload" class="action-btn" @click="onClickUpload">上传</el-button>
+        <el-button icon="el-icon-folder-add" class="action-btn" @click="onCreateFolder">新建</el-button>
+      </div>
+    </div>
+
+    <transition name="el-zoom-in-bottom">
+      <div v-if="selectedTable.length > 0" class="selection-bar">
+        <span class="selection-count">已选择 {{ selectedTable.length }} 项</span>
+        <el-divider direction="vertical" />
+        <el-button type="text" icon="el-icon-collection" @click="addToAlbum">保存到合集</el-button>
+        <el-button type="text" icon="el-icon-rank" @click="moveToFolder">移动</el-button>
+        <el-button type="text" icon="el-icon-delete" class="delete-text" @click="onDeleteFile">删除</el-button>
+        <el-button type="text" @click="toggleSelection()">取消</el-button>
+      </div>
+    </transition>
+
+    <div
+      v-infinite-scroll="loadMore"
+      class="table-wrapper"
+      infinite-scroll-disabled="infiniteDisabled"
+      infinite-scroll-distance="20"
+    >
       <el-table
       <el-table
         ref="multipleTable"
         ref="multipleTable"
         :data="dataList"
         :data="dataList"
-        :show-header="true"
-        style="width: 100%"
+        class="pikpak-table"
         @selection-change="handleTableSectionChange"
         @selection-change="handleTableSectionChange"
       >
       >
-        <el-table-column
-          align="center"
-          type="selection"
-        />
-        <el-table-column
-          prop="filename"
-          label="文件名"
-        >
+        <el-table-column type="selection" width="55" />
+
+        <el-table-column label="文件名" min-width="400">
           <template slot-scope="scope">
           <template slot-scope="scope">
-            <span v-if="scope.row.fileType === 1000" class="el-icon-folder">
-              <a style="text-decoration-line: none" href="javascript:void(0)" @click="onClickFilename(scope.row)">{{ scope.row.filename }}</a>
-            </span>
-            <span v-else-if="scope.row.fileType === 1001" class="el-icon-picture">
-              <a style="text-decoration-line: none" href="javascript:void(0)" @click="onClickFilename(scope.row)">{{ scope.row.filename }}</a>
-            </span>
-            <span v-else-if="scope.row.fileType === 1002" class="el-icon-film">
-              <a style="text-decoration-line: none" href="javascript:void(0)" @click="onClickFilename(scope.row)">{{ scope.row.filename }}</a>
-            </span>
-            <span v-else-if="scope.row.fileType === 1003" class="el-icon-headset">
-              <a style="text-decoration-line: none" href="javascript:void(0)" @click="onClickFilename(scope.row)">{{ scope.row.filename }}</a>
-            </span>
-            <span v-else-if="scope.row.fileType === 1004" class="el-icon-document">
-              <a style="text-decoration-line: none" href="javascript:void(0)" @click="onClickFilename(scope.row)">{{ scope.row.filename }}</a>
-            </span>
-            <span v-else-if="scope.row.fileType === 1005" class="el-icon-files">
-              <a style="text-decoration-line: none" href="javascript:void(0)" @click="onClickFilename(scope.row)">{{ scope.row.filename }}</a>
-            </span>
-            <span v-else-if="scope.row.fileType === 1006" class="el-icon-files">
-              <a style="text-decoration-line: none" href="javascript:void(0)" @click="onClickFilename(scope.row)">{{ scope.row.filename }}</a>
-            </span>
-            <span v-else class="el-icon-files">
-              <a style="text-decoration-line: none" href="javascript:void(0)" @click="onClickFilename(scope.row)">{{ scope.row.filename }}</a>
-            </span>
+            <div class="file-name-cell" @click="onClickFilename(scope.row)">
+              <i :class="getFileIcon(scope.row.fileType)" :style="getIconStyle(scope.row.fileType)" />
+              <span class="file-text">{{ scope.row.filename }}</span>
+            </div>
           </template>
           </template>
         </el-table-column>
         </el-table-column>
-        <el-table-column
-          prop="size"
-          label="大小"
-        />
-        <el-table-column
-          prop="fileTypeStr"
-          label="类型"
-        />
-        <el-table-column
-          prop="updateTime"
-          label="修改时间"
-        />
+
+        <el-table-column prop="size" label="大小" width="120" />
+        <el-table-column prop="updateTime" label="修改时间" width="180" align="right" />
       </el-table>
       </el-table>
-      <div style="margin-top: 20px">
-        <el-button v-if="selectedTable.length !== 0" type="primary" size="small" @click="addToAlbum">添加 {{ selectedTable.length }} 到合集</el-button>
-        <el-button v-if="selectedTable.length !== 0" type="primary" size="small" @click="moveToFolder">移动 {{ selectedTable.length }} 个文件</el-button>
-        <el-button v-if="selectedTable.length !== 0" type="primary" size="small" @click="onDeleteFile">删除 {{ selectedTable.length }} 个文件/文件夹</el-button>
+
+      <div class="load-status">
+        <p v-if="loading"><i class="el-icon-loading" /> 加载中...</p>
+        <p v-if="noMore && dataList.length > 0">没有更多文件了</p>
+        <el-empty v-if="dataList.length === 0 && !loading" description="暂无文件" />
       </div>
       </div>
-      <el-pagination
-        background
-        hide-on-single-page
-        :small="screenWidth <= 768"
-        layout="prev, pager, next"
-        :page-size="pageSize"
-        :current-page="currentPage"
-        :total="totalSize"
-        @current-change="handleCurrentChange"
-        @prev-click="handleCurrentChange"
-        @next-click="handleCurrentChange"
-      />
-    </el-row>
+    </div>
 
 
     <el-dialog
     <el-dialog
       :visible.sync="showPreviewDialog"
       :visible.sync="showPreviewDialog"
       :before-close="handlePreviewClose"
       :before-close="handlePreviewClose"
-      width="100%"
-      center
+      width="70%"
+      top="5vh"
+      custom-class="pikpak-preview-dialog"
+      append-to-body
     >
     >
-      <div v-if="fileDetail !== null">
-        <el-row style="padding: 5px">
-          <span style="color: #0a84ff">{{ fileDetail.filename }}</span>
-        </el-row>
-        <el-row style="padding: 5px">
-          <el-col v-if="fileType === 1001" :md="12">
-            <div>
-              <el-image
-                lazy
-                fit="cover"
-                class="coverImg"
-                :src="fileDetail.url"
-              />
-            </div>
-          </el-col>
-          <el-col v-else-if="fileType === 1002" :md="12">
-            <video
-              :src="fileDetail.url"
-              class="video"
-              width="100%"
-              controls
-              autoplay
-              loop
-            />
-          </el-col>
-          <el-col v-else-if="fileType === 1003" :md="12">
-            <audio
-              :src="fileDetail.url"
-              controls
-              autoplay
-              class="audio"
-            />
-          </el-col>
-          <el-col v-else-if="fileType === 1004" :md="12">
-            文档
-          </el-col>
-          <el-col v-else-if="fileType === 1005" :md="12">
-            文件
-          </el-col>
-          <el-col v-else-if="fileType === 1006" :md="12">
-            <iframe :src="getPdfUrl(fileDetail.url)" width="100%" height="480px" />
-          </el-col>
-          <el-col v-else :md="12">
-            未知文件类型
-          </el-col>
-        </el-row>
+      <div slot="title" class="preview-header">
+        <i :class="getFileIcon(fileType)" :style="getIconStyle(fileType)" />
+        <span class="preview-filename">{{ fileDetail ? fileDetail.filename : '预览' }}</span>
       </div>
       </div>
-    </el-dialog>
-    <el-dialog
-      :visible.sync="showCreateFolderDialog"
-      width="100%"
-      center
-    >
-      <div>
-        <el-form ref="createFolderForm" :model="createFolderForm">
-          <el-form-item label="新文件夹名" label-width="120px" prop="title">
-            <el-input
-              v-model="createFolderForm.folderName"
-              style="margin-left: 5px"
-              clearable
-            />
-          </el-form-item>
-          <el-button
-            type="primary"
-            plain
-            size="small"
-            icon="el-icon-plus"
-            style="margin-left: 10px"
-            @click="createFolder"
-          >
-            创建文件夹
-          </el-button>
-        </el-form>
+
+      <div v-if="fileDetail" class="preview-body">
+        <template v-if="fileType === 1001">
+          <el-image
+            :src="fileDetail.url"
+            fit="contain"
+            class="preview-img"
+            :preview-src-list="[fileDetail.url]"
+          />
+        </template>
+
+        <template v-else-if="fileType === 1002">
+          <video
+            ref="videoPlayer"
+            :src="fileDetail.url"
+            class="preview-video"
+            controls
+            autoplay
+          />
+        </template>
+
+        <template v-else-if="fileType === 1003">
+          <div class="audio-container">
+            <i class="el-icon-headset audio-icon" />
+            <audio :src="fileDetail.url" controls autoplay class="preview-audio" />
+          </div>
+        </template>
+
+        <template v-else-if="fileType === 1006">
+          <iframe :src="getPdfUrl(fileDetail.url)" width="100%" height="600px" frameborder="0" />
+        </template>
+
+        <template v-else>
+          <el-empty description="该文件格式暂不支持直接预览">
+            <el-button type="primary" plain @click="handleDownload(fileDetail)">下载到本地查看</el-button>
+          </el-empty>
+        </template>
+      </div>
+
+      <div slot="footer" class="preview-footer">
+        <div class="file-info">
+          <span>大小: {{ fileDetail ? fileDetail.size : '-' }}</span>
+          <el-divider direction="vertical" />
+          <span>修改时间: {{ fileDetail ? fileDetail.updateTime : '-' }}</span>
+        </div>
+        <el-button type="primary" round icon="el-icon-download" @click="handleDownload(fileDetail)">下载文件</el-button>
       </div>
       </div>
     </el-dialog>
     </el-dialog>
+
     <el-dialog
     <el-dialog
+      title="上传文件"
       :visible.sync="showUploadDialog"
       :visible.sync="showUploadDialog"
+      width="560px"
       :before-close="handleUploadClose"
       :before-close="handleUploadClose"
-      :close-on-click-modal="false"
-      width="100%"
-      center
+      append-to-body
+      custom-class="pikpak-upload-dialog"
     >
     >
-      <div>
-        <uploader
-          v-if="options !== null"
-          style="padding: 5px;"
-          :options="options"
-          :auto-start="true"
-          @file-added="onFileAdded"
-          @file-success="onFileSuccess"
-          @file-progress="onFileProgress"
-          @file-error="onFileError"
-        >
-          <uploader-unsupport />
-          <uploader-drop>
-            <p>拖动文件到此处或</p>
-            <uploader-btn :attrs="attrs">选择文件</uploader-btn>
-          </uploader-drop>
-          <uploader-list />
-        </uploader>
-        <el-button
-          style="padding: 5px;"
-          type="primary"
-          @click="onUploadComplete"
-        >完成上传</el-button>
-      </div>
+      <uploader
+        v-if="options"
+        :options="options"
+        :auto-start="true"
+        class="uploader-app"
+        @file-added="onFileAdded"
+        @file-success="onFileSuccess"
+        @file-progress="onFileProgress"
+        @file-error="onFileError"
+        @complete="onUploadComplete"
+      >
+        <uploader-unsupport />
+
+        <uploader-drop class="upload-drop-zone">
+          <div class="drop-content">
+            <i class="el-icon-upload" />
+            <p>将文件拖到此处,或 <uploader-btn :attrs="attrs" class="select-btn">点击上传</uploader-btn></p>
+            <span class="upload-tip">支持大文件秒传及断点续传</span>
+          </div>
+        </uploader-drop>
+
+        <uploader-list class="upload-list-view" />
+      </uploader>
     </el-dialog>
     </el-dialog>
+
     <el-dialog
     <el-dialog
-      :visible.sync="showAddToAlbumUploadDialog"
-      width="50%"
-      center
+      title="新建文件夹"
+      :visible.sync="showCreateFolderDialog"
+      width="400px"
+      append-to-body
+      custom-class="pikpak-dialog"
     >
     >
-      <el-table
-        :data="albumList"
-        :show-header="true"
-        style="width: 100%"
+      <el-form
+        ref="folderForm"
+        :model="createFolderForm"
+        @submit.native.prevent="createFolder"
       >
       >
-        <el-table-column
-          prop="albumName"
-          label="合集名"
-        />
-        <el-table-column
-          prop="fileType"
-          label="合集类型"
+        <el-form-item
+          prop="folderName"
+          :rules="[{ required: true, message: '请输入文件夹名称', trigger: 'blur' }]"
         >
         >
-          <template slot-scope="scope">
-            <span v-if="scope.row.fileType === 1001" class="el-icon-picture">
-              图片
-            </span>
-            <span v-else-if="scope.row.fileType === 1002" class="el-icon-film">
-              视频
-            </span>
-            <span v-else-if="scope.row.fileType === 1003" class="el-icon-headset">
-              音频
-            </span>
-            <span v-else class="el-icon-files">
-              图片
-            </span>
-          </template>
-        </el-table-column>
-        <el-table-column label="操作">
-          <template slot-scope="scope">
-            <el-button
-              size="mini"
-              @click="onAddToAlbum(scope.row)"
-            >选择</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-    </el-dialog>
-    <el-dialog
-      :visible.sync="showMoveToFolderDialog"
-      width="50%"
-      center
-    >
-      <div style="padding: 5px;">
-        <el-card style="padding: 5px;">
-          <div slot="header" class="clearfix">
-            <span>目录树</span>
-          </div>
-          <div class="text item">
-            <el-tree :data="folderTree" :props="defaultProps" @node-click="handleNodeClick" />
-          </div>
-        </el-card>
+          <el-input
+            v-model="createFolderForm.folderName"
+            placeholder="文件夹名称"
+            prefix-icon="el-icon-folder"
+            clearable
+            autofocus
+          />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button round @click="showCreateFolderDialog = false">取 消</el-button>
         <el-button
         <el-button
-          style="padding: 5px;"
-          size="mini"
-          @click="onMoveToFolder"
-        >确定</el-button>
+          type="primary"
+          :loading="loading"
+          round
+          @click="createFolder"
+        >确 定</el-button>
       </div>
       </div>
     </el-dialog>
     </el-dialog>
   </div>
   </div>
@@ -314,6 +237,8 @@ export default {
       fileType: 0,
       fileType: 0,
       videoProp: null,
       videoProp: null,
       pathList: [],
       pathList: [],
+      loading: false,
+      noMore: false,
       queryForm: {
       queryForm: {
         pn: 1,
         pn: 1,
         path: '/',
         path: '/',
@@ -353,6 +278,11 @@ export default {
       }
       }
     }
     }
   },
   },
+  computed: {
+    infiniteDisabled() {
+      return this.loading || this.noMore
+    }
+  },
   created() {
   created() {
     const pn = this.$route.query.pn
     const pn = this.$route.query.pn
     if (pn !== undefined) {
     if (pn !== undefined) {
@@ -363,10 +293,83 @@ export default {
     if (path !== undefined) {
     if (path !== undefined) {
       this.queryForm.path = path
       this.queryForm.path = path
     }
     }
-    document.title = '所有文件'
-    this.getData()
   },
   },
   methods: {
   methods: {
+    loadMore() {
+      if (this.infiniteDisabled) return
+      this.loading = true
+
+      getDiskFile(this.queryForm).then(resp => {
+        if (resp.code === 0) {
+          const { list, hasNext } = resp.data.pageList
+
+          // 追加数据而不是覆盖
+          this.dataList = [...this.dataList, ...list]
+
+          // 更新路径列表(仅在第一次或路径改变时)
+          if (this.queryForm.pn === 1) {
+            this.updatePathInfo(resp.data)
+          }
+
+          // 判断是否还有更多
+          if (!hasNext) {
+            this.noMore = true
+          } else {
+            this.queryForm.pn++
+          }
+        }
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    updatePathInfo(respData) {
+      const { namePathList } = respData
+      this.currentPid = respData.currentPid
+
+      this.pathList = []
+      if (!namePathList || namePathList.length === 0) {
+        this.pathList.push({ path: '/disk', name: '全部文件' })
+      } else {
+        // 这里的逻辑要根据你的后端返回格式调整
+        namePathList.forEach(item => {
+          this.pathList.push({
+            path: `/disk?path=${item.path}`,
+            name: item.filename
+          })
+        })
+      }
+    },
+    // 切换目录时重置状态
+    changeDirectory(newPath) {
+      this.queryForm.path = newPath
+      this.queryForm.pn = 1
+      this.dataList = []
+      this.noMore = false
+      this.loadMore()
+    },
+
+    // 动态图标映射
+    getFileIcon(type) {
+      const iconMap = {
+        1000: 'el-icon-folder-opened',
+        1001: 'el-icon-picture-outline',
+        1002: 'el-icon-video-play',
+        1003: 'el-icon-headset',
+        1004: 'el-icon-document',
+        1005: 'el-icon-files'
+      }
+      return iconMap[type] || 'el-icon-files'
+    },
+
+    getIconStyle(type) {
+      const colorMap = {
+        1000: '#ffca28', // 文件夹黄
+        1001: '#4caf50', // 图片绿
+        1002: '#f44336', // 视频红
+        1003: '#9c27b0' // 音频紫
+      }
+      return { color: colorMap[type] || '#909399', fontSize: '22px', marginRight: '12px' }
+    },
     // ****************************************************************************************************************
     // ****************************************************************************************************************
     onFileAdded(file) {
     onFileAdded(file) {
       if (file.file.size > 1024 * 1024 * 1024 * 20) {
       if (file.file.size > 1024 * 1024 * 1024 * 20) {
@@ -405,13 +408,24 @@ export default {
     onFileError(rootFile, file, response, chunk) {
     onFileError(rootFile, file, response, chunk) {
       this.$message.error('文件上传错误')
       this.$message.error('文件上传错误')
     },
     },
+    // 上传全部完成后,刷新当前目录列表
     onUploadComplete() {
     onUploadComplete() {
-      this.showUploadDialog = false
+      this.$message.success('所有文件处理完成')
+      // 延迟一秒刷新,给后端处理索引留出时间
+      setTimeout(() => {
+        this.changeDirectory(this.queryForm.path)
+      }, 1000)
     },
     },
+
+    // 修改原来的 handleUploadClose
     handleUploadClose() {
     handleUploadClose() {
       this.showUploadDialog = false
       this.showUploadDialog = false
+      // 可以在这里提示:关闭弹窗不会停止后台上传(取决于 simple-uploader 配置)
     },
     },
     onClickUpload() {
     onClickUpload() {
+      // 确保上传到当前正在浏览的目录
+      this.uploadForm.pid = this.currentPid
+
       getDiskChannelInfo().then(resp => {
       getDiskChannelInfo().then(resp => {
         if (resp.code === 0) {
         if (resp.code === 0) {
           const respData = resp.data
           const respData = resp.data
@@ -454,7 +468,14 @@ export default {
       })
       })
     },
     },
     // ****************************************************************************************************************
     // ****************************************************************************************************************
+    handleDownload(file) {
+      if (!file || !file.url) return
+      window.open(file.url, '_blank')
+    },
     handlePreviewClose() {
     handlePreviewClose() {
+      if (this.$refs.videoPlayer) {
+        this.$refs.videoPlayer.pause()
+      }
       this.showPreviewDialog = false
       this.showPreviewDialog = false
       this.fileDetail = null
       this.fileDetail = null
       this.fileType = 0
       this.fileType = 0
@@ -462,22 +483,14 @@ export default {
     onClickFilename(row) {
     onClickFilename(row) {
       this.fileType = row.fileType
       this.fileType = row.fileType
       if (this.fileType === 1000) {
       if (this.fileType === 1000) {
-        const filename = row.filename
-        const url = this.pathList[this.pathList.length - 1].path
-        const arr = url.split('?')
-        let path = ''
-        if (arr.length === 1) {
-          path = '/' + filename
-        } else {
-          path = arr[1].split('=')[1] + '/' + filename
-        }
-        this.$router.push({
-          path: '/disk',
-          query: {
-            path: path
-          }
-        })
-        this.$router.go(0)
+        // 文件夹类型
+        const newPath = this.queryForm.path === '/'
+          ? `/${row.filename}`
+          : `${this.queryForm.path}/${row.filename}`
+
+        // 更新路由不刷新页面,而是触发逻辑跳转
+        this.$router.push({ query: { path: newPath }})
+        this.changeDirectory(`path=${newPath}`)
       } else {
       } else {
         getFileDetail(row.fileId).then(resp => {
         getFileDetail(row.fileId).then(resp => {
           if (resp.code === 0) {
           if (resp.code === 0) {
@@ -500,51 +513,39 @@ export default {
       return '/pdfjs/web/viewer.html?file=' + encodeURIComponent(url)
       return '/pdfjs/web/viewer.html?file=' + encodeURIComponent(url)
     },
     },
     // ****************************************************************************************************************
     // ****************************************************************************************************************
+    // 1. 打开对话框
     onCreateFolder() {
     onCreateFolder() {
+      this.createFolderForm.folderName = '' // 清空旧数据
       this.showCreateFolderDialog = true
       this.showCreateFolderDialog = true
-    },
-    createFolder() {
-      this.showCreateFolderDialog = false
-      this.createFolderForm.pid = this.currentPid
-      createFolder(this.createFolderForm).then(resp => {
-        if (resp.code === 0) {
-          this.$router.go(0)
-        } else {
-          this.$message.error(resp.msg)
-        }
-      }).catch(error => {
-        this.$message.error(error.message)
+      // 自动聚焦处理 (可选)
+      this.$nextTick(() => {
+        this.$refs.folderForm.clearValidate()
       })
       })
     },
     },
-    // ****************************************************************************************************************
-    handleCurrentChange(pageNumber) {
-      this.currentPage = pageNumber
-      this.queryForm.pn = this.currentPage
-      this.getData()
-      // 回到顶部
-      scrollTo(0, 0)
-    },
-    getData() {
-      getDiskFile(this.queryForm).then(resp => {
-        if (resp.code === 0) {
-          const respData = resp.data
-          const namePathList = respData.namePathList
-          const pageList = respData.pageList
-          this.currentPid = respData.currentPid
-
-          this.dataList = pageList.list
-          this.totalSize = pageList.totalSize
-          this.pathList = []
-          if (namePathList.length === 0) {
-            this.pathList.push({ path: '/disk', name: '全部文件' })
+
+    // 2. 执行创建逻辑
+    createFolder() {
+      this.$refs.folderForm.validate((valid) => {
+        if (!valid) return
+
+        this.loading = true
+        // 设置父级 ID 为当前目录 ID
+        this.createFolderForm.pid = this.currentPid
+
+        createFolder(this.createFolderForm).then(resp => {
+          if (resp.code === 0) {
+            this.$message.success('创建成功')
+            this.showCreateFolderDialog = false
+            // 刷新当前列表
+            this.changeDirectory(this.queryForm.path)
           } else {
           } else {
-            for (const namePath of namePathList) {
-              this.pathList.push({ path: '/disk?path=' + namePath.path, name: namePath.filename })
-            }
+            this.$message.error(resp.msg || '创建失败')
           }
           }
-        } else {
-          this.$message.error(resp.msg)
-        }
+        }).catch(error => {
+          this.$message.error(error.message)
+        }).finally(() => {
+          this.loading = false
+        })
       })
       })
     },
     },
     // 处理表格被选中
     // 处理表格被选中
@@ -664,10 +665,264 @@ export default {
 }
 }
 </script>
 </script>
 
 
-<style>
-.coverImg {
+<style scoped>
+.file-list-container {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+
+/* 顶部工具栏 */
+.toolbar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.search-input {
+  width: 240px;
+  margin-right: 12px;
+}
+.search-input ::v-deep .el-input__inner {
+  border-radius: 20px;
+  background-color: #f1f3f4;
+  border: none;
+}
+
+.action-btn {
+  border-radius: 10px;
+  font-weight: 500;
+}
+
+/* 表格样式重塑 */
+.table-wrapper {
+  flex-grow: 1;
+  overflow-y: auto;
+}
+
+.pikpak-table ::v-deep .el-table__row {
+  cursor: pointer;
+  transition: background 0.2s;
+}
+
+.pikpak-table ::v-deep .el-table__row:hover {
+  background-color: #f8faff !important;
+}
+
+.pikpak-table ::v-deep .el-table__header th {
+  background-color: transparent;
+  color: #5f6368;
+  font-weight: normal;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.file-name-cell {
+  display: flex;
+  align-items: center;
+  font-size: 14px;
+  color: #333;
+}
+
+.file-text:hover {
+  color: #1a73e8;
+}
+
+/* 悬浮操作条 */
+.selection-bar {
+  position: fixed;
+  bottom: 40px;
+  left: 50%;
+  transform: translateX(-50%);
+  background: #1a1a1a;
+  color: #fff;
+  padding: 10px 24px;
+  border-radius: 30px;
+  z-index: 1000;
+  display: flex;
+  align-items: center;
+  box-shadow: 0 10px 30px rgba(0,0,0,0.3);
+}
+
+.selection-count {
+  font-size: 14px;
+  margin-right: 15px;
+}
+
+.selection-bar .el-button--text {
+  color: #fff;
+  margin: 0 10px;
+}
+
+.selection-bar .delete-text {
+  color: #ff4d4f;
+}
+
+.load-status {
+  text-align: center;
+  padding: 20px;
+  color: #909399;
+  font-size: 13px;
+}
+
+/* Dialog 整体圆角优化 */
+::v-deep .pikpak-preview-dialog {
+  border-radius: 16px;
+  overflow: hidden;
+  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
+}
+
+/* 标题栏 */
+.preview-header {
+  display: flex;
+  align-items: center;
+  padding-right: 30px;
+}
+.preview-filename {
+  font-size: 16px;
+  font-weight: 600;
+  color: #333;
+  margin-left: 8px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+/* 预览主体内容区 */
+.preview-body {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  min-height: 400px;
+  background-color: #f9f9f9;
+  border-radius: 8px;
+}
+
+.preview-img {
+  max-width: 100%;
+  max-height: 70vh;
+}
+
+.preview-video {
   width: 100%;
   width: 100%;
-  height: 480px;
+  max-height: 70vh;
+  outline: none;
+  background: #000;
+}
+
+/* 音频样式 */
+.audio-container {
+  text-align: center;
+  padding: 40px;
+}
+.audio-icon {
+  font-size: 60px;
+  color: #409EFF;
+  margin-bottom: 20px;
+  display: block;
+}
+.preview-audio {
+  width: 400px;
+}
+
+/* 底部操作区 */
+.preview-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 10px 0;
+}
+.file-info {
+  font-size: 13px;
+  color: #909399;
+}
+
+/* 适配移动端 */
+@media screen and (max-width: 768px) {
+  ::v-deep .pikpak-preview-dialog {
+    width: 95% !important;
+  }
+  .preview-audio {
+    width: 100%;
+  }
+}
+
+/* 上传弹窗样式 */
+::v-deep .pikpak-upload-dialog {
+  border-radius: 16px;
+}
+
+.upload-drop-zone {
+  border: 2px dashed #dcdfe6;
+  border-radius: 12px;
+  background-color: #f8f9fa;
+  padding: 40px 0;
+  text-align: center;
+  transition: border-color 0.3s;
+}
+
+.upload-drop-zone:hover {
+  border-color: #1a73e8;
+}
+
+.drop-content i {
+  font-size: 48px;
+  color: #1a73e8;
+  margin-bottom: 16px;
+}
+
+.select-btn {
+  background: none;
+  border: none;
+  color: #1a73e8;
+  font-weight: bold;
+  padding: 0;
+  cursor: pointer;
+  text-decoration: underline;
+}
+
+.upload-tip {
+  font-size: 12px;
+  color: #909399;
   display: block;
   display: block;
+  margin-top: 10px;
+}
+
+/* 上传列表美化 */
+.upload-list-view {
+  max-height: 300px;
+  overflow-y: auto;
+  margin-top: 20px;
+}
+
+/* 隐藏 simple-uploader 默认的一些难看按钮 */
+::v-deep .uploader-file-icon {
+  display: none;
+}
+::v-deep .uploader-file-actions > span {
+  margin-right: 6px;
+  cursor: pointer;
+}
+
+/* 复用之前的 pikpak-dialog 样式,或者单独微调 */
+::v-deep .pikpak-dialog {
+  border-radius: 16px;
+}
+
+::v-deep .pikpak-dialog .el-dialog__body {
+  padding: 10px 25px;
+}
+
+/* 底部按钮平铺或靠右 */
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+}
+
+/* 输入框聚焦时的动画感 */
+::v-deep .el-input__inner:focus {
+  background-color: #fff;
+  border-color: #1a73e8;
 }
 }
 </style>
 </style>

+ 271 - 0
src/views/disk/DiskMe.vue

@@ -0,0 +1,271 @@
+<template>
+  <div class="me-container">
+    <el-row :gutter="20">
+      <el-col :md="10" :sm="24">
+        <el-card class="user-card" shadow="hover">
+          <div class="user-profile">
+            <el-avatar :size="80" :src="user ? user.avatarUrl : ''" class="avatar" />
+            <div class="user-info">
+              <div class="username">
+                {{ user ? user.name : '未登录' }}
+                <el-tag size="mini" type="warning" effect="dark" round>黄金VIP</el-tag>
+              </div>
+              <div class="user-id">ID: {{ user ? user.userId : '-' }}</div>
+              <el-button type="text" size="small" icon="el-icon-edit">修改资料</el-button>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+
+      <el-col :md="14" :sm="24">
+        <el-card class="storage-card" shadow="hover">
+          <div slot="header" class="card-header">
+            <span class="title">存储空间</span>
+            <el-button type="text">扩容套餐</el-button>
+          </div>
+
+          <div class="usage-summary">
+            <span class="used">{{ usedSpaceGB }} GB</span>
+            <span class="total">/ {{ totalSpaceGB }} GB</span>
+          </div>
+
+          <div class="multi-progress-bar">
+            <div class="segment video" :style="{ width: videoPercent + '%' }" title="视频" />
+            <div class="segment photo" :style="{ width: photoPercent + '%' }" title="照片" />
+            <div class="segment other" :style="{ width: otherPercent + '%' }" title="其他" />
+          </div>
+
+          <div class="legend">
+            <div class="legend-item"><i class="dot video" />视频 ({{ videoPercent }}%)</div>
+            <div class="legend-item"><i class="dot photo" />照片 ({{ photoPercent }}%)</div>
+            <div class="legend-item"><i class="dot other" />其他 ({{ otherPercent }}%)</div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <el-row :gutter="20" class="action-grid">
+      <el-col v-for="item in actionList" :key="item.title" :md="8" :sm="12">
+        <el-card class="action-card" shadow="hover" @click.native="handleAction(item)">
+          <div class="action-inner">
+            <i :class="item.icon" :style="{ color: item.color }" />
+            <div class="action-text">
+              <div class="action-title">{{ item.title }}</div>
+              <div class="action-desc">{{ item.desc }}</div>
+            </div>
+            <i class="el-icon-arrow-right arrow" />
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <el-dialog
+      title="账号设置"
+      :visible.sync="showSettings"
+      width="450px"
+      append-to-body
+      custom-class="pikpak-dialog"
+    >
+      <el-form label-position="left" label-width="120px">
+        <el-form-item label="离线下载通知">
+          <el-switch v-model="switchEnabled" @change="onSwitchChange" />
+        </el-form-item>
+        <el-form-item label="隐私保护模式">
+          <el-switch v-model="privacyEnabled" />
+        </el-form-item>
+        <el-form-item label="客户端版本">
+          <span class="version-text">v1.2.0 (Stable)</span>
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button type="primary" round class="save-btn" @click="showSettings = false">关闭</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getAuthedUser } from '@/utils/auth'
+// 假设你有一个提交状态的接口
+import { submitActivity } from '@/api/disk'
+
+export default {
+  name: 'DiskMe',
+  data() {
+    return {
+      user: null,
+      usedSpaceGB: 120,
+      totalSpaceGB: 6144, // 6TB
+      videoPercent: 45,
+      photoPercent: 15,
+      otherPercent: 10,
+      showSettings: false,
+      switchEnabled: false,
+      privacyEnabled: true,
+      actionList: [
+        { title: '离线下载', desc: '支持磁力链接与 BT 种子', icon: 'el-icon-link', color: '#1989fa' },
+        { title: '回收站', desc: '已删除文件保留 30 天', icon: 'el-icon-delete', color: '#f56c6c' },
+        { title: '传输中心', desc: '查看当前上传与下载任务', icon: 'el-icon-sort', color: '#67c23a' },
+        { title: '个人设置', desc: '修改头像、密码与偏好', icon: 'el-icon-setting', color: '#909399', action: 'settings' },
+        { title: '帮助反馈', desc: '遇到问题?联系我们的客服', icon: 'el-icon-chat-dot-round', color: '#e6a23c' }
+      ]
+    }
+  },
+  created() {
+    this.user = getAuthedUser()
+  },
+  methods: {
+    handleAction(item) {
+      if (item.action === 'settings') {
+        this.showSettings = true
+      } else {
+        this.$message.info(`跳转到: ${item.title}`)
+      }
+    },
+    onSwitchChange(val) {
+      submitActivity({ enabled: val }).then(resp => {
+        if (resp.code === 0) {
+          this.$message.success('设置更新成功')
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.me-container {
+  padding: 10px;
+
+  /* 用户卡片 */
+  .user-card {
+    border-radius: 16px;
+    height: 200px;
+    display: flex;
+    align-items: center;
+
+    .user-profile {
+      display: flex;
+      align-items: center;
+      padding: 10px;
+
+      .user-info {
+        margin-left: 24px;
+        .username {
+          font-size: 22px;
+          font-weight: bold;
+          margin-bottom: 8px;
+          display: flex;
+          align-items: center;
+          gap: 10px;
+        }
+        .user-id {
+          color: #909399;
+          font-size: 14px;
+          margin-bottom: 10px;
+        }
+      }
+    }
+  }
+
+  /* 存储卡片 */
+  .storage-card {
+    border-radius: 16px;
+    height: 200px;
+
+    .card-header {
+      display: flex;
+      justify-content: space-between;
+      .title { font-weight: bold; }
+    }
+
+    .usage-summary {
+      margin-bottom: 15px;
+      .used { font-size: 28px; font-weight: bold; color: #1a73e8; }
+      .total { font-size: 16px; color: #909399; margin-left: 5px; }
+    }
+
+    .multi-progress-bar {
+      height: 10px;
+      background: #f0f2f5;
+      border-radius: 5px;
+      display: flex;
+      overflow: hidden;
+      margin-bottom: 15px;
+
+      .segment {
+        height: 100%;
+        transition: width 0.4s ease;
+        &.video { background: #1989fa; }
+        &.photo { background: #67c23a; }
+        &.other { background: #ff976a; }
+      }
+    }
+
+    .legend {
+      display: flex;
+      gap: 20px;
+      .legend-item {
+        font-size: 12px;
+        color: #606266;
+        display: flex;
+        align-items: center;
+        .dot {
+          width: 8px;
+          height: 8px;
+          border-radius: 50%;
+          margin-right: 6px;
+          &.video { background: #1989fa; }
+          &.photo { background: #67c23a; }
+          &.other { background: #ff976a; }
+        }
+      }
+    }
+  }
+
+  /* 功能卡片网格 */
+  .action-grid {
+    margin-top: 20px;
+
+    .action-card {
+      margin-bottom: 20px;
+      border-radius: 12px;
+      cursor: pointer;
+      transition: all 0.3s;
+
+      &:hover {
+        transform: translateY(-4px);
+        box-shadow: 0 8px 20px rgba(0,0,0,0.08);
+      }
+
+      .action-inner {
+        display: flex;
+        align-items: center;
+        padding: 10px;
+
+        i:first-child {
+          font-size: 32px;
+          margin-right: 20px;
+        }
+
+        .action-text {
+          flex-grow: 1;
+          .action-title { font-weight: bold; font-size: 15px; margin-bottom: 4px; }
+          .action-desc { font-size: 12px; color: #909399; }
+        }
+
+        .arrow { color: #dcdfe6; }
+      }
+    }
+  }
+}
+
+/* 弹窗样式 */
+::v-deep .pikpak-dialog {
+  border-radius: 20px;
+  .el-dialog__header { padding: 25px 25px 10px; }
+  .el-dialog__title { font-weight: bold; }
+  .save-btn { width: 100%; margin-top: 10px; }
+  .version-text { color: #909399; font-family: monospace; }
+}
+</style>

+ 319 - 0
src/views/disk/DiskPhoto.vue

@@ -0,0 +1,319 @@
+<template>
+  <div v-loading="refreshing" class="photos-container">
+    <div class="photo-header">
+      <div class="header-left">
+        <span class="title">相册</span>
+        <span v-if="isEditMode" class="selection-info">已选中 {{ selectedIds.length }} 项</span>
+      </div>
+      <div class="header-right">
+        <el-button
+          :type="isEditMode ? 'default' : 'primary'"
+          size="small"
+          @click="toggleEditMode"
+        >
+          {{ isEditMode ? '取消管理' : '批量管理' }}
+        </el-button>
+      </div>
+    </div>
+
+    <div
+      v-infinite-scroll="onLoad"
+      class="scroll-wrapper"
+      :infinite-scroll-disabled="disabled"
+    >
+      <div v-for="group in groupedPhotos" :key="group.date" class="photo-group">
+        <div class="group-header">
+          <span class="date-title">{{ formatGroupDate(group.date) }}</span>
+          <el-checkbox
+            v-if="isEditMode"
+            :value="isGroupSelected(group)"
+            @change="selectGroup(group)"
+          >
+            全选
+          </el-checkbox>
+        </div>
+
+        <div class="photo-grid">
+          <div
+            v-for="item in group.children"
+            :key="item.fileId"
+            class="photo-item"
+            :class="{ 'is-selected': selectedIds.includes(item.fileId) }"
+            @click="onItemClick(item)"
+          >
+            <el-image
+              class="image-content"
+              :src="item.url"
+              fit="cover"
+              lazy
+            />
+
+            <div v-if="item.fileType === 1002" class="video-badge">
+              <i class="el-icon-video-play" />
+              <span>{{ item.duration || '视频' }}</span>
+            </div>
+
+            <div v-if="isEditMode" class="select-mask">
+              <div class="check-circle">
+                <i v-if="selectedIds.includes(item.fileId)" class="el-icon-check" />
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div class="load-status">
+        <p v-if="loading"><i class="el-icon-loading" /> 加载中...</p>
+        <p v-if="finished && photoList.length > 0">没有更多照片了</p>
+        <el-empty v-if="finished && photoList.length === 0" description="相册空空如也" />
+      </div>
+    </div>
+
+    <transition name="el-zoom-in-bottom">
+      <div v-if="isEditMode && selectedIds.length > 0" class="batch-footer">
+        <div class="footer-content">
+          <el-button type="primary" icon="el-icon-download" @click="handleBatchDownload">下载</el-button>
+          <el-button type="danger" icon="el-icon-delete" @click="handleBatchDelete">删除</el-button>
+        </div>
+      </div>
+    </transition>
+
+    <el-dialog
+      :visible.sync="showPlayerModal"
+      width="800px"
+      custom-class="video-dialog"
+      append-to-body
+      @closed="onVideoPopupClosed"
+    >
+      <div v-if="currentVideoItem" class="video-container">
+        <video
+          ref="videoPlayer"
+          :src="currentVideoItem.url"
+          controls
+          class="full-video"
+        />
+        <div class="video-detail">
+          <h3>{{ currentVideoItem.filename }}</h3>
+          <p>更新时间:{{ currentVideoItem.updateTime }}</p>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getPhotoItems } from '@/api/disk'
+
+export default {
+  name: 'DiskPhoto',
+  data() {
+    return {
+      photoList: [],
+      loading: false,
+      finished: false,
+      refreshing: false,
+      isEditMode: false,
+      selectedIds: [],
+      queryParams: {
+        pn: 1
+      },
+      showPlayerModal: false,
+      currentVideoItem: null
+    }
+  },
+  computed: {
+    groupedPhotos() {
+      return this.groupDataByDate(this.photoList)
+    },
+    disabled() {
+      return this.loading || this.finished
+    }
+  },
+  methods: {
+    async onLoad() {
+      if (this.finished) return
+      this.loading = true
+
+      try {
+        const resp = await getPhotoItems(this.queryParams)
+        if (resp.code === 0) {
+          const list = resp.data.list || []
+          this.photoList = [...this.photoList, ...list]
+
+          if (!resp.data.hasNext) {
+            this.finished = true
+          } else {
+            this.queryParams.pn++
+          }
+        }
+      } catch (e) {
+        this.$message.error('加载失败')
+      } finally {
+        this.loading = false
+      }
+    },
+
+    groupDataByDate(flatList) {
+      const groups = {}
+      flatList.forEach(item => {
+        const date = item.updateTime.split(' ')[0]
+        if (!groups[date]) groups[date] = []
+        groups[date].push(item)
+      })
+      return Object.keys(groups).map(date => ({
+        date,
+        children: groups[date]
+      })).sort((a, b) => new Date(b.date) - new Date(a.date))
+    },
+
+    formatGroupDate(dateStr) {
+      const d = new Date(dateStr)
+      const now = new Date()
+      if (dateStr === now.toISOString().split('T')[0]) return '今天'
+      return `${d.getMonth() + 1}月${d.getDate()}日`
+    },
+
+    onItemClick(item) {
+      if (this.isEditMode) {
+        this.handleSelect(item.fileId)
+      } else {
+        if (item.fileType === 1002) {
+          this.currentVideoItem = item
+          this.showPlayerModal = true
+        } else {
+          // 利用 Element UI 没有内置 ImagePreview 全屏 API 的特点,
+          // 建议直接在 el-image 上设置 preview-src-list
+          // 或者手动调用你已有的预览组件
+        }
+      }
+    },
+
+    handleSelect(id) {
+      const idx = this.selectedIds.indexOf(id)
+      idx > -1 ? this.selectedIds.splice(idx, 1) : this.selectedIds.push(id)
+    },
+
+    toggleEditMode() {
+      this.isEditMode = !this.isEditMode
+      this.selectedIds = []
+    },
+
+    selectGroup(group) {
+      const groupIds = group.children.map(i => i.fileId)
+      if (this.isGroupSelected(group)) {
+        this.selectedIds = this.selectedIds.filter(id => !groupIds.includes(id))
+      } else {
+        groupIds.forEach(id => {
+          if (!this.selectedIds.includes(id)) this.selectedIds.push(id)
+        })
+      }
+    },
+
+    isGroupSelected(group) {
+      return group.children.every(i => this.selectedIds.includes(i.fileId))
+    },
+
+    onVideoPopupClosed() {
+      if (this.$refs.videoPlayer) this.$refs.videoPlayer.pause()
+    }
+
+    // ... 其他业务逻辑 (handleBatchDelete 等)
+  }
+}
+</script>
+
+<style scoped>
+.photos-container {
+  padding: 20px;
+  background: #fff;
+  border-radius: 12px;
+  min-height: 80vh;
+
+  .photo-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 30px;
+    .title { font-size: 20px; font-weight: bold; }
+    .selection-info { margin-left: 15px; color: #909399; font-size: 14px; }
+  }
+
+  .group-header {
+    margin: 25px 0 15px;
+    display: flex;
+    align-items: center;
+    .date-title { font-size: 16px; font-weight: 600; margin-right: 15px; }
+  }
+
+  .photo-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+    gap: 12px;
+  }
+
+  .photo-item {
+    position: relative;
+    aspect-ratio: 1 / 1;
+    cursor: pointer;
+    border-radius: 8px;
+    overflow: hidden;
+    transition: transform 0.2s;
+
+    &:hover { transform: scale(1.02); }
+
+    .image-content { width: 100%; height: 100%; }
+
+    &.is-selected {
+      border: 3px solid #409EFF;
+    }
+  }
+
+  .video-badge {
+    position: absolute;
+    bottom: 8px;
+    right: 8px;
+    background: rgba(0,0,0,0.6);
+    color: #fff;
+    padding: 2px 6px;
+    border-radius: 4px;
+    font-size: 12px;
+    display: flex;
+    align-items: center;
+    i { margin-right: 4px; }
+  }
+
+  .select-mask {
+    position: absolute;
+    top: 8px;
+    right: 8px;
+    .check-circle {
+      width: 20px;
+      height: 20px;
+      border-radius: 50%;
+      background: rgba(255,255,255,0.8);
+      border: 1px solid #ccc;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      i { color: #409EFF; font-weight: bold; }
+    }
+  }
+
+  .batch-footer {
+    position: fixed;
+    bottom: 30px;
+    left: 50%;
+    transform: translateX(-50%);
+    background: #fff;
+    padding: 12px 40px;
+    border-radius: 50px;
+    box-shadow: 0 4px 20px rgba(0,0,0,0.15);
+    z-index: 2000;
+  }
+}
+
+.video-container {
+  .full-video { width: 100%; border-radius: 8px; }
+  .video-detail { margin-top: 15px; }
+}
+</style>

+ 0 - 1
src/views/disk/DiskShare.vue

@@ -97,7 +97,6 @@ export default {
     }
     }
   },
   },
   created() {
   created() {
-    document.title = '分享'
     this.getData()
     this.getData()
   },
   },
   methods: {
   methods: {