Просмотр исходного кода

新增文件管理功能

- 添加文件管理页面,支持文件列表展示
- 实现文件上传功能(拖拽上传)
- 实现文件下载功能
- 实现文件删除功能(带确认对话框)
- 实现图片预览功能
- 支持按文件名和文件类型搜索筛选
- 支持分页展示
- 添加文件管理相关 Mock API
- 添加默认菜单配置包含文件管理

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tanlie 3 недель назад
Родитель
Сommit
26fa3b7dd8
6 измененных файлов с 1324 добавлено и 1 удалено
  1. 791 0
      package-lock.json
  2. 1 0
      package.json
  3. 110 0
      src/mock/index.ts
  4. 6 0
      src/router/index.ts
  5. 21 1
      src/store/user.ts
  6. 395 0
      src/views/file/index.vue

+ 791 - 0
package-lock.json

@@ -18,6 +18,7 @@
       "devDependencies": {
         "@types/node": "^25.5.0",
         "@vitejs/plugin-vue": "^5.0.0",
+        "sass-embedded": "^1.98.0",
         "typescript": "^5.3.0",
         "vite": "^5.0.0",
         "vite-plugin-mock": "^3.0.2",
@@ -66,6 +67,12 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/@bufbuild/protobuf": {
+      "version": "2.11.0",
+      "resolved": "https://registry.npmmirror.com/@bufbuild/protobuf/-/protobuf-2.11.0.tgz",
+      "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==",
+      "dev": true
+    },
     "node_modules/@ctrl/tinycolor": {
       "version": "3.6.1",
       "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
@@ -512,6 +519,315 @@
         "node": ">= 8"
       }
     },
+    "node_modules/@parcel/watcher": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.6.tgz",
+      "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
+      "dev": true,
+      "hasInstallScript": true,
+      "optional": true,
+      "dependencies": {
+        "detect-libc": "^2.0.3",
+        "is-glob": "^4.0.3",
+        "node-addon-api": "^7.0.0",
+        "picomatch": "^4.0.3"
+      },
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      },
+      "optionalDependencies": {
+        "@parcel/watcher-android-arm64": "2.5.6",
+        "@parcel/watcher-darwin-arm64": "2.5.6",
+        "@parcel/watcher-darwin-x64": "2.5.6",
+        "@parcel/watcher-freebsd-x64": "2.5.6",
+        "@parcel/watcher-linux-arm-glibc": "2.5.6",
+        "@parcel/watcher-linux-arm-musl": "2.5.6",
+        "@parcel/watcher-linux-arm64-glibc": "2.5.6",
+        "@parcel/watcher-linux-arm64-musl": "2.5.6",
+        "@parcel/watcher-linux-x64-glibc": "2.5.6",
+        "@parcel/watcher-linux-x64-musl": "2.5.6",
+        "@parcel/watcher-win32-arm64": "2.5.6",
+        "@parcel/watcher-win32-ia32": "2.5.6",
+        "@parcel/watcher-win32-x64": "2.5.6"
+      }
+    },
+    "node_modules/@parcel/watcher-android-arm64": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
+      "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-darwin-arm64": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
+      "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-darwin-x64": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
+      "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-freebsd-x64": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
+      "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm-glibc": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
+      "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm-musl": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
+      "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm64-glibc": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
+      "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm64-musl": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
+      "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-x64-glibc": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
+      "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-x64-musl": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
+      "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-arm64": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
+      "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-ia32": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
+      "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-x64": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
+      "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher/node_modules/picomatch": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
+      "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+      "dev": true,
+      "optional": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
     "node_modules/@popperjs/core": {
       "name": "@sxzz/popperjs-es",
       "version": "2.11.8",
@@ -1198,6 +1514,12 @@
         "fsevents": "~2.3.2"
       }
     },
+    "node_modules/colorjs.io": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmmirror.com/colorjs.io/-/colorjs.io-0.5.2.tgz",
+      "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
+      "dev": true
+    },
     "node_modules/combined-stream": {
       "version": "1.0.8",
       "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -1296,6 +1618,16 @@
         "node": ">=0.4.0"
       }
     },
+    "node_modules/detect-libc": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz",
+      "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+      "dev": true,
+      "optional": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/dunder-proto": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -1633,6 +1965,15 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/has-symbols": {
       "version": "1.1.0",
       "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
@@ -1678,6 +2019,12 @@
         "he": "bin/he"
       }
     },
+    "node_modules/immutable": {
+      "version": "5.1.5",
+      "resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.5.tgz",
+      "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==",
+      "dev": true
+    },
     "node_modules/is-binary-path": {
       "version": "2.1.0",
       "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -1868,6 +2215,13 @@
         "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
       }
     },
+    "node_modules/node-addon-api": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz",
+      "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+      "dev": true,
+      "optional": true
+    },
     "node_modules/normalize-path": {
       "version": "3.0.0",
       "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -2094,6 +2448,395 @@
         "queue-microtask": "^1.2.2"
       }
     },
+    "node_modules/rxjs": {
+      "version": "7.8.2",
+      "resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.2.tgz",
+      "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+      "dev": true,
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/sass": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass/-/sass-1.98.0.tgz",
+      "integrity": "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "chokidar": "^4.0.0",
+        "immutable": "^5.1.5",
+        "source-map-js": ">=0.6.2 <2.0.0"
+      },
+      "bin": {
+        "sass": "sass.js"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "optionalDependencies": {
+        "@parcel/watcher": "^2.4.1"
+      }
+    },
+    "node_modules/sass-embedded": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded/-/sass-embedded-1.98.0.tgz",
+      "integrity": "sha512-Do7u6iRb6K+lrllcTkB1BXcHwOxcKe3rEfOF/GcCLE2w3WpddakRAosJOHFUR37DpsvimQXEt5abs3NzUjEIqg==",
+      "dev": true,
+      "dependencies": {
+        "@bufbuild/protobuf": "^2.5.0",
+        "colorjs.io": "^0.5.0",
+        "immutable": "^5.1.5",
+        "rxjs": "^7.4.0",
+        "supports-color": "^8.1.1",
+        "sync-child-process": "^1.0.2",
+        "varint": "^6.0.0"
+      },
+      "bin": {
+        "sass": "dist/bin/sass.js"
+      },
+      "engines": {
+        "node": ">=16.0.0"
+      },
+      "optionalDependencies": {
+        "sass-embedded-all-unknown": "1.98.0",
+        "sass-embedded-android-arm": "1.98.0",
+        "sass-embedded-android-arm64": "1.98.0",
+        "sass-embedded-android-riscv64": "1.98.0",
+        "sass-embedded-android-x64": "1.98.0",
+        "sass-embedded-darwin-arm64": "1.98.0",
+        "sass-embedded-darwin-x64": "1.98.0",
+        "sass-embedded-linux-arm": "1.98.0",
+        "sass-embedded-linux-arm64": "1.98.0",
+        "sass-embedded-linux-musl-arm": "1.98.0",
+        "sass-embedded-linux-musl-arm64": "1.98.0",
+        "sass-embedded-linux-musl-riscv64": "1.98.0",
+        "sass-embedded-linux-musl-x64": "1.98.0",
+        "sass-embedded-linux-riscv64": "1.98.0",
+        "sass-embedded-linux-x64": "1.98.0",
+        "sass-embedded-unknown-all": "1.98.0",
+        "sass-embedded-win32-arm64": "1.98.0",
+        "sass-embedded-win32-x64": "1.98.0"
+      }
+    },
+    "node_modules/sass-embedded-all-unknown": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.98.0.tgz",
+      "integrity": "sha512-6n4RyK7/1mhdfYvpP3CClS3fGoYqDvRmLClCESS6I7+SAzqjxvGG6u5Fo+cb1nrPNbbilgbM4QKdgcgWHO9NCA==",
+      "cpu": [
+        "!arm",
+        "!arm64",
+        "!riscv64",
+        "!x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "sass": "1.98.0"
+      }
+    },
+    "node_modules/sass-embedded-android-arm": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-android-arm/-/sass-embedded-android-arm-1.98.0.tgz",
+      "integrity": "sha512-LjGiMhHgu7VL1n7EJxTCre1x14bUsWd9d3dnkS2rku003IWOI/fxc7OXgaKagoVzok1kv09rzO3vFXJR5ZeONQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass-embedded-android-arm64": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.98.0.tgz",
+      "integrity": "sha512-M9Ra98A6vYJHpwhoC/5EuH1eOshQ9ZyNwC8XifUDSbRl/cGeQceT1NReR9wFj3L7s1pIbmes1vMmaY2np0uAKQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass-embedded-android-riscv64": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.98.0.tgz",
+      "integrity": "sha512-WPe+0NbaJIZE1fq/RfCZANMeIgmy83x4f+SvFOG7LhUthHpZWcOcrPTsCKKmN3xMT3iw+4DXvqTYOCYGRL3hcQ==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass-embedded-android-x64": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-android-x64/-/sass-embedded-android-x64-1.98.0.tgz",
+      "integrity": "sha512-zrD25dT7OHPEgLWuPEByybnIfx4rnCtfge4clBgjZdZ3lF6E7qNLRBtSBmoFflh6Vg0RlEjJo5VlpnTMBM5MQQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass-embedded-darwin-arm64": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.98.0.tgz",
+      "integrity": "sha512-cgr1z9rBnCdMf8K+JabIaYd9Rag2OJi5mjq08XJfbJGMZV/TA6hFJCLGkr5/+ZOn4/geTM5/3aSfQ8z5EIJAOg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass-embedded-darwin-x64": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.98.0.tgz",
+      "integrity": "sha512-OLBOCs/NPeiMqTdOrMFbVHBQFj19GS3bSVSxIhcCq16ZyhouUkYJEZjxQgzv9SWA2q6Ki8GCqp4k6jMeUY9dcA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass-embedded-linux-arm": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.98.0.tgz",
+      "integrity": "sha512-03baQZCxVyEp8v1NWBRlzGYrmVT/LK7ZrHlF1piscGiGxwfdxoLXVuxsylx3qn/dD/4i/rh7Bzk7reK1br9jvQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass-embedded-linux-arm64": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.98.0.tgz",
+      "integrity": "sha512-axOE3t2MTBwCtkUCbrdM++Gj0gC0fdHJPrgzQ+q1WUmY9NoNMGqflBtk5mBZaWUeha2qYO3FawxCB8lctFwCtw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass-embedded-linux-musl-arm": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.98.0.tgz",
+      "integrity": "sha512-OBkjTDPYR4hSaueOGIM6FDpl9nt/VZwbSRpbNu9/eEJcxE8G/vynRugW8KRZmCFjPy8j/jkGBvvS+k9iOqKV3g==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass-embedded-linux-musl-arm64": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.98.0.tgz",
+      "integrity": "sha512-LeqNxQA8y4opjhe68CcFvMzCSrBuJqYVFbwElEj9bagHXQHTp9xVPJRn6VcrC+0VLEDq13HVXMv7RslIuU0zmA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass-embedded-linux-musl-riscv64": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.98.0.tgz",
+      "integrity": "sha512-7w6hSuOHKt8FZsmjRb3iGSxEzM87fO9+M8nt5JIQYMhHTj5C+JY/vcske0v715HCVj5e1xyTnbGXf8FcASeAIw==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass-embedded-linux-musl-x64": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.98.0.tgz",
+      "integrity": "sha512-QikNyDEJOVqPmxyCFkci8ZdCwEssdItfjQFJB+D+Uy5HFqcS5Lv3d3GxWNX/h1dSb23RPyQdQc267ok5SbEyJw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass-embedded-linux-riscv64": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.98.0.tgz",
+      "integrity": "sha512-E7fNytc/v4xFBQKzgzBddV/jretA4ULAPO6XmtBiQu4zZBdBozuSxsQLe2+XXeb0X4S2GIl72V7IPABdqke/vA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass-embedded-linux-x64": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.98.0.tgz",
+      "integrity": "sha512-VsvP0t/uw00mMNPv3vwyYKUrFbqzxQHnRMO+bHdAMjvLw4NFf6mscpym9Bzf+NXwi1ZNKnB6DtXjmcpcvqFqYg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass-embedded-unknown-all": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.98.0.tgz",
+      "integrity": "sha512-C4MMzcAo3oEDQnW7L8SBgB9F2Fq5qHPnaYTZRMOH3Mp/7kM4OooBInXpCiiFjLnjY95hzP4KyctVx0uYR6MYlQ==",
+      "dev": true,
+      "optional": true,
+      "os": [
+        "!android",
+        "!darwin",
+        "!linux",
+        "!win32"
+      ],
+      "dependencies": {
+        "sass": "1.98.0"
+      }
+    },
+    "node_modules/sass-embedded-win32-arm64": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.98.0.tgz",
+      "integrity": "sha512-nP/10xbAiPbhQkMr3zQfXE4TuOxPzWRQe1Hgbi90jv2R4TbzbqQTuZVOaJf7KOAN4L2Bo6XCTRjK5XkVnwZuwQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass-embedded-win32-x64": {
+      "version": "1.98.0",
+      "resolved": "https://registry.npmmirror.com/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.98.0.tgz",
+      "integrity": "sha512-/lbrVsfbcbdZQ5SJCWcV0NVPd6YRs+FtAnfedp4WbCkO/ZO7Zt/58MvI4X2BVpRY/Nt5ZBo1/7v2gYcQ+J4svQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/sass/node_modules/chokidar": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz",
+      "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "readdirp": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 14.16.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/sass/node_modules/readdirp": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz",
+      "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+      "dev": true,
+      "optional": true,
+      "engines": {
+        "node": ">= 14.18.0"
+      },
+      "funding": {
+        "type": "individual",
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
     "node_modules/semver": {
       "version": "7.7.4",
       "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz",
@@ -2123,6 +2866,42 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/supports-color": {
+      "version": "8.1.1",
+      "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz",
+      "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/supports-color?sponsor=1"
+      }
+    },
+    "node_modules/sync-child-process": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/sync-child-process/-/sync-child-process-1.0.2.tgz",
+      "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==",
+      "dev": true,
+      "dependencies": {
+        "sync-message-port": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=16.0.0"
+      }
+    },
+    "node_modules/sync-message-port": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/sync-message-port/-/sync-message-port-1.2.0.tgz",
+      "integrity": "sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg==",
+      "dev": true,
+      "engines": {
+        "node": ">=16.0.0"
+      }
+    },
     "node_modules/to-regex-range": {
       "version": "5.0.1",
       "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -2135,6 +2914,12 @@
         "node": ">=8.0"
       }
     },
+    "node_modules/tslib": {
+      "version": "2.8.1",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
+      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+      "dev": true
+    },
     "node_modules/typescript": {
       "version": "5.9.3",
       "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
@@ -2172,6 +2957,12 @@
         "node": ">= 0.4.0"
       }
     },
+    "node_modules/varint": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/varint/-/varint-6.0.0.tgz",
+      "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
+      "dev": true
+    },
     "node_modules/vite": {
       "version": "5.4.21",
       "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz",

+ 1 - 0
package.json

@@ -19,6 +19,7 @@
   "devDependencies": {
     "@types/node": "^25.5.0",
     "@vitejs/plugin-vue": "^5.0.0",
+    "sass-embedded": "^1.98.0",
     "typescript": "^5.3.0",
     "vite": "^5.0.0",
     "vite-plugin-mock": "^3.0.2",

+ 110 - 0
src/mock/index.ts

@@ -202,6 +202,12 @@ export default [
             path: "/ai-chat",
             icon: "ChatDotRound",
           },
+          {
+            id: "6",
+            name: "文件管理",
+            path: "/file",
+            icon: "Folder",
+          },
         ],
       };
     },
@@ -371,4 +377,108 @@ export default [
       res.end();
     },
   },
+
+  // ========== 文件管理接口 ==========
+
+  // 获取文件列表
+  {
+    url: "/api/file/list",
+    method: "get",
+    response: ({ query }) => {
+      const page = parseInt(query.page as string) || 1;
+      const size = parseInt(query.size as string) || 10;
+      const filename = (query.filename as string) || '';
+      const fileType = (query.fileType as string) || '';
+
+      // 模拟文件数据
+      const allFiles = [
+        { id: '1', filename: '项目需求文档.pdf', fileType: 'document', size: 2048576, uploader: '张三', uploadTime: '2026-03-15 10:30:00', url: '' },
+        { id: '2', filename: '产品原型图.png', fileType: 'image', size: 512000, uploader: '李四', uploadTime: '2026-03-15 09:15:00', url: 'https://via.placeholder.com/800x600.png?text=Product+Prototype' },
+        { id: '3', filename: '会议记录.docx', fileType: 'document', size: 102400, uploader: '王五', uploadTime: '2026-03-14 16:45:00', url: '' },
+        { id: '4', filename: '演示视频.mp4', fileType: 'video', size: 52428800, uploader: '赵六', uploadTime: '2026-03-14 14:20:00', url: '' },
+        { id: '5', filename: '背景音乐.mp3', fileType: 'audio', size: 3145728, uploader: '孙七', uploadTime: '2026-03-13 11:00:00', url: '' },
+        { id: '6', filename: '数据库备份.sql', fileType: 'other', size: 10485760, uploader: '张三', uploadTime: '2026-03-13 09:30:00', url: '' },
+        { id: '7', filename: '系统架构图.jpg', fileType: 'image', size: 1536000, uploader: '李四', uploadTime: '2026-03-12 15:20:00', url: 'https://via.placeholder.com/800x600.png?text=Architecture' },
+        { id: '8', filename: 'API文档.pdf', fileType: 'document', size: 3145728, uploader: '王五', uploadTime: '2026-03-12 10:00:00', url: '' },
+        { id: '9', filename: '测试报告.xlsx', fileType: 'document', size: 512000, uploader: '赵六', uploadTime: '2026-03-11 16:30:00', url: '' },
+        { id: '10', filename: '部署脚本.sh', fileType: 'other', size: 10240, uploader: '孙七', uploadTime: '2026-03-11 09:00:00', url: '' },
+        { id: '11', filename: '用户手册.pdf', fileType: 'document', size: 4194304, uploader: '张三', uploadTime: '2026-03-10 14:00:00', url: '' },
+        { id: '12', filename: '团队合影.jpg', fileType: 'image', size: 2621440, uploader: '李四', uploadTime: '2026-03-10 10:30:00', url: 'https://via.placeholder.com/800x600.png?text=Team+Photo' },
+      ];
+
+      // 筛选
+      let filteredFiles = allFiles.filter(file => {
+        const matchName = filename ? file.filename.toLowerCase().includes(filename.toLowerCase()) : true;
+        const matchType = fileType ? file.fileType === fileType : true;
+        return matchName && matchType;
+      });
+
+      // 分页
+      const total = filteredFiles.length;
+      const start = (page - 1) * size;
+      const end = start + size;
+      const list = filteredFiles.slice(start, end);
+
+      return {
+        code: 200,
+        message: "success",
+        data: {
+          list,
+          total,
+          page,
+          size
+        }
+      };
+    },
+  },
+
+  // 上传文件
+  {
+    url: "/api/file/upload",
+    method: "post",
+    response: () => {
+      return {
+        code: 200,
+        message: "上传成功",
+        data: {
+          id: String(Date.now()),
+          filename: '新上传文件.pdf',
+          fileType: 'document',
+          size: 1024000,
+          uploader: 'admin',
+          uploadTime: new Date().toLocaleString(),
+          url: ''
+        }
+      };
+    },
+  },
+
+  // 删除文件
+  {
+    url: "/api/file/delete",
+    method: "delete",
+    response: () => {
+      return {
+        code: 200,
+        message: "删除成功",
+        data: null
+      };
+    },
+  },
+
+  // 下载文件
+  {
+    url: "/api/file/download",
+    method: "get",
+    rawResponse: async (req, res) => {
+      const url = new URL(req.url || '', `http://${req.headers.host}`);
+      const id = url.searchParams.get('id') || 'unknown';
+      const filename = `file_${id}.txt`;
+
+      res.setHeader('Content-Type', 'application/octet-stream');
+      res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
+      res.write('This is a mock file content for download.');
+      res.end();
+    },
+  },
 ] as MockMethod[];

+ 6 - 0
src/router/index.ts

@@ -52,6 +52,12 @@ const router = createRouter({
           name: 'AIChat',
           component: () => import('@/views/ai-chat/index.vue'),
           meta: { title: 'AI 助手' }
+        },
+        {
+          path: '/file',
+          name: 'File',
+          component: () => import('@/views/file/index.vue'),
+          meta: { title: '文件管理' }
         }
       ]
     }

+ 21 - 1
src/store/user.ts

@@ -3,6 +3,25 @@ import { ref, computed } from 'vue'
 import { http } from '@/utils/request'
 import type { MenuItem, UserInfo } from '@/types/menu'
 
+// 默认菜单数据
+const defaultMenus: MenuItem[] = [
+  { id: '1', name: '仪表盘', path: '/dashboard', icon: 'Odometer' },
+  { id: '2', name: '用户管理', path: '/user', icon: 'User' },
+  {
+    id: '3',
+    name: '系统管理',
+    path: '/system',
+    icon: 'Setting',
+    children: [
+      { id: '3-1', name: '菜单管理', path: '/system/menu', icon: 'Menu' },
+      { id: '3-2', name: '角色管理', path: '/system/role', icon: 'UserFilled' }
+    ]
+  },
+  { id: '4', name: '数据统计', path: '/statistics', icon: 'TrendCharts' },
+  { id: '5', name: 'AI 助手', path: '/ai-chat', icon: 'ChatDotRound' },
+  { id: '6', name: '文件管理', path: '/file', icon: 'Folder' }
+]
+
 export const useUserStore = defineStore('user', () => {
   // State
   const token = ref<string>(localStorage.getItem('token') || '')
@@ -35,7 +54,8 @@ export const useUserStore = defineStore('user', () => {
   }
 
   const setMenus = (menuList: MenuItem[]) => {
-    menus.value = menuList
+    // 如果后端返回的菜单为空,使用默认菜单
+    menus.value = menuList && menuList.length > 0 ? menuList : defaultMenus
   }
 
   const toggleCollapse = () => {

+ 395 - 0
src/views/file/index.vue

@@ -0,0 +1,395 @@
+<template>
+  <div class="file-container">
+    <el-card>
+      <template #header>
+        <div class="card-header">
+          <span>文件管理</span>
+          <el-button type="primary" @click="handleUpload">
+            <el-icon><Upload /></el-icon>
+            上传文件
+          </el-button>
+        </div>
+      </template>
+
+      <el-form :inline="true" :model="searchForm" class="search-form">
+        <el-form-item label="文件名">
+          <el-input v-model="searchForm.filename" placeholder="请输入文件名" clearable />
+        </el-form-item>
+        <el-form-item label="文件类型">
+          <el-select v-model="searchForm.fileType" placeholder="请选择" clearable style="width: 140px">
+            <el-option label="全部类型" value="" />
+            <el-option label="图片" value="image">
+              <el-icon style="margin-right: 6px; vertical-align: middle"><Picture /></el-icon>
+              <span style="vertical-align: middle">图片</span>
+            </el-option>
+            <el-option label="文档" value="document">
+              <el-icon style="margin-right: 6px; vertical-align: middle"><Document /></el-icon>
+              <span style="vertical-align: middle">文档</span>
+            </el-option>
+            <el-option label="视频" value="video">
+              <el-icon style="margin-right: 6px; vertical-align: middle"><VideoCamera /></el-icon>
+              <span style="vertical-align: middle">视频</span>
+            </el-option>
+            <el-option label="音频" value="audio">
+              <el-icon style="margin-right: 6px; vertical-align: middle"><Headset /></el-icon>
+              <span style="vertical-align: middle">音频</span>
+            </el-option>
+            <el-option label="其他" value="other">
+              <el-icon style="margin-right: 6px; vertical-align: middle"><Files /></el-icon>
+              <span style="vertical-align: middle">其他</span>
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleSearch">搜索</el-button>
+          <el-button @click="handleReset">重置</el-button>
+        </el-form-item>
+      </el-form>
+
+      <el-table :data="tableData" style="width: 100%" v-loading="loading">
+        <el-table-column type="index" label="序号" width="60" />
+        <el-table-column prop="filename" label="文件名" min-width="200">
+          <template #default="{ row }">
+            <div class="file-name-cell">
+              <el-icon class="file-icon" :size="20">
+                <component :is="getFileIcon(row.fileType)" />
+              </el-icon>
+              <span class="filename">{{ row.filename }}</span>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="fileType" label="类型" width="100">
+          <template #default="{ row }">
+            <el-tag :type="getFileTypeTag(row.fileType)" size="small">
+              {{ getFileTypeText(row.fileType) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="size" label="大小" width="100">
+          <template #default="{ row }">
+            {{ formatFileSize(row.size) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="uploader" label="上传者" width="120" />
+        <el-table-column prop="uploadTime" label="上传时间" width="180" />
+        <el-table-column label="操作" width="200" fixed="right">
+          <template #default="{ row }">
+            <el-button type="primary" link @click="handleDownload(row)">下载</el-button>
+            <el-button type="primary" link @click="handlePreview(row)">预览</el-button>
+            <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="pagination">
+        <el-pagination
+          v-model:current-page="pageInfo.page"
+          v-model:page-size="pageInfo.pageSize"
+          :total="pageInfo.total"
+          :page-sizes="[10, 20, 50, 100]"
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </el-card>
+
+    <!-- 上传文件对话框 -->
+    <el-dialog
+      v-model="uploadDialogVisible"
+      title="上传文件"
+      width="500px"
+      :close-on-click-modal="false"
+    >
+      <el-upload
+        ref="uploadRef"
+        drag
+        action="/api/file/upload"
+        :headers="uploadHeaders"
+        :before-upload="beforeUpload"
+        :on-success="handleUploadSuccess"
+        :on-error="handleUploadError"
+        :file-list="fileList"
+        :auto-upload="true"
+        accept="*"
+      >
+        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+        <div class="el-upload__text">
+          拖拽文件到此处或 <em>点击上传</em>
+        </div>
+        <template #tip>
+          <div class="el-upload__tip">
+            支持上传任意类型文件,单个文件最大 100MB
+          </div>
+        </template>
+      </el-upload>
+      <template #footer>
+        <el-button @click="uploadDialogVisible = false">关闭</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 图片预览对话框 -->
+    <el-dialog
+      v-model="previewDialogVisible"
+      title="文件预览"
+      width="800px"
+      :close-on-click-modal="true"
+    >
+      <div class="preview-container">
+        <img v-if="previewType === 'image'" :src="previewUrl" alt="预览" class="preview-image" />
+        <div v-else class="preview-not-support">
+          <el-icon :size="60"><Document /></el-icon>
+          <p>该文件类型暂不支持预览</p>
+          <el-button type="primary" @click="handleDownload(currentFile)">下载查看</el-button>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, computed } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Upload, UploadFilled, Document, Picture, VideoCamera, Headset, Files } from '@element-plus/icons-vue'
+import type { UploadFile, UploadInstance } from 'element-plus'
+import { http } from '@/utils/request'
+
+const loading = ref(false)
+const uploadDialogVisible = ref(false)
+const previewDialogVisible = ref(false)
+const previewUrl = ref('')
+const previewType = ref('')
+const currentFile = ref<any>(null)
+const uploadRef = ref<UploadInstance>()
+const fileList = ref<UploadFile[]>([])
+
+const uploadHeaders = computed(() => {
+  const token = localStorage.getItem('token') || ''
+  return {
+    Authorization: `Bearer ${token}`
+  }
+})
+
+const searchForm = reactive({
+  filename: '',
+  fileType: ''
+})
+
+const pageInfo = reactive({
+  page: 1,
+  pageSize: 10,
+  total: 0
+})
+
+const tableData = ref<any[]>([])
+
+// 获取文件图标
+const getFileIcon = (fileType: string) => {
+  const iconMap: Record<string, any> = {
+    image: Picture,
+    document: Document,
+    video: VideoCamera,
+    audio: Headset,
+    other: Files
+  }
+  return iconMap[fileType] || Files
+}
+
+// 获取文件类型标签样式
+const getFileTypeTag = (fileType: string) => {
+  const typeMap: Record<string, any> = {
+    image: 'success',
+    document: 'primary',
+    video: 'warning',
+    audio: 'info',
+    other: ''
+  }
+  return typeMap[fileType] || ''
+}
+
+// 获取文件类型文本
+const getFileTypeText = (fileType: string) => {
+  const textMap: Record<string, string> = {
+    image: '图片',
+    document: '文档',
+    video: '视频',
+    audio: '音频',
+    other: '其他'
+  }
+  return textMap[fileType] || '其他'
+}
+
+// 格式化文件大小
+const formatFileSize = (bytes: number) => {
+  if (bytes === 0) return '0 B'
+  const k = 1024
+  const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
+  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+}
+
+const handleSearch = () => {
+  ElMessage.success('搜索成功')
+  // 实际项目中这里调用 API 获取数据
+  fetchFileList()
+}
+
+const handleReset = () => {
+  searchForm.filename = ''
+  searchForm.fileType = ''
+  fetchFileList()
+}
+
+const fetchFileList = async () => {
+  loading.value = true
+  try {
+    const res = await http.get<{ list: any[], total: number }>('/file/list', {
+      page: pageInfo.page,
+      size: pageInfo.pageSize,
+      ...searchForm
+    })
+    tableData.value = res.list
+    pageInfo.total = res.total
+  } catch (error) {
+    console.error('获取文件列表失败:', error)
+    ElMessage.error('获取文件列表失败')
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleUpload = () => {
+  uploadDialogVisible.value = true
+  fileList.value = []
+}
+
+const beforeUpload = (file: File) => {
+  const maxSize = 100 * 1024 * 1024 // 100MB
+  if (file.size > maxSize) {
+    ElMessage.error('文件大小不能超过 100MB')
+    return false
+  }
+  return true
+}
+
+const handleUploadSuccess = (response: any) => {
+  ElMessage.success('上传成功')
+  fetchFileList()
+}
+
+const handleUploadError = (error: any) => {
+  ElMessage.error('上传失败,请重试')
+}
+
+const handleDownload = (row: any) => {
+  http.download('/file/download', { id: row.id }, row.filename)
+    .then(() => {
+      ElMessage.success('开始下载')
+    })
+    .catch(() => {
+      ElMessage.error('下载失败')
+    })
+}
+
+const handlePreview = (row: any) => {
+  currentFile.value = row
+  if (row.fileType === 'image') {
+    previewType.value = 'image'
+    previewUrl.value = row.url || '/api/file/preview/' + row.id
+    previewDialogVisible.value = true
+  } else {
+    previewType.value = 'other'
+    previewDialogVisible.value = true
+  }
+}
+
+const handleDelete = (row: any) => {
+  ElMessageBox.confirm(`确定要删除文件 "${row.filename}" 吗?`, '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(async () => {
+    try {
+      await http.delete('/file/delete', { id: row.id })
+      ElMessage.success('删除成功')
+      fetchFileList()
+    } catch (error) {
+      ElMessage.error('删除失败')
+    }
+  }).catch(() => {
+    // 取消删除
+  })
+}
+
+const handleSizeChange = (val: number) => {
+  pageInfo.pageSize = val
+  pageInfo.page = 1
+  fetchFileList()
+}
+
+const handleCurrentChange = (val: number) => {
+  pageInfo.page = val
+  fetchFileList()
+}
+
+// 初始化
+fetchFileList()
+</script>
+
+<style scoped lang="scss">
+.file-container {
+  .card-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+
+  .search-form {
+    margin-bottom: 20px;
+  }
+
+  .file-name-cell {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+
+    .file-icon {
+      color: #409EFF;
+      flex-shrink: 0;
+    }
+
+    .filename {
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
+
+  .pagination {
+    margin-top: 20px;
+    display: flex;
+    justify-content: flex-end;
+  }
+
+  .preview-container {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    min-height: 400px;
+
+    .preview-image {
+      max-width: 100%;
+      max-height: 600px;
+    }
+
+    .preview-not-support {
+      text-align: center;
+      color: #909399;
+
+      p {
+        margin: 16px 0;
+      }
+    }
+  }
+}
+</style>