├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml └── workflows │ ├── ci.yml │ └── docker.yml ├── .gitignore ├── .prettierrc.js ├── LICENSE ├── README.md ├── docs ├── .nojekyll ├── README.md ├── _navbar.md ├── en │ ├── README.md │ ├── _navbar.md │ ├── _sidebar.md │ └── installation.md ├── index.html └── zh-cn │ ├── README.md │ ├── _navbar.md │ ├── _sidebar.md │ ├── advance.md │ ├── configuration.md │ └── installation.md ├── package.json ├── packages ├── sharelist-core │ ├── .prettierrc.js │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── index.js │ ├── lib │ │ ├── driver.js │ │ ├── index.js │ │ ├── rectifier.js │ │ ├── request.js │ │ └── utils.js │ ├── package-lock.json │ ├── package.json │ └── test │ │ ├── index.js │ │ └── plugin │ │ └── driver.test.js ├── sharelist-plugin │ ├── CHANGELOG.md │ ├── LICENSE │ ├── lib │ │ ├── auth.js │ │ ├── driver.189cloud.js │ │ ├── driver.aliyundrive.js │ │ ├── driver.baidu.js │ │ ├── driver.caiyun.js │ │ ├── driver.fs.js │ │ ├── driver.googledrive.js │ │ ├── driver.http.js │ │ ├── driver.https.js │ │ ├── driver.onedrive.js │ │ └── driver.sld.js │ └── package.json ├── sharelist-web │ ├── .env │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.js │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── public │ │ └── favicon.ico │ ├── src │ │ ├── App.tsx │ │ ├── assets │ │ │ ├── auto.mp3 │ │ │ ├── img │ │ │ │ └── line.png │ │ │ ├── logo.png │ │ │ └── style │ │ │ │ ├── icon.less │ │ │ │ └── index.less │ │ ├── components │ │ │ ├── audio │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── header │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── hintbar │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── icon │ │ │ │ ├── icon-svg.js │ │ │ │ ├── index.less │ │ │ │ └── index.ts │ │ │ ├── player │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── power │ │ │ │ └── index.js │ │ │ ├── qrcode │ │ │ │ └── index.tsx │ │ │ ├── sider │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── subscribe │ │ │ │ └── index.tsx │ │ │ ├── switch-mode │ │ │ │ └── index.tsx │ │ │ └── user-menu │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ ├── config │ │ │ ├── api.ts │ │ │ └── setting.ts │ │ ├── hooks │ │ │ ├── useConfirm.ts │ │ │ ├── useDisk.ts │ │ │ ├── useHooks.ts │ │ │ ├── useLoad.ts │ │ │ ├── useLocalStorage.ts │ │ │ ├── useSetting.ts │ │ │ ├── useStore.ts │ │ │ ├── useUrlState.ts │ │ │ └── utils.ts │ │ ├── index.html │ │ ├── main.ts │ │ ├── router │ │ │ └── index.ts │ │ ├── store │ │ │ ├── index.ts │ │ │ ├── mutation-types.ts │ │ │ └── state.ts │ │ ├── types │ │ │ ├── IDrive.ts │ │ │ ├── IRequest.ts │ │ │ ├── shim.d.ts │ │ │ └── source.d.ts │ │ ├── utils │ │ │ ├── format.ts │ │ │ ├── ipc.ts │ │ │ ├── promisify.js │ │ │ └── request.ts │ │ └── views │ │ │ ├── home │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ └── partial │ │ │ │ ├── auth │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ │ ├── breadcrumb │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ │ ├── error │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ │ ├── header │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ │ └── search │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ └── manage │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ └── partial │ │ │ ├── disk.tsx │ │ │ ├── drive-modifier.tsx │ │ │ ├── general.tsx │ │ │ └── signin.tsx │ ├── tsconfig.json │ └── vite.config.ts ├── sharelist-webdav │ ├── CHANGELOG.md │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── context.ts │ │ ├── index.ts │ │ ├── operations │ │ │ ├── commands.ts │ │ │ ├── copy.ts │ │ │ ├── delete.ts │ │ │ ├── get.ts │ │ │ ├── head.ts │ │ │ ├── lock.ts │ │ │ ├── mkcol.ts │ │ │ ├── move.ts │ │ │ ├── not-implemented.ts │ │ │ ├── options.ts │ │ │ ├── post.ts │ │ │ ├── propfind.ts │ │ │ ├── proppatch.ts │ │ │ ├── put.ts │ │ │ ├── shared.ts │ │ │ └── unlock.ts │ │ └── types.ts │ └── tsconfig.json └── sharelist │ ├── CHANGELOG.md │ ├── Dockerfile │ ├── LICENSE │ ├── README.md │ ├── app.js │ ├── app │ ├── config.js │ ├── controller │ │ ├── api.js │ │ └── shared.js │ ├── core │ │ ├── app.js │ │ ├── loader.js │ │ ├── request.js │ │ └── utils.js │ ├── index.js │ ├── middleware │ │ └── auth.js │ └── router.js │ ├── docker-compose.yml │ ├── example │ ├── 123.txt │ ├── README.md │ ├── fileSystem_current.d.ln │ ├── filesystem_linux_root.d.ln │ ├── filesystem_windows_disk_c.d.ln │ ├── github.com.url │ ├── google_drive.d.ln │ ├── google_drive2.d.ln │ ├── h5ai.d.ln │ ├── joy.d.ln │ ├── lanzou.d.ln │ ├── linkTo_download_ubuntu_18.iso.ln │ ├── link_webdav.d.ln │ ├── magnet.txt │ ├── one_drive.d.ln │ ├── one_drive_url.d.ln │ ├── preview │ │ ├── 123.torrent │ │ ├── 123.txt │ │ ├── audio.mp3 │ │ ├── image.jpg │ │ ├── movie.a.mp4 │ │ ├── movie.a.vtt │ │ ├── pdf.pdf │ │ ├── sintel.torrent │ │ └── work.docx │ ├── secret_folder │ │ ├── .passwd │ │ ├── Secret2 │ │ │ └── .passwd │ │ └── hello.txt │ ├── sharelist github repo.d.ln │ └── sharelist_drive.sld.ln │ ├── package.json │ └── package │ ├── guide │ ├── driver │ │ ├── aliyundrive.js │ │ ├── baidu.js │ │ ├── googledrive.js │ │ ├── onedrive.js │ │ └── shared.js │ └── index.js │ ├── sharelist │ ├── cache.js │ ├── config.js │ ├── db.js │ ├── index.js │ ├── reactivity │ │ ├── effect.js │ │ ├── index.js │ │ ├── reactive.js │ │ ├── scheduler.js │ │ ├── utils.js │ │ └── watch.js │ └── utils.js │ └── webdav │ └── index.js └── scripts ├── build.js ├── changelog.js ├── netinstall.sh └── release.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'vue-eslint-parser', 3 | parserOptions: { 4 | // set script parser 5 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 6 | ecmaVersion: 2021, // Allows for the parsing of modern ECMAScript features 7 | sourceType: 'module', // Allows for the use of imports 8 | ecmaFeatures: { 9 | jsx: true, // Allows for the parsing of JSX 10 | }, 11 | validate: [], 12 | }, 13 | extends: [ 14 | 'plugin:vue/vue3-recommended', 15 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 16 | 'plugin:prettier/recommended', 17 | ], 18 | rules: { 19 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 20 | // e.g. "@typescript-eslint/explicit-function-return-type": "off", 21 | '@typescript-eslint/no-unused-vars': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | '@typescript-eslint/no-empty-function': 'off', 24 | '@typescript-eslint/no-var-requires': 'off', 25 | 26 | 'prettier/prettier': ['off', { endOfLine: 'auto' }], 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "Bug report" 2 | description: 问题报告 3 | labels: [pending triage] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | - type: textarea 10 | id: bug-description 11 | attributes: 12 | label: 问题描述 / Describe the bug 13 | validations: 14 | required: true 15 | - type: dropdown 16 | id: version 17 | attributes: 18 | label: Sharelist 版本 / Sharelist Version 19 | description: Sharelist Version 20 | options: 21 | - v0.1 22 | - next 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: reproduction 27 | attributes: 28 | label: 复现链接 / Reproduction 29 | description: | 30 | 请提供能复现此问题的链接 31 | Please provide a link to a repo that can reproduce the problem you ran into. 32 | validations: 33 | required: false 34 | - type: textarea 35 | id: logs 36 | attributes: 37 | label: 日志 / Logs 38 | description: | 39 | 请复制错误日志,或者截图 40 | Please copy paste the log text. 41 | render: shell -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Questions & Discussions 4 | url: https://github.com/reruin/sharelist/discussions 5 | about: Use GitHub discussions for message-board style questions and discussions. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "Feature request" 2 | description: 功能需求 3 | labels: ["enhancement: pending triage"] 4 | body: 5 | - type: textarea 6 | id: feature-description 7 | attributes: 8 | label: 需求描述 / Description of the feature 9 | validations: 10 | required: true 11 | - type: textarea 12 | id: suggested-solution 13 | attributes: 14 | label: 实现思路 / Suggested solution 15 | description: 实现此需求的解决思路。 16 | validations: 17 | required: false 18 | - type: textarea 19 | id: additional-context 20 | attributes: 21 | label: 附件 / Additional context 22 | description: | 23 | 相关的任何其他上下文或截图。 24 | Any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - name: Use Node.js 16 | uses: actions/setup-node@v2 17 | with: 18 | node-version: '16' 19 | - name: Get Env 20 | uses: actions/github-script@v4 21 | with: 22 | script: | 23 | const tag = process.env.GITHUB_REF.split('/').slice(-1)[0] 24 | const createChangelog = require('./scripts/changelog.js') 25 | const content = await createChangelog('https://github.com/'+process.env.GITHUB_REPOSITORY) 26 | core.exportVariable('CHANGELOG', content) 27 | core.exportVariable('VERSION', tag) 28 | - name: Build 29 | run: | 30 | rm -rf ./packages/sharelist-webdav 31 | yarn install 32 | yarn build-web 33 | mkdir -p ./packages/sharelist/theme/default 34 | mkdir -p ./packages/sharelist/plugins 35 | cp -r ./packages/sharelist-web/dist/* ./packages/sharelist/theme/default 36 | cp -r ./packages/sharelist-plugin/lib/* ./packages/sharelist/plugins 37 | yarn build-server 38 | - name: Release 39 | run: | 40 | cd ./packages/sharelist/build 41 | tar --transform='flags=r;s|sharelist-win-x64.exe|sharelist.exe|' -zcvf sharelist_windows_amd64.tar.gz sharelist-win-x64.exe 42 | tar --transform='flags=r;s|sharelist-macos-x64|sharelist|' -zcvf sharelist_macos_amd64.tar.gz sharelist-macos-x64 43 | tar --transform='flags=r;s|sharelist-linux-x64|sharelist|' -zcvf sharelist_linux_amd64.tar.gz sharelist-linux-x64 44 | tar --transform='flags=r;s|sharelist-linux-arm64|sharelist|' -zcvf sharelist_linux_arm64.tar.gz sharelist-linux-arm64 45 | tar --transform='flags=r;s|sharelist-linuxstatic-armv7|sharelist|' -zcvf sharelist_linux_armv7.tar.gz sharelist-linuxstatic-armv7 46 | gh release create ${{ env.VERSION }} -n "${{ env.NOTE }}" -t "${{ env.VERSION }}" ${{ env.FILES }} 47 | env: 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | VERSION: ${{ env.VERSION }} 50 | NOTE: ${{ env.CHANGELOG }} 51 | TITLE: ${{ env.VERSION }} 52 | FILES: ./*.gz -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | on: 4 | push: 5 | # branches: 6 | # - next 7 | tags: 8 | - 'v*' 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | - name: Use Node.js 16 | uses: actions/setup-node@v2 17 | with: 18 | node-version: '14' 19 | 20 | - name: Install dependencies 21 | run: yarn install 22 | 23 | - name: Pre Build 24 | run: | 25 | yarn build-web 26 | mkdir -p ./packages/sharelist/theme/default 27 | mkdir -p ./packages/sharelist/plugins 28 | cp -r ./packages/sharelist-web/dist/* ./packages/sharelist/theme/default 29 | cp -r ./packages/sharelist-plugin/lib/* ./packages/sharelist/plugins 30 | 31 | - name: Login to Docker Hub 32 | uses: docker/login-action@v1 33 | with: 34 | username: ${{ secrets.DOCKER_HUB_USERNAME }} 35 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 36 | 37 | - name: Set up QEMU 38 | uses: docker/setup-qemu-action@v1 39 | 40 | - name: Set up Docker Buildx 41 | id: buildx 42 | uses: docker/setup-buildx-action@v1 43 | - name: Build and push 44 | id: docker_build 45 | uses: docker/build-push-action@v2 46 | with: 47 | context: ./packages/sharelist/ 48 | platforms: linux/amd64,linux/arm64 49 | file: ./packages/sharelist/Dockerfile 50 | builder: ${{ steps.buildx.outputs.name }} 51 | push: true 52 | tags: ${{ secrets.DOCKER_HUB_USERNAME }}/sharelist:${{ env.VERSION }} 53 | env: 54 | VERSION: next 55 | - name: Image digest 56 | run: echo ${{ steps.docker_build.outputs.digest }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build/ 3 | dist/ 4 | /packages/sharelist/theme/ 5 | /packages/sharelist/cache/ 6 | .vscode/ 7 | *.lock 8 | *.log 9 | *.exe -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // https://prettier.io/docs/en/configuration.html 2 | module.exports = { 3 | //分号终止符 4 | semi: false, 5 | 6 | //行尾逗号 7 | trailingComma: "all", 8 | 9 | // 使用单引号, 默认false(在jsx中配置无效, 默认都是双引号) 10 | singleQuote: true, 11 | 12 | printWidth: 120, 13 | // 换行符 14 | endOfLine: "auto", 15 | 16 | //缩进 default:2 17 | tabWidth: 2, 18 | 19 | endOfLine: "auto", 20 | 21 | arrowParens: "avoid" 22 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-present, Reruin and Sharelist contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShareList 2 | 3 | [![Build Status](https://github.com/reruin/sharelist/actions/workflows/ci.yml/badge.svg)](https://github.com/reruin/sharelist/actions/workflows/ci.yml) 4 | 5 | ShareList 是一个易用的网盘工具,支持快速挂载 GoogleDrive、OneDrive ,可通过插件扩展功能。 6 | 新版正在开发中,欢迎[提交反馈](https://github.com/reruin/sharelist/issues/new/choose)。[查看旧版](https://github.com/reruin/sharelist/tree/0.1)。 7 | 8 | ## 文档 9 | [查看文档](https://reruin.github.io/sharelist/docs/#/zh-cn/) 10 | 11 | ## 进度 12 | - [x] 核心库支持 13 | - [x] 新主题 14 | - [x] 插件:onedrive/aliyundrive/caiyun/ctcloud/baidu/localfile 15 | - [x] webdav 16 | 17 | | | 下载 | 上传 | 列目录 | 创建目录 | 删除 | 重命名 | 远程移动 | 18 | | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | 19 | Local File | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 20 | AliyunDrive | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 21 | CaiYun | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 22 | CTCloud | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 23 | Baidu Netdisk | ✓ | x | ✓ | ✓ | ✓ | ✓ | ✓ | 24 | OneDrive | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 25 | GoogleDrive | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 26 | 27 | - [ ] 中转器 28 | 29 | 30 | ## 安装 31 | ```docker 32 | docker run -d -v /etc/sharelist:/sharelist/cache -p 33001:33001 --name="sharelist" reruin/sharelist:next 33 | ``` 34 | 35 | [release](https://github.com/reruin/sharelist/releases)下载二进制版。 36 | 37 | 38 | ## 许可 39 | [MIT](https://opensource.org/licenses/MIT) 40 | Copyright (c) 2018-present, Reruin 41 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # ShareList 2 | 3 | [![Build Status](https://github.com/reruin/sharelist/actions/workflows/ci.yml/badge.svg)](https://github.com/reruin/sharelist/actions/workflows/ci.yml) 4 | 5 | ## Introduction 6 | ShareList is an easy-to-use netdisk tool which supports that quickly mount GoogleDrive and OneDrive, and extends functions with plugins. 7 | 8 | ## Documentation 9 | Check out [docs](https://reruin.github.io/sharelist/#/en/). 10 | 11 | ## License 12 | [Apache-2](http://www.apache.org/licenses/LICENSE-2.0) 13 | Copyright (c) 2018-present, Reruin -------------------------------------------------------------------------------- /docs/_navbar.md: -------------------------------------------------------------------------------- 1 | - Translations 2 | - [:uk: English](/) 3 | - [:cn: 中文](/zh-cn/) 4 | -------------------------------------------------------------------------------- /docs/en/README.md: -------------------------------------------------------------------------------- 1 | # ShareList 2 | 3 | [![Build Status](https://api.travis-ci.com/reruin/sharelist.svg?branch=master)](https://travis-ci.com/reruin/sharelist) 4 | 5 | ## Introduction 6 | ShareList is an easy-to-use netdisk tool which supports that quickly mount GoogleDrive and OneDrive, and extends functions with plugins. 7 | 8 | ## Documentation 9 | Check out [docs](https://reruin.github.io/sharelist/#/en/). 10 | 11 | ## License 12 | [Apache-2](http://www.apache.org/licenses/LICENSE-2.0) 13 | Copyright (c) 2018-present, Reruin -------------------------------------------------------------------------------- /docs/en/_navbar.md: -------------------------------------------------------------------------------- 1 | - Translations 2 | - [中文](/zh-cn/) 3 | - [English](/en/) -------------------------------------------------------------------------------- /docs/en/_sidebar.md: -------------------------------------------------------------------------------- 1 | * [Installation](en/installation.md) 2 | * [Configuration](en/configuration.md) 3 | * [Advance](en/advance.md) -------------------------------------------------------------------------------- /docs/en/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | Translation needed! 4 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ShareList Docs 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /docs/zh-cn/README.md: -------------------------------------------------------------------------------- 1 | # ShareList 2 | 3 | [![Build Status](https://github.com/reruin/sharelist/actions/workflows/ci.yml/badge.svg)](https://github.com/reruin/sharelist/actions/workflows/ci.yml) 4 | 5 | ## 介绍 6 | ShareList 是一个易用的网盘工具,支持快速挂载 GoogleDrive、OneDrive ,可通过插件扩展功能。 7 | 8 | ## 许可 9 | [Apache-2](http://www.apache.org/licenses/LICENSE-2.0) 10 | Copyright (c) 2018-present, Reruin -------------------------------------------------------------------------------- /docs/zh-cn/_navbar.md: -------------------------------------------------------------------------------- 1 | - Translations 2 | - [中文](/zh-cn/) 3 | - [English](/en/) -------------------------------------------------------------------------------- /docs/zh-cn/_sidebar.md: -------------------------------------------------------------------------------- 1 | * [安装](zh-cn/installation.md) 2 | * [后台配置](zh-cn/configuration.md) 3 | * [高级用法](zh-cn/advance.md) -------------------------------------------------------------------------------- /docs/zh-cn/advance.md: -------------------------------------------------------------------------------- 1 | ## 目录加密 2 | 在需加密目录内新建 ```.passwd``` 文件(此项可修改),```type```为验证方式,```data```为验证内容。 3 | 例如: 4 | ```yaml 5 | type: basic 6 | data: 7 | - 123456 8 | - abcdef 9 | ``` 10 | 11 | 可使用密码```123456```,```abcdef```验证。 12 | 13 | *** 14 | 15 | ## 获取文件夹ID 16 | 保持后台登录状态,回到首页列表,点击文件夹后的 '!' 按钮 可查看文件夹ID。 17 | 18 | *** 19 | 20 | ## Nginx(Caddy)反向代理 21 | 使用反代时,请添加以下配置。 22 | 23 | #### Nginx 24 | ```ini 25 | proxy_set_header Host $host; 26 | proxy_set_header X-Real-IP $remote_addr; 27 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 28 | proxy_set_header X-Forwarded-Proto $scheme; 29 | 30 | proxy_set_header Range $http_range; 31 | proxy_set_header If-Range $http_if_range; 32 | proxy_no_cache $http_range $http_if_range; 33 | ``` 34 | 如果使用上传功能,请调整 nginx 上传文件大小限制。 35 | ``` 36 | client_max_body_size 8000m; 37 | ``` 38 | #### Caddy 39 | ```ini 40 | header_upstream Host {host} 41 | header_upstream X-Real-IP {remote} 42 | header_upstream X-Forwarded-For {remote} 43 | header_upstream X-Forwarded-Proto {scheme} 44 | ``` -------------------------------------------------------------------------------- /docs/zh-cn/configuration.md: -------------------------------------------------------------------------------- 1 | # 后台管理 2 | 访问 ```http://localhost:33001/@manage```,填写口令即可进入后台管理。 3 | 4 | ## 常规 5 | 6 | ### 后台管理 7 | 设置后台管理密码。默认 ```sharelist```。 8 | 9 | ### 网站标题 10 | 设置网站标题。 11 | 12 | ### 目录索引 13 | 默认启用。如果只提供下载功能,可禁用此项。 14 | 15 | ### 展开单一挂载盘 16 | 默认启用。如果只有一个挂载盘,将默认展开。 17 | 18 | ### 允许下载 19 | 默认启用。 20 | 21 | ### 忽略路径 22 | 设置禁止访问的目录/文件路径。支持 [gitignore](http://git-scm.com/docs/gitignore) 表达式 23 | 24 | ### 加密文件名 25 | 默认```.passwd```,修改此项自定义加密文件名。 26 | 27 | ### WebDAV 路径 28 | WebDAV路径。 29 | 30 | ### WebDAV 代理 31 | 默认启用。 32 | 33 | ### WebDAV 用户 34 | 默认 ```admin```。 35 | 36 | ### WebDAV 密码 37 | 默认 ```sharelist```。 38 | 39 | ### 自定义脚本 40 | 默认主题支持自定义脚本。可用于插入统计脚本。 41 | 42 | ### 自定义样式 43 | 默认主题支持自定义样式。 -------------------------------------------------------------------------------- /docs/zh-cn/installation.md: -------------------------------------------------------------------------------- 1 | # 安装 2 | Sharelist支持多种安装方式。 3 | 4 | ## Docker 5 | ```bash 6 | docker run -d -v /etc/sharelist:/sharelist/cache -p 33001:33001 --name="sharelist" reruin/sharelist:next 7 | ``` 8 | 9 | ## 二进制版 10 | [release](https://github.com/reruin/sharelist/releases)下载二进制版。 11 | 12 | 13 | ## Heroku 14 | 请 Fork [sharelist-heroku](https://github.com/reruin/sharelist-heroku/tree/next),然后在个人仓库下点 Deploy to HeroKu。 15 | 16 | 17 | 安装完成首次访问 `http://localhost:33001`地址,将进入默认界面。访问`http://localhost:33001/@manage` 进入后台管理,默认口令为 ```sharelist```。 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sharelist-monorepo", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/*" 6 | ], 7 | "engines": { 8 | "node": ">=14.0.0" 9 | }, 10 | "scripts": { 11 | "dev": "cd packages/sharelist && yarn dev", 12 | "build-server": "cd packages/sharelist && yarn pkg", 13 | "dev-web": "cd packages/sharelist-web && yarn dev", 14 | "build-web": "cd packages/sharelist-web && yarn build", 15 | "build": "node scripts/build.js", 16 | "lint": "eslint --ext .ts packages/*/src/**.ts", 17 | "format": "prettier --write --parser typescript \"packages/**/*.ts?(x)\"", 18 | "release": "node scripts/release.js", 19 | "ci": "cd packages/sharelist && yarn release", 20 | "changelog": "node scripts/changelog.js", 21 | "pkg": "cd packages/sharelist && yarn pkg-demo" 22 | }, 23 | "devDependencies": { 24 | "chalk": "^4.1.2", 25 | "conventional-changelog-cli": "^2.1.1", 26 | "cross-env": "^7.0.3", 27 | "eslint": "^7.28.0", 28 | "execa": "^5.1.1", 29 | "minimist": "^1.2.5", 30 | "nodemon": "^2.x", 31 | "pkg": "^5.3.1", 32 | "prompts": "^2.4.1", 33 | "semver": "^7.3.5" 34 | } 35 | } -------------------------------------------------------------------------------- /packages/sharelist-core/.prettierrc.js: -------------------------------------------------------------------------------- 1 | // https://prettier.io/docs/en/configuration.html 2 | module.exports = { 3 | //分号终止符 4 | semi: false, 5 | 6 | //行尾逗号 7 | trailingComma: "all", 8 | 9 | // 使用单引号, 默认false(在jsx中配置无效, 默认都是双引号) 10 | singleQuote: true, 11 | 12 | printWidth: 120, 13 | // 换行符 14 | endOfLine: "auto", 15 | 16 | //缩进 default:2 17 | tabWidth: 2 18 | } -------------------------------------------------------------------------------- /packages/sharelist-core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.1.7](https://github.com/reruin/sharelist/compare/v0.3.6...v0.1.7) (2021-10-14) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **core:** fix some bugs ([c8ee9e6](https://github.com/reruin/sharelist/commit/c8ee9e655687d49420c54c9331a91b19aae10beb)) 7 | 8 | 9 | 10 | ## [0.1.6](https://github.com/reruin/sharelist/compare/v0.3.5...v0.1.6) (2021-10-12) 11 | 12 | 13 | 14 | ## [0.1.5](https://github.com/reruin/sharelist/compare/v0.3.3...v0.1.5) (2021-10-10) 15 | 16 | 17 | ### Bug Fixes 18 | 19 | * **core:** add createReadStream ([27350fb](https://github.com/reruin/sharelist/commit/27350fb6a036ab13e65dc0b52dab3f85732d5667)) 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /packages/sharelist-core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-present, Reruin and Sharelist contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/sharelist-core/README.md: -------------------------------------------------------------------------------- 1 | # @sharelist/core [![npm](https://img.shields.io/npm/v/@sharelist/core.svg)](https://npmjs.com/package/@sharelist/core) 2 | 3 | It's a framework for mounting netdisk. 4 | 5 | ## Useage 6 | -------------------------------------------------------------------------------- /packages/sharelist-core/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/index') 2 | -------------------------------------------------------------------------------- /packages/sharelist-core/lib/rectifier.js: -------------------------------------------------------------------------------- 1 | const { resolve4 } = require('dns') 2 | const { Readable, Writable } = require('stream') 3 | 4 | exports.createReadStream = function (readStream, { highWaterMark } = { highWaterMark: 10 * 1024 * 1024 }) { 5 | const buffers = []// 缓冲区 6 | 7 | let total = 0 8 | let tasks = [] 9 | let ended = false 10 | const sliceBuffer = (n) => { 11 | let part = Buffer.alloc(n) 12 | let b 13 | let index = 0 14 | let flag = false 15 | while (null != (b = buffers.shift())) { 16 | for (let i = 0; i < b.length; i++) { 17 | part[index++] = b[i] 18 | if (index == n) {//填充完毕 19 | //将多出的部分存回头部 20 | b = b.slice(i + 1) 21 | buffers.unshift(b) 22 | flag = true 23 | break; 24 | } 25 | } 26 | if (flag) break; 27 | } 28 | total -= n 29 | return part 30 | } 31 | 32 | 33 | const check = () => { 34 | let task = tasks[0] 35 | 36 | if (task) { 37 | if (total >= task.size) { 38 | let chunk = sliceBuffer(task.size) 39 | task.done(chunk) 40 | tasks.shift() 41 | } else { 42 | if (ended) { 43 | let chunk = sliceBuffer(total) 44 | task.done(chunk) 45 | tasks.shift() 46 | } 47 | } 48 | } 49 | 50 | if (!ended) { 51 | if (tasks.length == 0 && total > highWaterMark) { 52 | readStream.pause() 53 | } else { 54 | if (readStream.isPaused()) { 55 | readStream.resume() 56 | } 57 | } 58 | } 59 | } 60 | 61 | const read = (size) => new Promise((resolve, reject) => { 62 | if (ended && total == 0) { 63 | resolve() 64 | } else { 65 | tasks.push({ size, done: resolve }) 66 | check() 67 | } 68 | }) 69 | 70 | 71 | readStream.on('data', (chunk) => { 72 | total += chunk.length 73 | buffers.push(chunk.slice(0)) 74 | check() 75 | }) 76 | 77 | readStream.on('end', () => { 78 | ended = true 79 | check() 80 | //let task = tasks.pop() 81 | }) 82 | return { read } 83 | } 84 | 85 | exports.rectifier = function (size = 1 * 1024 * 1024, cb) { 86 | 87 | const buffers = []// 缓冲区 88 | 89 | let total = 0 90 | let part = 0 91 | let partSize = 0 92 | let cacheRate = 3 93 | 94 | const writable = new Writable({ 95 | write(chunk, encoding, callback) { 96 | let bytesRead = chunk.length 97 | 98 | partSize += bytesRead 99 | total += bytesRead 100 | 101 | buffers.push(chunk.slice(0)) 102 | 103 | //截取片段 104 | if (partSize >= size) { 105 | let n = size 106 | let partBuffer = Buffer.alloc(n) 107 | let b 108 | let index = 0 109 | let flag = false 110 | while (null != (b = buffers.shift())) { 111 | for (let i = 0; i < b.length; i++) { 112 | partBuffer[index++] = b[i] 113 | if (index == n) {//填充完毕 114 | 115 | //将多出的部分存回头部 116 | b = b.slice(i + 1) 117 | buffers.unshift(b) 118 | partSize = b.length 119 | flag = true 120 | break; 121 | } 122 | } 123 | if (flag) break; 124 | } 125 | 126 | // console.log('partSize', size, partSize, total) 127 | //缓冲 128 | if (partSize < size * cacheRate) { 129 | callback() 130 | } 131 | 132 | cb({ total, chunk: partBuffer, chunk_index: part++ }, () => { 133 | //callback() 134 | }) 135 | } else { 136 | //继续写入 137 | callback() 138 | } 139 | }, 140 | 141 | // 处理剩余的部分 142 | final(callback) { 143 | let n = total - size * part 144 | 145 | // 文件大小是 size 的倍数时 146 | if (n == 0) { 147 | callback() 148 | return 149 | } 150 | 151 | //处理掉剩余部分 152 | let partBuffer = Buffer.alloc(n) 153 | let b 154 | let index = 0 155 | while (null != (b = buffers.shift())) { 156 | for (let i = 0; i < b.length; i++) { 157 | partBuffer[index++] = b[i] 158 | } 159 | } 160 | 161 | cb({ total, chunk: partBuffer, chunk_index: part++, ended: true }, () => { 162 | callback() 163 | }) 164 | } 165 | }) 166 | 167 | 168 | return writable 169 | 170 | } -------------------------------------------------------------------------------- /packages/sharelist-core/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sharelist/core", 3 | "version": "0.1.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "imurmurhash": { 8 | "version": "0.1.4", 9 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 10 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" 11 | }, 12 | "is-typedarray": { 13 | "version": "1.0.0", 14 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 15 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 16 | }, 17 | "mime": { 18 | "version": "2.5.2", 19 | "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", 20 | "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==" 21 | }, 22 | "node-fetch": { 23 | "version": "2.6.1", 24 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 25 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" 26 | }, 27 | "signal-exit": { 28 | "version": "3.0.3", 29 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", 30 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" 31 | }, 32 | "typedarray-to-buffer": { 33 | "version": "3.1.5", 34 | "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", 35 | "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", 36 | "requires": { 37 | "is-typedarray": "^1.0.0" 38 | } 39 | }, 40 | "write-file-atomic": { 41 | "version": "3.0.3", 42 | "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", 43 | "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", 44 | "requires": { 45 | "imurmurhash": "^0.1.4", 46 | "is-typedarray": "^1.0.0", 47 | "signal-exit": "^3.0.2", 48 | "typedarray-to-buffer": "^3.1.5" 49 | } 50 | }, 51 | "yaml": { 52 | "version": "1.10.2", 53 | "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", 54 | "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/sharelist-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sharelist/core", 3 | "version": "0.1.7", 4 | "repository": "https://github.com/reruin/sharelist", 5 | "license": "MIT", 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "echo \"Info: no specified\" && exit ", 9 | "test": "node test/index.js", 10 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path .", 11 | "release": "node ../../scripts/release.js --commit-path ." 12 | }, 13 | "dependencies": { 14 | "mime": "^2.5.2", 15 | "node-fetch": "^2.6.1", 16 | "write-file-atomic": "^3.0.3", 17 | "yaml": "^1.10.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/sharelist-core/test/index.js: -------------------------------------------------------------------------------- 1 | const createDriver = require('../') 2 | 3 | const path = require('path') 4 | 5 | const testdriver = () => { 6 | const list = (id) => { 7 | return { 8 | id: 'folder', 9 | files: new Array(10).fill(0).map((i) => ({ 10 | id: 'folder' + i, 11 | name: 'file' + i, 12 | size: 0, 13 | type: 'folder', 14 | ctime: Date.now(), 15 | mtime: Date.now(), 16 | })), 17 | } 18 | } 19 | 20 | const get = (id) => { 21 | return { 22 | id: id, 23 | name: 'file' + id, 24 | size: 0, 25 | type: 'file', 26 | ctime: Date.now(), 27 | mtime: Date.now(), 28 | } 29 | } 30 | 31 | return { name: 'test', protocol: 'test', mountable: true, list, get } 32 | }; 33 | 34 | (async () => { 35 | const driver = await createDriver({ 36 | plugins: [testdriver] 37 | }) 38 | 39 | console.log(await driver.list()) 40 | })() 41 | -------------------------------------------------------------------------------- /packages/sharelist-core/test/plugin/driver.test.js: -------------------------------------------------------------------------------- 1 | modules.export = () => { 2 | const list = (id) => { 3 | return { 4 | id: 'folder', 5 | files: new Array(10).fill(0).map((i) => ({ 6 | id: 'folder' + i, 7 | name: 'file' + i, 8 | size: 0, 9 | type: 'folder', 10 | ctime: Date.now(), 11 | mtime: Date.now(), 12 | })), 13 | } 14 | } 15 | 16 | const get = (id) => { 17 | return { 18 | id: id, 19 | name: 'file' + id, 20 | size: 0, 21 | type: 'file', 22 | ctime: Date.now(), 23 | mtime: Date.now(), 24 | } 25 | } 26 | 27 | return { name: 'test', protocol: 'test', mountable: true, list, get } 28 | } 29 | -------------------------------------------------------------------------------- /packages/sharelist-plugin/CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-plugin/CHANGELOG.md -------------------------------------------------------------------------------- /packages/sharelist-plugin/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-present, Reruin and Sharelist contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/sharelist-plugin/lib/auth.js: -------------------------------------------------------------------------------- 1 | 2 | const authMethods = { 3 | 'basic': (key, data) => { 4 | return data.find(i => '' + i == key) 5 | }, 6 | 'http': (key, data, { request }) => { 7 | let { data: res } = request(data.replace('{key}', key), { responseType: 'text' }) 8 | return res === 'success' 9 | } 10 | } 11 | 12 | const cache = {} 13 | 14 | module.exports = (app) => { 15 | const { onListed, config, driver, utils: { yaml, safeCall } } = app 16 | 17 | onListed(async (data, query) => { 18 | let hit = data?.files.find(i => i.name == config.acl_file) 19 | if (hit) { 20 | let auth = query?.auth 21 | 22 | if (auth) { 23 | let content 24 | 25 | if (cache[hit.id]) { 26 | content = cache[hit.id] 27 | } else { 28 | await safeCall(async () => { 29 | content = yaml.parse(await driver.getContent(hit.id)) 30 | }) 31 | } 32 | 33 | if (content) { 34 | cache[hit.id] = content 35 | 36 | if (authMethods?.[content.type]('' + auth, content.data, app)) { 37 | return data 38 | } 39 | } 40 | 41 | return { error: { code: 401, message: 'Invalid password' } } 42 | } else { 43 | return { error: { code: 401, message: 'Invalid password' } } 44 | } 45 | } 46 | 47 | return data 48 | }) 49 | 50 | } -------------------------------------------------------------------------------- /packages/sharelist-plugin/lib/driver.http.js: -------------------------------------------------------------------------------- 1 | /* 2 | * http 3 | */ 4 | 5 | const name = 'HTTPFile' 6 | 7 | const version = '1.0' 8 | 9 | const protocol = 'http' 10 | 11 | module.exports = ({ wrapReadableStream, request }) => { 12 | const file = (id) => { 13 | return { 14 | id, 15 | name: id.split('/').pop(), 16 | ext: id.split('.').pop(), 17 | url: `http:${id}`, 18 | protocol: 'http', 19 | } 20 | } 21 | 22 | const folder = file 23 | 24 | const getFileSize = async (url, headers) => { 25 | try { 26 | let nh = await request.header(decodeURIComponent(url), { headers }) 27 | if (nh && nh['content-length']) { 28 | return nh['content-length'] 29 | } else { 30 | return null 31 | } 32 | } catch (e) { 33 | return null 34 | } 35 | } 36 | 37 | const createReadStream = async ({ id, options = {} } = {}) => { 38 | let url = encodeURI(`http:${id}`) 39 | let size = await getFileSize(url) 40 | console.log('get file size', size) 41 | let readstream = request({ url: decodeURIComponent(url), method: 'get' }) 42 | return wrapReadableStream(readstream, { size }) 43 | } 44 | 45 | return { name, version, drive: { protocol, folder, file, createReadStream, mountable: false } } 46 | } 47 | -------------------------------------------------------------------------------- /packages/sharelist-plugin/lib/driver.https.js: -------------------------------------------------------------------------------- 1 | /* 2 | * https 3 | */ 4 | 5 | const name = 'HTTPSFile' 6 | 7 | const version = '1.0' 8 | 9 | const protocol = 'https' 10 | 11 | module.exports = ({ wrapReadableStream, request }) => { 12 | const file = (id) => { 13 | return { 14 | id, 15 | name: id.split('/').pop(), 16 | ext: id.split('.').pop(), 17 | url: `https:${id}`, 18 | protocol: 'https', 19 | } 20 | } 21 | 22 | const folder = file 23 | 24 | const getFileSize = async (url, headers) => { 25 | try { 26 | let nh = await request.header(decodeURIComponent(url), { headers }) 27 | if (nh && nh['content-length']) { 28 | return nh['content-length'] 29 | } else { 30 | return null 31 | } 32 | } catch (e) { 33 | return null 34 | } 35 | } 36 | 37 | const createReadStream = async ({ id, options = {} } = {}) => { 38 | let url = encodeURI(`https:${id}`) 39 | let size = await getFileSize(url) 40 | console.log('get file size', size) 41 | let readstream = request({ url: decodeURIComponent(url), method: 'get' }) 42 | return wrapReadableStream(readstream, { size }) 43 | } 44 | 45 | return { name, version, drive: { protocol, folder, file, createReadStream, mountable: false } } 46 | } 47 | -------------------------------------------------------------------------------- /packages/sharelist-plugin/lib/driver.sld.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ShareListDrive 是 sharelist 内置的使用yaml/json描述的网盘系统 , 没有缓存 3 | */ 4 | 5 | const name = 'ShareListDrive' 6 | 7 | const version = '1.0' 8 | 9 | const protocol = 'sld' 10 | 11 | const yaml = require('yaml') 12 | 13 | const { URL } = require('url') 14 | 15 | const crypto = require('crypto') 16 | 17 | const md5 = (v) => { 18 | return crypto.createHash('md5').update(v).digest('hex') 19 | } 20 | 21 | module.exports = (app) => { 22 | const diskMap = {} 23 | 24 | const parse = (id) => { 25 | let data = new URL(id) 26 | 27 | let rootId = data.hostname 28 | let path = data.pathname.replace(/^\/+/, '').split('/') 29 | return { 30 | disk: diskMap[rootId], 31 | path, 32 | } 33 | } 34 | 35 | /* 递归生成 索引 id */ 36 | const createId = (d, rootId) => { 37 | d.forEach((i, index) => { 38 | if (isObject(i)) { 39 | let name = i.name.replace(/\.d\.ln$/, '').replace(/\.ln$/, '') 40 | i.id = rootId + '/' + name 41 | i.protocol = protocol 42 | if (i.children) { 43 | i.type = 'folder' 44 | i.protocol = protocol 45 | createId(i.children, i.id) 46 | } else { 47 | i.ext = i.name.split('.').pop() 48 | } 49 | } else if (isArray(i)) { 50 | createId(i, rootId) 51 | } 52 | }) 53 | return d 54 | } 55 | 56 | const mount = async (key, data) => { 57 | if (data) { 58 | let key = md5(data) 59 | 60 | let id = protocol + '://' + key 61 | 62 | let resp = { id, type: 'folder', protocol: protocol } 63 | 64 | let json = yaml.parse(data) 65 | json = createId(json, id) 66 | resp.children = json 67 | resp.updated_at = Date.now() 68 | 69 | diskMap[key] = resp 70 | return resp 71 | } else { 72 | return undefined 73 | } 74 | } 75 | 76 | const findById = (id) => { 77 | let { disk, path } = parse(id) 78 | if (disk) { 79 | for (let i = 0; i < path.length && disk; i++) { 80 | disk = disk.children 81 | disk = disk.find((j) => { 82 | return j.name == decodeURIComponent(path[i]) 83 | 84 | if (j.type == 'folder') { 85 | return `${j.name}.${j.ext}` == path[i] 86 | } else { 87 | return `${j.name}` == path[i] 88 | } 89 | }) //[ parseInt(path[i]) ] 90 | } 91 | return disk 92 | } else { 93 | return [] 94 | } 95 | } 96 | 97 | const folder = async (id, { content } = {}) => { 98 | try { 99 | let json = yaml.parse(id) 100 | if (json && typeof json == 'object') { 101 | return mount(null, id) 102 | } else { 103 | throw new Error('') 104 | } 105 | } catch (e) { 106 | if (content) { 107 | return mount(id, content) 108 | } else { 109 | return findById(id) 110 | } 111 | } 112 | } 113 | 114 | const file = async (id) => { 115 | let data = findById(id) 116 | return data 117 | } 118 | 119 | return { name, version, drive: { protocol, folder, file } } 120 | } 121 | -------------------------------------------------------------------------------- /packages/sharelist-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sharelist-plugin", 3 | "version": "0.2.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "echo \"\" && exit ", 7 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path .", 8 | "release": "node ../../scripts/release.js --skipNpmPublish" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/sharelist-web/.env: -------------------------------------------------------------------------------- 1 | PORT = 8889 2 | MAIN_ES = 0 -------------------------------------------------------------------------------- /packages/sharelist-web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'vue-eslint-parser', 3 | parserOptions: { 4 | // set script parser 5 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 6 | ecmaVersion: 2021, // Allows for the parsing of modern ECMAScript features 7 | sourceType: 'module', // Allows for the use of imports 8 | ecmaFeatures: { 9 | jsx: true, // Allows for the parsing of JSX 10 | }, 11 | validate: [], 12 | }, 13 | extends: [ 14 | 'plugin:vue/vue3-recommended', 15 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 16 | 'plugin:prettier/recommended', 17 | ], 18 | rules: { 19 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 20 | // e.g. "@typescript-eslint/explicit-function-return-type": "off", 21 | '@typescript-eslint/no-unused-vars': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | '@typescript-eslint/no-empty-function': 'off', 24 | 25 | // 'object-curly-spacing': ['error', 'always'], 26 | }, 27 | } 28 | -------------------------------------------------------------------------------- /packages/sharelist-web/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | yarn-error.log 7 | package-lock.json 8 | yarn.lock -------------------------------------------------------------------------------- /packages/sharelist-web/.prettierrc.js: -------------------------------------------------------------------------------- 1 | // https://prettier.io/docs/en/configuration.html 2 | module.exports = { 3 | //分号终止符 4 | semi: false, 5 | 6 | //行尾逗号 7 | trailingComma: "all", 8 | 9 | // 使用单引号, 默认false(在jsx中配置无效, 默认都是双引号) 10 | singleQuote: true, 11 | 12 | printWidth: 120, 13 | // 换行符 14 | endOfLine: "auto", 15 | 16 | //缩进 default:2 17 | tabWidth: 2 18 | } -------------------------------------------------------------------------------- /packages/sharelist-web/CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-web/CHANGELOG.md -------------------------------------------------------------------------------- /packages/sharelist-web/README.md: -------------------------------------------------------------------------------- 1 | # @sharelist/web [![npm](https://img.shields.io/npm/v/@sharelist/web.svg)](https://npmjs.com/package/@sharelist/web) 2 | 3 | It's a part for sharelist 4 | 5 | ## Useage 6 | -------------------------------------------------------------------------------- /packages/sharelist-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sharelist/web", 3 | "version": "0.2.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path .", 8 | "release": "node ../../scripts/release.js --skipBuild --skipNpmPublish" 9 | }, 10 | "dependencies": { 11 | "@ant-design/icons-vue": "^6.0.1", 12 | "ant-design-vue": "2.x", 13 | "axios": "^0.21.0", 14 | "plyr": "^3.6.8", 15 | "store2": "^2.12.0", 16 | "vite": "2.3.8", 17 | "vue": "3.x", 18 | "vue-router": "4.x", 19 | "vuex": "4.x", 20 | "vuex-persistedstate": "^4.x" 21 | }, 22 | "devDependencies": { 23 | "@types/node": "^15.9.0", 24 | "@typescript-eslint/eslint-plugin": "^4.23.0", 25 | "@typescript-eslint/parser": "^4.23.0", 26 | "@vitejs/plugin-legacy": "^1.5.1", 27 | "@vitejs/plugin-vue": "^1.2.2", 28 | "@vitejs/plugin-vue-jsx": "^1.1.4", 29 | "@vue/compiler-sfc": "^3.0.5", 30 | "eslint": "^7.26.0", 31 | "eslint-config-prettier": "^8.3.0", 32 | "eslint-plugin-prettier": "^3.4.0", 33 | "eslint-plugin-vue": "^7.9.0", 34 | "less": "^4.1.1", 35 | "minimist": "^1.2.5", 36 | "prettier": "^2.3.0", 37 | "typescript": "^4.1.3", 38 | "vite": "2.3.8", 39 | "vue-eslint-parser": "^7.6.0", 40 | "vue-tsc": "^0.0.24" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/sharelist-web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-web/public/favicon.ico -------------------------------------------------------------------------------- /packages/sharelist-web/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import { ConfigProvider } from 'ant-design-vue' 3 | import { RouterView } from 'vue-router' 4 | 5 | export default defineComponent({ 6 | setup() { 7 | return () => ( 8 | 9 | 10 | 11 | ) 12 | }, 13 | }) 14 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/assets/auto.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-web/src/assets/auto.mp3 -------------------------------------------------------------------------------- /packages/sharelist-web/src/assets/img/line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-web/src/assets/img/line.png -------------------------------------------------------------------------------- /packages/sharelist-web/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-web/src/assets/logo.png -------------------------------------------------------------------------------- /packages/sharelist-web/src/components/audio/index.less: -------------------------------------------------------------------------------- 1 | .widget-audio{ 2 | display: none; 3 | position: fixed; 4 | bottom:16px; 5 | margin:auto; 6 | display: none; 7 | .aplayer{ 8 | margin: auto; 9 | max-width: 500px; 10 | position: relative !important; 11 | } 12 | 13 | .aplayer.aplayer-fixed{ 14 | .aplayer-body{ 15 | position: relative !important; 16 | } 17 | .aplayer-list{ 18 | margin-bottom: 0 !important; 19 | } 20 | } 21 | } 22 | 23 | .widget-audio--visible{ 24 | display: block; 25 | // align-items: top; 26 | } 27 | 28 | .widget-close{ 29 | position: absolute; 30 | z-index:1002; 31 | right:0; 32 | top:0; 33 | font-size:20px; 34 | color:#666; 35 | width:18px;height:18px; 36 | text-align: center; 37 | line-height: 18px; 38 | cursor: pointer; 39 | 40 | &:hover{ 41 | color:rgb(223,73,62); 42 | } 43 | } -------------------------------------------------------------------------------- /packages/sharelist-web/src/components/audio/index.tsx: -------------------------------------------------------------------------------- 1 | import { ref, defineComponent, onMounted, computed } from 'vue' 2 | import { CaretRightOutlined, PauseOutlined } from '@ant-design/icons-vue' 3 | import './index.less' 4 | import APlayer from 'aplayer' 5 | import 'aplayer/dist/APlayer.min.css' 6 | export default defineComponent({ 7 | props: { 8 | src: { 9 | type: String, 10 | default: '', 11 | }, 12 | }, 13 | emits: ['loaded'], 14 | setup(props, ctx) { 15 | const el = ref() 16 | const visible = ref(false) 17 | let player: any 18 | const onAdd = () => { 19 | visible.value = true 20 | } 21 | 22 | const onClose = () => { 23 | player.pause() 24 | visible.value = false 25 | } 26 | onMounted(() => { 27 | player = new APlayer({ 28 | container: el.value, 29 | fixed: true, 30 | // theme: '#000000', 31 | }) 32 | 33 | player.on('listadd', onAdd) 34 | ctx.emit('loaded', player) 35 | }) 36 | 37 | return () => ( 38 |
39 |
40 |
41 | × 42 |
43 |
44 | ) 45 | }, 46 | }) 47 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/components/header/index.less: -------------------------------------------------------------------------------- 1 | .content-header { 2 | padding: 8px; 3 | // height:54px; 4 | background: #fff; 5 | position: relative; 6 | 7 | .content-header-fixed{ 8 | position:absolute; 9 | right:0; 10 | top:0; 11 | margin:6px; 12 | } 13 | .content-header-body{ 14 | display: flex; 15 | justify-content: space-between; 16 | align-items:center; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/components/header/index.tsx: -------------------------------------------------------------------------------- 1 | import { computed, defineComponent, PropType, inject } from 'vue' 2 | import UserMenu from '../user-menu' 3 | import HintBar from '../hintbar' 4 | import SwitchMode from '../switch-mode' 5 | import Subscribe from '../subscribe' 6 | 7 | import './index.less' 8 | 9 | export default defineComponent({ 10 | setup() { 11 | return ( 12 |
13 | 14 |
15 | 16 |
17 | 18 |
19 |
20 |
21 | ) 22 | }, 23 | }) 24 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/components/hintbar/index.less: -------------------------------------------------------------------------------- 1 | .hintbar { 2 | button { 3 | font-size: 12px; 4 | color: rgb(170, 173, 174); 5 | // padding:0 8px; 6 | } 7 | 8 | button:hover { 9 | color: #333; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/components/hintbar/index.tsx: -------------------------------------------------------------------------------- 1 | import { computed, defineComponent, PropType, inject } from 'vue' 2 | import { Modal, message, Button } from 'ant-design-vue' 3 | 4 | import './index.less' 5 | 6 | type IStyle = { 7 | [key: string]: string | number 8 | } 9 | export default defineComponent({ 10 | props: { 11 | color: { 12 | type: String, 13 | default: '', 14 | }, 15 | }, 16 | setup(props, ctx) { 17 | const { obj } = props 18 | const $electron = inject('$electron') 19 | const minimize = () => $electron.ipcRenderer.send('minimize') 20 | 21 | const close = () => { 22 | Modal.confirm({ 23 | title: '提示', 24 | content: '此操作将Curve, 是否继续?', 25 | type: 'warning', 26 | onOk: () => { 27 | $electron.ipcRenderer.send('close') 28 | }, 29 | }) 30 | } 31 | const style: IStyle = {} 32 | // eslint-disable-next-line vue/no-setup-props-destructure 33 | if (props.color) style.color = props.color 34 | return ( 35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 | ) 44 | }, 45 | }) 46 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/components/icon/index.less: -------------------------------------------------------------------------------- 1 | // .sl-icon { 2 | // display: inline-block; 3 | // font-style: normal; 4 | // vertical-align: -0.125em; 5 | // text-align: center; 6 | // text-transform: none; 7 | // line-height: 0; 8 | // text-rendering: optimizeLegibility; 9 | // -webkit-font-smoothing: antialiased; 10 | // } 11 | @type: { 12 | icon-folder:#f8d673; 13 | icon-other:#4285f4; 14 | icon-audio:#db4437; 15 | icon-video:#db4437; 16 | icon-word:#2F97FE; 17 | icon-pdf:#FC5A5A; 18 | icon-image:#db4437; 19 | icon-doc:#4285f4; 20 | icon-ppt:#db4437; 21 | } 22 | // :root{ 23 | // each(@type, { 24 | // --@{key}: @value; 25 | // }); 26 | // } 27 | 28 | each(@type, { 29 | #@{key}{ 30 | color:~"var(--@{key},@{value})"; 31 | } 32 | }); -------------------------------------------------------------------------------- /packages/sharelist-web/src/components/icon/index.ts: -------------------------------------------------------------------------------- 1 | import { createFromIconfontCN } from '@ant-design/icons-vue' 2 | import config from '../../config/setting' 3 | import './icon-svg' 4 | import './index.less' 5 | const IconFont = createFromIconfontCN({ 6 | scriptUrl: [], 7 | }) 8 | 9 | export default IconFont 10 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/components/power/index.js: -------------------------------------------------------------------------------- 1 | import IPC from '@/utils/ipc' 2 | 3 | export default { 4 | methods: { 5 | data() { 6 | return {} 7 | }, 8 | created() { 9 | IPC.getStatus((resp) => { }) 10 | }, 11 | onChange(v) { 12 | IPC.setStatus(v) 13 | }, 14 | }, 15 | render() { 16 | /* 17 | this.link('option')} class={{'no-drag':true , 'active':this.active}} type="link"> 18 | 19 | 20 | */ 21 | return 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/components/qrcode/index.tsx: -------------------------------------------------------------------------------- 1 | import QRCode from 'qrcode' 2 | import { watchEffect, defineComponent, render } from 'vue' 3 | export default defineComponent({ 4 | name: 'SlQrcode', 5 | 6 | props: { 7 | content: { 8 | type: String, 9 | default: '', 10 | }, 11 | width: { 12 | type: Number, 13 | default: 200, 14 | }, 15 | height: { 16 | type: Number, 17 | default: 200, 18 | }, 19 | margin: { 20 | type: Number, 21 | default: 0, 22 | }, 23 | transparent: { 24 | type: Boolean, 25 | default: false, 26 | }, 27 | }, 28 | setup(props) { 29 | const render = () => { 30 | const options = { 31 | margin: props.margin, 32 | width: props.width, 33 | height: props.height, 34 | } 35 | 36 | if (props.transparent) { 37 | options.color = { 38 | light: '#0000', // Transparent background 39 | } 40 | } 41 | 42 | QRCode.toCanvas(this.$el, this.content, options, (err, data) => { 43 | if (err) throw err 44 | }) 45 | } 46 | 47 | watchEffect(() => { 48 | if (props.content) { 49 | render(props.content) 50 | } 51 | }) 52 | 53 | return 54 | }, 55 | }) 56 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/components/sider/index.less: -------------------------------------------------------------------------------- 1 | .sider { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: space-between; 5 | height: 100%; 6 | 7 | .menu{ 8 | button{ 9 | color:#aaa; 10 | font-size:20px; 11 | display: block; 12 | width:100%; 13 | height:48px; 14 | line-height: 48px; 15 | text-align: center; 16 | border:none; 17 | border-radius:0; 18 | i{ 19 | color:currentColor; 20 | } 21 | 22 | &:hover{ 23 | color:#fff; 24 | } 25 | 26 | &.active{ 27 | background:#bb040f; 28 | color:#fff; 29 | } 30 | } 31 | } 32 | 33 | .logo { 34 | text-align:center; 35 | font-size: 14px; 36 | padding:24px 0; 37 | font-weight: 600; 38 | color: #fff; 39 | line-height: 1em; 40 | font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, Helvetica, Roboto, Arial, PingFang SC, Hiragino Sans GB, Microsoft Yahei, Microsoft Jhenghei, sans-serif; 41 | text-shadow: 0 0 1px rgba(0, 0, 0, .1); 42 | } 43 | 44 | .sider-body{ 45 | 46 | } 47 | .sider-footer { 48 | 49 | font-size: 13px; 50 | color: #fff; 51 | margin: 0 auto; 52 | padding: 16px 0; 53 | button{ 54 | border-color:transparent; 55 | color:#aaa; 56 | &.active{ 57 | color:#fff; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/components/sider/index.tsx: -------------------------------------------------------------------------------- 1 | import { computed, defineComponent } from 'vue' 2 | import { useStore } from 'vuex' 3 | import { useRoute, useRouter } from 'vue-router' 4 | import { Modal, message, Button } from 'ant-design-vue' 5 | import { GlobalOutlined, SettingOutlined } from '@ant-design/icons-vue' 6 | 7 | import './index.less' 8 | 9 | export default defineComponent({ 10 | setup() { 11 | const store = useStore() 12 | const route = useRoute() 13 | const router = useRouter() 14 | 15 | const defActive = computed(() => route.name) 16 | const link = (name: string) => router.push({ name }) 17 | 18 | const signout = (): void => { 19 | Modal.confirm({ 20 | title: '提示', 21 | content: '确定要注销登录吗 ?', 22 | onOk: () => { 23 | return store 24 | .dispatch('signout', {}) 25 | .then(() => { 26 | window.location.reload() 27 | }) 28 | .catch((err) => { 29 | message.error({ 30 | title: '错误', 31 | description: err.message, 32 | }) 33 | }) 34 | }, 35 | onCancel() { 36 | // 37 | }, 38 | }) 39 | } 40 | return ( 41 |
42 |
43 | 44 | 57 | {/* 58 | 59 | 60 | 用户 61 | 62 | 63 | 64 | 订单 65 | 66 | 67 | 68 | 设置 69 | 70 | */} 71 |
72 | 84 |
85 | ) 86 | }, 87 | }) 88 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/components/subscribe/index.tsx: -------------------------------------------------------------------------------- 1 | import Qrcode from '../qrcode' 2 | import { ref, defineComponent } from 'vue' 3 | import { message, Modal } from 'ant-design-vue' 4 | import { AppleFilled } from '@ant-design/icons-vue' 5 | import request from '@/utils/request' 6 | 7 | //https://d3V0aW5nMDEyMkAxNjMuY29tOjA1NTkwNTU5QGtyLWEuaHVieS54eXo6NDQzMw?cert=&peer=#YL-SGP 8 | 9 | export default defineComponent({ 10 | setup() { 11 | const visible = ref(false) 12 | const url = ref('') 13 | const handleClick = async () => { 14 | visible.value = true 15 | const resp = await request.subscribe() 16 | if (resp.status) { 17 | message.error(resp.result) 18 | } else { 19 | url.value = resp.result.url 20 | } 21 | } 22 | 23 | const handleClose = () => { 24 | visible.value = false 25 | } 26 | 27 | return ( 28 |
29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 |

37 | 38 | 打开小火箭扫码订阅 39 |

40 |
41 |
42 |
43 | ) 44 | }, 45 | }) 46 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/components/switch-mode/index.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from 'vuex' 2 | import IPC from '@/utils/ipc' 3 | import { computed, defineComponent, PropType, inject } from 'vue' 4 | 5 | export default defineComponent({ 6 | setup() { 7 | const store = useStore() 8 | 9 | const modeLabel = computed((): string => { 10 | const mode = store.state.app 11 | if (mode == 0) return '直连' 12 | else if (mode == 1) return '智能路由' 13 | else if (mode == 2) return '全局' 14 | else return '' 15 | }) 16 | 17 | const handleChangeMode = (e: any) => { 18 | IPC.setMode(e.key) 19 | } 20 | 21 | return ( 22 | 23 | 24 | 直连 25 | 智能路由 26 | 全局 27 | 28 | {modeLabel} 29 | 30 | ) 31 | }, 32 | }) 33 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/components/user-menu/index.less: -------------------------------------------------------------------------------- 1 | .user-menu { 2 | display: flex; 3 | align-items: center; 4 | padding: 24px 12px; 5 | 6 | .avatar { 7 | margin-right: 12px; 8 | background-color:#bb040f; 9 | } 10 | 11 | .userinfo { 12 | h3 { 13 | color: rgba(0, 0, 0, .8); 14 | font-size: 14px; 15 | font-weight: 400; 16 | line-height: 1em; 17 | text-transform: uppercase; 18 | margin-bottom: 5px; 19 | } 20 | 21 | span { 22 | font-size: 11px; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/components/user-menu/index.tsx: -------------------------------------------------------------------------------- 1 | import { computed, defineComponent } from 'vue' 2 | import { useStore } from 'vuex' 3 | import './index.less' 4 | 5 | export default defineComponent({ 6 | setup() { 7 | const store = useStore() 8 | const user = computed(() => store.state.user) 9 | 10 | const username = computed(() => { 11 | if (store.state.user) { 12 | return store.state.user.name.split('@')[0] 13 | } else { 14 | return '' 15 | } 16 | }) 17 | 18 | return ( 19 |
20 | 21 | {user.value.name} 22 | 23 |
24 |

{username.value}

25 | {user.value.info.userLicense ? user.value.info.userLicense.expiredTime : null} 26 |
27 |
28 | ) 29 | }, 30 | }) 31 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/config/api.ts: -------------------------------------------------------------------------------- 1 | export type IAPI = [ 2 | name: string, 3 | url: string | ((...args: any[]) => string), 4 | options?: { 5 | [key: string]: number | string | boolean 6 | }, 7 | ] 8 | 9 | const api: IAPI[] = [ 10 | ['siginin', 'POST /signin'], 11 | ['book', 'POST /search'], 12 | ['list', 'GET /api/drive/path/:path'], 13 | ['setting', 'GET /api/setting', { token: true }], 14 | ['exportSetting', 'GET /api/setting?raw=true', { token: true }], 15 | ['saveSetting', 'POST /api/setting', { token: true }], 16 | ['config', 'GET /api/configs'], 17 | ['clearCache', 'PUT /api/cache/clear'], 18 | ['reload', 'PUT /api/reload'], 19 | // 20 | ['file', 'POST /api/drive/get', { token: true }], 21 | ['files', 'POST /api/drive/list', { token: true }], 22 | // ['parents', 'GET /api/drive/files/:fileId/parents', { token: true }], 23 | ] 24 | export default api 25 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/config/setting.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | iconFontCN: 'https://at.alicdn.com/t/font_2637962_voky2m76mr.js', 3 | } 4 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/hooks/useConfirm.ts: -------------------------------------------------------------------------------- 1 | import { Modal } from 'ant-design-vue' 2 | import { createVNode } from 'vue' 3 | import { ExclamationCircleOutlined } from '@ant-design/icons-vue' 4 | 5 | export default (fn: { (): any }, title = '确认', content = '') => 6 | (): { destroy(): void; update(p: any): void } => { 7 | const modal = Modal.confirm({ 8 | title, 9 | content, 10 | icon: createVNode(ExclamationCircleOutlined), 11 | onOk() { 12 | fn() 13 | }, 14 | }) 15 | return modal 16 | } 17 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/hooks/useDisk.ts: -------------------------------------------------------------------------------- 1 | import request, { ReqResponse } from '@/utils/request' 2 | import { ref, Ref, watch, reactive, computed } from 'vue' 3 | import { byte, getFileType, time } from '@/utils/format' 4 | import { useRouter, useRoute } from 'vue-router' 5 | import { useLocalStorageState } from '@/hooks/useLocalStorage' 6 | import { message } from 'ant-design-vue' 7 | 8 | export type IFile = { 9 | id: string 10 | name: string 11 | size: number 12 | type: 'folder' | 'file' 13 | ctime: number 14 | mtime: number 15 | path: string 16 | extra?: Record 17 | [key: string]: any 18 | } 19 | 20 | const format = (d: Array, isSearch = false): Array => { 21 | d.forEach((i) => { 22 | i.ext = i.name.split('.').pop() 23 | i.iconType = getFileType(i.name, i.type) 24 | i.ctimeDisplay = time(i.ctime) 25 | i.sizeDisplay = byte(i.size) 26 | if (isSearch) { 27 | i.isSearchResult = isSearch 28 | } 29 | }) 30 | return d 31 | } 32 | 33 | const useFolderAuth = () => { 34 | const data = useLocalStorageState('auth', {}) 35 | const hasAuth = (path: string) => !!data.value[path] 36 | 37 | const addAuth = (path: string, v: any) => { 38 | data.value[path] = v 39 | } 40 | 41 | const getAuth = (path: string) => { 42 | return data.value[path] 43 | } 44 | 45 | const removeAuth = (path: string) => { 46 | delete data.value[path] 47 | } 48 | return { hasAuth, addAuth, getAuth, removeAuth } 49 | } 50 | 51 | type IUseDisk = { 52 | (): any 53 | [key: string]: any 54 | } 55 | 56 | const useDisk: IUseDisk = (): any => { 57 | if (useDisk.instance) { 58 | return useDisk.instance 59 | } 60 | 61 | const router = useRouter() 62 | const routes = useRoute() 63 | 64 | const files = ref([]) 65 | const loading = ref(false) 66 | const error = reactive({ code: 0, message: '' }) 67 | 68 | const { hasAuth, addAuth, getAuth, removeAuth } = useFolderAuth() 69 | 70 | const paths = computed(() => { 71 | const ret: Array = (routes.params.path as string).split('/').filter(Boolean) 72 | if (routes.query.search) { 73 | ret.push(`${routes.query.search} 的搜索结果`) 74 | } 75 | return ret 76 | }) 77 | 78 | const getDiskContent = (): any => { 79 | loading.value = true 80 | const isSearch = Object.keys(routes.query).length > 0 81 | const params: Record = { path: routes.params.path, ...routes.query } 82 | if (!params.auth && hasAuth(params.path)) params.auth = getAuth(params.path) 83 | 84 | request.files(params).then((resp: ReqResponse) => { 85 | if (resp.error) { 86 | error.code = resp.error.code 87 | error.message = resp.error.message || '' 88 | 89 | if (error.code == 401) { 90 | if (error.message) { 91 | message.error(error.message) 92 | } 93 | removeAuth(params.path) 94 | } 95 | } else { 96 | if (resp.files) { 97 | format(resp.files, isSearch) 98 | files.value = resp.files 99 | } 100 | error.code = 0 101 | error.message = '' 102 | 103 | // save auth 104 | if (params.auth && getAuth(params.path) != params.auth) { 105 | addAuth(params.path, params.auth) 106 | } 107 | } 108 | loading.value = false 109 | }) 110 | } 111 | 112 | const setPath = async (data: { id?: string; path?: string; isSearchResult?: boolean }) => { 113 | if (data.isSearchResult) { 114 | const resp = await request.file({ id: data.id }) 115 | router.push('/' + resp.path) 116 | } else { 117 | router.push('/' + data.path) 118 | } 119 | } 120 | 121 | watch([() => routes.params, () => routes.query], getDiskContent, { immediate: true }) 122 | 123 | useDisk.instance = { 124 | getDiskContent, 125 | setPath, 126 | files, 127 | paths, 128 | loading, 129 | error, 130 | } 131 | 132 | return useDisk.instance 133 | } 134 | 135 | export default useDisk 136 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/hooks/useHooks.ts: -------------------------------------------------------------------------------- 1 | import { ref, reactive, Ref, UnwrapRef, onMounted, onUnmounted, getCurrentInstance } from 'vue' 2 | 3 | export function safeOnMounted(hook: () => any): void { 4 | const instance = getCurrentInstance() 5 | console.log(instance) 6 | if (instance?.isMounted || (instance as any)?._isMounted) { 7 | hook() 8 | } else { 9 | onMounted(hook) 10 | } 11 | } 12 | 13 | type ToggleValue = number | string | boolean | undefined 14 | 15 | export const useToggle = (defaultValue: ToggleValue = false, reverseValue: ToggleValue = undefined): any => { 16 | if (reverseValue === undefined) { 17 | reverseValue = !Boolean(defaultValue) 18 | } 19 | 20 | const state: Ref = ref(defaultValue) 21 | 22 | const toggle = () => { 23 | state.value = state.value === defaultValue ? reverseValue : defaultValue 24 | } 25 | 26 | const setLeft = () => (state.value = defaultValue) 27 | 28 | const setRight = () => (state.value = reverseValue) 29 | 30 | return { state, toggle, setLeft, setRight } 31 | } 32 | 33 | export const useBoolean = (defaultValue = false): any => { 34 | const state: Ref = ref(defaultValue) 35 | 36 | const toggle = () => { 37 | state.value = state.value === true ? false : true 38 | } 39 | 40 | const setTrue = () => (state.value = true) 41 | 42 | const setFalse = () => (state.value = false) 43 | 44 | return [state, { toggle, setTrue, setFalse }] 45 | } 46 | 47 | export const useObject = (defaultValue: Record): [any, (k?: Array) => void] => { 48 | const state = reactive(defaultValue) 49 | 50 | const clear = (excludes: Array = []): void => { 51 | Object.keys(state as Record) 52 | .filter((i) => !excludes.includes(i)) 53 | .forEach((key: string) => Reflect.deleteProperty(state as Record, key)) 54 | } 55 | 56 | return [state, clear] 57 | } 58 | 59 | export const useState = >(initialState: T = {} as T): [T, (state: T) => T, () => T] => { 60 | const state = reactive(initialState) 61 | const setState = (val: T, clear = false) => { 62 | Object.keys(val).forEach((key) => { 63 | Reflect.set(state, key, val[key]) 64 | }) 65 | return state as T 66 | } 67 | 68 | const clearState = () => { 69 | Object.keys(state).forEach((key) => Reflect.deleteProperty(state, key)) 70 | return state as T 71 | } 72 | 73 | return [state as T, setState, clearState] 74 | } 75 | 76 | const singleHook = new WeakMap() 77 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 78 | export const useSingle = (hook: any, args: Array): any => { 79 | if (singleHook.has(hook)) { 80 | return singleHook.get(hook) 81 | } 82 | const res = hook.apply(hook, args) 83 | singleHook.set(hook, res) 84 | return res 85 | } 86 | 87 | const useBoot = (cb: () => any): any => { } 88 | // const useBoot = (cb: any) => { 89 | // useBoot.ready = true 90 | 91 | // if (useBoot.ready) { 92 | // cb() 93 | // } 94 | // } 95 | 96 | // const useWindowEvent = (event: string) => { 97 | // const handler = new Set() 98 | // const eventMap = new Map() 99 | // window.addEventListener(event, (event) => { 100 | // console.log('location: ' + document.location + ', state: ' + JSON.stringify(event.state)) 101 | // }) 102 | 103 | // const onMessage = (cb: any) => { 104 | // handler.add(cb) 105 | // } 106 | 107 | // const initListener = () => { 108 | // if (document) { 109 | // document.addEventListener('message', (e) => { 110 | // console.log(e) 111 | // }) 112 | // } 113 | // } 114 | 115 | // initListener() 116 | 117 | // return { 118 | // onMessage, 119 | // } 120 | // } 121 | 122 | type useTitleOptions = { 123 | restoreOnUnmount: boolean 124 | } 125 | export const useTitle = (title: string, options?: useTitleOptions): void => { 126 | const lastTitle = ref('') 127 | 128 | const run = () => { 129 | lastTitle.value = document.title 130 | document.title = title 131 | } 132 | safeOnMounted(() => { 133 | run() 134 | }) 135 | 136 | if (options?.restoreOnUnmount === true) { 137 | onUnmounted(() => { 138 | document.title = lastTitle.value 139 | }) 140 | } 141 | 142 | run() 143 | } 144 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/hooks/useLoad.ts: -------------------------------------------------------------------------------- 1 | import { ref, Ref } from 'vue' 2 | 3 | export const useLoad = (cb: (() => Promise) | Promise): Ref => { 4 | const loading = ref(false) 5 | Promise.resolve(cb).then(() => { 6 | loading.value = true 7 | }) 8 | return loading 9 | } 10 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/hooks/useLocalStorage.ts: -------------------------------------------------------------------------------- 1 | import { Ref, ref, watch } from 'vue' 2 | 3 | export type LocalStateKey = string 4 | 5 | export function useLocalStorageState(key: LocalStateKey, defaultValue?: T | (() => T)): any { 6 | const raw = localStorage.getItem(key) 7 | if (raw) { 8 | try { 9 | defaultValue = JSON.parse(raw) 10 | } catch { 11 | // 12 | } 13 | } 14 | if (typeof defaultValue === 'function') { 15 | defaultValue = (defaultValue as () => T)() 16 | } 17 | const state = ref(defaultValue) as Ref 18 | 19 | const setState = () => { 20 | localStorage.setItem(key, JSON.stringify(state.value)) 21 | } 22 | 23 | watch( 24 | state, 25 | (nv) => { 26 | setState() 27 | }, 28 | { deep: true, immediate: false }, 29 | ) 30 | 31 | return state 32 | } 33 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/hooks/useSetting.ts: -------------------------------------------------------------------------------- 1 | import { ref, Ref, reactive, unref, readonly, toRaw } from 'vue' 2 | import request, { ReqResponse } from '@/utils/request' 3 | import storage from 'store2' 4 | import { message } from 'ant-design-vue' 5 | import { useBoolean } from './useHooks' 6 | import { saveFile } from '../utils/format' 7 | 8 | type IUseSetting = { 9 | (): any 10 | instance?: any 11 | } 12 | export const useSetting: IUseSetting = (): any => { 13 | if (useSetting.instance) { 14 | return useSetting.instance 15 | } 16 | 17 | const config: ISetting = reactive({}) 18 | const [isLoading, { setFalse: hideLoading }] = useBoolean(true) 19 | const loginState = ref(0) 20 | 21 | const getConfig = (val: string) => { 22 | storage.set('ACCESS_TOKEN', val) 23 | request.setting().then((resp: ReqResponse) => { 24 | if (resp.error) { 25 | if (resp.error.code == 401) { 26 | storage.remove('ACCESS_TOKEN') 27 | loginState.value = 2 28 | if (resp.error.message) { 29 | message.error(resp.error.message) 30 | } 31 | } 32 | } else { 33 | loginState.value = 1 34 | updateSetting(resp.data as ISetting) 35 | } 36 | 37 | hideLoading() 38 | }) 39 | } 40 | 41 | const setConfig = (data: ISetting) => { 42 | // console.log(data) 43 | request.saveSetting(data).then((resp: ReqResponse) => { 44 | if (resp.error) { 45 | message.error(resp.error.message || 'error') 46 | } else { 47 | updateSetting(resp.data as ISetting) 48 | } 49 | }) 50 | } 51 | 52 | const updateSetting = (data: ISetting) => { 53 | for (const i in data) { 54 | config[i] = data[i] 55 | } 56 | } 57 | const clearSetting = () => { 58 | Object.keys(config).forEach((key) => Reflect.deleteProperty(config, key)) 59 | } 60 | 61 | const getValue = (code: string) => { 62 | return config[code] 63 | } 64 | 65 | const signout = () => { 66 | storage.remove('ACCESS_TOKEN') 67 | loginState.value = 2 68 | Object.keys(config).forEach((key) => Reflect.deleteProperty(config, key)) 69 | } 70 | 71 | const reload = () => { 72 | request.reload().then((resp: any) => { 73 | // hidden() 74 | if (resp.status) { 75 | message.error(resp.msg) 76 | } else { 77 | message.success('操作成功') 78 | } 79 | }) 80 | } 81 | // eslint-disable-next-line prettier/prettier 82 | const noop = () => { } 83 | 84 | const clearCache = () => { 85 | // const hidden = message.loading('正在清除缓存', 0) 86 | request.clearCache().then((resp: any) => { 87 | // hidden() 88 | if (resp.status) { 89 | message.error(resp.msg) 90 | } else { 91 | message.success('操作成功') 92 | } 93 | }) 94 | } 95 | 96 | const exportConfig = () => { 97 | request.exportSetting().then((resp: any) => { 98 | // hidden() 99 | if (resp.status) { 100 | message.error(resp.msg) 101 | } else { 102 | saveFile(JSON.stringify(resp.data), 'config.json') 103 | } 104 | }) 105 | } 106 | 107 | if (!config.token && storage.get('ACCESS_TOKEN')) { 108 | getConfig(storage.get('ACCESS_TOKEN')) 109 | } else { 110 | loginState.value = 2 111 | hideLoading() 112 | } 113 | 114 | return (useSetting.instance = { 115 | signout, 116 | reload, 117 | loginState, 118 | isLoading, 119 | getValue, 120 | 121 | config, 122 | setConfig, 123 | getConfig, 124 | exportConfig, 125 | clearCache, 126 | }) 127 | } 128 | 129 | export const useConfig: IUseSetting = (): any => { 130 | const config: Record = reactive({}) 131 | 132 | request.config().then((resp: ReqResponse) => { 133 | if (!resp.error) { 134 | for (const i in resp.data) { 135 | config[i] = resp.data[i] 136 | } 137 | } 138 | }) 139 | 140 | return { config } 141 | } 142 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/hooks/useStore.ts: -------------------------------------------------------------------------------- 1 | import { provide, inject, Ref, ref } from 'vue' 2 | 3 | type InjectType = 'root' | 'optional' 4 | 5 | export interface FunctionalStore { 6 | (): T 7 | key?: symbol 8 | root?: T 9 | } 10 | 11 | export const regStore = (store: FunctionalStore): T => { 12 | if (!store.key) { 13 | store.key = Symbol('functional store') 14 | } 15 | const depends = store() 16 | provide(store.key, depends) 17 | return depends 18 | } 19 | 20 | export const useStore = (store: FunctionalStore, type?: InjectType): any => { 21 | const key = store.key 22 | const root = store.root 23 | 24 | switch (type) { 25 | case 'optional': 26 | return inject(key) || store.root || null 27 | case 'root': 28 | if (!store.root) store.root = store() 29 | return store.root 30 | default: 31 | if (inject(key)) { 32 | return inject(key) 33 | } 34 | if (root) return store.root 35 | throw new Error(`状态钩子函数${store.name}未在上层组件通过调用useProvider提供`) 36 | } 37 | } 38 | 39 | export const useAsync = (cb: (() => Promise) | Promise): Ref => { 40 | const val = ref() 41 | Promise.resolve(typeof cb === 'function' ? cb() : cb).then((resp) => { 42 | val.value = resp 43 | }) 44 | return val 45 | } 46 | 47 | const jsbridge = { 48 | call: (name: string, param?: any, callback?: (value?: unknown) => void) => name, 49 | getGrayScaleValue: (v: any) => v, 50 | } 51 | 52 | type BridgeValue = { 53 | returnValue: string 54 | } 55 | 56 | type BridgeFunctions = 'getGrayScaleValue' 57 | 58 | export const useBridgeValue = ( 59 | fn: BridgeFunctions, 60 | params: P, 61 | ): { value: Ref; ready: Ref } => { 62 | const value: Ref = ref() 63 | const ready = ref(false) 64 | Promise.resolve(jsbridge[fn](params)).then((resp: T) => { 65 | value.value = resp 66 | ready.value = true 67 | }) 68 | return { value, ready } 69 | } 70 | 71 | type GrayParams = 'code1' | 'code2' 72 | 73 | type GrapValue = { 74 | returnValue: string 75 | } 76 | const { ready, value } = useBridgeValue('getGrayScaleValue', 'code') 77 | 78 | type ICdpSpaceInfo = { 79 | returnValue: string 80 | } 81 | function getCdpSpaceInfo(spaceCode: string): Promise { 82 | return new Promise((resolve) => { 83 | jsbridge.call('getCdpSpaceInfo', { spaceCode }, (resp: unknown) => { 84 | resolve(resp as ICdpSpaceInfo) 85 | }) 86 | }) 87 | } 88 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/hooks/useUrlState.ts: -------------------------------------------------------------------------------- 1 | import { useRouter, useRoute } from 'vue-router' 2 | import { reactive, watch } from 'vue' 3 | import { useState } from './useHooks' 4 | type initial = { 5 | params: Record 6 | query: Record 7 | } 8 | 9 | export default ({ params: initialParams, query: initialQuery }: initial = { params: {}, query: {} }): any => { 10 | const router = useRouter() 11 | const route = useRoute() 12 | 13 | const [params, updateParams] = useState({ 14 | ...initialParams, 15 | ...route.params, 16 | }) 17 | 18 | const [query, updateQuery] = useState({ 19 | ...initialQuery, 20 | ...route.query, 21 | }) 22 | 23 | const setQuery = (data: Record) => { 24 | router.push({ 25 | query: { 26 | ...route.query, 27 | ...updateQuery(data), 28 | }, 29 | }) 30 | } 31 | 32 | const setParams = (data: Record) => { 33 | router.push({ 34 | ...route.params, 35 | ...updateParams(data), 36 | }) 37 | } 38 | 39 | const setPath = (path: string) => { 40 | router.push(path) 41 | } 42 | 43 | watch(() => route.params, updateParams) 44 | watch(() => route.query, updateQuery) 45 | 46 | watch(query, (nv) => { 47 | console.log('>>>', nv) 48 | }) 49 | return { params, query, setQuery, setParams, setPath } 50 | } 51 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/hooks/utils.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance, isRef, onMounted as vueOnMounted, onUnmounted as vueOnUnmounted, Ref } from 'vue' 2 | 3 | export const onMounted = (cb: () => any): void => { 4 | const instance = getCurrentInstance() 5 | if (instance) { 6 | if (instance?.isMounted) { 7 | cb() 8 | } else { 9 | vueOnMounted(cb) 10 | } 11 | } 12 | } 13 | 14 | export function onUnmounted(cb: () => any): void { 15 | if (getCurrentInstance()) { 16 | vueOnUnmounted(cb) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ShareList 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App' 3 | import store from './store' 4 | import router from './router' 5 | import { message } from 'ant-design-vue' 6 | // console.log('electron-store', new Store()) 7 | import 'ant-design-vue/dist/antd.less' 8 | import '@/assets/style/index.less' 9 | 10 | createApp(App).use(router).use(store).provide('$message', message).mount('#app') 11 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { defineAsyncComponent } from 'vue' 2 | import { createRouter, createWebHashHistory, createWebHistory, RouteRecordRaw } from 'vue-router' 3 | 4 | const routes: Array = [ 5 | { 6 | path: '/:path(.*)', 7 | name: 'home', 8 | component: defineAsyncComponent(() => import('../views/home')), 9 | }, 10 | { 11 | path: '/@manage', 12 | name: 'manage', 13 | component: defineAsyncComponent(() => import('../views/manage')), 14 | }, 15 | ] 16 | console.log('here') 17 | const router = createRouter({ 18 | history: createWebHistory(), 19 | routes, 20 | }) 21 | 22 | export default router 23 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { state } from './state' 2 | import { createStore } from 'vuex' 3 | import storage from 'store2' 4 | import request from '@/utils/request' 5 | import { ACCESS_TOKEN, USER_NAME, THEME } from './mutation-types' 6 | import createPersistedState from 'vuex-persistedstate' 7 | 8 | export default createStore({ 9 | state, 10 | mutations: { 11 | SET_TOKEN: (state, data) => { 12 | state.accessToken = data 13 | //storage.set('ACCESS_TOKEN', data) 14 | }, 15 | SET_THEME: (state, data) => { 16 | state.setting.theme = data 17 | //storage.set('SETTING', state.setting) 18 | }, 19 | SET_LAYOUT: (state, data) => { 20 | state.setting.layout = data 21 | //storage.set('SETTING', state.setting) 22 | }, 23 | }, 24 | actions: { 25 | signin({ commit }, data) { 26 | return new Promise((resolve, reject) => { 27 | request 28 | .signin(data) 29 | .then((response: any) => { 30 | const result = response.result 31 | //storage.set(ACCESS_TOKEN, result.token, 30 * 24 * 60 * 60 * 1000) 32 | //storage.set(USER_NAME, result.user_name, 30 * 24 * 60 * 60 * 1000) 33 | commit('SET_TOKEN', result.token) 34 | commit('SET_NAME', result.user_name) 35 | resolve(response) 36 | }) 37 | .catch((error: any) => { 38 | reject(error) 39 | }) 40 | }) 41 | }, 42 | signout({ commit }) { 43 | return new Promise((resolve, reject) => { 44 | commit('SET_TOKEN', '') 45 | //storage.remove(ACCESS_TOKEN) 46 | request 47 | .signout(state.accessToken) 48 | .then(resolve) 49 | .catch(() => { 50 | resolve({}) 51 | }) 52 | }) 53 | }, 54 | }, 55 | modules: {}, 56 | plugins: [createPersistedState()], 57 | }) 58 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/store/mutation-types.ts: -------------------------------------------------------------------------------- 1 | export const ACCESS_TOKEN = 'Access-Token' 2 | export const USER_NAME = 'User-Name' 3 | export const THEME = 'THEME' -------------------------------------------------------------------------------- /packages/sharelist-web/src/store/state.ts: -------------------------------------------------------------------------------- 1 | type ISetting = { 2 | theme: 'night' | 'dark' 3 | layout: 'list' | 'grid' 4 | } 5 | 6 | export type State = { 7 | accessToken: string 8 | setting: ISetting 9 | } 10 | 11 | export const state: State = { 12 | accessToken: '', 13 | setting: { 14 | theme: 'night', 15 | layout: 'list', 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/types/IDrive.ts: -------------------------------------------------------------------------------- 1 | type DrivePath = { 2 | protocol: string 3 | [key: string]: string | number 4 | } 5 | 6 | declare type DriverField = { 7 | key: string 8 | label: string 9 | value?: string | number | boolean 10 | options?: Array 11 | type?: 'string' | 'hidden' | 'number' | 'boolean' | 'list' 12 | help?: string 13 | fields?: Array 14 | required?: boolean 15 | } 16 | 17 | declare type IDrive = { 18 | name: string 19 | path: DrivePath 20 | } 21 | 22 | declare type DriverGuide = { 23 | key?: string 24 | label?: string 25 | fields: Array 26 | } 27 | 28 | declare type Driver = { 29 | protocol: string 30 | name?: string 31 | guide?: Array 32 | } 33 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/types/IRequest.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-web/src/types/IRequest.ts -------------------------------------------------------------------------------- /packages/sharelist-web/src/types/shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, any> 4 | export default component 5 | } 6 | 7 | declare module 'aplayer' { 8 | const aplayer: any 9 | export default aplayer 10 | } 11 | 12 | declare type ISetting = { 13 | title?: string 14 | index_enable?: boolean 15 | default_ignores?: Array 16 | [key: string]: any 17 | } 18 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/types/source.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.json' 2 | declare module '*.png' 3 | declare module '*.jpg' 4 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/utils/format.ts: -------------------------------------------------------------------------------- 1 | export const getFileType = (v: string, type: string): string => { 2 | if (type == 'folder') { 3 | return 'folder' 4 | } else { 5 | if (v) v = v.toLowerCase().split('.').pop() || '' 6 | if (['mp4', 'mpeg', 'wmv', 'webm', 'avi', 'rmvb', 'mov', 'mkv', 'f4v', 'flv'].includes(v)) { 7 | return 'video' 8 | } else if (['mp3', 'm4a', 'wav', 'wma', 'ape', 'flac', 'ogg'].includes(v)) { 9 | return 'audio' 10 | } else if (['doc', 'docx', 'wps'].includes(v)) { 11 | return 'word' 12 | } else if (['ppt', 'pptx'].includes(v)) { 13 | return 'ppt' 14 | } else if (['pdf'].includes(v)) { 15 | return 'pdf' 16 | } else if (['doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'pdf', 'txt', 'yaml', 'ini', 'cfg'].includes(v)) { 17 | return 'doc' 18 | } else if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'wmf', 'tif'].includes(v)) { 19 | return 'image' 20 | } else if (['zip', 'rar', '7z', 'tar', 'gz', 'gz2'].includes(v)) { 21 | return 'archive' 22 | } else { 23 | return 'other' 24 | } 25 | } 26 | } 27 | 28 | export const byte = (v: number): string => { 29 | if (v === undefined || v === null || isNaN(v)) { 30 | return '-' 31 | } 32 | 33 | let lo = 0 34 | 35 | while (v >= 1024) { 36 | v /= 1024 37 | lo++ 38 | } 39 | 40 | return Math.floor(v * 100) / 100 + ' ' + ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'][lo] 41 | } 42 | 43 | const fix0 = (v: any) => (v > 9 ? v : '0' + v) 44 | 45 | export const time = (v: number): string => { 46 | if (!v) return '-' 47 | const date = new Date(v) 48 | const thisYear = new Date().getFullYear() 49 | let ret: string = 50 | fix0(date.getMonth() + 1) + '/' + fix0(date.getDay()) + ' ' + fix0(date.getHours()) + ':' + fix0(date.getMinutes()) 51 | if (thisYear != date.getFullYear()) { 52 | ret = date.getFullYear() + '/' + ret 53 | } 54 | return ret 55 | } 56 | 57 | export const isAudioSupport = (name: string): boolean => { 58 | const ext: string = name.split('.').pop() || '' 59 | return ['mp3', 'm4a', 'acc', 'wav', 'ogg'].includes(ext) 60 | } 61 | 62 | export const isVideoSupport = (name: string): boolean => { 63 | const ext: string = name.split('.').pop() || '' 64 | return ['mpeg', 'mp4', 'mkv'].includes(ext) 65 | } 66 | 67 | export const isSupportType = (name: string): string | undefined => { 68 | const ext: string = name.split('.').pop() || '' 69 | if (['mp3', 'm4a', 'acc', 'wav', 'ogg'].includes(ext)) { 70 | return 'audio' 71 | } else if (['mpeg', 'mp4', 'mkv'].includes(ext)) { 72 | return 'video' 73 | } 74 | } 75 | 76 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 77 | export const isType = (type: string) => (obj: any) => Object.prototype.toString.call(obj) === `[object ${type}]` 78 | 79 | export const isArray = isType('Array') 80 | 81 | export const isObject = isType('Object') 82 | 83 | export const isBlob = isType('Blob') 84 | 85 | export const isString = (v: string): boolean => typeof v == 'string' 86 | 87 | export const getBlob = (data: string, filename: string): Blob | undefined => { 88 | let blob 89 | try { 90 | blob = new Blob([data], { type: 'application/octet-stream' }) 91 | } catch (e) { 92 | /**/ 93 | } 94 | return blob 95 | } 96 | 97 | export const saveFile = (data: Blob | string, filename: string): void => { 98 | let blob: Blob | undefined 99 | if (isString(data as string)) { 100 | blob = getBlob(data as string, filename) 101 | } else { 102 | blob = data as Blob 103 | } 104 | 105 | if (isBlob(blob)) { 106 | const URL = window.URL || window.webkitURL 107 | 108 | const link = document.createElement('a') 109 | 110 | link.href = URL.createObjectURL(blob) 111 | link.download = filename 112 | 113 | const evt = document.createEvent('MouseEvents') 114 | evt.initEvent('click', false, false) 115 | // link.click() 116 | link.dispatchEvent(evt) 117 | URL.revokeObjectURL(link.href) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/utils/ipc.ts: -------------------------------------------------------------------------------- 1 | import { ipcRenderer } from 'electron' 2 | 3 | const setMode = (v: string): void => { 4 | ipcRenderer.send('set_mode', v) 5 | } 6 | 7 | const setServer = (data: string): void => { 8 | ipcRenderer.send('set_server', data) 9 | } 10 | 11 | const getMode = (): void => { 12 | ipcRenderer.sendSync('get_mode') 13 | } 14 | 15 | const on = (evt: string, handler: (...args: any[]) => void): any => ipcRenderer.on(evt, handler) 16 | 17 | const send = (channel: string, ...args: any[]): void => ipcRenderer.send(channel, ...args) 18 | 19 | export default { setMode, setServer, getMode, on, send } 20 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/utils/promisify.js: -------------------------------------------------------------------------------- 1 | const customArgumentsToken = '__ES6-PROMISIFY--CUSTOM-ARGUMENTS__' 2 | 3 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 4 | export function promisify(original) { 5 | if (typeof original !== 'function') { 6 | throw new TypeError('Argument to promisify must be a function') 7 | } 8 | 9 | const argumentNames = original[customArgumentsToken] 10 | 11 | return function (...args) { 12 | return new Promise((resolve, reject) => { 13 | // Append the callback bound to the context 14 | args.push(function callback(err, ...values) { 15 | if (err) { 16 | return reject(err) 17 | } 18 | 19 | if (values.length === 1 || !argumentNames) { 20 | return resolve(values[0]) 21 | } 22 | 23 | const o = {} 24 | values.forEach((value, index) => { 25 | const name = argumentNames[index] 26 | if (name) { 27 | o[name] = value 28 | } 29 | }) 30 | 31 | resolve(o) 32 | }) 33 | 34 | // Call the function. 35 | original.apply(this, args) 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' 2 | import apis, { IAPI } from '../config/api' 3 | import router from '../router' 4 | import storage from 'store2' 5 | 6 | type ReqConfig = { 7 | url: string 8 | method: string 9 | data?: any 10 | params?: any 11 | responseType?: string 12 | token?: boolean 13 | headers?: any 14 | } 15 | 16 | export type ReqResponse = { 17 | error?: { code: number; message?: string } 18 | [key: string]: any 19 | [key: number]: any 20 | } 21 | 22 | const methods: string[] = ['post', 'get', 'put', 'head', 'delete', 'patch'] 23 | 24 | const baseURL = '' 25 | 26 | axios.defaults.timeout = 60 * 1000 27 | axios.defaults.baseURL = baseURL 28 | 29 | const service: AxiosInstance = axios.create() 30 | 31 | // http response 拦截器 32 | service.interceptors.response.use( 33 | (response) => { 34 | return response.data 35 | }, 36 | 37 | (error) => { 38 | // 由接口返回的错误 39 | if (error.response) { 40 | switch (error.response.status) { 41 | case 401: 42 | router.push('error/401') 43 | case 403: 44 | router.push('error/403') 45 | } 46 | return { status: 500 } 47 | } else { 48 | log(`服务器错误!错误代码:${error}`) 49 | return { status: 500, msg: '' } 50 | } 51 | }, 52 | ) 53 | 54 | const log = (content: string, type = 'error'): void => { 55 | console.log(content) 56 | } 57 | 58 | const urlReplace = (url: string, params: any): string => 59 | url.replace(/(?:\:)([\w\$]+)/g, ($0, $1) => { 60 | if ($1 in params) { 61 | return params[$1] 62 | } else { 63 | return $0 64 | } 65 | }) 66 | 67 | const convFormData = (data: any) => { 68 | const fd = new FormData() 69 | for (const i in data) { 70 | if (Array.isArray(data[i])) { 71 | const item = [] 72 | data[i].forEach((j: any, idx: number) => { 73 | fd.append(`${i}[${idx}]`, j) 74 | }) 75 | } else { 76 | fd.append(i, data[i]) 77 | } 78 | } 79 | return fd 80 | } 81 | 82 | const createReq = (opts: ReqConfig) => { 83 | if (opts.token !== false) { 84 | if (!opts.headers) { 85 | opts.headers = {} 86 | } 87 | const token: string = storage.get('ACCESS_TOKEN') 88 | if (token) { 89 | opts.headers['Authorization'] = `${token}` 90 | } 91 | } 92 | 93 | if (opts.headers['Content-Type'] == 'multipart/form-data') { 94 | opts.data = convFormData(opts.data) 95 | } 96 | return service.request(opts as AxiosRequestConfig) 97 | } 98 | 99 | const request: any = (name: string) => request[name] 100 | /* 101 | { 102 | call(name:string , ...rest:any){ 103 | return request[name](...rest) 104 | } 105 | } 106 | */ 107 | methods.forEach((method: string) => { 108 | request[method] = (options: any = {}) => 109 | createReq({ 110 | method, 111 | headers: { 112 | 'Content-Type': options.type == 'json' ? 'application/json' : 'multipart/form-data', 113 | }, 114 | ...options, 115 | }) 116 | }) 117 | 118 | apis.forEach((item: IAPI) => { 119 | request[item[0]] = (...args: any[]) => { 120 | let url = item[1] 121 | if (typeof url === 'function') { 122 | url = url(...args) 123 | } 124 | const t = url.split(/\s/) 125 | const method: string = t[0] || 'GET' 126 | let argsObj: any = {} 127 | if (typeof args[0] == 'object') { 128 | argsObj = { ...args[0] } 129 | } 130 | args.forEach((key, idx) => { 131 | argsObj['$' + (idx + 1)] = key 132 | }) 133 | url = t.slice(1).join(' ') 134 | url = urlReplace(url, argsObj) 135 | const options: any = item[2] || {} 136 | 137 | const params: any = { 138 | method, 139 | url, 140 | } 141 | 142 | if (params.method == 'GET') { 143 | if (argsObj.qs) { 144 | params['params'] = argsObj.qs 145 | } 146 | } else { 147 | params['data'] = args[0] || options.data || {} 148 | } 149 | params.headers = params.headers || {} 150 | params.responseType = options.responseType 151 | 152 | if (options.type == 'formdata') { 153 | params.headers['Content-Type'] = 'multipart/form-data' 154 | } else { 155 | params.headers['Content-Type'] = 'application/json' 156 | } 157 | // console.log(params) 158 | if (options.token) { 159 | params.token = true 160 | } 161 | return createReq(params) 162 | } 163 | }) 164 | 165 | export default request 166 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/views/home/index.less: -------------------------------------------------------------------------------- 1 | .drive-body{ 2 | padding:12px 28px; 3 | .item{ 4 | display: flex; 5 | width:100%; 6 | justify-content: space-between; 7 | align-items: center; 8 | padding:12px; 9 | transition: all 0.3s; 10 | cursor: pointer; 11 | position: relative; 12 | &:hover{ 13 | background-color: rgba(132,133,141,0.08); 14 | } 15 | 16 | .item-info{ 17 | padding:0 6px; 18 | color:rgba(0,0,0,.5); 19 | flex:0 0 auto; 20 | position:absolute; 21 | right:0; 22 | top:0; 23 | } 24 | .item-name{ 25 | flex: 1; 26 | min-width: 0; 27 | width:100%; 28 | overflow: hidden; 29 | text-overflow: ellipsis; 30 | white-space: nowrap; 31 | // display: flex; 32 | position: relative; 33 | } 34 | .item-icon{ 35 | margin-right: 16px; 36 | flex:none; 37 | } 38 | 39 | .item-icon__ext{ 40 | position: absolute; 41 | font-size: 12px; 42 | left:20px; 43 | bottom: 12px; 44 | color:#fff; 45 | text-transform: uppercase; 46 | transform: scale(0.8); 47 | width:42px; 48 | // text-align: center; 49 | transform-origin: left; 50 | } 51 | 52 | .item-meta{ 53 | flex:auto; 54 | display: flex; 55 | justify-content:space-between; 56 | align-items: center; 57 | overflow: hidden; 58 | color:#25262b; 59 | } 60 | .item-ctime{ 61 | flex: 0 0 auto; 62 | // padding:0 24px; 63 | // width: 200px; 64 | color:rgba(37, 38, 43, 0.72); 65 | font-size: 12px; 66 | text-align: left; 67 | } 68 | 69 | .item-size{ 70 | color:rgba(37, 38, 43, 0.72); 71 | font-size: 12px; 72 | text-align: right; 73 | flex:0 0 80px; 74 | } 75 | } 76 | } 77 | 78 | .drive-body--grid{ 79 | display: grid; 80 | grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); 81 | grid-gap: 12px; 82 | .item{ 83 | .item-meta{ 84 | flex-direction: column; 85 | align-items: flex-start; 86 | width:100%; 87 | overflow: hidden; 88 | } 89 | .item-icon__ext--md{ 90 | transform: scale(0.55); 91 | } 92 | .item-icon__ext--sm{ 93 | display: none; 94 | } 95 | .item-ctime{ 96 | // display: none; 97 | } 98 | .item-size{ 99 | display: none; 100 | } 101 | } 102 | } 103 | 104 | .drive-header{ 105 | padding:12px 40px; 106 | background-color: #e3e6e9; 107 | color: #fff; 108 | font-size: 21px; 109 | overflow: hidden; 110 | display: flex; 111 | justify-content: space-between; 112 | border-bottom: 1px solid rgba(0,0,0,.02); 113 | color:#555; 114 | 115 | .drive-header__name{ 116 | cursor: pointer; 117 | } 118 | .drive-action{ 119 | color:#555; 120 | } 121 | .drive-action-search{ 122 | margin-right:16px; 123 | } 124 | 125 | } 126 | 127 | .drive-routes{ 128 | padding:16px 40px 0px; 129 | // background-color: #c1c1c1; 130 | .ant-breadcrumb-link{ 131 | // background-color: rgb(66, 133, 244); 132 | // border-radius: 20px; 133 | // color:#fff; 134 | // padding:6px 8px; 135 | // a{ 136 | // color: #fff; 137 | // } 138 | } 139 | } 140 | 141 | .drive-routes--hidden{ 142 | display: none; 143 | } 144 | 145 | footer{ 146 | border-top: 1px solid #ddd; 147 | margin: 35px 15px; 148 | padding: 15px; 149 | text-align: center; 150 | a{ 151 | padding:0 12px; 152 | color:#666; 153 | } 154 | } 155 | @media screen and (max-width:480px) { 156 | .drive-header{ 157 | padding:12px; 158 | } 159 | .drive-routes{ 160 | padding: 12px 12px 0px; 161 | } 162 | .drive-body{ 163 | padding:12px 0; 164 | } 165 | } -------------------------------------------------------------------------------- /packages/sharelist-web/src/views/home/partial/auth/index.less: -------------------------------------------------------------------------------- 1 | .auth-box{ 2 | height:70vh; 3 | width:100%; 4 | display:flex; 5 | align-items:center; 6 | justify-content:center; 7 | flex-direction:column; 8 | box-sizing:border-box; 9 | .auth-box__input{ 10 | padding:8px 11px; 11 | } 12 | .auth-box__btn{ 13 | width:100%; 14 | margin-top:24px; 15 | height:42px;line-height: 42px;; 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/views/home/partial/auth/index.tsx: -------------------------------------------------------------------------------- 1 | import { ref, defineComponent, watch, onMounted, toRef, watchEffect, reactive } from 'vue' 2 | import { useStore } from 'vuex' 3 | import { RouterView, useRoute, useRouter } from 'vue-router' 4 | import { Layout, Button, Form, Input } from 'ant-design-vue' 5 | import { useSetting } from '@/hooks/useSetting' 6 | import useUrlState from '@/hooks/useUrlState' 7 | 8 | import './index.less' 9 | 10 | export default defineComponent({ 11 | setup() { 12 | const { setQuery } = useUrlState() 13 | 14 | const token = ref('') 15 | const onEnter = () => { 16 | setQuery({ auth: token.value }) 17 | } 18 | 19 | return () => ( 20 |
21 |
22 | (token.value = e.target.value)} 26 | placeholder="输入密码" 27 | onPressEnter={onEnter} 28 | /> 29 | 32 |
33 |
34 | ) 35 | }, 36 | }) 37 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/views/home/partial/breadcrumb/index.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-web/src/views/home/partial/breadcrumb/index.less -------------------------------------------------------------------------------- /packages/sharelist-web/src/views/home/partial/breadcrumb/index.tsx: -------------------------------------------------------------------------------- 1 | import { ref, defineComponent, watch, onMounted, computed, watchEffect } from 'vue' 2 | import Icon from '@/components/icon' 3 | import { Breadcrumb } from 'ant-design-vue' 4 | import useUrlState from '@/hooks/useUrlState' 5 | import useDisk from '@/hooks/useDisk' 6 | 7 | const { Item: BreadcrumbItem } = Breadcrumb 8 | import './index.less' 9 | export default defineComponent({ 10 | props: { 11 | paths: { 12 | type: Array 13 | } 14 | }, 15 | setup(props, ctx) { 16 | const { paths, setPath } = useDisk() 17 | 18 | const onclick = (path: string, idx: number) => { 19 | if (idx < paths.value.length) { 20 | setPath({ path: paths.value.slice(0, idx).join('/') }) 21 | } 22 | } 23 | 24 | return () => ( 25 |
26 | 27 | onclick('/', 0)}> 28 | 29 | 30 | 31 | 32 | {paths.value.map((i: string, idx: number) => ( 33 | onclick(i, idx + 1)}> 34 | {i} 35 | 36 | ))} 37 | 38 |
39 | ) 40 | }, 41 | }) 42 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/views/home/partial/error/index.less: -------------------------------------------------------------------------------- 1 | 2 | .err{ 3 | height:70vh; 4 | width:100%; 5 | display:flex; 6 | align-items:center; 7 | justify-content:center; 8 | flex-direction:column; 9 | box-sizing:border-box; 10 | .err__status{ 11 | font-size: 36px; 12 | font-family: inherit; 13 | font-weight: 500; 14 | line-height: 1.1; 15 | color:rgba(0,0,0,.8); 16 | } 17 | .err__msg{ 18 | color:rgba(0,0,0,.45); 19 | font-size: 16px; 20 | font-family: 'Source Code Pro','microsoft yahei',Lato,Helvetica,Arial,sans-serif; 21 | } 22 | } -------------------------------------------------------------------------------- /packages/sharelist-web/src/views/home/partial/error/index.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, watch } from 'vue' 2 | import './index.less' 3 | import AuthBox from '../auth' 4 | import { message } from 'ant-design-vue' 5 | 6 | export default defineComponent({ 7 | props: { 8 | value: { 9 | type: Object, 10 | required: true, 11 | }, 12 | }, 13 | setup(props, ctx) { 14 | if (props.value.code == 401 && props.value.message) { 15 | message.error(props.value.message) 16 | } 17 | console.log(props.value) 18 | return () => { 19 | if (props.value.code) { 20 | return props.value.code == 401 ? ( 21 | 22 | ) : ( 23 |
24 |

{props.value.code}

25 |
{props.value.message}
26 |
27 | ) 28 | } else { 29 | return ctx.slots.default?.() 30 | } 31 | } 32 | }, 33 | }) 34 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/views/home/partial/header/index.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reruin/sharelist/8761eb527ea85737859e61f3a5bacd767601923d/packages/sharelist-web/src/views/home/partial/header/index.less -------------------------------------------------------------------------------- /packages/sharelist-web/src/views/home/partial/header/index.tsx: -------------------------------------------------------------------------------- 1 | import { ref, defineComponent, watch, onMounted } from 'vue' 2 | import './index.less' 3 | import Icon from '@/components/icon' 4 | import { useStore } from 'vuex' 5 | import { useToggle, useTitle } from '@/hooks/useHooks' 6 | // import Search from '../search' 7 | import { Modal, InputSearch } from 'ant-design-vue' 8 | import { RouterView, useRoute, useRouter } from 'vue-router' 9 | import useUrlState from '@/hooks/useUrlState' 10 | import { useConfig } from '@/hooks/useSetting' 11 | 12 | export default defineComponent({ 13 | setup() { 14 | const { state, commit } = useStore() 15 | 16 | const { setQuery, setPath } = useUrlState() 17 | 18 | const { config } = useConfig() 19 | 20 | watch(() => config.title, (nv) => { 21 | useTitle(nv) 22 | }) 23 | 24 | 25 | const onChangeLayout = () => { 26 | commit('SET_LAYOUT', state.setting.layout == 'list' ? 'grid' : 'list') 27 | } 28 | 29 | const onToggleSearch = () => { 30 | const onSearch = (value: string) => { 31 | if (value) { 32 | // router.push({ path: router.currentRoute.value.path, query: { search: value } }) 33 | setQuery({ search: value }) 34 | modal.destroy() 35 | } 36 | } 37 | 38 | const modal = Modal.confirm({ 39 | class: 'fix-modal--alone', 40 | width: '560px', 41 | maskClosable: true, 42 | content: ( 43 |
44 | 45 |
46 | ), 47 | }) 48 | } 49 | 50 | const navHome = () => setPath('/') 51 | 52 | return () => ( 53 |
54 |
55 | {config.title || 'Sharelist'} 56 |
57 |
58 | {/* */} 59 | 64 |
65 |
66 | ) 67 | }, 68 | }) 69 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/views/home/partial/search/index.less: -------------------------------------------------------------------------------- 1 | .search-box{ 2 | position: absolute; 3 | } -------------------------------------------------------------------------------- /packages/sharelist-web/src/views/home/partial/search/index.tsx: -------------------------------------------------------------------------------- 1 | import { ref, defineComponent, watch, onMounted } from 'vue' 2 | import Icon from '@/components/icon' 3 | import { useStore } from 'vuex' 4 | import { Modal, InputSearch } from 'ant-design-vue' 5 | import { SearchOutlined } from '@ant-design/icons-vue' 6 | import './index.less' 7 | export default defineComponent({ 8 | props: { 9 | visible: { 10 | type: Boolean, 11 | default: false, 12 | }, 13 | }, 14 | setup(props, ctx) { 15 | const visible = ref(false) 16 | const key = ref('') 17 | 18 | const setModalVisible = () => { 19 | visible.value = false 20 | } 21 | 22 | const onSearch = () => { } 23 | return () => 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/views/manage/index.less: -------------------------------------------------------------------------------- 1 | .setting { 2 | // max-width:960px; 3 | margin: 0 auto; 4 | width: 90%; 5 | margin: 0 auto; 6 | padding: 25px 0; 7 | 8 | .setting-action { 9 | color: rgba(0, 0, 0, .65); 10 | display: flex; 11 | align-items: center; 12 | } 13 | 14 | .settiing-header { 15 | display: flex; 16 | align-items: center; 17 | justify-content: space-between; 18 | margin-bottom: 2em; 19 | color: rgba(0, 0, 0, .65); 20 | font-size: 22px; 21 | 22 | .settiing-header__back { 23 | margin-right: 1em; 24 | } 25 | } 26 | 27 | .setting-drive__header { 28 | text-align: right; 29 | } 30 | 31 | .item { 32 | border-bottom: 1px solid #e8e8e8; 33 | padding: 15px 0; 34 | display: flex; 35 | align-items: center; 36 | 37 | .item__header { 38 | flex: 1 1 auto; 39 | display: flex; 40 | align-items: center; 41 | } 42 | 43 | .item__icon { 44 | margin-right: 8px; 45 | } 46 | 47 | .item__meta { 48 | flex: 1 1 auto; 49 | } 50 | 51 | .item__meta-title { 52 | margin-bottom: 4px; 53 | color: rgba(0, 0, 0, .65); 54 | font-size: 14px; 55 | line-height: 22px; 56 | } 57 | 58 | .item__meta-desc { 59 | color: rgba(0, 0, 0, .45); 60 | font-size: 14px; 61 | line-height: 22px; 62 | } 63 | 64 | .item-action a { 65 | padding: 0 8px; 66 | } 67 | } 68 | } 69 | 70 | .setting-signin { 71 | position: fixed; 72 | top: 45%; 73 | left: 50%; 74 | transform: translate(-50%, -50%); 75 | width: 320px; 76 | 77 | .setting-signin__input { 78 | padding: 8px 11px; 79 | } 80 | 81 | .setting-signin__btn { 82 | width: 100%; 83 | margin-top: 24px; 84 | height: 42px; 85 | } 86 | } -------------------------------------------------------------------------------- /packages/sharelist-web/src/views/manage/index.tsx: -------------------------------------------------------------------------------- 1 | import { ref, defineComponent, watch, onMounted, toRef, reactive } from 'vue' 2 | import { useStore } from 'vuex' 3 | import { RouterView, useRoute, useRouter } from 'vue-router' 4 | import { Layout, Button, Spin } from 'ant-design-vue' 5 | import request from '@/utils/request' 6 | import Icon from '@/components/icon' 7 | import './index.less' 8 | import { SettingOutlined, DatabaseOutlined, PoweroffOutlined, DeleteOutlined, ReloadOutlined, SaveOutlined } from '@ant-design/icons-vue' 9 | import { Tabs } from 'ant-design-vue' 10 | const { TabPane } = Tabs 11 | import General from './partial/general' 12 | import Drive from './partial/disk' 13 | import Signin from './partial/signin' 14 | import { useSetting, useConfig } from '@/hooks/useSetting' 15 | import useConfirm from '@/hooks/useConfirm' 16 | import { useTitle } from '@/hooks/useHooks' 17 | 18 | export default defineComponent({ 19 | setup() { 20 | const { loginState, isLoading, signout, exportConfig, reload, clearCache } = useSetting() 21 | 22 | const confirmClearCache = useConfirm(clearCache, '确认', '确认清除缓存?') 23 | 24 | const confirmSignout = useConfirm(signout, '确认', '确认退出?') 25 | 26 | const confirmReload = useConfirm(reload, '确认', '确认重启?') 27 | 28 | const tabsSlots = { 29 | tabBarExtraContent: () =>
导出配置
30 | } 31 | const { config } = useConfig() 32 | 33 | watch(() => config.title, (nv) => { 34 | useTitle(nv) 35 | }) 36 | 37 | return () => ( 38 |
39 |
40 | {/* */} 41 |
设置
42 | {loginState.value == 1 ? ( 43 |
44 | 45 | 46 | 47 |
48 | ) : null} 49 |
50 | 51 | {loginState.value == 1 ? ( 52 | 53 | 54 | {{ 55 | default: () => , 56 | tab: () => ( 57 | 58 | 59 | 常规 60 | 61 | ), 62 | }} 63 | 64 | 65 | {{ 66 | default: () => , 67 | tab: () => ( 68 | 69 | 70 | 挂载盘 71 | 72 | ), 73 | }} 74 | 75 | 76 | ) : loginState.value == 2 ? ( 77 | 78 | ) : null} 79 | 80 |
81 | ) 82 | }, 83 | }) 84 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/views/manage/partial/disk.tsx: -------------------------------------------------------------------------------- 1 | import { ref, defineComponent, watch, onMounted, toRef, toRefs, reactive, watchEffect } from 'vue' 2 | import Icon from '@/components/icon' 3 | import { useSetting } from '@/hooks/useSetting' 4 | import { Layout, Button, Form, Modal, Popconfirm } from 'ant-design-vue' 5 | import { PlusOutlined } from '@ant-design/icons-vue' 6 | 7 | import Modifier from './drive-modifier' 8 | 9 | export default defineComponent({ 10 | setup() { 11 | const { config, setConfig } = useSetting() 12 | 13 | const createModifier = (data: IDrive, idx = -1) => { 14 | const updateData = (modifyData: IDrive) => { 15 | const saveData = [...config.drives] 16 | // console.log(saveData, idx) 17 | if (idx == -1) { 18 | saveData.push(modifyData) 19 | } else { 20 | saveData[idx] = modifyData 21 | } 22 | setConfig({ drives: saveData }) 23 | modal.destroy() 24 | } 25 | 26 | const modal = Modal.confirm({ 27 | class: 'fix-modal--alone', 28 | width: '720px', 29 | closable: true, 30 | content: ( 31 |
32 | 33 |
34 | ), 35 | onOk: () => { }, 36 | }) 37 | } 38 | 39 | const onCreateDrive = () => { 40 | createModifier( 41 | { 42 | name: '', 43 | path: { 44 | protocol: '', 45 | }, 46 | }, 47 | -1, 48 | ) 49 | } 50 | 51 | const remove = (data: IDrive, idx: number) => { 52 | setConfig({ drives: config.drives.filter((_: any, i: number) => i != idx) }) 53 | } 54 | 55 | const orderUp = (data: IDrive, idx: number) => { 56 | if (idx > 0) { 57 | const drives = config.drives 58 | const saveData = [...drives] 59 | const a = drives[idx - 1], 60 | b = drives[idx] 61 | saveData[idx - 1] = b 62 | saveData[idx] = a 63 | setConfig({ drives: saveData }) 64 | } 65 | } 66 | 67 | return () => ( 68 |
69 |
70 | 76 |
77 |
78 | {config.drives.map((i: IDrive, idx: number) => ( 79 |
80 |
81 | 82 |
83 |

{i.name}

84 |
{i.path.protocol}
85 |
86 |
87 |
88 | createModifier(i, idx)}>修改 89 | remove(i, idx)}> 90 | 移除 91 | 92 | orderUp(i, idx)}>上移 93 |
94 |
95 | ))} 96 |
97 |
98 | ) 99 | }, 100 | }) 101 | -------------------------------------------------------------------------------- /packages/sharelist-web/src/views/manage/partial/general.tsx: -------------------------------------------------------------------------------- 1 | import { ref, defineComponent, watch, onMounted, toRef, toRefs, reactive } from 'vue' 2 | import { useStore } from 'vuex' 3 | import { RouterView, useRoute, useRouter } from 'vue-router' 4 | import { Layout, Button, Form } from 'ant-design-vue' 5 | import request from '@/utils/request' 6 | import Icon from '@/components/icon' 7 | import { SettingOutlined, DatabaseOutlined } from '@ant-design/icons-vue' 8 | import { useSetting } from '@/hooks/useSetting' 9 | import { Switch, Modal, Input } from 'ant-design-vue' 10 | const { TextArea } = Input 11 | const valueDisplay = (value: any, type: string) => { 12 | if (type == 'boolean') return Boolean(value) ? '启用' : '禁用' 13 | else if (type == 'string') return value 14 | else if (type == 'array') { 15 | const len = value.length 16 | const nodes = value.slice(0, 3).map((i: string) =>
{i}
) 17 | if (len > 3) { 18 | nodes.push(
等{len}项
) 19 | } 20 | return nodes 21 | } 22 | } 23 | 24 | export default defineComponent({ 25 | setup() { 26 | const fields = [ 27 | { code: 'token', label: '后台密码', type: 'string', secret: true }, 28 | { code: 'title', label: '网站标题', type: 'string' }, 29 | { code: 'index_enable', label: '目录索引', type: 'boolean' }, 30 | { code: 'expand_single_disk', label: '展开单一挂载盘', type: 'boolean' }, 31 | { code: 'anonymous_download_enable', label: '允许下载', type: 'boolean' }, 32 | { code: 'fast_mode', label: '快速模式', type: 'boolean' }, 33 | { code: 'ignores', label: '忽略路径', type: 'array' }, 34 | { code: 'acl_file', label: '加密文件名', type: 'string' }, 35 | { code: 'webdav_path', label: 'WebDAV 路径', type: 'string' }, 36 | { code: 'webdav_proxy', label: 'WebDAV 代理', type: 'boolean' }, 37 | { code: 'webdav_user', label: 'WebDAV 用户名', type: 'string' }, 38 | { code: 'webdav_pass', label: 'WebDAV 密码', type: 'string' }, 39 | { code: 'script', label: '自定义脚本', type: 'string' }, 40 | { code: 'style', label: '自定义样式', type: 'string' }, 41 | ] 42 | const { config, setConfig } = useSetting() 43 | 44 | const createInputModifier = (label: string, code: string, isSecret: boolean | undefined) => { 45 | const modifier = ref(isSecret ? '' : config[code]) 46 | const handleChange = (e: any) => modifier.value = e.target.value 47 | 48 | //modal 下的input v-model 有bug 49 | Modal.confirm({ 50 | title: label, 51 | class: 'fix-modal--narrow-padding', 52 | content: ( 53 |
54 |