Merge branch 'refs/heads/dev' into search

# Conflicts:
#	src/components/ShowFile/ShowFile.vue
This commit is contained in:
jmal 2024-07-16 09:49:22 +08:00
commit 07613e7924
93 changed files with 4641 additions and 631 deletions

View File

@ -15,7 +15,7 @@ change-template: '- $TITLE (#$NUMBER)'
no-changes-template: '- No changes' no-changes-template: '- No changes'
template: | template: |
[docker-docker.yml](https://github.com/jamebal/jmal-cloud-server/blob/master/docker-compose.example2.yml) [docker-docker.yml](https://github.com/jamebal/jmal-cloud-server/blob/master/docker-compose.base.yml)
#### 更新前备份数据库 #### 更新前备份数据库
@ -27,6 +27,11 @@ template: |
docker-compose pull && docker-compose up -d docker-compose pull && docker-compose up -d
``` ```
#### 清理旧镜像
```shell
docker images --filter=reference='jmal/jmalcloud*' --filter "dangling=true" -q | xargs -r docker rmi
```
## Changes in this release: ## Changes in this release:
$CHANGES $CHANGES

View File

@ -0,0 +1,32 @@
name: Build nginx with Draw.io Docker Image
on:
workflow_dispatch:
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: docker/nginx-drawio/Dockerfile
push: true
tags: jmal/nginx-drawio:latest
platforms: linux/amd64,linux/arm64
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

View File

@ -1,4 +1,4 @@
name: Build and Push Multi-Platform Docker Image name: Build Docker Image
on: on:
workflow_dispatch: workflow_dispatch:
@ -12,10 +12,10 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Set up Node.js 16.20.2 - name: Set up Node.js 16.20.2
uses: actions/setup-node@v2 uses: actions/setup-node@v4
with: with:
node-version: 16.20.2 node-version: 16.20.2
@ -31,23 +31,30 @@ jobs:
echo "PROJECT_VERSION=$VERSION" >> $GITHUB_ENV echo "PROJECT_VERSION=$VERSION" >> $GITHUB_ENV
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v1 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ secrets.GHCR_IO_USERNAME }}
password: ${{ secrets.GHCR_IO_TOKEN }}
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v2 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
push: true push: true
tags: jmal/jmalcloud-nginx:${{ env.PROJECT_VERSION }},jmal/jmalcloud-nginx:latest tags: |
jmal/jmalcloud-nginx:${{ env.PROJECT_VERSION }},jmal/jmalcloud-nginx:latest
ghcr.io/${{ secrets.GHCR_IO_USERNAME }}/jmalcloud-nginx:${{ env.PROJECT_VERSION }},ghcr.io/${{ secrets.GHCR_IO_USERNAME }}/jmalcloud-nginx:latest
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
build-args: | build-args: |
VERSION=${{ env.PROJECT_VERSION }} VERSION=${{ env.PROJECT_VERSION }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

87
.github/workflows/pushAliyun.yml vendored Normal file
View File

@ -0,0 +1,87 @@
name: Build and Push Docker Image to Aliyun
on:
workflow_dispatch:
release:
types: [ published ]
tags: [ v* ]
jobs:
build_and_push:
runs-on: ubuntu-latest
strategy:
matrix:
arch:
- amd64
- arm64
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Node.js 16.20.2
uses: actions/setup-node@v4
with:
node-version: 16.20.2
- name: Install dependencies
run: npm install && npm install --save core-js@2
- name: Build
run: npm run build:prod
- name: Extract project version from package.json
run: |
VERSION=$(node -p "require('./package.json').version")
echo "PROJECT_VERSION=$VERSION" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Aliyun Container Registry guangzhou
uses: docker/login-action@v3
with:
registry: registry.cn-guangzhou.aliyuncs.com
username: ${{ secrets.ALIYUN_DOCKER_USERNAME }}
password: ${{ secrets.ALIYUN_DOCKER_PASSWORD }}
- name: Log in to Aliyun Container Registry beijing
uses: docker/login-action@v3
with:
registry: registry.cn-beijing.aliyuncs.com
username: ${{ secrets.ALIYUN_DOCKER_USERNAME }}
password: ${{ secrets.ALIYUN_DOCKER_PASSWORD }}
- name: Set tag for amd64
if: matrix.arch == 'amd64'
run: |
echo "TAG_LATEST_0=registry.cn-guangzhou.aliyuncs.com/jmalcloud/jmalcloud-nginx:latest" >> $GITHUB_ENV
echo "TAG_VERSION_0=registry.cn-guangzhou.aliyuncs.com/jmalcloud/jmalcloud-nginx:${{ env.PROJECT_VERSION }}" >> $GITHUB_ENV
echo "TAG_LATEST_1=registry.cn-beijing.aliyuncs.com/jmalcloud/jmalcloud-nginx:latest" >> $GITHUB_ENV
echo "TAG_VERSION_1=registry.cn-beijing.aliyuncs.com/jmalcloud/jmalcloud-nginx:${{ env.PROJECT_VERSION }}" >> $GITHUB_ENV
- name: Set tag for non-amd64
if: matrix.arch != 'amd64'
run: |
echo "TAG_LATEST_0=registry.cn-guangzhou.aliyuncs.com/jmalcloud/jmalcloud-nginx:latest-${{ matrix.arch }}" >> $GITHUB_ENV
echo "TAG_VERSION_0=registry.cn-guangzhou.aliyuncs.com/jmalcloud/jmalcloud-nginx:${{ env.PROJECT_VERSION }}-${{ matrix.arch }}" >> $GITHUB_ENV
echo "TAG_LATEST_1=registry.cn-beijing.aliyuncs.com/jmalcloud/jmalcloud-nginx:latest-${{ matrix.arch }}" >> $GITHUB_ENV
echo "TAG_VERSION_1=registry.cn-beijing.aliyuncs.com/jmalcloud/jmalcloud-nginx:${{ env.PROJECT_VERSION }}-${{ matrix.arch }}" >> $GITHUB_ENV
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
platforms: linux/${{ matrix.arch }}
push: true
tags: |
${{ env.TAG_LATEST_0 }}
${{ env.TAG_VERSION_0 }}
${{ env.TAG_LATEST_1 }}
${{ env.TAG_VERSION_1 }}
build-args: |
VERSION=${{ env.PROJECT_VERSION }}

View File

@ -10,7 +10,7 @@ jobs:
update_release_draft: update_release_draft:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: release-drafter/release-drafter@v5 - uses: release-drafter/release-drafter@v5
with: with:
config-name: release-drafter.yml config-name: release-drafter.yml

View File

@ -1,4 +1,4 @@
name: Build and Push Multi-Platform Docker Image name: Build Test Docker Image
on: on:
push: push:
@ -11,10 +11,10 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Set up Node.js 16.20.2 - name: Set up Node.js 16.20.2
uses: actions/setup-node@v2 uses: actions/setup-node@v4
with: with:
node-version: 16.20.2 node-version: 16.20.2
@ -30,23 +30,30 @@ jobs:
echo "PROJECT_VERSION=$VERSION" >> $GITHUB_ENV echo "PROJECT_VERSION=$VERSION" >> $GITHUB_ENV
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v1 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ secrets.GHCR_IO_USERNAME }}
password: ${{ secrets.GHCR_IO_TOKEN }}
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v2 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
push: true push: true
tags: jmal/jmalcloud-nginx:nightly tags: |
jmal/jmalcloud-nginx:test
ghcr.io/${{ secrets.GHCR_IO_USERNAME }}/jmalcloud-nginx:test
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
build-args: | build-args: |
VERSION=${{ env.PROJECT_VERSION }} VERSION=${{ env.PROJECT_VERSION }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

View File

@ -1,11 +1,11 @@
FROM nginx:latest FROM jmal/nginx-drawio:latest
COPY nginx.conf /etc/nginx/nginx.conf COPY nginx.conf /etc/nginx/nginx.conf
COPY nginx.conf /etc/nginx/nginx.conf.template
COPY dist /var/www/public COPY dist /var/www/public
COPY config.js.template /var/www/public/config.js.template
COPY docker-entrypoint.sh /docker-entrypoint.sh COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh RUN chmod +x /docker-entrypoint.sh

View File

@ -21,7 +21,7 @@ JmalCloud 是一款私有云存储网盘项目,能够简单安全管理您的
### 部署 ### 部署
- 必须安装:`Docker` 和 `Docker Compose v2.0+` - 必须安装:`Docker` 和 `Docker Compose v2.0+`
参考[docker-compose.yml](https://github.com/jamebal/jmal-cloud-server/blob/master/docker-compose.example2.yml) 参考[docker-compose.yml](https://github.com/jamebal/jmal-cloud-server/blob/master/docker-compose.base.yml)
```shell ```shell
docker compose up -d docker compose up -d
@ -44,7 +44,7 @@ docker restart jmalcloud_server
``` ```
#### 恢复数据库 #### 恢复数据库
```shell ```shell
docker exec -it jmalcloud_mongodb mongorestore -d jmalcloud --dir /dump/xxx --gzip --quiet docker exec -it jmalcloud_mongodb mongorestore --gzip --nsInclude=jmalcloud.* --dir /dump/xxx --quiet
``` ```
### dev ### dev

View File

@ -16,13 +16,13 @@ JmalCloud It's a private cloud storage project that makes it simple and secure t
✅ supports WebDAV ✅ supports WebDAV
### Online Demo: ### Online Demo:
[View Demo](https://jmal.cc/demo) , username:demo, password:demo1234 [Live Demo](https://jmal.cc/demo) , username:demo, password:demo1234
### Deploy ### Deploy
- Prerequisites:`Docker` and `Docker Compose v2.0+` - Prerequisites:`Docker` and `Docker Compose v2.0+`
Refer to [docker-compose.yml](https://github.com/jamebal/jmal-cloud-server/blob/master/docker-compose.example2.yml) Refer to [docker-compose.yml](https://github.com/jamebal/jmal-cloud-server/blob/master/docker-compose.base.yml)
```shell ```shell
docker compose up -d docker compose up -d

4
config.js.template Normal file
View File

@ -0,0 +1,4 @@
window._env_ = {
API_URL: "${API_URL}",
API_OFFICE_URL: "${API_OFFICE_URL}"
};

View File

@ -1,8 +1,4 @@
#!/bin/bash #!/bin/bash
set -e set -e
envsubst '${API_URL} ${API_OFFICE_URL}' < /var/www/public/config.js.template > /var/www/public/config.js
# 使用envsubst命令替换nginx.conf.template文件中的环境变量
envsubst '$API_BASE_URL $API_DRAWIO_URL $API_OFFICE_URL' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
# 启动Nginx
exec "$@" exec "$@"

View File

@ -0,0 +1,17 @@
FROM nginx:latest
RUN apt-get update && \
apt-get install -y unzip wget && \
mkdir -p /etc/nginx/servers /var/www && \
wget https://github.com/jgraph/drawio/releases/download/v24.4.10/draw.war -O /var/www/draw.war && \
wget https://github.com/mozilla/pdf.js/releases/download/v4.3.136/pdfjs-4.3.136-dist.zip -O /var/www/pdfjs.zip && \
unzip /var/www/draw.war -d /var/www/draw && \
unzip /var/www/pdfjs.zip -d /var/www/pdfjs && \
rm -rf /var/www/pdfjs.zip /var/www/draw.war /var/www/draw/META-INF /var/www/draw/WEB-INF && \
apt-get remove -y unzip wget && \
apt-get autoremove -y && \
apt-get clean
COPY docker/nginx-drawio/drawio.conf /etc/nginx/servers/drawio.conf
COPY docker/nginx-drawio/pdfjs.conf /etc/nginx/servers/pdfjs.conf
COPY docker/nginx-drawio/mime.types /etc/nginx/mime.types

View File

@ -0,0 +1,10 @@
server {
listen 8080;
server_name localhost;
root /var/www/draw;
location / {
try_files $uri $uri/ /index.html;
index index.html index.htm;
}
}

View File

@ -0,0 +1,98 @@
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js mjs;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/avif avif;
image/png png;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/webp webp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
font/woff woff;
font/woff2 woff2;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.oasis.opendocument.graphics odg;
application/vnd.oasis.opendocument.presentation odp;
application/vnd.oasis.opendocument.spreadsheet ods;
application/vnd.oasis.opendocument.text odt;
application/vnd.openxmlformats-officedocument.presentationml.presentation
pptx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
xlsx;
application/vnd.openxmlformats-officedocument.wordprocessingml.document
docx;
application/vnd.wap.wmlc wmlc;
application/wasm wasm;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}

View File

@ -0,0 +1,5 @@
server {
listen 8081;
server_name localhost;
root /var/www/pdfjs;
}

View File

@ -89,6 +89,36 @@ http {
index index.html index.htm; index index.html index.htm;
} }
location = /blog {
proxy_pass http://jmalcloud:8088/articles;
proxy_set_header Host $proxy_host;
proxy_set_header X-real-ip $remote_addr;
}
location = /blog/ {
proxy_pass http://jmalcloud:8088/articles;
proxy_set_header Host $proxy_host;
proxy_set_header X-real-ip $remote_addr;
}
location /blog/api/ {
proxy_pass http://jmalcloud:8088/;
proxy_set_header Host $proxy_host;
proxy_set_header X-real-ip $remote_addr;
}
location /blog/articles {
proxy_pass http://jmalcloud:8088/articles/;
proxy_set_header Host $proxy_host;
proxy_set_header X-real-ip $remote_addr;
}
location /blog/ {
proxy_pass http://jmalcloud:8088/articles/;
proxy_set_header Host $proxy_host;
proxy_set_header X-real-ip $remote_addr;
}
location /api/ { location /api/ {
proxy_pass http://jmalcloud:8088/; proxy_pass http://jmalcloud:8088/;
proxy_http_version 1.1; proxy_http_version 1.1;
@ -179,11 +209,29 @@ http {
} }
location /drawio/webapp/ { location /drawio/webapp/ {
proxy_pass http://drawio-webapp:8080/; proxy_pass http://localhost:8080/;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-PORT $remote_port; proxy_set_header X-Real-PORT $remote_port;
proxy_set_header X-Forwarded-Host $the_host/drawio/webapp; proxy_set_header X-Forwarded-Host $the_host//drawio/webapp;
proxy_set_header X-Forwarded-Proto $the_scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme;
proxy_set_header Server-Protocol $server_protocol;
proxy_set_header Server-Name $server_name;
proxy_set_header Server-Addr $server_addr;
proxy_set_header Server-Port $server_port;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
location /pdf.js/ {
proxy_pass http://localhost:8081/;
proxy_http_version 1.1;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-PORT $remote_port;
proxy_set_header X-Forwarded-Host $the_host//pdf.js;
proxy_set_header X-Forwarded-Proto $the_scheme; proxy_set_header X-Forwarded-Proto $the_scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;

View File

@ -1,6 +1,6 @@
{ {
"name": "jmal-cloud-view", "name": "jmal-cloud-view",
"version": "2.8.3", "version": "2.9.2",
"description": "jmalcloud", "description": "jmalcloud",
"author": "Jmal <zhushilun084@163.com>", "author": "Jmal <zhushilun084@163.com>",
"license": "MIT", "license": "MIT",
@ -16,7 +16,6 @@
}, },
"dependencies": { "dependencies": {
"ant-design-vue": "^1.6.5", "ant-design-vue": "^1.6.5",
"artplayer": "^5.1.1",
"clipboard": "^2.0.6", "clipboard": "^2.0.6",
"cropperjs": "^1.5.6", "cropperjs": "^1.5.6",
"e-vue-contextmenu": "^0.1.3", "e-vue-contextmenu": "^0.1.3",

8
prettier.config.js Normal file
View File

@ -0,0 +1,8 @@
module.exports = {
singleQuote: true,
semi: false,
trailingComma: 'es6',
tabWidth: 2,
printWidth: 80,
endOfLine: 'lf'
}

4
public/config.js Normal file
View File

@ -0,0 +1,4 @@
window._env_ = {
API_URL: "",
API_OFFICE_URL: "",
};

View File

@ -17,8 +17,8 @@
<!--axios.js--> <!--axios.js-->
<script src="<%= htmlWebpackPlugin.options.path %>/resource/axios@0.21.1.js"></script> <script src="<%= htmlWebpackPlugin.options.path %>/resource/axios@0.21.1.js"></script>
<!--vant.js--> <!--vant.js-->
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.path %>/resource/vant@2.12.css"/> <link rel="stylesheet" href="<%= htmlWebpackPlugin.options.path %>/resource/vant@2.12.54.css"/>
<script src="<%= htmlWebpackPlugin.options.path %>/resource/vant@2.12.js"></script> <script src="<%= htmlWebpackPlugin.options.path %>/resource/vant@2.12.54.js"></script>
<!--element-ui.js--> <!--element-ui.js-->
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.path %>/resource/element-ui@2.13.0.css"> <link rel="stylesheet" href="<%= htmlWebpackPlugin.options.path %>/resource/element-ui@2.13.0.css">
<script src="<%= htmlWebpackPlugin.options.path %>/resource/element-ui@2.13.0.js"></script> <script src="<%= htmlWebpackPlugin.options.path %>/resource/element-ui@2.13.0.js"></script>
@ -35,7 +35,9 @@
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= webpackConfig.name %></title> <title><%= webpackConfig.name %></title>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.path %>/resource/font-awesome/free-6.4.2/css/all.min.css"> <link rel="stylesheet" href="<%= htmlWebpackPlugin.options.path %>/resource/font-awesome/free-6.4.2/css/all.min.css">
<script src="/config.js"></script>
<!-- <script type="module" src="<%= htmlWebpackPlugin.options.path %>/resource/model-viewer.min.js"></script>-->
</head> </head>
<body> <body>
<noscript> <noscript>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,282 @@
body {
margin: 0;
background: #fafafa;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #333;
position: absolute;
height: 100%;
width: 100%;
min-height: 800px;
}
#title {
width: 900px;
min-height: 18px;
margin: 10px auto;
text-align: center;
font-size: 16px;
color: #E2E2E2;
font-weight: 400;
}
#title:hover {
color: #777;
}
#viewer.spreads {
width: 900px;
height: 600px;
box-shadow: 0 0 4px #ccc;
border-radius: 5px;
padding: 0;
position: relative;
margin: 10px auto;
background: white url('ajax-loader.gif') center center no-repeat;
top: calc(50vh - 400px);
}
#viewer.spreads .epub-view > iframe {
background: white;
}
#viewer.scrolled {
overflow: hidden;
width: 800px;
margin: 0 auto;
position: relative;
background: url('ajax-loader.gif') center center no-repeat;
box-shadow: 0 0 4px #ccc;
padding: 20px;
background: white;
}
#viewer.scrolled .epub-view > iframe {
background: white;
}
#prev {
left: 0;
}
#next {
right: 0;
}
#toc {
display: block;
margin: 10px auto;
}
@media (min-width: 1000px) {
#viewer.spreads:after {
position: absolute;
width: 1px;
border-right: 1px #000 solid;
height: 90%;
z-index: 1;
left: 50%;
margin-left: -1px;
top: 5%;
opacity: .15;
box-shadow: -2px 0 15px rgba(0, 0, 0, 1);
content: "";
}
#viewer.spreads.single:after {
display: none;
}
#prev {
left: 40px;
}
#next {
right: 40px;
}
}
.arrow {
position: fixed;
top: 50%;
margin-top: -32px;
font-size: 64px;
color: #E2E2E2;
font-family: arial, sans-serif;
font-weight: bold;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
text-decoration: none;
}
.navlink {
margin: 14px;
display: block;
text-align: center;
text-decoration: none;
color: #ccc;
}
.arrow:hover, .navlink:hover {
color: #777;
}
.arrow:active, .navlink:hover {
color: #000;
}
#book-wrapper {
width: 480px;
height: 640px;
overflow: hidden;
border: 1px solid #ccc;
margin: 28px auto;
background: #fff;
border-radius: 0 5px 5px 0;
position: absolute;
}
#book-viewer {
width: 480px;
height: 660px;
margin: -30px auto;
-moz-box-shadow: inset 10px 0 20px rgba(0,0,0,.1);
-webkit-box-shadow: inset 10px 0 20px rgba(0,0,0,.1);
box-shadow: inset 10px 0 20px rgba(0,0,0,.1);
}
#book-viewer iframe {
padding: 40px 40px;
}
#controls {
position: absolute;
bottom: 16px;
left: 50%;
width: 400px;
margin-left: -200px;
text-align: center;
display: none;
}
#controls > input[type=range] {
width: 400px;
}
#navigation {
width: 400px;
height: 100vh;
position: absolute;
overflow: auto;
top: 0;
left: 0;
background: #777;
-webkit-transition: -webkit-transform .25s ease-out;
-moz-transition: -moz-transform .25s ease-out;
-ms-transition: -moz-transform .25s ease-out;
transition: transform .25s ease-out;
}
#navigation.fixed {
position: fixed;
}
#navigation h1 {
width: 200px;
font-size: 16px;
font-weight: normal;
color: #fff;
margin-bottom: 10px;
}
#navigation h2 {
font-size: 14px;
font-weight: normal;
color: #B0B0B0;
margin-bottom: 20px;
}
#navigation ul {
padding-left: 36px;
margin-left: 0;
margin-top: 12px;
margin-bottom: 12px;
width: 340px;
}
#navigation ul li {
list-style: decimal;
margin-bottom: 10px;
color: #cccddd;
font-size: 12px;
padding-left: 0;
margin-left: 0;
}
#navigation ul li a {
color: #ccc;
text-decoration: none;
}
#navigation ul li a:hover {
color: #fff;
text-decoration: underline;
}
#navigation ul li a.active {
color: #fff;
}
#navigation #cover {
display: block;
margin: 24px auto;
}
#navigation #closer {
position: absolute;
top: 0;
right: 0;
padding: 12px;
color: #cccddd;
width: 24px;
}
#navigation.closed {
-webkit-transform: translate(-400px, 0);
-moz-transform: translate(-400px, 0);
-ms-transform: translate(-400px, 0);
transform: translate(-400px, 0);
}
svg {
display: block;
}
.close-x {
stroke: #cccddd;
fill: transparent;
stroke-linecap: round;
stroke-width: 5;
}
.close-x:hover {
stroke: #fff;
}
#opener {
position: absolute;
top: 0;
left: 0;
padding: 10px;
stroke: #E2E2E2;
fill: #E2E2E2;
}
#opener:hover {
stroke: #777;
fill: #777;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,167 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>EPUB.js Spreads Example</title>
<script src="./jszip@3.10.1.min.js"></script>
<script src="./epubjs@0.3.88.min.js"></script>
<link rel="stylesheet" type="text/css" href="./epub.css">
</head>
<body>
<!-- <div id="title"></div> -->
<select id="toc"></select>
<div id="viewer" class="spreads"></div>
<a id="prev" href="#prev" class="arrow">‹</a>
<a id="next" href="#next" class="arrow">›</a>
<script>
var params =
URLSearchParams &&
new URLSearchParams(document.location.search.substring(1))
var url =
params && params.get("url") && decodeURIComponent(params.get("url"))
var currentSectionIndex =
params && params.get("loc") ? params.get("loc") : undefined
let blob = fetch(url).then((res) => res.blob())
var book = ePub(
blob.then((blob) => blob.arrayBuffer()),
{
restore: true,
reload: true,
spreads: true,
}
)
var rendition = book.renderTo("viewer", {
width: "100%",
height: 600,
spread: "always",
})
rendition.display(currentSectionIndex);
book.ready.then(function() {
var next = document.getElementById("next");
next.addEventListener("click", function(e){
book.package.metadata.direction === "rtl" ? rendition.prev() : rendition.next();
e.preventDefault();
}, false);
var prev = document.getElementById("prev");
prev.addEventListener("click", function(e){
book.package.metadata.direction === "rtl" ? rendition.next() : rendition.prev();
e.preventDefault();
}, false);
var keyListener = function(e){
// Left Key
if ((e.keyCode || e.which) == 37) {
book.package.metadata.direction === "rtl" ? rendition.next() : rendition.prev();
}
// Right Key
if ((e.keyCode || e.which) == 39) {
book.package.metadata.direction === "rtl" ? rendition.prev() : rendition.next();
}
};
rendition.on("keyup", keyListener);
document.addEventListener("keyup", keyListener, false);
})
var title = document.getElementById("title");
rendition.on("rendered", function(section){
var current = book.navigation && book.navigation.get(section.href);
if (current) {
var $select = document.getElementById("toc");
var $selected = $select.querySelector("option[selected]");
if ($selected) {
$selected.removeAttribute("selected");
}
var $options = $select.querySelectorAll("option");
for (var i = 0; i < $options.length; ++i) {
let selected = $options[i].getAttribute("ref") === current.href;
if (selected) {
$options[i].setAttribute("selected", "");
}
}
}
});
rendition.on("relocated", function(location){
console.log(location);
var next = book.package.metadata.direction === "rtl" ? document.getElementById("prev") : document.getElementById("next");
var prev = book.package.metadata.direction === "rtl" ? document.getElementById("next") : document.getElementById("prev");
if (location.atEnd) {
next.style.visibility = "hidden";
} else {
next.style.visibility = "visible";
}
if (location.atStart) {
prev.style.visibility = "hidden";
} else {
prev.style.visibility = "visible";
}
});
rendition.on("layout", function(layout) {
let viewer = document.getElementById("viewer");
if (layout.spread) {
viewer.classList.remove('single');
} else {
viewer.classList.add('single');
}
});
window.addEventListener("unload", function () {
console.log("unloading");
this.book.destroy();
});
book.loaded.navigation.then(function(toc){
var $select = document.getElementById("toc"),
docfrag = document.createDocumentFragment();
toc.forEach(function(chapter) {
var option = document.createElement("option");
option.textContent = chapter.label;
option.setAttribute("ref", chapter.href);
docfrag.appendChild(option);
});
$select.appendChild(docfrag);
$select.onchange = function(){
var index = $select.selectedIndex,
url = $select.options[index].getAttribute("ref");
rendition.display(url);
return false;
};
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1093
public/resource/model-viewer.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,22 +5,21 @@
</template> </template>
<script> <script>
const pcList = ['/', '/s']
const pcList = ['/','/s'] const mobileList = ['/_m', '/_m/']
const mobileList = ['/_m','/_m/','/s_m','/s_m/']
export default { export default {
name: 'App', name: 'App',
components: {}, components: {},
watch: { watch: {
$route: function(to) { $route: function(to) {
const path = to.path const path = to.path
if(pcList.includes(path) && !this.$pc){ if (pcList.includes(path) && !this.$pc) {
this.$router.push({path: path+'_m', query: to.query}) this.$router.push({ path: path + '_m', query: to.query })
} }
if(mobileList.includes(path) && this.$pc){ if (mobileList.includes(path) && this.$pc) {
this.$router.push({path: path.split('_m')[0], query: to.query}) this.$router.push({ path: path.split('_m')[0], query: to.query })
} }
} },
} },
} }
</script> </script>

View File

@ -16,11 +16,11 @@ export default {
}) })
}, },
// 检查文件是否存在 // 检查文件是否存在
checkExist: function (params) { checkExist: function (data) {
return request({ return request({
url: "checkExist", url: "checkExist",
method: "post", method: "post",
params data
}) })
}, },
// 上传文件夹 // 上传文件夹
@ -100,6 +100,35 @@ export default {
} }
}) })
}, },
// 返回原处
restore: function(params) {
return request({
url: 'restore',
method: 'post',
params,
paramsSerializer: function(params) {
return qs.stringify(params, { arrayFormat: 'repeat' })
}
})
},
// 彻底删除
sweep: function(params) {
return request({
url: 'sweep',
method: 'delete',
params,
paramsSerializer: function(params) {
return qs.stringify(params, { arrayFormat: 'repeat' })
}
})
},
// 清空回收站
clearTrash: function() {
return request({
url: 'clear-trash',
method: 'delete'
})
},
// 重名名 // 重名名
rename: function(params) { rename: function(params) {
return request({ return request({
@ -189,6 +218,14 @@ export default {
data: data data: data
}) })
}, },
// 生成shareToken
generateShareToken: function(params) {
return request({
url: '/share/generate/share-token',
method: 'get',
params
})
},
// 取消分享 // 取消分享
cancelShareLink: function(params) { cancelShareLink: function(params) {
return request({ return request({
@ -241,9 +278,9 @@ export default {
}) })
}, },
// 获取分享信息 // 获取分享信息
getShare: function(params) { getShareByFileId: function(params) {
return request({ return request({
url: '/get/share', url: '/get/share/by/fileId',
method: 'get', method: 'get',
params params
}) })
@ -338,5 +375,22 @@ export default {
method: 'get', method: 'get',
params params
}) })
} },
// 获取office jwt
getOfficeJwt: function(data) {
return request({
url: '/office/signature',
method: 'post',
data
})
},
// 获取office jwt
getPublicOfficeJwt: function(params, data) {
return request({
url: '/public/office/signature',
method: 'post',
params,
data
})
},
} }

View File

@ -1,13 +1,55 @@
import request from '@/utils/request' import request from '@/utils/request'
export default { export default {
sync(params) { getPreviewConfig(params) {
return request({
url: '/cloud/setting/preview/config',
method: 'get',
params
})
},
updatePreviewConfig(data) {
return request({
url: '/cloud/setting/preview/config',
method: 'put',
data
})
},
getTranscodeConfig(params) {
return request({
url: '/cloud/setting/transcode/config',
method: 'get',
params
})
},
setTranscodeConfig(data) {
return request({
url: '/cloud/setting/transcode/config',
method: 'put',
data
})
},
cancelTranscodeTask(data) {
return request({
url: '/cloud/setting/transcode/cancel-task',
method: 'put',
data
})
},
syncUser(params) {
return request({ return request({
url: '/user/setting/sync', url: '/user/setting/sync',
method: 'get', method: 'get',
params params
}) })
}, },
syncCloud(params) {
return request({
url: '/cloud/setting/sync',
method: 'get',
params
})
},
isSync(params) { isSync(params) {
return request({ return request({
url: '/user/setting/isSync', url: '/user/setting/isSync',
@ -62,6 +104,29 @@ export default {
url: '/user/setting/resetMenuAndRole', url: '/user/setting/resetMenuAndRole',
method: 'put', method: 'put',
}) })
},
// 获取office配置
getPublicOfficeConfig: function(params) {
return request({
url: 'public/office/config',
method: 'get',
params
})
},
// 获取office配置
getOfficeConfig: function() {
return request({
url: '/office/config',
method: 'get'
})
},
// 更新office配置
updateOfficeConfig: function(data) {
return request({
url: '/office/config',
method: 'put',
data
})
} }
} }
@ -96,3 +161,19 @@ export function getHeartwings(params) {
params params
}) })
} }
export function getTaskProgress(params) {
return request({
url: '/cloud/task/progress',
method: 'get',
params
})
}
export function getTranscodeStatus(params) {
return request({
url: '/cloud/transcode/status',
method: 'get',
params
})
}

View File

@ -62,14 +62,6 @@ export function getUserInfo(params) {
}) })
} }
export function getUsername(params) {
return request({
url: 'user/username',
method: 'get',
params
})
}
export function userList(params) { export function userList(params) {
return request({ return request({
url: 'user/userList', url: 'user/userList',

View File

@ -35,7 +35,7 @@
</div> </div>
</template> </template>
<script> <script>
import { iconClass,suffix } from '@/utils/file-type' import { findIconClass,suffix } from '@/utils/file-type'
import 'jquery.fancytree/dist/skin-win8/ui.fancytree.less'; import 'jquery.fancytree/dist/skin-win8/ui.fancytree.less';
import 'jquery.fancytree/dist/modules/jquery.fancytree.edit'; import 'jquery.fancytree/dist/modules/jquery.fancytree.edit';
import fancytree from 'jquery.fancytree'; import fancytree from 'jquery.fancytree';
@ -450,34 +450,7 @@
}) })
}, },
icon(event,data) { icon(event,data) {
return { html: this.loadSvg(this.findIconClass(data.node.data)) } return { html: this.loadSvg(findIconClass(data.node.data)) }
},
findIconClass(file){
if(!file){
return
}
if(file.isFolder){
return 'folder'
}
if (file.contentType) {
if(file.contentType.indexOf('video') > -1){
return 'video'
}
if(file.contentType.startsWith('image')){
return 'image'
}
if(file.contentType.indexOf('audio') > -1){
return 'audio'
}
}
let suffix = file.suffix;
if(!suffix && file.name){
suffix = file.name.substring(file.name.lastIndexOf('.') + 1)
}
if(iconClass.has(suffix)){
return iconClass.get(suffix)
}
return 'file'
}, },
loadSvg(svgName){ loadSvg(svgName){
if(svgName){ if(svgName){

View File

@ -5,18 +5,17 @@
import { Icon } from 'ant-design-vue' import { Icon } from 'ant-design-vue'
const IconFont = Icon.createFromIconfontCN({ const IconFont = Icon.createFromIconfontCN({
scriptUrl: document.location.protocol + '/resource/font_2024484_ziwmm4cu4r.js' scriptUrl: document.location.protocol + '/resource/font_2024484_jfax4gppvg.js'
}) })
// 把此段代码贴到iconfont项目页面的控制台中, 以获取图标列表 // 把此段代码贴到iconfont项目页面的控制台中, 以获取图标列表
// const nodeArray = Array.from(document.querySelectorAll('.block-icon-list .icon-item .icon-code.icon-code-show')); // const nodeArray = Array.from(document.querySelectorAll('.block-icon-list .icon-item .icon-code.icon-code-show'));
// const iconNames = nodeArray.map(item => item.outerText.replace('icon-','')) // const iconNames = nodeArray.map(item => item.outerText.replace('icon-',''))
// console.log(iconNames.slice(0,80)) // console.log(iconNames.slice(0,80))
// console.log(iconNames.slice(80,160)) // console.log(iconNames.slice(80,160))
export const iconFonts = { export const iconFonts = {
list: ["qrcode", "arrow1_left", "wulianwang-","zuji","disk-drive", "menu-fuzhi", "iso", "zuijinhuoyue","key", "shouquan", "sort", "grid", "list", "emoji-a", "emoji-bizui", "emoji-baiyan", "emoji-aixin", "emoji-dajing", "emoji-ziya", "emoji-daxiao", "emoji-esi", "emoji-fadai", "emoji-fankun", "emoji-ganga", "emoji-fennu", "emoji-hanyan", "emoji-jingkong", "emoji-haochi", "emoji-emo", "emoji-jingsong", "emoji-jingya", "emoji-kaixin", "emoji-lengku", "emoji-danao", "emoji-liukoushui", "emoji-liulei", "emoji-mengbi", "mianwubiaoqing", "nanguo", "shuizhuo", "taoyan", "tanchi", "siliao", "tiaopi", "xiaochulei", "wuliao", "xingxingyan", "xieyan", "xiasi", "xiaolian", "ku", "shengqi", "yousiliao", "en", "bushufu", "bianbian", "fankun1", "feiwen", "ganmao", "huaixiao", "liuhan", "outu", "keshui", "renzhe", "santiaoxian", "guaiwu", "shoushang", "tianshi", "shuai", "xianwen", "xiaodiaodaya", "xiong", "yiwen", "yun", "liubixie", "shimo", "dianzan", "biti", "fendou", "huqi", "heng", "kulou", "leng", "taoyan1", "shuixing", "aini", "aixin1", "zhadan", "xinsui", list: ['huanyuan', 'huishouzhan', 'glb', 'gengxinjindu', 'duigou', 'bars-solid', 'sort-amount-down-solid', 'sort-amount-up-alt-solid', 'tag2', 'guazai', 'duplicate', 'play1', "qrcode", "arrow1_left", "wulianwang-","zuji","disk-drive", "menu-fuzhi", "iso", "zuijinhuoyue","key", "shouquan", "sort", "grid", "list", "emoji-a", "emoji-bizui", "emoji-baiyan", "emoji-aixin", "emoji-dajing", "emoji-ziya", "emoji-daxiao", "emoji-esi", "emoji-fadai", "emoji-fankun", "emoji-ganga", "emoji-fennu", "emoji-hanyan", "emoji-jingkong", "emoji-haochi", "emoji-emo", "emoji-jingsong", "emoji-jingya", "emoji-kaixin", "emoji-lengku", "emoji-danao", "emoji-liukoushui", "emoji-liulei", "emoji-mengbi", "mianwubiaoqing", "nanguo", "shuizhuo", "taoyan", "tanchi", "siliao", "tiaopi", "xiaochulei", "wuliao", "xingxingyan", "xieyan", "xiasi", "xiaolian", "ku", "shengqi", "yousiliao", "en", "bushufu", "bianbian", "fankun1", "feiwen", "ganmao", "huaixiao", "liuhan", "outu", "keshui", "renzhe", "santiaoxian", "guaiwu", "shoushang", "tianshi", "shuai", "xianwen", "xiaodiaodaya", "xiong", "yiwen", "yun", "liubixie", "shimo", "dianzan", "biti", "fendou", "huqi", "heng", "kulou", "leng", "taoyan1", "shuixing", "aini", "aixin1", "zhadan", "xinsui",
"maren", "zhutou", "qie", "youling", "apple-fill", "attachment", "android-fill", "apple", "download", "arrow-up-circle", "arrow-right-circle", "arrow-down", "ashbin", "add", "file_music", "menu", "role", "jiaoseguanli", "yemianshezhi", "drop_down", "yuandian", "tag", "service-directory", "fabu", "wailian", "bianji-", "WEBSITE", "wangzhanguanli", "Up", "up", "fenlei", "guanlizhuanjiawenzhang", "pifu", "tuichudenglu", "shezhi", "wangzhan", "huaban", "yunshezhi", "UserSettings", "blogger", "biaoqian", "myself", "guidang", "quxiaoyulan1-copy", "yulan-copy", "quxiaoyulan1", "yulan"] "maren", "zhutou", "qie", "youling", "apple-fill", "attachment", "android-fill", "apple", "download", "arrow-up-circle", "arrow-right-circle", "arrow-down", "ashbin", "add", "file_music", "menu", "role", "jiaoseguanli", "yemianshezhi", "drop_down", "yuandian", "tag", "service-directory", "fabu", "wailian", "bianji-", "WEBSITE", "wangzhanguanli", "Up", "up", "fenlei", "guanlizhuanjiawenzhang", "pifu", "tuichudenglu", "shezhi", "wangzhan", "huaban", "yunshezhi", "UserSettings", "blogger", "biaoqian", "myself", "guidang", "quxiaoyulan1-copy", "yulan-copy", "quxiaoyulan1", "yulan"]
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<span> <div :style="{position: grid ? 'relative': 'unset'}">
<div v-if="item.isFavorite && !public"> <div v-if="item.isFavorite && !public">
<div v-if="pc"> <div v-if="pc">
<svg-icon v-if="!grid" class="pc list icon-favorite" icon-class="Favoritestarrate"/> <svg-icon v-if="!grid" class="pc list icon-favorite" icon-class="Favoritestarrate"/>
@ -57,9 +57,9 @@
<div v-if="grid && pc" class="grid-play-icon"> <div v-if="grid && pc" class="grid-play-icon">
<svg-icon icon-class="play1"/> <svg-icon icon-class="play1"/>
</div> </div>
<el-image lazy v-if="grid" :style="{'height':details?'110px':(gridWidth-35) + 'px'}" fit="contain" <el-image lazy v-if="grid" :style="{'height':details?'110px':(gridWidth-50) + 'px'}" fit="contain"
:src="item.fileId ? (audioCoverUrl+item.fileId) : (audioCoverUrl+item.id)"> :src="item.fileId ? (audioCoverUrl+item.fileId) : (audioCoverUrl+item.id)">
<div slot="error" class="image-slot"> <div slot="error" class="image-slot-error">
<svg-icon icon-class="video"/> <svg-icon icon-class="video"/>
</div> </div>
</el-image> </el-image>
@ -75,9 +75,9 @@
<div v-else-if="item.contentType && item.contentType.indexOf('audio') > -1"> <div v-else-if="item.contentType && item.contentType.indexOf('audio') > -1">
<div v-if="item.music !== undefined"> <div v-if="item.music !== undefined">
<div v-if="item.music.name !== null"> <div v-if="item.music.name !== null">
<el-image v-if="grid" :style="{'height':details?'110px':(gridWidth-35) + 'px'}" fit="contain" <el-image v-if="grid" :style="{'height':details?'110px':(gridWidth-50) + 'px'}" fit="contain"
:src="item.fileId ? (audioCoverUrl+item.fileId) : (audioCoverUrl+item.id)"> :src="item.fileId ? (audioCoverUrl+item.fileId) : (audioCoverUrl+item.id)">
<div slot="error" class="image-slot"> <div slot="error" class="image-slot-error">
<svg-icon icon-class="audio"/> <svg-icon icon-class="audio"/>
</div> </div>
</el-image> </el-image>
@ -94,7 +94,7 @@
<div v-else-if="item.contentType && item.contentType.startsWith('image')"> <div v-else-if="item.contentType && item.contentType.startsWith('image')">
<el-image v-if="grid || grid === 'details'" :style="{'height':details?'110px':(gridWidth-35) + 'px'}" <el-image v-if="grid || grid === 'details'" :style="{'height':details?'110px':(gridWidth-35) + 'px'}"
fit="contain" :src="item.fileId ? (imageUrl+item.fileId) : (imageUrl+item.id)"> fit="contain" :src="item.fileId ? (imageUrl+item.fileId) : (imageUrl+item.id)">
<div slot="error" class="image-slot"> <div slot="error" class="image-slot-error">
<svg-icon icon-class="image"/> <svg-icon icon-class="image"/>
</div> </div>
</el-image> </el-image>
@ -102,12 +102,8 @@
<el-avatar v-if="!grid" shape="square" <el-avatar v-if="!grid" shape="square"
:src="item.fileId ? (imageUrl+item.fileId) : (imageUrl+item.id)"></el-avatar> :src="item.fileId ? (imageUrl+item.fileId) : (imageUrl+item.id)"></el-avatar>
</div> </div>
<!--<svg-icon v-else-if="item.contentType.indexOf('application/pdf') > -1" icon-class="file-pdf"/>-->
<!--<svg-icon v-else-if="item.contentType.indexOf('word') > -1" icon-class="file-word"/>-->
<!--<svg-icon v-else-if="item.contentType.indexOf('zip') > -1" icon-class="zip"/>-->
<!--<svg-icon v-else-if="item.contentType.indexOf('excel') > -1" icon-class="file-excel"/>-->
<svg-icon v-else :icon-class="findIconClass"/> <svg-icon v-else :icon-class="findIconClass"/>
</span> </div>
</template> </template>
<script> <script>
import {iconClass} from '@/utils/file-type' import {iconClass} from '@/utils/file-type'
@ -191,6 +187,9 @@ export default {
line-height: 35px; line-height: 35px;
} }
>>> .image-slot-error {
}
.icon-favorite { .icon-favorite {
position: absolute; position: absolute;
color: #f5222d; color: #f5222d;

View File

@ -28,23 +28,32 @@
<el-col :span="12" v-if="shareOptionConfig.shared" class="shared-expires-text">到期时间</el-col> <el-col :span="12" v-if="shareOptionConfig.shared" class="shared-expires-text">到期时间</el-col>
<el-col :span="1" v-if="!shareOptionConfig.shared">&nbsp</el-col> <el-col :span="1" v-if="!shareOptionConfig.shared">&nbsp</el-col>
<el-col v-if="shareOption.expiresDateOption === 2" :span="12" class="share-expires-data"> <el-col v-if="shareOption.expiresDateOption === 2" :span="12" class="share-expires-data">
<el-form-item prop="expiresDate"> <el-date-picker
<el-date-picker ref="expiresDatePicker"
ref="expiresDatePicker" size="small"
size="small" :clearable="false"
:clearable="false" v-model="shareOption.expiresDate"
v-model="shareOption.expiresDate" type="date"
type="date" :placeholder="!shareOption.expiresDate ? '永久有效' : '选择到期时间'"
:placeholder="!shareOption.expiresDate ? '永久有效' : '选择到期时间'" align="right"
align="right" format="yyyy-MM-dd HH:mm"
format="yyyy-MM-dd HH:mm" value-format="timestamp"
value-format="timestamp" @change="pickerChange"
@change="pickerChange" :picker-options="pickerOptions">
:picker-options="pickerOptions"> </el-date-picker>
</el-date-picker>
</el-form-item>
</el-col> </el-col>
</el-form-item> </el-form-item>
<el-form-item v-if="shareOptionConfig.shared" label="">
<el-col :span="12" class="shared-expires-text">分享形式</el-col>
<el-col :span="12">
<el-select size="small" v-model="shareOption.isPrivacy" style="width: 100%" @change="shareFormChange">
<el-option label="公开链接" :value="false"></el-option>
<el-option label="私密链接" :value="true"></el-option>
</el-select>
</el-col>
</el-form-item>
<el-form-item v-if="!shareOptionConfig.shared" class="share-option-form" label="分享形式" width="100"> <el-form-item v-if="!shareOptionConfig.shared" class="share-option-form" label="分享形式" width="100">
<el-col :span="20"> <el-col :span="20">
<el-select size="small" v-model="shareOption.isPrivacy" style="width: 100%"> <el-select size="small" v-model="shareOption.isPrivacy" style="width: 100%">
@ -77,7 +86,7 @@
<el-collapse-item title="操作权限" name="1"> <el-collapse-item title="操作权限" name="1">
<el-checkbox-group v-model="shareOption.operationPermissionList" @change="permissionActionChange"> <el-checkbox-group v-model="shareOption.operationPermissionList" @change="permissionActionChange">
<el-checkbox label="UPLOAD">上传</el-checkbox> <el-checkbox label="UPLOAD">上传</el-checkbox>
<el-checkbox label="PUT">修改</el-checkbox> <el-checkbox label="PUT">编辑</el-checkbox>
<el-checkbox label="DELETE">删除</el-checkbox> <el-checkbox label="DELETE">删除</el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</el-collapse-item> </el-collapse-item>
@ -89,6 +98,7 @@
</div> </div>
</div> </div>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button size="small" v-if="shareOptionConfig.shared" type="danger" @click="cancelShare">取消分享</el-button>
<el-button size="small" v-if="!shareOptionConfig.shared" type="primary" @click="submitShare" <el-button size="small" v-if="!shareOptionConfig.shared" type="primary" @click="submitShare"
v-loading="generateShareLinkLoading">创建分享 v-loading="generateShareLinkLoading">创建分享
</el-button> </el-button>
@ -202,7 +212,8 @@ export default {
shared: false, shared: false,
linkLabel: "选择有效期", linkLabel: "选择有效期",
}, },
filename: '',// 文件名 filename: '',
shareId: ''
} }
}, },
computed: { computed: {
@ -221,23 +232,17 @@ export default {
}).then(res => { }).then(res => {
this.filename = res.data.path + this.file.name this.filename = res.data.path + this.file.name
}) })
this.showSharedPage(this.file)
this.shareOptionConfig.shared = true
this.shareOptionConfig.linkLabel = '分享链接'
this.setShareLink(this.getShareLink(this.file.shareId))
this.shareOption.isPrivacy = this.file.isPrivacy
this.shareOption.operationPermissionList = this.file.operationPermissionList || []
if (this.file.expireDate) {
this.shareOption.expiresDate = moment(this.file.expireDate).format('x')
}
this.shareOption.expiresDateOption = 2
this.extractionCode = this.file.extractionCode
} else { } else {
this.shareOptionConfig.shared = false if (this.file.shareBase) {
this.shareOptionConfig.linkLabel = '选择有效期' api.getShareByFileId({fileId: this.file.fileId}).then(res => {
this.shareOption.isPrivacy = false const shareObject = res.data
this.shareOption.expiresDate = null shareObject.shareId = res.data.id
this.shareOption.expiresDateOption = 1 this.showSharedPage(shareObject)
})
} else {
this.showNotSharedPage()
}
} }
} }
} }
@ -256,6 +261,26 @@ export default {
console.error(error) console.error(error)
}) })
}, },
showSharedPage(shareObject) {
this.shareOptionConfig.shared = true
this.shareOptionConfig.linkLabel = '分享链接'
this.setShareLink(this.getShareLink(shareObject.shareId, shareObject.shortId))
this.shareOption.isPrivacy = shareObject.isPrivacy
this.shareOption.operationPermissionList = shareObject.operationPermissionList || []
if (shareObject.expireDate) {
this.shareOption.expiresDate = moment(shareObject.expireDate).format('x')
}
this.shareOption.expiresDateOption = 2
this.extractionCode = shareObject.extractionCode
this.shareId = shareObject.shareId
},
showNotSharedPage() {
this.shareOptionConfig.shared = false
this.shareOptionConfig.linkLabel = '选择有效期'
this.shareOption.isPrivacy = false
this.shareOption.expiresDate = null
this.shareOption.expiresDateOption = 1
},
shareDialogClose() { shareDialogClose() {
this.shareOptionConfig.shared = false this.shareOptionConfig.shared = false
this.$emit('update:status', this.shareDialogVisible) this.$emit('update:status', this.shareDialogVisible)
@ -272,9 +297,34 @@ export default {
this.shareOption.expiresDate = null this.shareOption.expiresDate = null
} }
if (this.shareOptionConfig.shared) { if (this.shareOptionConfig.shared) {
this.createShare(true) this.createShare('pickerChange')
} }
}, },
// 切换分享形式
shareFormChange() {
if (!this.shareOption.isPrivacy) {
this.shareOption.operationPermissionList = []
}
this.createShare('shareFormChange')
},
// 取消分享
cancelShare() {
let title = '确定要取消分享 "' + this.filename + '" 吗?'
this.$confirm(title, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
api.cancelShareLink({
userId: this.$store.state.user.userId,
shareId: [this.shareId]
}).then(() => {
this.$emit('onCancelShare')
this.shareDialogVisible = false
this.shareDialogClose()
})
})
},
submitShare() { submitShare() {
this.$refs.form.validate((valid) => { this.$refs.form.validate((valid) => {
if (valid) { if (valid) {
@ -289,8 +339,11 @@ export default {
timeFormat(time) { timeFormat(time) {
return moment(time).format("yyyy-MM-DD HH:mm") return moment(time).format("yyyy-MM-DD HH:mm")
}, },
getShareLink(shareId) { getShareLink(shareId, shortId) {
return window.location.origin + '/s?s=' + shareId if (!shortId) {
shortId = shareId
}
return `${window.location.origin}/s/${shortId}`
}, },
createShare(update) { createShare(update) {
this.generateShareLinkLoading = true this.generateShareLinkLoading = true
@ -303,6 +356,9 @@ export default {
break break
case 2: case 2:
if (this.shareOption.expiresDate) { if (this.shareOption.expiresDate) {
if (typeof this.shareOption.expiresDate === 'string') {
this.shareOption.expiresDate = Number(this.shareOption.expiresDate)
}
expireDate = this.timeFormat(this.shareOption.expiresDate) expireDate = this.timeFormat(this.shareOption.expiresDate)
} }
} }
@ -315,8 +371,9 @@ export default {
operationPermissionList: this.shareOption.operationPermissionList operationPermissionList: this.shareOption.operationPermissionList
}).then(res => { }).then(res => {
if (res.data) { if (res.data) {
let {shareId, extractionCode, operationPermissionList} = res.data let {shareId, shortId, extractionCode, operationPermissionList} = res.data
this.setShareLink(this.getShareLink(shareId)) this.setShareLink(this.getShareLink(shareId, shortId))
this.shareId = shareId
this.generateShareLinkLoading = false this.generateShareLinkLoading = false
this.shareOptionConfig.shared = true this.shareOptionConfig.shared = true
this.shareOption.expiresDateOption = 2 this.shareOption.expiresDateOption = 2
@ -324,23 +381,38 @@ export default {
this.extractionCode = extractionCode this.extractionCode = extractionCode
this.shareOption.operationPermissionList = operationPermissionList || [] this.shareOption.operationPermissionList = operationPermissionList || []
this.$emit('onSuccess', shareId) this.$emit('onSuccess', shareId)
if (update) { if (update === 'pickerChange') {
if (!expireDate) { this.afterPickerChange(shareId, expireDate)
expireDate = '永久有效' }
} if (update === 'shareFormChange') {
this.$emit("onUpdateExpireData", shareId, expireDate); this.afterShareFormChange(shareId)
this.$message({
duration: 3000,
dangerouslyUseHTMLString: true,
iconClass: 'el-icon-success',
message: '<strong>&nbsp;&nbsp;有效期已设置为: <span>' + expireDate + '</span> </strong>'
});
} }
} }
}).catch(() => { }).catch(() => {
this.generateShareLinkLoading = false this.generateShareLinkLoading = false
}) })
}, },
afterPickerChange (shareId, expireDate) {
if (!expireDate) {
expireDate = '永久有效'
}
this.$emit("onUpdateExpireData", shareId, expireDate);
this.$message({
duration: 3000,
dangerouslyUseHTMLString: true,
iconClass: 'el-icon-success',
message: '<strong>&nbsp;&nbsp;有效期已设置为: <span>' + expireDate + '</span> </strong>'
});
},
afterShareFormChange(shareId) {
this.$emit("onUpdateShareForm", shareId, this.shareOption.isPrivacy);
this.$message({
duration: 3000,
dangerouslyUseHTMLString: true,
iconClass: 'el-icon-success',
message: '<strong>&nbsp;&nbsp;链接已设置为: <span>' + (this.shareOption.isPrivacy ? '私密链接' : '公开链接') + '</span> </strong>'
});
},
// 复制分享链接 // 复制分享链接
copyShareLink(msg) { copyShareLink(msg) {
let clipboard = new Clipboard('.tag-share-link') let clipboard = new Clipboard('.tag-share-link')
@ -370,7 +442,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
> > > .el-dialog { >>> .el-dialog {
max-width: 460px; max-width: 460px;
.el-dialog__title { .el-dialog__title {
@ -395,7 +467,7 @@ export default {
display: none; display: none;
} }
.share-icon-font > > > .svg-icon { .share-icon-font >>> .svg-icon {
font-size: 8rem; font-size: 8rem;
} }
} }
@ -403,26 +475,27 @@ export default {
.share-filename { .share-filename {
margin: 25px 0 50px 0; margin: 25px 0 50px 0;
text-align: center; text-align: center;
word-break: break-word;
} }
.share-option { .share-option {
> > > .el-form-item--mini.el-form-item { >>> .el-form-item--mini.el-form-item {
margin-bottom: 10px; margin-bottom: 10px;
} }
.share-expires-data { .share-expires-data {
> > > .el-input__inner { >>> .el-input__inner {
cursor: pointer; cursor: pointer;
padding-right: 0; padding-right: 0;
} }
> > > .el-date-editor.el-input { >>> .el-date-editor.el-input {
width: 100%; width: 100%;
} }
} }
> > > .el-form-item__label { >>> .el-form-item__label {
text-align: left; text-align: left;
line-height: 32px; line-height: 32px;
} }
@ -434,21 +507,20 @@ export default {
} }
} }
> > > .dialog-footer { >>> .dialog-footer {
.el-loading-spinner .circular { .el-loading-spinner .circular {
width: 25px !important; width: 25px !important;
} }
} }
.share-link { .share-link {
margin-top: -10px;
margin-bottom: 25px; margin-bottom: 25px;
.share-link-code { .share-link-code {
width: 130px; width: 130px;
margin-top: 10px; margin-top: 10px;
> > > .el-input-group__prepend { >>> .el-input-group__prepend {
padding: 0 10px; padding: 0 10px;
font-weight: 500; font-weight: 500;
} }
@ -462,7 +534,7 @@ export default {
font-size: 1.5rem; font-size: 1.5rem;
} }
> > > .el-input__inner { >>> .el-input__inner {
background-color: #84858d14; background-color: #84858d14;
text-align: center; text-align: center;
padding: 0 10px; padding: 0 10px;

View File

@ -17,7 +17,7 @@
> >
<uploader-unsupport></uploader-unsupport> <uploader-unsupport></uploader-unsupport>
<uploader-drop v-if="dragover && enableDragUplaod" class="uploader-drop"> <uploader-drop v-if="dragover && enableDragUpload" class="uploader-drop">
<span>上传文件到当前目录下</span> <span>上传文件到当前目录下</span>
</uploader-drop> </uploader-drop>
@ -112,7 +112,6 @@
<script> <script>
// import { ACCEPT_CONFIG } from '@/assets/js/config'
import store from '@/store' import store from '@/store'
import $ from 'jquery' import $ from 'jquery'
import SparkMD5 from 'spark-md5' import SparkMD5 from 'spark-md5'
@ -129,11 +128,11 @@ export default {
username: this.$store.state.user.name, username: this.$store.state.user.name,
options: { options: {
target: api.simpleUploadURL, target: api.simpleUploadURL,
chunkSize: 5 * 1024 * 1024, chunkSize: localStorage.getItem('uploader_chunk_size') || 1024 * 1024,
// speedSmoothingFactor: 0.1, // speedSmoothingFactor: 0.1,
// progressCallbacksInterval: 500, // progressCallbacksInterval: 500,
maxChunkRetries: 3, // 最大重试次数 maxChunkRetries: 3, // 最大重试次数
simultaneousUploads: 5, // 并发上传数 simultaneousUploads: 3, // 并发上传数
testChunks: true, // 是否开启服务器分片校验 testChunks: true, // 是否开启服务器分片校验
// 服务器分片校验函数,秒传及断点续传基础 // 服务器分片校验函数,秒传及断点续传基础
checkChunkUploadedByResponse: function (chunk, message) { checkChunkUploadedByResponse: function (chunk, message) {
@ -190,7 +189,7 @@ export default {
fileListScrollTop: 0, fileListScrollTop: 0,
dragoverLoop: null, dragoverLoop: null,
successMsg: null, successMsg: null,
enableDragUplaod: false,// 是否启用拖拽上传 enableDragUpload: false,// 是否启用拖拽上传
uploader: null, uploader: null,
} }
}, },
@ -200,12 +199,12 @@ export default {
watch: { watch: {
$route(route) { $route(route) {
// 只有首页才启用拖拽上传 // 只有首页才启用拖拽上传
this.enableDragUplaod = route.path === '/' this.enableDragUpload = route.path === '/'
}, },
message(msg) { message(msg) {
switch (msg.event) { switch (msg.event) {
case 'storageTypeChange': case 'uploaderChunkSize':
this.onStorageTypeChange(msg.data) this.onStorageTypeChange(msg.data.body)
break break
case 'fileListScrollTop': case 'fileListScrollTop':
this.fileListScrollTop = msg.data this.fileListScrollTop = msg.data
@ -234,12 +233,12 @@ export default {
} }
}, },
mounted() { mounted() {
this.enableDragUplaod = this.$route.path === '/' this.enableDragUpload = this.$route.path === '/'
let that = this let that = this
let dropbox = document.body let dropbox = document.body
document.body.ondragstart = function (e) { document.body.ondragstart = function (e) {
if (that.enableDragUplaod) { if (that.enableDragUpload) {
if (e.target.slot === 'jmal') { if (e.target.slot === 'jmal') {
that.isDragStart = true that.isDragStart = true
} }
@ -281,17 +280,10 @@ export default {
destroyed() { destroyed() {
}, },
methods: { methods: {
onStorageTypeChange(storageType) { onStorageTypeChange(chunkSize) {
if (storageType === 'File') { localStorage.setItem('uploader_chunk_size', chunkSize)
if (this.options.chunkSize === 1024 * 1024) { if (this.options.chunkSize !== chunkSize && !this.panelShow) {
return this.updateChunkSize(chunkSize)
}
this.updateChunkSize(1024 * 1024)
} else {
if (this.options.chunkSize === 5 * 1024 * 1024) {
return
}
this.updateChunkSize(5 * 1024 * 1024)
} }
}, },
initUploader() { initUploader() {
@ -351,12 +343,23 @@ export default {
}).then(() => { }).then(() => {
this.doUploadBefore(files) this.doUploadBefore(files)
}).catch(() => { }).catch(() => {
this.uploader.cancel() this.uploaderCancel()
}) })
} else { } else {
this.doUploadBefore(files) this.doUploadBefore(files)
} }
}, },
uploaderCancel() {
this.uploader.cancel()
this.displayPanel(false)
const chunkSize = localStorage.getItem('uploader_chunk_size');
if (chunkSize !== this.uploader.opts.chunkSize) {
this.updateChunkSize(Number.parseInt(chunkSize))
}
},
displayPanel(display) {
this.panelShow = display
},
doUploadBefore(files) { doUploadBefore(files) {
this.fileListLength = this.uploader.fileList.length this.fileListLength = this.uploader.fileList.length
const filePaths = this.uploader.filePaths const filePaths = this.uploader.filePaths
@ -374,15 +377,12 @@ export default {
currentDirectory: encodeIfNeeded(this.params.currentDirectory), currentDirectory: encodeIfNeeded(this.params.currentDirectory),
username: this.params.username, username: this.params.username,
userId: this.params.userId userId: this.params.userId
}).then(() => {
this.getFileList()
}).catch(e => {
}) })
}) })
} }
if (window.pc) { if (window.pc) {
this.pc = true this.pc = true
this.panelShow = true this.displayPanel(true)
} else { } else {
this.pc = false this.pc = false
this.shrink() this.shrink()
@ -400,14 +400,20 @@ export default {
this.uploader.resume() this.uploader.resume()
}) })
}, },
onFileProgress(rootFile, file, chunk) { setPageTitle() {
this.netSpeed = formatNetSpeed(file.currentSpeed)
this.process = Math.trunc(window.uploader.progress() * 100)
if (this.process === -10 || this.process === 100 || this.fileListLength === 0) { if (this.process === -10 || this.process === 100 || this.fileListLength === 0) {
document.title = `${this.$route.meta.title}` document.title = `${this.$route.meta.title}`
} else { } else {
document.title = `${this.process}% | ${this.$route.meta.title}` document.title = `${this.process}% | ${this.$route.meta.title}`
} }
},
onFileProgress(rootFile, file, chunk) {
this.netSpeed = formatNetSpeed(file.currentSpeed, false)
this.process = Math.trunc(window.uploader.progress() * 100)
this.setPageTitle()
if (rootFile.isFolder && this.process < 100) {
this.statusSet(rootFile.id, 'progress', formatNetSpeed(file.currentSpeed, true))
}
if (this.process > 0 && this.process < 100 && window.uploader.fileList.length > 0) { if (this.process > 0 && this.process < 100 && window.uploader.fileList.length > 0) {
window.onbeforeunload = function () { window.onbeforeunload = function () {
return "还有文件正在上传, 确定退出吗?"; return "还有文件正在上传, 确定退出吗?";
@ -425,7 +431,6 @@ export default {
message: res.message, message: res.message,
type: 'error' type: 'error'
}) })
this.uploader.cancel()
this.close() this.close()
return; return;
} }
@ -489,8 +494,7 @@ export default {
} }
if (this.process === -10 || this.process === 100 || this.fileListLength === 0) { if (this.process === -10 || this.process === 100 || this.fileListLength === 0) {
this.uploader.cancel() this.uploaderCancel()
this.panelShow = false
} }
}, },
onFileError(rootFile, file, response) { onFileError(rootFile, file, response) {
@ -640,16 +644,14 @@ export default {
}, },
close() { close() {
if (this.process === -10 || this.process === 100 || this.fileListLength === 0) { if (this.process === -10 || this.process === 100 || this.fileListLength === 0) {
this.uploader.cancel() this.uploaderCancel()
this.panelShow = false
} else { } else {
this.$confirm('还有文件正在上传, 确定要关闭吗?', '提示', { this.$confirm('还有文件正在上传, 确定要关闭吗?', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
this.uploader.cancel() this.uploaderCancel()
this.panelShow = false
}) })
} }
}, },
@ -659,8 +661,12 @@ export default {
* @param id * @param id
* @param status * @param status
*/ */
statusSet(id, status) { statusSet(id, status, progressText) {
const statusMap = { const statusMap = {
progress: {
text: progressText,
bgc: '#ffffff00'
},
md5: { md5: {
text: '校验MD5', text: '校验MD5',
bgc: '#fff' bgc: '#fff'
@ -683,6 +689,11 @@ export default {
} }
} }
if (status === 'progress') {
$('.uploader-file-status').find('span:eq(1) em').text(progressText)
return
}
this.$nextTick(() => { this.$nextTick(() => {
$(`<p class="myStatus_${id}"></p>`).appendTo(`.file_${id} .uploader-file-status`).css({ $(`<p class="myStatus_${id}"></p>`).appendTo(`.file_${id} .uploader-file-status`).css({
'position': 'absolute', 'position': 'absolute',
@ -812,7 +823,7 @@ export default {
padding: 0; padding: 0;
margin: 0; margin: 0;
> li { >>> li {
background-color: #fff; background-color: #fff;
} }
} }
@ -832,7 +843,7 @@ export default {
font-size: 16px; font-size: 16px;
} }
> .uploader-file-icon { >>> .uploader-file-icon {
&:before { &:before {
content: "" !important; content: "" !important;
} }
@ -860,21 +871,21 @@ export default {
} }
} }
> .uploader-file-actions > span { >>> .uploader-file-actions > span {
margin-right: 6px; margin-right: 6px;
} }
} }
> .uploader-file-status { >>> .uploader-file-status {
width: 32%; width: 32%;
text-indent: 20px; text-indent: 20px;
} }
> .uploader-file-meta { >>> .uploader-file-meta {
width: 0; width: 0;
} }
> .uploader-file-icon { >>> .uploader-file-icon {
width: 32px; width: 32px;
height: 32px; height: 32px;
display: inline-block; display: inline-block;
@ -883,7 +894,7 @@ export default {
margin-right: 8px; margin-right: 8px;
} }
> .uploader-file-actions > span { >>> .uploader-file-actions > span {
margin-right: 6px; margin-right: 6px;
margin-left: 8px; margin-left: 8px;
} }

View File

@ -0,0 +1,75 @@
<template>
<div class="container">
<table-list
ref="tableList"
:less-client-height="245"
:tableData="dataList"
:loading="loading"
:hasSelection="false"
:border="false"
:tableHeader="tableHeader"
></table-list>
</div>
</template>
<script>
import TableList from "@/components/table/TableList"
import {mapState} from "vuex";
export default {
name: 'TaskProgress',
components: {
TableList
},
props: {
data: {
type: Array,
default: []
},
},
computed: {
...mapState(['message'])
},
data() {
return {
dataList: [],
loading: false,
tableHeader: [
{prop: 'type', width: 100, label: '任务类型'},
{prop: 'name', minWidth: 100, label: '名称'},
{prop: 'progress', width: 200, label: '进度'},
]
}
},
watch: {
data(val) {
this.dataList = val
},
message(msg) {
if (msg.event === 'msg/taskProgress') {
const taskProgress = msg.data.body
const index = this.dataList.findIndex(item => item.taskId === taskProgress.taskId)
if (index > -1) {
if (taskProgress.progress) {
this.dataList[index].progress = taskProgress.progress
} else {
this.dataList.splice(index, 1)
}
} else {
if (taskProgress.progress) {
this.dataList.push(taskProgress)
}
}
}
}
},
methods: {
}
}
</script>
<style lang="scss" scoped>
.container {
padding: 0 !important;
}
</style>

View File

@ -84,7 +84,7 @@ export default {
let chrome = this.readOnly ? 0 : 1 let chrome = this.readOnly ? 0 : 1
let theme = 'kennedy' let theme = 'kennedy'
let title = this.file.name ? encodeURIComponent(this.file.name) : '' let title = this.file.name ? encodeURIComponent(this.file.name) : ''
let query = `?title=${title}&chrome=${chrome}&lightbox=${lightbox}&ui=${theme}&lang=${language}&offline=1&pwa=0&embed=1&noLangIcon=1&noExitBtn=1&noSaveBtn=1&saveAndExit=0&spin=1&proto=json` let query = `?title=${title}&chrome=${chrome}&lightbox=${lightbox}&ui=${theme}&lang=${language}&offline=0&pwa=0&embed=1&noLangIcon=1&noExitBtn=1&noSaveBtn=1&saveAndExit=0&spin=1&proto=json`
this.url = $J.apiUrl(`../drawio/webapp/${query}`) this.url = $J.apiUrl(`../drawio/webapp/${query}`)
}, },
mounted() { mounted() {
@ -322,7 +322,7 @@ export default {
top: 2.5rem; top: 2.5rem;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: calc(100% - 2.5rem);
>>>.dark-button { >>>.dark-button {
background: #3e3e3e; background: #3e3e3e;

View File

@ -0,0 +1,76 @@
<template>
<div>
<div v-if="show">
<model-viewer ref="modelViewer" class="model-viewer" alt="" :src="fileUrl" autoplay animation-name="Running" shadow-intensity="1" camera-controls touch-action="pan-y"></model-viewer>
</div>
</div>
</template>
<script>
import { loadScript } from '@/utils/load-script'
export default {
name: "ModelPreview",
components: {},
props: {
file: {
type: Object,
default: function () {
return {}
}
},
fileUrl: {
type: String,
default: ''
},
shareId: {
type: String,
default: ''
},
readOnly: {
type: Boolean,
default: false
},
},
data() {
return {
show: false,
width: window.innerWidth,
height: window.innerHeight
}
},
computed: {
},
created() {
},
mounted() {
const modelViewerJsUrl = window.location.origin + '/resource/model-viewer.min.js';
loadScript(modelViewerJsUrl, 'module').then(() => {
this.onRead();
})
},
beforeDestroy() {
},
destroyed() {
},
watch: {
},
methods: {
onRead() {
this.$emit('onReady')
this.show = true
this.$nextTick(() => {
this.$refs.modelViewer.style.width = this.width + "px"
this.$refs.modelViewer.style.height = this.height + "px"
})
}
},
}
</script>
<style lang="scss" scoped>
.model-viewer {
position: absolute;
}
</style>

View File

@ -22,6 +22,7 @@
<script> <script>
import api from "@/api/file-api" import api from "@/api/file-api"
import settingApi from "@/api/setting-api"
import fileConfig from "@/utils/file-config"; import fileConfig from "@/utils/file-config";
import SparkMD5 from 'spark-md5' import SparkMD5 from 'spark-md5'
import HistoryPopover from "@/components/HistoryPopover/index.vue"; import HistoryPopover from "@/components/HistoryPopover/index.vue";
@ -49,6 +50,10 @@ export default {
type: String, type: String,
default: '' default: ''
}, },
sharer: {
type: String,
default: undefined
},
file: { file: {
type: Object, type: Object,
default: function () { default: function () {
@ -75,7 +80,12 @@ export default {
historyPageIndex: 1, historyPageIndex: 1,
historyPageSize: 50, historyPageSize: 50,
viewHistory: false, viewHistory: false,
historyOperationLoading: false historyOperationLoading: false,
officeServerConfig: {
documentServer: undefined,
callbackServer: undefined,
tokenEnabled: false
}
} }
}, },
beforeDestroy() { beforeDestroy() {
@ -93,25 +103,18 @@ export default {
if (!id) { if (!id) {
return return
} }
let officeApiUrl = fileConfig.officeApiUrl()
$J.loadScript($J.apiUrl(officeApiUrl), (e) => { if (window.shareId) {
if (e !== null) { settingApi.getPublicOfficeConfig({shareId: window.shareId, shareToken: this.$store.getters.shareToken}).then(res => {
this.$emit('onClose') this.officeServerConfig = res.data
this.$store.dispatch('updateMessage', { event: 'loadFileFailed'}) this.loadOfficeApi()
return })
} } else {
if (this.shareId) { settingApi.getOfficeConfig().then(res => {
api.getPublicFileInfoById({fileId: this.file.id, shareId: this.shareId}).then(res => { this.officeServerConfig = res.data
this.file = res.data this.loadOfficeApi()
this.loadFile() })
}) }
} else {
api.getFileInfoById({id: this.file.id}).then(res => {
this.file = res.data
this.loadFile()
})
}
})
}, },
immediate: true, immediate: true,
}, },
@ -134,6 +137,27 @@ export default {
destroyed() { destroyed() {
}, },
methods: { methods: {
loadOfficeApi() {
let officeApiUrl = fileConfig.officeApiUrl(this.officeServerConfig.documentServer)
$J.loadScript($J.apiUrl(officeApiUrl), (e) => {
if (e !== null) {
this.$emit('onClose')
this.$store.dispatch('updateMessage', { event: 'loadFileFailed'})
return
}
if (this.shareId) {
api.getPublicFileInfoById({fileId: this.file.id, shareId: this.shareId}).then(res => {
this.file = res.data
this.loadFile()
})
} else {
api.getFileInfoById({id: this.file.id}).then(res => {
this.file = res.data
this.loadFile()
})
}
})
},
viewHistoryFile({historyInfo}) { viewHistoryFile({historyInfo}) {
const historyUrl = window.location.origin + fileConfig.previewHistoryUrl(historyInfo.id, this.$store.state.user.name, this.$store.state.user.token) const historyUrl = window.location.origin + fileConfig.previewHistoryUrl(historyInfo.id, this.$store.state.user.name, this.$store.state.user.token)
this.onRequestHistoryData(null, historyInfo, historyUrl) this.onRequestHistoryData(null, historyInfo, historyUrl)
@ -188,11 +212,15 @@ export default {
this.docEditor = new DocsAPI.DocEditor(this.id, this.docEditorConfig) this.docEditor = new DocsAPI.DocEditor(this.id, this.docEditorConfig)
}, },
onRequestHistoryData(event, historyInfo, url) { onRequestHistoryData(event, historyInfo, url) {
this.docEditor.setHistoryData({ let historyConfig = {
"fileType": this.fileType, "fileType": this.fileType,
"key": `${new Date(historyInfo.metadata.time).getTime()}-${SparkMD5.hash(this.file.id)}`, "key": `${new Date(historyInfo.metadata.time).getTime()}-${SparkMD5.hash(this.file.id)}`,
"url": url, "url": url,
"version": historyInfo.metadata.time, "version": historyInfo.metadata.time,
}
api.getOfficeJwt(historyConfig).then(res => {
historyConfig.token = res.data
this.docEditor.setHistoryData(historyConfig)
}) })
}, },
onRequestHistory() { onRequestHistory() {
@ -221,14 +249,25 @@ export default {
this.$refs.historyPopover.loadHistoryList(this.file.id) this.$refs.historyPopover.loadHistoryList(this.file.id)
}) })
this.destroyEditor() this.destroyEditor()
this.fileUrl = window.location.origin + fileConfig.previewUrl(this.$store.state.user.name, this.file, this.$store.getters.token)
if (this.readOnly && window.shareId) { const file_username = this.sharer ? this.sharer : this.$store.state.user.name
this.fileUrl = window.location.origin + fileConfig.publicPreviewUrl(this.file, window.shareId, this.$store.getters.shareToken)
let callbackServer = this.officeServerConfig.callbackServer
if (callbackServer) {
if (callbackServer.endsWith('/')) {
// 去掉最后的/
callbackServer = callbackServer.substring(0, this.officeServerConfig.callbackServer.length - 1)
}
} else {
callbackServer = 'http://jmalcloud:8088'
} }
this.fileKey = `${new Date(this.file.updateDate).getTime()}-${SparkMD5.hash(this.file.id)}` this.fileUrl = fileConfig.previewUrl(file_username, this.file, this.$store.getters.token, undefined, callbackServer)
this.fileKey = `${new Date(this.file.updateDate).getTime()}-${SparkMD5.hash(this.file.id + callbackServer)}`
let callbackUrl = fileConfig.officeCallBackUrl(this.$store.getters.token, this.$store.getters.name, this.file.id) if (this.readOnly && window.shareId) {
this.fileUrl = fileConfig.publicPreviewUrl(this.file, window.shareId, this.$store.getters.shareToken, callbackServer)
this.fileKey = `${new Date(this.file.updateDate).getTime()}-${SparkMD5.hash(window.shareId + callbackServer)}`
}
let callbackUrl = fileConfig.officeCallBackUrl(this.officeServerConfig.callbackServer, this.$store.getters.token, this.$store.getters.name, this.file.id)
this.docEditorConfig = { this.docEditorConfig = {
document: { document: {
fileType: this.fileType, fileType: this.fileType,
@ -241,7 +280,8 @@ export default {
lang: "zh", lang: "zh",
user: { user: {
id: this.$store.state.user.userId, id: this.$store.state.user.userId,
name: this.$store.state.user.name name: this.$store.state.user.name,
image: `${window.location.origin}${process.env.VUE_APP_BASE_API}/public/s/view/thumbnail?id=${this.$store.state.user.avatar}`
}, },
customization: { customization: {
autosave: false, autosave: false,
@ -260,6 +300,28 @@ export default {
callbackUrl: callbackUrl, callbackUrl: callbackUrl,
} }
} }
if (this.readOnly && window.shareId) {
const shareToken = this.$store.getters.shareToken ? this.$store.getters.shareToken : 'token'
this.docEditorConfig.editorConfig.callbackUrl = null
api.getPublicOfficeJwt({shareId: window.shareId, shareToken: shareToken}, this.docEditorConfig).then(res => {
this.loadOffice(res.data)
})
} else {
if (this.officeServerConfig.tokenEnabled) {
api.getOfficeJwt(this.docEditorConfig).then(res => {
this.loadOffice(res.data)
})
} else {
this.loadOffice()
}
}
},
loadOffice(token) {
if (token) {
this.docEditorConfig.token = token
}
if (!this.$pc) { if (!this.$pc) {
this.docEditorConfig.type = 'mobile' this.docEditorConfig.type = 'mobile'
} }
@ -276,6 +338,7 @@ export default {
this.docEditorConfig.editorConfig.user.name = "Visitor_" + visitor this.docEditorConfig.editorConfig.user.name = "Visitor_" + visitor
} }
} }
this.docEditorConfig.events = { this.docEditorConfig.events = {
"onAppReady": this.onAppReady, "onAppReady": this.onAppReady,
"onDocumentReady": this.onDocumentReady, "onDocumentReady": this.onDocumentReady,
@ -316,6 +379,7 @@ export default {
if (this.hasHistoryVersion && !doc.getElementById('box-doc-name-history-btn')) { if (this.hasHistoryVersion && !doc.getElementById('box-doc-name-history-btn')) {
const toolbar = doc.getElementById('box-doc-name') const toolbar = doc.getElementById('box-doc-name')
toolbar.style.justifyContent = 'right' toolbar.style.justifyContent = 'right'
toolbar.style.padding = '0'
let versionButton = document.createElement("button") let versionButton = document.createElement("button")
versionButton.classList.add('btn', 'btn-header') versionButton.classList.add('btn', 'btn-header')
versionButton.setAttribute('id', 'box-doc-name-history-btn') versionButton.setAttribute('id', 'box-doc-name-history-btn')

View File

@ -1,9 +1,9 @@
<template> <template>
<div ref="artRef"></div> <div ref="artRef"></div>
</template> </template>
<script> <script>
import Artplayer from 'artplayer';
import { loadScript } from '@/utils/load-script'
export default { export default {
name: 'Artplayer', name: 'Artplayer',
@ -19,19 +19,26 @@ export default {
}, },
}, },
mounted() { mounted() {
this.instance = new Artplayer({ const artPlayerJsUrl = window.location.origin + '/resource/artplayer@5.1.6.js';
...this.option, loadScript(artPlayerJsUrl, undefined).then(() => {
container: this.$refs.artRef, this.onRead();
}); })
this.$nextTick(() => {
this.$emit('get-instance', this.instance);
});
}, },
beforeDestroy() { beforeDestroy() {
if (this.instance && this.instance.destroy) { if (this.instance && this.instance.destroy) {
this.instance.destroy(false); this.instance.destroy(false);
} }
}, },
methods: {
onRead() {
this.instance = new Artplayer({
...this.option,
container: this.$refs.artRef,
})
this.$nextTick(() => {
this.$emit('get-instance', this.instance);
})
},
},
}; };
</script> </script>

View File

@ -0,0 +1,273 @@
<template>
<div>
<!--文件详细信息-->
<!-- <el-drawer :title="rowContextData.name" :visible.sync="visible">-->
<!-- -->
<!-- </el-drawer>-->
<el-dialog :visible.sync="visible" width="420px">
<div class="drawer-icon">
<icon-file
class="drawer-icon-font"
:grid="true"
:details="true"
:item="rowContextData"
:image-url="imageUrl"
:audio-cover-url="audioCoverUrl"
></icon-file>
</div>
<el-form class="details-form">
<el-form-item label="重建索引:" class="details-scan">
<el-popover
placement="bottom"
trigger="click">
<p class="el-popconfirm__main">
<i class="el-popconfirm__icon el-icon-question" style="color: rgb(255, 153, 0);"></i>
是否开始扫描?
</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="cancelScan()">取消</el-button>
<el-button type="primary" size="mini" @click="scanDirectory(rowContextData)">确定</el-button>
</div>
<el-button slot="reference" id="scanDirectoryBtn-file" type="primary" size="small" :loading="syncLoading"><i class="el-icon-refresh"></i></el-button>
</el-popover>
<span v-show="syncPercent < 100">正在同步文件基本信息: {{ syncPercent }}%</span>
<span v-show="indexingPercent > 0 && indexingPercent < 100">正在为文件内容创建索引: </span><span v-show="indexingPercent > 0 && indexingPercent < 100">{{ indexingPercent }}%</span>
</el-form-item>
<el-scrollbar wrap-class="scrollbar-wrapper" class="details-form-list">
<el-form-item label="名称:">
<span>{{ rowContextData.name }}</span>
</el-form-item>
<el-form-item label="类型:">
<span>{{
rowContextData.isFolder ? '文件夹' : rowContextData.contentType
}}</span>
</el-form-item>
<div v-if="rowContextData.music">
<el-form-item label="🎵 歌手:">
<span>{{ rowContextData.music.singer }}</span>
</el-form-item>
<el-form-item label="🎵 专辑:">
<span>{{ '《' + rowContextData.music.album + '》' }}</span>
</el-form-item>
<el-form-item label="🎵 歌名:">
<span>{{ '《' + rowContextData.music.songName + '》' }}</span>
</el-form-item>
</div>
<el-form-item
v-show="rowContextData.w && rowContextData.h"
label="尺寸:"
class="details-resolution"
>
<span>{{ rowContextData.w + ' x ' + rowContextData.h }}</span>
</el-form-item>
<el-form-item label="大小:">
<span> {{ formatSize(rowContextData.size) }}</span>
</el-form-item>
<el-form-item label="位置:" class="details-position">
<a :href="'/?path=' + rowContextData.path + '&highlight=' + rowContextData.name">{{ rowContextData.path }}</a>
</el-form-item>
<el-form-item label="创建时间:">
<span>{{ rowContextData.uploadDate }}</span>
</el-form-item>
<el-form-item label="修改时间:">
<span>{{ rowContextData.updateDate }}</span>
</el-form-item>
<el-form-item v-if="rowContextData.exif" label="">
<span style="white-space: break-spaces;">{{ formatExif(rowContextData.exif) }}</span>
</el-form-item>
<el-form-item v-if="rowContextData.video" label="">
<span style="white-space: break-spaces;">{{ formatVideo(rowContextData.video) }}</span>
</el-form-item>
</el-scrollbar>
</el-form>
</el-dialog>
</div>
</template>
<script>
import { formatSize } from '@/utils/number'
import IconFile from '@/components/Icon/IconFile'
import {mapState} from "vuex";
import {formatExif,formatVideo} from "@/utils/media";
import settingApi from "@/api/setting-api";
export default {
name: 'FileDetails',
components: {
IconFile
},
props: {
visible: {
type: Boolean,
required: true,
},
rowContextData: {
type: Object,
required: true,
},
imageUrl: {
type: String,
required: true,
},
audioCoverUrl: {
type: String,
required: true,
},
},
data() {
return {
syncLoading: false,
clickSync: false,
indexingPercent: 100,
syncPercent: 100,
}
},
mounted() {
},
computed: {
...mapState(['message']),
},
watch: {
message(msg) {
if (msg.event === 'msg/synced') {
this.updateSyncStatus(msg.data.body)
}
},
visible(val) {
this.$emit('update:visible', val)
if (val) {
this.getIsSync()
}
}
},
methods: {
formatSize(size) {
return formatSize(size)
},
formatExif(exifInfo) {
return formatExif(exifInfo)
},
formatVideo(videoInfo) {
return formatVideo(videoInfo)
},
getIsSync() {
settingApi.isSync({username: this.$store.state.user.name}).then((res) => {
this.updateSyncStatus(res.data)
})
},
updateSyncStatus(dataPercent) {
if (this.clickSync) {
return
}
const {syncPercent, indexingPercent} = dataPercent
if (syncPercent) {
this.syncPercent = syncPercent
} else {
this.syncPercent = 0
}
if (indexingPercent) {
this.indexingPercent = indexingPercent
} else {
this.indexingPercent = 0
}
this.syncLoading = !((this.syncPercent === 100 && this.indexingPercent === 100) || (this.syncPercent === 0 && this.indexingPercent === 0));
},
cancelScan() {
document.getElementById('scanDirectoryBtn-file').click()
},
scanDirectory(file) {
this.cancelScan()
this.syncLoading = true
this.syncPercent = 0
this.clickSync = true
settingApi.syncUser({username: this.$store.state.user.name, path: decodeURIComponent(file.path + file.name)}).then(() => {
this.clickSync = false
}).catch(() => {
this.clickSync = false
})
},
}
}
</script>
<style scoped lang="scss">
>>> .el-drawer__header {
color: #000000;
span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.details-form {
padding: 20px 0;
>>> .el-scrollbar__view {
max-height: 50vh;
}
>>> .el-form-item__content {
white-space: normal;
word-break: break-all;
word-wrap: break-word;
line-height: 25px;
}
>>> .el-form-item__label {
line-height: 25px;
}
>>> .el-form-item {
margin-bottom: 0;
}
>>> .details-scan {
.el-form-item__label {
line-height: 32.5px;
}
margin-bottom: 10px;
.el-button--mini, .el-button--mini.is-round {
padding: 5px 15px;
}
}
>>> .details-position {
margin: 10px 0;
.el-form-item__content {
line-height: 20px;
color: #84a0c3;
}
.el-form-item__label {
line-height: 20px;
}
}
a:hover {
color: #409eff;
}
}
.drawer-icon {
text-align: center;
position: relative;
>>> .icon-favorite {
display: none;
}
>>> .icon-share {
display: none;
}
>>> .icon-tag {
display: none;
}
}
.drawer-icon-font >>> .svg-icon {
font-size: 8rem;
}
</style>

View File

@ -0,0 +1,162 @@
<template>
<div>
<div class="top-bar">
<div class="top-left"></div>
<div class="top-center">{{file.name}}</div>
<div class="top-right">
<div class="top-right-option">
<el-select v-if="services.length > 1" size="medium" v-model="url" placeholder="请选择">
<el-option
v-for="item in services"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<el-button size="medium" type="primary" @click="openUrl"><svg-icon icon-class="wailian"/></el-button>
</div>
</div>
</div>
<div class="iframe-content">
<iframe ref="iframeContent" name="Iframe Content Preview" :title="file.name" :src="url" sandbox="allow-scripts allow-forms allow-same-origin allow-popups"></iframe>
</div>
</div>
</template>
<script>
export default {
name: "IframeContentPreview",
props: {
fileUrl: {
type: String,
default: ''
},
file: {
type: Object,
default: function () {
return {}
}
},
fileHandler: {
type: Object,
default: undefined
}
},
data() {
return {
url: undefined,
services: []
}
},
mounted() {
this.iframeLoad()
},
methods: {
iframeLoad() {
if (this.fileHandler) {
for (let service in this.fileHandler) {
const urlTemplate = this.fileHandler[service]
if (!urlTemplate) {
continue
}
const url = decodeURI(this.fileUrl)
if (urlTemplate.indexOf('$e_url') > 0) {
this.url = urlTemplate.replace('$e_url', encodeURIComponent(url))
this.services.push({label: service, value: this.url})
}
if (urlTemplate.indexOf('$eb_url') > 0) {
this.url = urlTemplate.replace('$eb_url', encodeURIComponent(this.base64EncodeUnicode(url)))
this.services.push({label: service, value: this.url})
}
}
this.$nextTick(() => {
this.onReady()
})
} else {
this.url = undefined
this.services = []
this.$emit('loadFileFailed')
}
},
base64EncodeUnicode(str) {
// 首先,将字符串编码为 UTF-8
const utf8Bytes = new TextEncoder().encode(str);
// 然后,将 Uint8Array 转换为字符串
const binaryStr = String.fromCharCode(...utf8Bytes);
// 最后,使用 btoa 编码为 Base64
return btoa(binaryStr);
},
onReady() {
this.$emit('onReady')
// this.url = 'http://192.168.0.233:8012/onlinePreview?url='+encodeURIComponent(this.base64EncodeUnicode('https://cloud.jmal.top/api/file/jmal/tewo/海印缤缤广场/冷机智能控制系统布线图.pdf'))
//this.url = 'https://alist-org.github.io/pdf.js/web/viewer.html?file=' + encodeURIComponent('http://localhost:9528/api/file/jmal/jmal/share/滨滨广场冷冻侧采集箱布线图_副本 (1).pdf')
if (!this.url) {
this.$emit('loadFileFailed')
}
},
openUrl() {
window.open(this.url)
}
}
}
</script>
<style lang="scss" scoped>
.top-bar {
width: 100%;
top: 0;
position: absolute;
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
background: #fff;
}
.top-left {
width: 25%;
}
.top-center {
width: 50%;
text-align: center;
align-content: center;
}
.top-right {
width: 25%;
.svg-icon {
font-size: 14px;
}
.top-right-option {
float: right;
margin-right: 3rem;
display: flex;
align-items: center;
justify-content: flex-end;
height: 2.5rem;
line-height: 2.5rem;
}
>>> .el-select {
width: 135px;
margin-right: 10px;
}
}
.iframe-content {
position: absolute;
top: 2.5rem;
left: 0;
right: 0;
bottom: 0;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
iframe {
width: 100%;
height: 100%;
border: 0;
}
}
</style>

View File

@ -16,10 +16,13 @@
<svg-icon class="preview-close" icon-class="close"/> <svg-icon class="preview-close" icon-class="close"/>
</div> </div>
<div class="wrapper"> <div class="wrapper">
<div>{{fileType}}</div>
<pdf-preview v-if="fileType === 'pdf'" :file="file" :shareId="shareId" :file-url="fileUrl" @onReady="onReady"></pdf-preview> <pdf-preview v-if="fileType === 'pdf'" :file="file" :shareId="shareId" :file-url="fileUrl" @onReady="onReady"></pdf-preview>
<drawio v-else-if="fileType === 'drawio'" v-show="fileReday" :file="file" :shareId="shareId" :read-only="readOnly" @onEdit="onEdit" @onReady="onReady" @onClose="close"></drawio> <drawio v-else-if="fileType === 'drawio'" v-show="fileReday" :file="file" :shareId="shareId" :read-only="readOnly" @onEdit="onEdit" @onReady="onReady" @onClose="close"></drawio>
<my-mind-editor v-else-if="fileType === 'mind'" :file="file" :shareId="shareId" :read-only="readOnly" @onEdit="onEdit" @onReady="onReady" @onClose="close"></my-mind-editor> <my-mind-editor v-else-if="fileType === 'mind'" :file="file" :shareId="shareId" :read-only="readOnly" @onEdit="onEdit" @onReady="onReady" @onClose="close"></my-mind-editor>
<only-office-editor ref="officeEditor" v-else :file="file" :file-url="fileUrl" :shareId="shareId" :read-only="readOnly" @onEdit="onEdit" @manualSave="manualSave" @onClose="close" @onReady="onReady"></only-office-editor> <model-preview v-else-if="fileType === 'glTF/GLB'" :file="file" :file-url="fileUrl" :shareId="shareId" @onReady="onReady"></model-preview>
<only-office-editor ref="officeEditor" v-else-if="fileType === 'office'" :file="file" :file-url="fileUrl" :shareId="shareId" :sharer="sharer" :read-only="readOnly" @onEdit="onEdit" @manualSave="manualSave" @onClose="close" @onReady="onReady"></only-office-editor>
<iframe-content-preview v-else :file="file" :fileHandler="fileHandler" :file-url="fileUrl" @onReady="onReady" @loadFileFailed="loadFileFailed"></iframe-content-preview>
</div> </div>
</div> </div>
</div> </div>
@ -28,17 +31,18 @@
<script> <script>
import ModelPreview from '@/components/office/ModelPreview.vue'
import OnlyOfficeEditor from "@/components/office/OnlyOfficeEditor"; import OnlyOfficeEditor from "@/components/office/OnlyOfficeEditor";
import MessageDialog from "@/components/message/MessageDialog"; import MessageDialog from "@/components/message/MessageDialog";
import Bus from "@/assets/js/bus"; import IframeContentPreview from '@/components/preview/IframeContentPreview.vue'
import fileConfig from "@/utils/file-config"; import fileConfig from "@/utils/file-config";
import PdfPreview from "@/components/office/PdfPreview"; import PdfPreview from "@/components/office/PdfPreview";
import Drawio from "@/components/office/Drawio"; import Drawio from "@/components/office/Drawio";
import MyMindEditor from "@/components/Minder/minder" import MyMindEditor from "@/components/Minder/minder"
export default { export default {
name: 'OfficePreview', name: 'IframePreview',
components: {Drawio, PdfPreview, MessageDialog, OnlyOfficeEditor, MyMindEditor}, components: { IframeContentPreview, ModelPreview, Drawio, PdfPreview, MessageDialog, OnlyOfficeEditor, MyMindEditor},
props: { props: {
id: { id: {
type: String, type: String,
@ -52,10 +56,18 @@ export default {
return {} return {}
} }
}, },
fileHandler: {
type: Object,
default: {}
},
shareId: { shareId: {
type: String, type: String,
default: undefined default: undefined
}, },
sharer: {
type: String,
default: undefined
},
status: { status: {
type: Boolean, type: Boolean,
default: false default: false
@ -115,6 +127,12 @@ export default {
} }
}, },
getType(suffix) { getType(suffix) {
if (Object.keys(this.fileHandler).length > 0) {
return 'other'
}
if (this.file.contentType.indexOf('office') > -1) {
return 'office'
}
switch (suffix) { switch (suffix) {
case 'drawio': case 'drawio':
return 'drawio' return 'drawio'
@ -122,8 +140,14 @@ export default {
return 'pdf' return 'pdf'
case 'mind': case 'mind':
return 'mind' return 'mind'
default: case 'gltf':
case 'glb':
return 'glTF/GLB'
case 'office':
case 'csv':
return 'office' return 'office'
default:
return 'other'
} }
}, },
/** /**
@ -207,7 +231,7 @@ export default {
this.$message.info('请手动保存') this.$message.info('请手动保存')
}, },
/** /**
* 检查要预览调文件是否为只读 * 检查要预览的文件是否为只读
* @param file userId * @param file userId
*/ */
checkReadOnly(file){ checkReadOnly(file){

View File

@ -153,11 +153,12 @@ export default {
this.images.forEach(image => { this.images.forEach(image => {
imageUrls.push(image.source) imageUrls.push(image.source)
}) })
const that = this
this.vantImagePreview = vant.ImagePreview({ this.vantImagePreview = vant.ImagePreview({
images: imageUrls, images: imageUrls,
startPosition: viewIndex, startPosition: viewIndex,
onClose() { onClose() {
this.$store.dispatch('updateMessage', { event: 'updateImageViewerStatus'}) that.$store.dispatch('updateMessage', { event: 'updateImageViewerStatus'})
} }
}) })
} }

View File

@ -207,6 +207,7 @@
mountFileId: undefined, mountFileId: undefined,
saveTitle: '', saveTitle: '',
options: { options: {
tabSize: 2,
fontSize: 14, fontSize: 14,
contextmenu: true, contextmenu: true,
codeLens: true, codeLens: true,

View File

@ -16,6 +16,7 @@
<script> <script>
import fileConfig from '@/utils/file-config' import fileConfig from '@/utils/file-config'
import Artplayer from "@/components/preview/Artplayer.vue"; import Artplayer from "@/components/preview/Artplayer.vue";
import artplayerPluginVttThumbnail from './artplayer-plugin-vtt-thumbnail';
import Hls from "hls.js"; import Hls from "hls.js";
import FlvJs from "flv.js"; import FlvJs from "flv.js";
@ -45,6 +46,7 @@ export default {
return { return {
pc: this.$pc, pc: this.$pc,
art: null, art: null,
switchQuality: false,
option: { option: {
url: '', url: '',
title: '', // 视频标题,目前会出现在 视频截图 和 迷你模式 下 title: '', // 视频标题,目前会出现在 视频截图 和 迷你模式 下
@ -78,7 +80,7 @@ export default {
style: { style: {
marginRight: this.$pc ? '20px' : '0px', marginRight: this.$pc ? '20px' : '0px',
}, },
html: `<a href="nplayer-videLink"><img style="width: 1.9rem;height: 1.9rem;line-height: 1.9rem" src="${require("@/assets/img/nplayer.webp")}"></a>`, html: `<a><img style="width: 1.9rem;height: 1.9rem;line-height: 1.9rem" src="${require("@/assets/img/nplayer.webp")}"></a>`,
index: 2, index: 2,
}, },
{ {
@ -88,7 +90,7 @@ export default {
style: { style: {
marginRight: this.$pc ? '20px' : '0px', marginRight: this.$pc ? '20px' : '0px',
}, },
html: `<a href="infuse://x-callback-url/play?url=videLink"><img style="width: 1.9rem;height: 1.9rem;line-height: 1.9rem" src="${require("@/assets/img/infuse.webp")}"></a>`, html: `<a><img style="width: 1.9rem;height: 1.9rem;line-height: 1.9rem" src="${require("@/assets/img/infuse.webp")}"></a>`,
index: 3, index: 3,
}, },
{ {
@ -101,7 +103,7 @@ export default {
}, },
click: () => this.copyToClipboard(this.videoLink), click: () => this.copyToClipboard(this.videoLink),
} }
], ]
}, },
videoLink: '', videoLink: '',
style: { style: {
@ -124,6 +126,12 @@ export default {
watch: { watch: {
status: function (visible) { status: function (visible) {
if (visible) { if (visible) {
if (!this.$pc) {
// 去掉this.option.controls中name为 name: 'iina'
this.option.controls = this.option.controls.filter(item => item.name === 'nplayer' || item.name === 'infuse')
}
let videoUrl = fileConfig.previewUrl(this.$store.state.user.name, this.file, this.$store.getters.token) let videoUrl = fileConfig.previewUrl(this.$store.state.user.name, this.file, this.$store.getters.token)
if (this.shareId) { if (this.shareId) {
videoUrl = fileConfig.publicPreviewUrl(this.file, window.shareId, this.$store.getters.shareToken) videoUrl = fileConfig.publicPreviewUrl(this.file, window.shareId, this.$store.getters.shareToken)
@ -137,8 +145,9 @@ export default {
} }
} }
this.option.url = videoUrl this.option.url = videoUrl
if (this.file.m3u8) { if (this.file.m3u8) {
const supported = ['flv', 'mp4', 'ogg', 'mkv', 'webm', 'hls', 'mov'] const supported = ['mp4', 'ogg', 'mkv', 'webm', 'hls', 'mov']
if (supported.includes(this.file.suffix.toLowerCase())) { if (supported.includes(this.file.suffix.toLowerCase())) {
this.option.quality = [ this.option.quality = [
{ {
@ -155,17 +164,30 @@ export default {
this.option.customType = { this.option.customType = {
m3u8: this.playM3u8 m3u8: this.playM3u8
} }
this.option.plugins = [
artplayerPluginVttThumbnail({
vtt: videoUrl.replace(/m3u8/g, 'vtt'),
})
]
} }
if (this.file.contentType.indexOf('flv') > -1) { if (this.file.contentType.indexOf('flv') > -1) {
this.option.url = originUrl
this.option.quality = []
this.option.customType = { this.option.customType = {
flv: this.playFlv flv: this.playFlv
} }
} }
this.videoLink = window.location.origin + videoUrl this.videoLink = window.location.origin + originUrl
this.title = this.file.name this.title = this.file.name
this.option.id = this.file.id this.option.id = this.file.id
this.show = true this.show = true
this.switchQuality = true
} else {
this.option.url = ''
this.videoLink = ''
this.option.quality = []
} }
} }
}, },
@ -197,15 +219,24 @@ export default {
getInstance(art) { getInstance(art) {
this.art = art this.art = art
let doc = this.art.controls.$parent let doc = this.art.controls.$parent
// iina if (this.$pc) {
const iina = doc.querySelector('[data-index="1"]').querySelector('a'); // iina
iina.href = `iina://weblink?url=${this.videoLink}` const iina = doc.querySelector('[data-index="1"]').querySelector('a');
// nplayer iina.href = `iina://weblink?url=${this.videoLink}`
const nplayer = doc.querySelector('[data-index="2"]').querySelector('a'); // nplayer
nplayer.href = `nplayer-${this.videoLink}` const nplayer = doc.querySelector('[data-index="2"]').querySelector('a');
// infuse nplayer.href = `nplayer-${this.videoLink}`
const infuse = doc.querySelector('[data-index="3"]').querySelector('a'); // infuse
infuse.href = `infuse://x-callback-url/play?url=${this.videoLink}` const infuse = doc.querySelector('[data-index="3"]').querySelector('a');
infuse.href = `infuse://x-callback-url/play?url=${this.videoLink}`
} else {
// nplayer
const nplayer = doc.querySelector('[data-index="2"]').querySelector('a');
nplayer.href = `nplayer-${this.videoLink}`
// infuse
const infuse = doc.querySelector('[data-index="3"]').querySelector('a');
infuse.href = `infuse://x-callback-url/play?url=${this.videoLink}`
}
}, },
playM3u8(video, url, art) { playM3u8(video, url, art) {
if (Hls.isSupported()) { if (Hls.isSupported()) {

View File

@ -0,0 +1,68 @@
function padEnd(str, targetLength, padString) {
if (str.length > targetLength) {
return String(str);
} else {
targetLength = targetLength - str.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length);
}
return String(str) + padString.slice(0, targetLength);
}
}
function t2d(time) {
var arr = time.split('.');
var left = arr[0].split(':') || [];
var right = padEnd(arr[1] || '0', 3, '0');
var ms = Number(right) / 1000;
var h = Number(left[left.length - 3] || 0) * 3600;
var m = Number(left[left.length - 2] || 0) * 60;
var s = Number(left[left.length - 1] || 0);
return h + m + s + ms;
}
export default async function getVttArray(vttUrl = '') {
const vttString = await (await fetch(vttUrl)).text();
const lines = vttString.split(/[\n\r]/gm).filter((item) => item.trim());
const vttArray = [];
for (let i = 1; i < lines.length; i += 2) {
const time = lines[i];
const text = lines[i + 1];
if (!text.trim()) continue;
const timeReg =
/((?:[0-9]{2}:)?(?:[0-9]{2}:)?[0-9]{2}(?:.[0-9]{3})?)(?: ?--> ?)((?:[0-9]{2}:)?(?:[0-9]{2}:)?[0-9]{2}(?:.[0-9]{3})?)/;
const timeMatch = time.match(timeReg);
const textReg = /(.*)#(\w{4})=(.*)/i;
const textMatch = text.match(textReg);
const start = Math.floor(t2d(timeMatch[1]));
const end = Math.floor(t2d(timeMatch[2]));
let url = textMatch[1];
const isAbsoluteUrl = /^\/|((https?|ftp|file):\/\/)/i.test(url);
if (!isAbsoluteUrl) {
const urlArr = vttUrl.split('/');
urlArr.pop();
urlArr.push(url);
url = urlArr.join('/');
}
const result = { start, end, url };
const keys = textMatch[2].split('');
const values = textMatch[3].split(',');
for (let j = 0; j < keys.length; j++) {
result[keys[j]] = values[j];
}
vttArray.push(result);
}
return vttArray;
}

View File

@ -0,0 +1,72 @@
import getVttArray from './getVttArray';
export default function artplayerPluginVttThumbnail(option) {
return async (art) => {
const {
constructor: {
utils: { setStyle, isMobile },
},
template: { $progress },
} = art;
let timer = null;
const thumbnails = await getVttArray(option.vtt);
function showThumbnails($control, find, width) {
setStyle($control, 'backgroundImage', `url(${find.url})`);
setStyle($control, 'height', `${find.h}px`);
setStyle($control, 'width', `${find.w}px`);
setStyle($control, 'backgroundPosition', `-${find.x}px -${find.y}px`);
if (width <= find.w / 2) {
setStyle($control, 'left', 0);
} else if (width > $progress.clientWidth - find.w / 2) {
setStyle($control, 'left', `${$progress.clientWidth - find.w}px`);
} else {
setStyle($control, 'left', `${width - find.w / 2}px`);
}
}
art.controls.add({
name: 'thumbnails',
position: 'top',
index: 20,
style: option.style || {},
mounted($control) {
art.on('setBar', async (type, percentage, event) => {
const isMobileDroging = type === 'played' && event && isMobile;
if (type === 'hover' || isMobileDroging) {
const width = $progress.clientWidth * percentage;
const second = percentage * art.duration;
setStyle($control, 'display', 'flex');
const find = thumbnails.find((item) => second >= item.start && second <= item.end);
if (!find) return setStyle($control, 'display', 'none');
if (width > 0 && width < $progress.clientWidth) {
showThumbnails($control, find, width);
} else {
if (!isMobile) {
setStyle($control, 'display', 'none');
}
}
if (isMobileDroging) {
clearTimeout(timer);
timer = setTimeout(() => {
setStyle($control, 'display', 'none');
}, 500);
}
}
});
},
});
return {
name: 'artplayerPluginVttThumbnail',
};
};
}
if (typeof window !== 'undefined') {
window['artplayerPluginVttThumbnail'] = artplayerPluginVttThumbnail;
}

1
src/icons/svg/file-c.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1716514768273" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="40143" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M923.2 245.6L536.8 23.2c-6.4-4-15.2-5.6-24.8-5.6-9.6 0-18.4 2.4-24.8 5.6l-384 223.2c-13.6 8-23.2 28-23.2 43.2v445.6c0 8.8 1.6 19.2 8 28l854.4-496c-4.8-9.6-12-16.8-19.2-21.6z" fill="#659AD3" p-id="40144"></path><path d="M85.6 762.4c4 6.4 9.6 12 15.2 15.2l385.6 223.2c6.4 4 15.2 5.6 24.8 5.6 9.6 0 18.4-2.4 24.8-5.6l384-223.2c13.6-8 23.2-28 23.2-43.2V288.8c0-7.2-0.8-15.2-4.8-22.4l-852.8 496z" fill="#03599C" p-id="40145"></path><path d="M682.4 608.8C648.8 668 584.8 708 512 708c-108 0-196-88-196-196s88-196 196-196c72.8 0 136.8 40 170.4 100l104-60c-54.4-95.2-156.8-160-274.4-160-174.4 0-316 141.6-316 316s141.6 316 316 316c116.8 0 219.2-64 273.6-158.4l-103.2-60.8z" fill="#FFFFFF" p-id="40146"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1718450747239" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6336" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M263.68 337.9712h572.5696v562.8416H302.4896a38.8096 38.8096 0 0 1-38.8096-38.8096z" fill="#2F68CB" p-id="6337"></path><path d="M184.32 255.3856l137.6768-71.68v519.2192L184.32 774.5536z" fill="#93BBFF" p-id="6338"></path><path d="M321.9456 183.7056h417.28a38.8096 38.8096 0 0 1 38.8096 38.8096v479.5392H321.9456z" fill="#478BFF" p-id="6339"></path><path d="M482.56 877.1584c27.0336 0 44.2368-13.2096 44.2368-29.4912 0-14.4384-10.24-20.48-30.4128-20.48h-15.36c-10.9056-0.1024-14.2848-3.328-14.2848-8.448a11.2128 11.2128 0 0 1 4.608-8.9088 29.3888 29.3888 0 0 0 11.8272 2.6112 28.8768 28.8768 0 0 0 31.6416-29.0304 22.8352 22.8352 0 0 0-5.5296-15.36h16.128v-13.312h-29.952a35.2256 35.2256 0 0 0-12.288-2.1504 30.72 30.72 0 0 0-32.6144 30.3104 28.928 28.928 0 0 0 10.9568 22.528v0.6144a20.8384 20.8384 0 0 0-9.3696 15.6672 15.36 15.36 0 0 0 7.68 13.9776v0.768a22.3232 22.3232 0 0 0-12.288 18.432c0 14.7456 14.7456 22.272 35.0208 22.272z m0.6144-76.032a16.4864 16.4864 0 0 1-15.9744-18.2272 16.0768 16.0768 0 0 1 15.9744-17.9712 16.1792 16.1792 0 0 1 15.9744 17.9712 16.4864 16.4864 0 0 1-15.9744 18.2272z m1.9968 64.3584c-14.1312 0-22.5792-4.9152-22.5792-13.2096a15.36 15.36 0 0 1 7.2192-12.288 53.7088 53.7088 0 0 0 11.4688 1.2288h12.7488c10.24 0 15.8208 2.1504 15.8208 9.216s-9.9328 15.0528-24.6784 15.0528z m78.4896-24.2688a28.3648 28.3648 0 0 0 9.7792-1.3824l-2.56-13.3632a12.288 12.288 0 0 1-2.9184 0.3072c-2.1504 0-4.3008-1.6896-4.3008-6.4512V716.8h-17.5104v102.4c0 13.8752 4.7616 22.016 17.5104 22.016z m71.2192 0c19.0464 0 36.4032-16.5888 36.4032-45.6192 0-25.9584-12.1344-43.008-33.4848-43.008a40.3456 40.3456 0 0 0-25.1904 11.0592l0.4608-14.4384V716.8h-17.4592v122.4192h13.6704l1.6896-8.7552h0.4608a35.072 35.072 0 0 0 23.4496 10.752z m-3.328-14.592a28.3136 28.3136 0 0 1-18.432-7.68v-41.1648a29.3888 29.3888 0 0 1 20.1216-10.24c14.1312 0 19.8144 10.9056 19.8144 28.5696 0 19.6096-9.216 30.5152-21.504 30.5152z" fill="#FFFFFF" p-id="6340"></path><path d="M498.2784 401.3056h23.04v90.4704h-23.04z" fill="#FFFFFF" p-id="6341"></path><path d="M498.2784 401.3056h23.04v90.4704h-23.04z" fill="#FFFFFF" p-id="6342"></path><path d="M477.8496 426.9056c-5.12 0-10.9056 7.424-10.9056 17.3568s5.7856 17.3568 10.9056 17.3568 10.9568-7.424 10.9568-17.3568-5.7856-17.3568-10.9568-17.3568m0-25.6a40.1408 40.1408 0 0 1 36.5056 42.9568 40.1408 40.1408 0 0 1-36.5056 42.9568 40.1408 40.1408 0 0 1-36.5056-42.9568 40.1408 40.1408 0 0 1 36.5056-42.9568z" fill="#FFFFFF" p-id="6343"></path><path d="M665.088 499.9168a318.3104 318.3104 0 0 1-59.904 18.5344 324.608 324.608 0 0 1-63.744 5.12 47.9744 47.9744 0 0 1-15.0528 11.9296 192.256 192.256 0 0 1-28.2112 10.752 263.9872 263.9872 0 0 0 92.16-4.2496 205.5168 205.5168 0 0 0 74.752-42.0864z" fill="#FFFFFF" p-id="6344"></path><path d="M647.168 382.6176a197.0176 197.0176 0 0 0-231.2192-22.8352c-114.8416 76.8 6.656 208.0768 77.7216 171.6736a53.4528 53.4528 0 0 0 27.4432-41.7792l-16.4352-14.0288s-8.8576 32-26.3168 34.9696a56.7808 56.7808 0 0 1-49.664-24.1152 78.9504 78.9504 0 0 1-10.24-71.9872c14.2848-37.888 52.8896-49.8688 93.6448-54.784a293.2224 293.2224 0 0 1 135.0656 22.8864z" fill="#FFFFFF" p-id="6345"></path><path d="M539.2896 401.8688h23.04v92.16h-23.04z" fill="#FFFFFF" p-id="6346"></path><path d="M599.1936 401.8688h23.04v92.16h-23.04z" fill="#FFFFFF" p-id="6347"></path><path d="M659.1488 401.8688h23.04v92.16h-23.04z" fill="#FFFFFF" p-id="6348"></path><path d="M576.1536 401.8688h69.1712v23.04h-69.1712z" fill="#FFFFFF" p-id="6349"></path><path d="M659.1488 401.8688h69.1712v23.04h-69.1712z" fill="#FFFFFF" p-id="6350"></path><path d="M659.1488 436.48h58.88v23.04h-58.88z" fill="#FFFFFF" p-id="6351"></path></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -221,10 +221,6 @@ export default {
tagClick(tag) { tagClick(tag) {
this.$router.push(`/tag?tagId=${tag.id}`); this.$router.push(`/tag?tagId=${tag.id}`);
}, },
mouseleave() {
},
onmousemove() {
},
progressFormat() { progressFormat() {
if (this.takeUpSpace === 0) { if (this.takeUpSpace === 0) {
this.takeUpSpace = this.userInfo.takeUpSpace this.takeUpSpace = this.userInfo.takeUpSpace

View File

@ -21,7 +21,7 @@ import ResizeMixin from './mixin/ResizeHandler'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import AudioPreview from "@/components/preview/AudioPreview"; import AudioPreview from "@/components/preview/AudioPreview";
import globalUploader from '@/components/SimpleUploader/globalUploader.vue' import globalUploader from '@/components/SimpleUploader/globalUploader.vue'
import { connectToSSE } from '@/sse/SSEClient' import { initiateSSE } from '@/sse/SSEClient'
export default { export default {
name: 'Layout', name: 'Layout',
@ -62,7 +62,7 @@ export default {
if(getToken()){ if(getToken()){
this.isShow = window.pc; this.isShow = window.pc;
// 与服务器建立sse连接 // 与服务器建立sse连接
connectToSSE(this.$store.state.user.name) initiateSSE(this.$store.state.user.name)
if(this.$route.path.startsWith('/setting')){ if(this.$route.path.startsWith('/setting')){
this.isShow = true this.isShow = true
} }

View File

@ -7,7 +7,7 @@ import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false }) // NProgress配置 NProgress.configure({ showSpinner: false }) // NProgress配置
const whiteList = ['/login','/s','/s/','/s_m','/s_m/'] // 白名单 const whiteList = ['/login','/s','/s/'] // 白名单
router.beforeEach(async(to, from, next) => { router.beforeEach(async(to, from, next) => {
// 开始进度条 // 开始进度条
@ -51,7 +51,7 @@ router.beforeEach(async(to, from, next) => {
} }
} else { } else {
// 没有登录的情况 // 没有登录的情况
if (whiteList.indexOf(to.path) !== -1) { if (hasWhiteList(to.path)) {
// 没有token但是在白名单中,直接进入 // 没有token但是在白名单中,直接进入
next() next()
} else { } else {
@ -65,6 +65,15 @@ router.beforeEach(async(to, from, next) => {
} }
}) })
function hasWhiteList(path) {
for (let i = 0; i < whiteList.length; i++) {
if (path.indexOf(whiteList[i]) !== -1) {
return true
}
}
return false
}
function setMenuList(next, to){ function setMenuList(next, to){
if(store.getters.menuList.length === 0){ if(store.getters.menuList.length === 0){
store.dispatch('user/setMenuList').then((res) => { store.dispatch('user/setMenuList').then((res) => {

View File

@ -47,12 +47,12 @@ export const constantRoutes = [
hidden: true hidden: true
}, },
{ {
path: '/s', path: '/s/:id',
component: Layout, component: Layout,
children: [ children: [
{ {
path: '', path: '',
name: 's', name: 'share',
component: () => import('@/views/public/share/shareList'), component: () => import('@/views/public/share/shareList'),
} }
] ]
@ -75,15 +75,15 @@ export const dynamicRouters = [
}] }]
}, },
{ {
path: '/s_m', path: '/upload/index_m',
component: Layout, component: Layout,
children: [ redirect: '/upload/index_m',
{ children: [{
path: '', path: '',
name: 's_m', name: 'Home_m',
component: () => import('@/views/public/share/shareList'), component: () => import('@/views/upload/mobile/index'),
} meta: { title: '文件传输' }
] }]
}, },
{ {
path: '/tag', path: '/tag',

View File

@ -1,88 +1,113 @@
import Bus from "@/assets/js/bus"; import Bus from "@/assets/js/bus";
import _ from "lodash"; import _ from "lodash";
import store from "@/store"; import store from "@/store";
import {generateUUID} from "ant-design-vue/lib/vc-select/util"; import { generateUUID } from "ant-design-vue/lib/vc-select/util";
let eventSource = null; let eventSource = null;
let timer = null; let timer = null;
let lastHeartbeat = new Date().getTime();
const uuid = generateUUID(); const uuid = generateUUID();
const throttledConnectSSE = _.throttle(username => { const throttledConnectSSE = _.throttle((username) => {
if (typeof (EventSource) !== 'undefined') { if (typeof EventSource !== 'undefined') {
const url = `/api/events?username=${username}&uuid=${uuid}`; // 判断cookie中是否有token
eventSource = new EventSource(url); if (store.getters.name) {
eventSource.addEventListener('message', function (event) {
if (event.data === 'h') {
// 心跳包, 超过5s没有收到心跳包, 则关闭连接
lastHeartbeat = new Date().getTime()
} else {
const msg = JSON.parse( event.data);
onMessage(msg)
}
});
eventSource.addEventListener('error', function (event) {
eventSource.close()
connectToSSE(username); connectToSSE(username);
});
// 清除定时器
if (timer) {
clearInterval(timer);
} }
// 3s检测一次连接状态
timer = setInterval(() => {
if (eventSource.readyState === 1) {
// 心跳包, 超过5s没有收到心跳包, 则关闭连接
if (new Date().getTime() - lastHeartbeat > 5000) {
eventSource.close()
}
}
if (eventSource.readyState === 2) {
connectToSSE(username);
}
}, 3000);
} else { } else {
console.error('EventSource is not supported by the browser'); console.error('EventSource is not supported by the browser');
} }
}, 3000); }, 3000);
export function connectToSSE(username) { function connectToSSE(username) {
if (eventSource) {
// 移除之前的事件监听器
eventSource.removeEventListener('message', handleMessage);
eventSource.removeEventListener('error', handleError.bind(null, username));
}
const url = `/api/events?username=${username}&uuid=${uuid}`;
eventSource = new EventSource(url);
eventSource.addEventListener('message', handleMessage);
eventSource.addEventListener('error', handleError.bind(null, username));
}
function handleMessage(event) {
const msg = JSON.parse(event.data);
onMessage(msg);
}
function handleError(username) {
eventSource.close();
throttledConnectSSE(username); throttledConnectSSE(username);
} }
function onMessage(msg) { function clearExistingTimer() {
const url = msg.url if (timer) {
if (url === 'synced') { clearInterval(timer);
store.dispatch('updateMessage', {event: 'msg/synced', data: msg})
} else {
store.dispatch('updateMessage', {event: 'msg/file/change', data: msg})
if ('operationFile' === url) {
let doc = msg.body
if (doc.code === 0) {
Bus.notify({
title: `${doc.operation}成功`,
dangerouslyUseHTMLString: true,
message: `
<div>
<p>form:</p>
<pre style="word-break: break-all;white-space: pre-wrap;font-size: 12px;">${doc.from}</pre>
</div>
<div>
<p>to:</p>
<pre style="word-break: break-all;white-space: pre-wrap;font-size: 12px;">${doc.to}</pre>
</div>`,
type: 'success',
});
} else {
store.dispatch('updateMessage', {event: 'msg/file/operation/fault', data: msg});
Bus.notify({
title: `${doc.operation}失败`,
dangerouslyUseHTMLString: true,
message: `<span style="font-size: 12px;">${doc.msg}</span>`,
type: 'error'
});
}
}
} }
} }
function onMessage(msg) {
const { url, body } = msg;
const eventMapping = {
synced: 'msg/synced',
taskProgress: 'msg/taskProgress',
transcodeStatus: 'msg/transcodeStatus',
uploaderChunkSize: 'uploaderChunkSize',
};
if (eventMapping[url]) {
store.dispatch('updateMessage', { event: eventMapping[url], data: msg });
} else {
handleFileChangeMessage(msg, url, body);
}
}
function handleFileChangeMessage(msg, url, body) {
store.dispatch('updateMessage', { event: 'msg/file/change', data: msg });
if (url === 'operationFile') {
const { operation, from, to, code, msg: message } = body;
const notificationOptions = {
title: `${operation} ${code === 0 ? '成功' : '失败'}`,
dangerouslyUseHTMLString: true,
message: code === 0
? `
<div>
<p>from:</p>
<pre style="word-break: break-all;white-space: pre-wrap;font-size: 12px;">${from}</pre>
</div>
<div>
<p>to:</p>
<pre style="word-break: break-all;white-space: pre-wrap;font-size: 12px;">${to}</pre>
</div>`
: `<span style="font-size: 12px;">${message}</span>`,
type: code === 0 ? 'success' : 'error',
};
Bus.notify(notificationOptions);
if (code !== 0) {
store.dispatch('updateMessage', { event: 'msg/file/operation/fault', data: msg });
}
}
if (url === 'operationTips') {
const { operation, success, msg: message } = body
const notificationOptions = {
title: `${operation} ${success ? '成功' : '失败'}`,
dangerouslyUseHTMLString: true,
message: success ? '' : message ? `<span style="font-size: 12px;">${message}</span>` : '',
type: success ? 'success' : 'error',
}
Bus.notify(notificationOptions)
}
}
export function initiateSSE(username) {
throttledConnectSSE(username);
}

View File

@ -7,6 +7,7 @@ const getters = {
name: state => state.user.name, name: state => state.user.name,
showName: state => state.user.showName, showName: state => state.user.showName,
newVersion: state => state.user.newVersion, newVersion: state => state.user.newVersion,
iframePreviewConfig: state => state.user.iframePreviewConfig,
userId: state => state.user.userId, userId: state => state.user.userId,
userInfo: state => state.user.userInfo, userInfo: state => state.user.userInfo,
menuList: state => state.user.menuList, menuList: state => state.user.menuList,

View File

@ -33,7 +33,8 @@ const getDefaultState = () => {
menuList: [], menuList: [],
netdiskName: '', netdiskName: '',
netdiskLogo: '', netdiskLogo: '',
newVersion: '' newVersion: '',
iframePreviewConfig: {}
} }
} }
@ -75,6 +76,9 @@ const mutations = {
}, },
SET_NEW_VERSION:(state,newVersion) => { SET_NEW_VERSION:(state,newVersion) => {
state.newVersion = newVersion; state.newVersion = newVersion;
},
SET_IFRAME_PREVIEW:(state, iframePreviewConfig) => {
state.iframePreviewConfig = iframePreviewConfig;
} }
} }
@ -147,7 +151,7 @@ const actions = {
// reject('Verification failed, please Login again.') // reject('Verification failed, please Login again.')
} }
const { id, username, showName, avatar, netdiskName, netdiskLogo, newVersion} = data const { id, username, showName, avatar, netdiskName, netdiskLogo, newVersion, iframe} = data
commit('SET_NAME', username) commit('SET_NAME', username)
commit('SET_SHOW_NAME', showName) commit('SET_SHOW_NAME', showName)
@ -156,6 +160,11 @@ const actions = {
commit('SET_USER_INFO', data) commit('SET_USER_INFO', data)
commit('SET_NETDISK_NAME', netdiskName) commit('SET_NETDISK_NAME', netdiskName)
commit('SET_NETDISK_LOGO', netdiskLogo) commit('SET_NETDISK_LOGO', netdiskLogo)
if (iframe) {
commit('SET_IFRAME_PREVIEW', JSON.parse(iframe))
} else {
commit('SET_IFRAME_PREVIEW', {})
}
if (newVersion) { if (newVersion) {
commit('SET_NEW_VERSION', newVersion) commit('SET_NEW_VERSION', newVersion)
} else { } else {

View File

@ -285,7 +285,7 @@
} }
>>> .van-grid { >>> .van-grid {
padding: 0; padding-left: 10px;
} }
>>> .van-grid-item__content .svg-icon{ >>> .van-grid-item__content .svg-icon{
@ -293,6 +293,21 @@
} }
>>> .grid-item-text { >>> .grid-item-text {
margin-top: 5px;
cursor: default;
text-align: center;
color: #606266;
font-size: smaller;
//flex: 1;
//align-items: center;
justify-content: space-evenly;
display: flex;
word-break: break-word;
display: -webkit-box;
overflow: hidden;
}
>>> .grid-item-text-input {
cursor: default; cursor: default;
text-align: center; text-align: center;
color: #606266; color: #606266;
@ -306,6 +321,7 @@
padding: 0 5px 0 5px; padding: 0 5px 0 5px;
bottom: 5px; bottom: 5px;
left: 0; left: 0;
z-index: 1;
textarea { textarea {
overflow: hidden; overflow: hidden;
resize:none; resize:none;
@ -330,20 +346,16 @@
} }
>>> .grid-item { >>> .grid-item {
border-radius: 5px; border-radius: 5px;
padding: 0 8px;
border: solid 1px #ccc0; border: solid 1px #ccc0;
.grid-item-icon{ .grid-item-icon{
text-align: center; //text-align: center;
position: absolute; //position: absolute;
top: 5px; //top: 0;
//margin: 9px auto 0;
//width: 84px;
//height: 84px;
} }
.grid-item-icon-image { .grid-item-icon-image {
width: 100%; width: 100%;
height: 100%; height: 100%;
top: 0px; top: 0;
border-radius: 5px; border-radius: 5px;
margin: 0; margin: 0;
-webkit-backdrop-filter: contrast(0.5); -webkit-backdrop-filter: contrast(0.5);

View File

@ -226,3 +226,16 @@ input {
.dark-file-history-list.el-table::before { .dark-file-history-list.el-table::before {
background-color: #3e3e3e; background-color: #3e3e3e;
} }
kbd {
display: inline-block;
padding: 5px 5px;
font: 14px var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace);
line-height: 10px;
color: #1f2328;
vertical-align: middle;
background-color: #f6f8fa;
border: solid 1px rgba(175, 184, 193, .2);
border-radius: 6px;
-webkit-box-shadow: inset 0 -1px 0 rgba(175, 184, 193, .2);
box-shadow: inset 0 -1px 0 rgba(175, 184, 193, .2);
}

View File

@ -197,4 +197,28 @@ a:hover {
} }
} }
} }
.setting-title-desc {
.setting-title-desc-text {
padding: 5px 0 20px 0;
}
>>>.el-divider--horizontal {
margin: 0;
}
}
>>> .el-form {
.form-instruction {
color: #9a9a9a;
font-size: .8125rem;
a {
color: #1890ff;
}
}
}
.form-item-flex {
display: flex;
align-items: center;
>>> .el-icon-question {
padding: 5px;
font-size: 1rem;
}
}

View File

@ -12,15 +12,23 @@ export default {
return window.location.origin + `/webDAV/${username}` return window.location.origin + `/webDAV/${username}`
}, },
// office api url // office api url
officeApiUrl: function() { officeApiUrl: function(documentServer) {
return `${window.location.origin}/office/web-apps/apps/api/documents/api.js` let serverUrl = documentServer || `${window.location.origin}/office`
if (!serverUrl.endsWith('/')) {
serverUrl = serverUrl + "/"
}
return serverUrl + "web-apps/apps/api/documents/api.js"
}, },
// office回调url // office回调url
officeCallBackUrl: function(token, username, fileId) { officeCallBackUrl: function(callbackServer, token, username, fileId) {
return `http://jmalcloud:8088/office/track?jmal-token=${token}&name=${username}&fileId=${fileId}` let callbackServerUrl = callbackServer || 'http://jmalcloud:8088'
if (!callbackServerUrl.endsWith('/')) {
callbackServerUrl = callbackServerUrl + "/"
}
return `${callbackServerUrl}office/track?jmal-token=${token}&name=${username}&fileId=${fileId}`
}, },
// 预览文件的url // 预览文件的url
previewUrl: function(username, file, token, shareToken) { previewUrl: function(username, file, token, shareToken, serverUrl) {
let owner = null let owner = null
if (username !== store.getters.name || localStorage.getItem('mountFileOwner') !== null) { if (username !== store.getters.name || localStorage.getItem('mountFileOwner') !== null) {
owner = localStorage.getItem('mountFileOwner') owner = localStorage.getItem('mountFileOwner')
@ -30,8 +38,10 @@ export default {
if (owner == null) { if (owner == null) {
owner = store.getters.name owner = store.getters.name
} }
let fileUrl = `${this.baseUrl}/file/${owner}${encodeURI(file.path)}${encodeURI(file.name)}` let baseUrl = serverUrl || this.baseUrl
fileUrl = fileUrl.replace(/%5C/g, '/') let fileUrl = `${baseUrl}/file/${owner}${encodeURI(file.path)}${encodeURI(file.name)}`
fileUrl = fileUrl.replaceAll('#', '%23')
fileUrl = fileUrl.replaceAll(/%5C/g, '/')
if (token) { if (token) {
return `${fileUrl}?jmal-token=${token}&name=${username}` return `${fileUrl}?jmal-token=${token}&name=${username}`
} }
@ -64,7 +74,8 @@ export default {
// 打包下载文件 // 打包下载文件
packageDownload: function(fileIds, token, username) { packageDownload: function(fileIds, token, username) {
fileApi.isAllowDownload().then(() => { fileApi.isAllowDownload().then(() => {
window.open(`${this.baseUrl}/packageDownload?fileIds=${fileIds}&jmal-token=${token}&name=${username}`, '_self') fileIds = fileIds.join(',')
window.open(`${this.baseUrl}/packageDownload?fileIds=${fileIds}`, '_self')
}) })
}, },
// 共享文件下载 // 共享文件下载
@ -77,11 +88,12 @@ export default {
window.open(url, '_blank') window.open(url, '_blank')
}, },
// 共享文件预览Url // 共享文件预览Url
publicPreviewUrl: function(file, shareId, shareToken) { publicPreviewUrl: function(file, shareId, shareToken, serverUrl) {
if (!shareToken) { if (!shareToken) {
shareToken = "none" shareToken = "none"
} }
return `${this.baseUrl}/public/s/preview/${file.name}?fileId=${file.id}&shareId=${shareId}&shareToken=${shareToken}` let baseUrl = serverUrl || this.baseUrl
return `${baseUrl}/public/s/preview/${file.name}?fileId=${file.id}&shareId=${shareId}&shareToken=${shareToken}`
}, },
// 共享文件打包下载 // 共享文件打包下载
publicPackageDownload: function(shareId, fileIds, shareToken) { publicPackageDownload: function(shareId, fileIds, shareToken) {
@ -96,6 +108,13 @@ export default {
}, },
// 共享文件打包下载Url // 共享文件打包下载Url
publicPackageDownloadUrl: function(shareId, fileIds, shareToken) { publicPackageDownloadUrl: function(shareId, fileIds, shareToken) {
return window.location.origin + `${this.baseUrl}/public/s/packageDownload?shareId=${shareId}&fileIds=${fileIds}&share-token=${shareToken}` const queryShareId = shareId ? `&shareId=${shareId}` : ''
const queryShareToken = shareToken ? `&share-token=${shareToken}` : ''
return window.location.origin + `${this.baseUrl}/public/s/packageDownload?fileIds=${fileIds}${queryShareId}${queryShareToken}`
},
// 文件打包下载Url
packageDownloadUrl: function(fileId, downloadName, shareToken) {
const queryShareToken = shareToken ? `?share-token=${shareToken}` : ''
return window.location.origin + `${this.baseUrl}/public/s/${fileId}/packageDownload/${downloadName}${queryShareToken}`
}, },
} }

View File

@ -8,7 +8,7 @@ export const suffix = {
'cmake','db','gradle','bat','conf','dart','plist' 'cmake','db','gradle','bat','conf','dart','plist'
], ],
compressedFile: [ compressedFile: [
'zip','tar','jar','tar.gz','tgz','tar.bz2' 'zip','tar','7z', 'rar', 'jar','tar.gz','tgz','tar.bz2'
], ],
} }
@ -25,6 +25,7 @@ export const lineWrapping = [
export const iconClass = new Map() export const iconClass = new Map()
iconClass.set('php','file-php') iconClass.set('php','file-php')
iconClass.set('c','file-c')
iconClass.set('js','file-js') iconClass.set('js','file-js')
iconClass.set('vue','file-vue') iconClass.set('vue','file-vue')
iconClass.set('xml','file-xml') iconClass.set('xml','file-xml')
@ -64,10 +65,12 @@ iconClass.set('pdf','file-pdf')
iconClass.set('mind','file-mind') iconClass.set('mind','file-mind')
iconClass.set('zip','zip') iconClass.set('zip','zip')
iconClass.set('rar','zip') iconClass.set('rar','zip')
iconClass.set('7z','zip')
iconClass.set('iml','file-idea') iconClass.set('iml','file-idea')
iconClass.set('key','file-key') iconClass.set('key','file-key')
iconClass.set('pem','file-pem') iconClass.set('pem','file-pem')
iconClass.set('gz','file-gzip') iconClass.set('gz','file-gzip')
iconClass.set('tar.gz','file-gzip')
iconClass.set('json','file-json') iconClass.set('json','file-json')
iconClass.set('tar','file-tar') iconClass.set('tar','file-tar')
iconClass.set('sh','file-shell') iconClass.set('sh','file-shell')
@ -83,5 +86,36 @@ iconClass.set('gradle','file-gradle')
iconClass.set('bat','file-bat') iconClass.set('bat','file-bat')
iconClass.set('swift','file-swift') iconClass.set('swift','file-swift')
iconClass.set('yml','file-yml') iconClass.set('yml','file-yml')
iconClass.set('yaml','file-yml')
iconClass.set('plist','file-plist') iconClass.set('plist','file-plist')
iconClass.set('iso','file-iso') iconClass.set('iso','file-iso')
iconClass.set('glb','file-glb')
iconClass.set('gltf','file-glb')
export function findIconClass(file){
if(!file){
return
}
if(file.isFolder){
return 'folder'
}
if (file.contentType) {
if(file.contentType.indexOf('video') > -1){
return 'video'
}
if(file.contentType.startsWith('image')){
return 'image'
}
if(file.contentType.indexOf('audio') > -1){
return 'audio'
}
}
let suffix = file.suffix;
if(!suffix && file.name){
suffix = file.name.substring(file.name.lastIndexOf('.') + 1)
}
if(iconClass.has(suffix)){
return iconClass.get(suffix)
}
return 'file'
}

25
src/utils/load-script.js Normal file
View File

@ -0,0 +1,25 @@
// loadScript.js
export function loadScript(scriptUrl, type) {
return new Promise((resolve, reject) => {
// 检查是否已经加载了相同的脚本
const existingScript = Array.from(document.head.getElementsByTagName('script')).find(
script => script.src === scriptUrl
)
if (existingScript) {
resolve()
} else {
const script = document.createElement('script');
script.src = scriptUrl;
if (type) {
script.type = type
}
script.onload = () => {
resolve()
}
script.onerror = () => {
reject(new Error(`Failed to load script with URL ${scriptUrl}`));
}
document.head.appendChild(script);
}
})
}

87
src/utils/media.js Normal file
View File

@ -0,0 +1,87 @@
export function formatExif(exifInfo) {
if (!exifInfo) {
return ''
}
let exifStr = ''
if (exifInfo['dateTimeOriginal']) {
exifStr += `内容创作时间: ${exifInfo['dateTimeOriginal']}`
}
if (exifInfo['resolution']) {
exifStr += `\r\n分辨率: ${exifInfo['resolution']}`
}
if (exifInfo['make']) {
exifStr += `\r\n设备制造商: ${exifInfo['make']}`
}
if (exifInfo['model']) {
exifStr += `\r\n设备型号: ${exifInfo['model']}`
}
if (exifInfo['aperture']) {
exifStr += `\r\n光圈值: ${exifInfo['aperture']}`
}
if (exifInfo['exposureTime']) {
exifStr += `\r\n曝光时间: ${exifInfo['exposureTime']}`
}
if (exifInfo['focalLength']) {
exifStr += `\r\n焦距: ${exifInfo['focalLength']} 毫米`
}
if (exifInfo['isoEquivalent']) {
exifStr += `\r\nISO感光度: ${exifInfo['isoEquivalent']}`
}
if (exifInfo['flash']) {
exifStr += `\r\n闪光灯: ${exifInfo['flash']}`
}
if (exifInfo['fNumber']) {
exifStr += `\r\n光圈数: ${exifInfo['fNumber']}`
}
if (exifInfo['exposureProgram']) {
exifStr += `\r\n曝光程序: ${exifInfo['exposureProgram']}`
}
if (exifInfo['meteringMode']) {
exifStr += `\r\n测光模式: ${exifInfo['meteringMode']}`
}
if (exifInfo['whiteBalanceMode']) {
exifStr += `\r\n白平衡: ${exifInfo['whiteBalanceMode']}`
}
if (exifInfo['software']) {
exifStr += `内容创作者: ${exifInfo['software']}`
}
if (exifInfo['longitude']) {
exifStr += `\r\n经度: ${exifInfo['longitude']}`
}
if (exifInfo['latitude']) {
exifStr += `\r\n纬度: ${exifInfo['latitude']}`
}
return exifStr
}
export function formatVideo(videoInfo) {
if (!videoInfo) {
return ''
}
let videoStr = ''
if (videoInfo['height'] && videoInfo['width']) {
videoStr += `尺寸: ${videoInfo['width']} x ${videoInfo['height']}`
}
if (videoInfo['bitrate']) {
videoStr += `\r\n码率: ${videoInfo['bitrate']}`
}
if (videoInfo['frameRate']) {
videoStr += `\r\n帧率: ${videoInfo['frameRate']} fps`
}
if (videoInfo['format']) {
videoStr += `\r\n格式: ${videoInfo['format']}`
}
if (videoInfo['duration']) {
videoStr += `\r\n时长: ${videoInfo['duration']}`
}
if (videoInfo['toBitrate']) {
videoStr += `\r\n转码后的码率: ${videoInfo['toBitrate']} kbps`
}
if (videoInfo['toFrameRate']) {
videoStr += `\r\n转码后的帧率: ${videoInfo['toFrameRate']} fps`
}
if (videoInfo['toHeight']) {
videoStr += `\r\n转码后的高度: ${videoInfo['toHeight']}`
}
return videoStr
}

View File

@ -143,17 +143,18 @@ export function formatSize(size) {
/** /**
* formatNetSpeed * formatNetSpeed
* @param {*} size * @param {*} size
* @param space
*/ */
export function formatNetSpeed(size) { export function formatNetSpeed(size, space) {
if (size === 0) { if (size === 0) {
return '0B/s' return space ? '0 B / s' : '0B/s'
} else if (size < 1024) { } else if (size < 1024) {
return size + 'B/s' return size + (space ? ' B / s' : 'B/s')
} else if (size < 1024 * 1024) { } else if (size < 1024 * 1024) {
return (size/1024).toFixed(2) + 'KB/s' return (size/1024).toFixed(2) + (space ? ' KB / s' : 'KB/s')
} else if (size < 1024 * 1024 * 1024) { } else if (size < 1024 * 1024 * 1024) {
return (size/(1024 * 1024)).toFixed(2) + 'MB/s' return (size/(1024 * 1024)).toFixed(2) + (space ? ' MB / s' : 'MB/s')
} else { } else {
return (size/(1024 * 1024 * 1024)).toFixed(2) + 'GB/s' return (size/(1024 * 1024 * 1024)).toFixed(2) + (space ? ' GB / s' : 'GB/s')
} }
} }

View File

@ -1,5 +1,6 @@
// import { MessageBox, Message } from 'element-ui' // import { MessageBox, Message } from 'element-ui'
import store from '@/store' import store from '@/store'
import _ from "lodash";
import {getShareId, getShareToken, getToken, getUsername} from '@/utils/auth' import {getShareId, getShareToken, getToken, getUsername} from '@/utils/auth'
// create an axios instance // create an axios instance
@ -31,12 +32,24 @@ service.interceptors.request.use(
} }
) )
const responseErr = _.throttle((error) => {
let msg = '哎呀!服务器出错了,请稍后再试!'
if (error.response && error.response.status === 403) {
msg = '您的请求被服务器拒绝!'
}
Vue.prototype.$message({
message: msg,
type: 'error',
duration: 5 * 1000
})
}, 3000)
// response interceptor // response interceptor
service.interceptors.response.use( service.interceptors.response.use(
/** /**
* If you want to get http information such as headers or status * If you want to get http information such as headers or status
* Please return response => response * Please return response => response
*/ */
/** /**
* Determine the request status by custom code * Determine the request status by custom code
@ -56,7 +69,7 @@ service.interceptors.response.use(
// to re-login // to re-login
setTimeout(function () { setTimeout(function () {
const hasToken = getToken() const hasToken = getToken()
if(hasToken){ if (hasToken) {
Vue.prototype.$confirm('登录已失效,请重新登录', '确认登出', { Vue.prototype.$confirm('登录已失效,请重新登录', '确认登出', {
confirmButtonText: '重新登录', confirmButtonText: '重新登录',
cancelButtonText: '取消', cancelButtonText: '取消',
@ -67,7 +80,7 @@ service.interceptors.response.use(
}) })
}) })
} }
},200) }, 200)
} else { } else {
Vue.prototype.$message({ Vue.prototype.$message({
message: res.message || '服务器开小差了...', message: res.message || '服务器开小差了...',
@ -78,10 +91,10 @@ service.interceptors.response.use(
return Promise.reject() return Promise.reject()
} else { } else {
let message = '' let message = ''
if(res.message){ if (res.message) {
message = res.message.toString() message = res.message.toString()
} }
if(message !== 'true'){ if (message !== 'true') {
Vue.prototype.$message({ Vue.prototype.$message({
message: res.message, message: res.message,
type: 'success', type: 'success',
@ -92,15 +105,7 @@ service.interceptors.response.use(
} }
}, },
error => { error => {
let msg = '哎呀!服务器出错了,请稍后再试!' responseErr(error)
if (error.response && error.response.status === 403) {
msg = '您的请求被服务器拒绝!'
}
Vue.prototype.$message({
message: msg,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error) return Promise.reject(error)
} }
) )

View File

@ -8,6 +8,7 @@
:defaultSort="sortable" :defaultSort="sortable"
:queryCondition="queryCondition" :queryCondition="queryCondition"
:contextMenus="contextMenus" :contextMenus="contextMenus"
homeHidden
> >
</show-file> </show-file>
</div> </div>

View File

@ -8,6 +8,8 @@
:queryCondition="queryCondition" :queryCondition="queryCondition"
singleFileType="音乐" singleFileType="音乐"
emptyStatus="还没有音乐呢~" emptyStatus="还没有音乐呢~"
:showUploadButton="false"
:contextMenus="contextMenus"
></show-file> ></show-file>
</div> </div>
</template> </template>
@ -24,7 +26,36 @@ export default {
}, },
queryCondition: { queryCondition: {
isFolder:false isFolder:false
} },
contextMenus: [
{ label: '查看', operation: 'viewMode' ,child: [
{ iconClass: this.grid?'':'menu-point', label: '列表', operation: 'vmode-list'},
{ iconClass: this.grid?'menu-point':'', label: '缩略图', operation: 'vmode-grid'},
{ label: '显示文件夹大小', operation: 'show-folder-size'},
]
},
{ label: '排列方式', operation: 'arrangement' ,child: [
{ label: '名称', operation: 'orderName', orderProp: 'name'},
{ label: '大小', operation: 'orderSize', orderProp: 'size'},
{ label: '日期', operation: 'orderUpdateDate', orderProp: 'updateDate'},
]
},
{ label: '刷新', operation: 'refresh'},
{ divider: true, operation: 'divider' },
{ label: '新建', operation: 'create' , homeDisable: true, child: [
{ label: '文件夹', operation: 'createFolder', iconClass: 'folder'},
{ divider: true, operation: 'divider' },
{ label: '文本', operation: 'createTextFile', iconClass: 'file-txt'},
{ label: '思维导图', operation: 'createMinderFile', iconClass: 'file-mind'},
{ label: '流程图', operation: 'createDrawioFile', iconClass: 'file-drawio'},
{ label: 'Word', operation: 'createWordFile', iconClass: 'file-word'},
{ label: 'Excel', operation: 'createExcelFile', iconClass: 'file-excel'},
{ label: 'PPT', operation: 'createPPTFile', iconClass: 'file-ppt'},
]
},
{ divider: true, operation: 'divider' },
{ label: '写文章', operation: 'createMarkdownFile'},
]
} }
}, },
} }

View File

@ -7,6 +7,8 @@
:sortable="sortable" :sortable="sortable"
:queryCondition="queryCondition" :queryCondition="queryCondition"
emptyStatus="还没有文档呢~" emptyStatus="还没有文档呢~"
:showUploadButton="false"
:contextMenus="contextMenus"
> >
</show-file> </show-file>
</div> </div>
@ -24,7 +26,36 @@ export default {
}, },
queryCondition: { queryCondition: {
isFolder: false isFolder: false
} },
contextMenus: [
{ label: '查看', operation: 'viewMode' ,child: [
{ iconClass: this.grid?'':'menu-point', label: '列表', operation: 'vmode-list'},
{ iconClass: this.grid?'menu-point':'', label: '缩略图', operation: 'vmode-grid'},
{ label: '显示文件夹大小', operation: 'show-folder-size'},
]
},
{ label: '排列方式', operation: 'arrangement' ,child: [
{ label: '名称', operation: 'orderName', orderProp: 'name'},
{ label: '大小', operation: 'orderSize', orderProp: 'size'},
{ label: '日期', operation: 'orderUpdateDate', orderProp: 'updateDate'},
]
},
{ label: '刷新', operation: 'refresh'},
{ divider: true, operation: 'divider' },
{ label: '新建', operation: 'create' , homeDisable: true, child: [
{ label: '文件夹', operation: 'createFolder', iconClass: 'folder'},
{ divider: true, operation: 'divider' },
{ label: '文本', operation: 'createTextFile', iconClass: 'file-txt'},
{ label: '思维导图', operation: 'createMinderFile', iconClass: 'file-mind'},
{ label: '流程图', operation: 'createDrawioFile', iconClass: 'file-drawio'},
{ label: 'Word', operation: 'createWordFile', iconClass: 'file-word'},
{ label: 'Excel', operation: 'createExcelFile', iconClass: 'file-excel'},
{ label: 'PPT', operation: 'createPPTFile', iconClass: 'file-ppt'},
]
},
{ divider: true, operation: 'divider' },
{ label: '写文章', operation: 'createMarkdownFile'},
]
} }
}, },
} }

View File

@ -6,6 +6,8 @@
:sortable="sortable" :sortable="sortable"
:queryCondition="queryCondition" :queryCondition="queryCondition"
emptyStatus="还没有图片呢~" emptyStatus="还没有图片呢~"
:showUploadButton="false"
:contextMenus="contextMenus"
> >
</show-file> </show-file>
</div> </div>
@ -23,7 +25,36 @@ export default {
}, },
queryCondition: { queryCondition: {
isFolder: false isFolder: false
} },
contextMenus: [
{ label: '查看', operation: 'viewMode' ,child: [
{ iconClass: this.grid?'':'menu-point', label: '列表', operation: 'vmode-list'},
{ iconClass: this.grid?'menu-point':'', label: '缩略图', operation: 'vmode-grid'},
{ label: '显示文件夹大小', operation: 'show-folder-size'},
]
},
{ label: '排列方式', operation: 'arrangement' ,child: [
{ label: '名称', operation: 'orderName', orderProp: 'name'},
{ label: '大小', operation: 'orderSize', orderProp: 'size'},
{ label: '日期', operation: 'orderUpdateDate', orderProp: 'updateDate'},
]
},
{ label: '刷新', operation: 'refresh'},
{ divider: true, operation: 'divider' },
{ label: '新建', operation: 'create' , homeDisable: true, child: [
{ label: '文件夹', operation: 'createFolder', iconClass: 'folder'},
{ divider: true, operation: 'divider' },
{ label: '文本', operation: 'createTextFile', iconClass: 'file-txt'},
{ label: '思维导图', operation: 'createMinderFile', iconClass: 'file-mind'},
{ label: '流程图', operation: 'createDrawioFile', iconClass: 'file-drawio'},
{ label: 'Word', operation: 'createWordFile', iconClass: 'file-word'},
{ label: 'Excel', operation: 'createExcelFile', iconClass: 'file-excel'},
{ label: 'PPT', operation: 'createPPTFile', iconClass: 'file-ppt'},
]
},
{ divider: true, operation: 'divider' },
{ label: '写文章', operation: 'createMarkdownFile'},
]
} }
}, },
} }

View File

@ -7,6 +7,8 @@
:sortable="sortable" :sortable="sortable"
:queryCondition="queryCondition" :queryCondition="queryCondition"
emptyStatus="还没有视频呢~" emptyStatus="还没有视频呢~"
:showUploadButton="false"
:contextMenus="contextMenus"
> >
</show-file> </show-file>
</div> </div>
@ -24,7 +26,36 @@ export default {
}, },
queryCondition: { queryCondition: {
isFolder: false isFolder: false
} },
contextMenus: [
{ label: '查看', operation: 'viewMode' ,child: [
{ iconClass: this.grid?'':'menu-point', label: '列表', operation: 'vmode-list'},
{ iconClass: this.grid?'menu-point':'', label: '缩略图', operation: 'vmode-grid'},
{ label: '显示文件夹大小', operation: 'show-folder-size'},
]
},
{ label: '排列方式', operation: 'arrangement' ,child: [
{ label: '名称', operation: 'orderName', orderProp: 'name'},
{ label: '大小', operation: 'orderSize', orderProp: 'size'},
{ label: '日期', operation: 'orderUpdateDate', orderProp: 'updateDate'},
]
},
{ label: '刷新', operation: 'refresh'},
{ divider: true, operation: 'divider' },
{ label: '新建', operation: 'create' , homeDisable: true, child: [
{ label: '文件夹', operation: 'createFolder', iconClass: 'folder'},
{ divider: true, operation: 'divider' },
{ label: '文本', operation: 'createTextFile', iconClass: 'file-txt'},
{ label: '思维导图', operation: 'createMinderFile', iconClass: 'file-mind'},
{ label: '流程图', operation: 'createDrawioFile', iconClass: 'file-drawio'},
{ label: 'Word', operation: 'createWordFile', iconClass: 'file-word'},
{ label: 'Excel', operation: 'createExcelFile', iconClass: 'file-excel'},
{ label: 'PPT', operation: 'createPPTFile', iconClass: 'file-ppt'},
]
},
{ divider: true, operation: 'divider' },
{ label: '写文章', operation: 'createMarkdownFile'},
]
} }
}, },
} }

View File

@ -268,7 +268,7 @@
<sim-text-preview :file.sync="textPreviewRow" :status.sync="textPreviewVisible"></sim-text-preview> <sim-text-preview :file.sync="textPreviewRow" :status.sync="textPreviewVisible"></sim-text-preview>
<image-viewer :fileList="fileList" :file="imagePreviewRow" :status.sync="imagePreviewVisible"></image-viewer> <image-viewer :fileList="fileList" :file="imagePreviewRow" :status.sync="imagePreviewVisible"></image-viewer>
<video-preview :file="videoPreviewRow" :status.sync="videoPreviewVisible"></video-preview> <video-preview :file="videoPreviewRow" :status.sync="videoPreviewVisible"></video-preview>
<office-preview :file="officePreviewRow" :status.sync="officePreviewVisible"></office-preview> <iframe-preview :file="officePreviewRow" :status.sync="officePreviewVisible"></iframe-preview>
</div> </div>
</template> </template>
<script> <script>
@ -283,14 +283,12 @@ import ImageViewer from "@/components/preview/ImageViewer";
import VideoPreview from "@/components/preview/VideoPreview"; import VideoPreview from "@/components/preview/VideoPreview";
import SimTextPreview from "@/components/preview/SimTextPreview"; import SimTextPreview from "@/components/preview/SimTextPreview";
import {suffix} from "@/utils/file-type"; import {suffix} from "@/utils/file-type";
import OfficePreview from "@/components/preview/OfficePreview"; import IframePreview from "@/components/preview/IframePreview.vue";
import Clipboard from "clipboard"; import Clipboard from "clipboard";
let pinyin = require("pinyin");
export default { export default {
components: { components: {
OfficePreview, IframePreview,
SimTextPreview, SimTextPreview,
VideoPreview, VideoPreview,
ImageViewer, IconFile, ImageViewer, IconFile,
@ -409,11 +407,7 @@ export default {
// 加载布局 // 加载布局
if (this.$route.query.vmode) { if (this.$route.query.vmode) {
this.vmode = this.$route.query.vmode this.vmode = this.$route.query.vmode
if (this.vmode === 'list') { this.grid = this.vmode !== 'list';
this.grid = false
} else {
this.grid = true
}
} }
// 加载url上的path // 加载url上的path
if (this.$route.query.path !== '/') { if (this.$route.query.path !== '/') {
@ -676,7 +670,8 @@ export default {
api.delete({ api.delete({
currentDirectory: this.rowContextData.path, currentDirectory: this.rowContextData.path,
username: this.$store.state.user.name, username: this.$store.state.user.name,
fileIds: fileIds fileIds: fileIds,
sweep: true
}).then(() => { }).then(() => {
// 刷新列表 // 刷新列表
this.getFileList() this.getFileList()
@ -929,7 +924,6 @@ export default {
this.searchFile(this.searchValue, onLoad) this.searchFile(this.searchValue, onLoad)
} }
} else { } else {
this.beforeLoadData(onLoad)
api.fileList({ api.fileList({
username: this.$store.state.user.name, username: this.$store.state.user.name,
userId: this.$store.state.user.userId, userId: this.$store.state.user.userId,

View File

@ -209,9 +209,11 @@ export default {
let data = new FormData() let data = new FormData()
data.append('username',this.loginForm.username) data.append('username',this.loginForm.username)
data.append('password',this.loginForm.password) data.append('password',this.loginForm.password)
this.loading = true
initialization(data).then(()=>{ initialization(data).then(()=>{
this.initialize = false this.initialize = false
this.$message.success('创建成功') this.$message.success('创建成功')
this.loading = false
}) })
}else{ }else{
// 登录 // 登录
@ -330,7 +332,7 @@ $light_gray:#eee;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100vh; height: calc(100vh - 6px);
} }
.box-card { .box-card {

View File

@ -223,7 +223,7 @@
</van-grid> </van-grid>
</van-checkbox-group> </van-checkbox-group>
</div> </div>
<div v-if="linkFailed && !showShareCode" class="share-header"> <div v-if="linkFailed && !showShareCode" class="share-header-prompt">
<p v-if="prompt !== ''">温馨提示:</p> <p v-if="prompt !== ''">温馨提示:</p>
<p>{{ prompt }}</p> <p>{{ prompt }}</p>
</div> </div>
@ -255,7 +255,7 @@
<image-viewer :fileList="fileList" :shareId="shareId" :file="imagePreviewRow" <image-viewer :fileList="fileList" :shareId="shareId" :file="imagePreviewRow"
:status.sync="imagePreviewVisible"></image-viewer> :status.sync="imagePreviewVisible"></image-viewer>
<video-preview :file="videoPreviewRow" :shareId="shareId" :status.sync="videoPreviewVisible"></video-preview> <video-preview :file="videoPreviewRow" :shareId="shareId" :status.sync="videoPreviewVisible"></video-preview>
<office-preview :file="officePreviewRow" :shareId="shareId" :status.sync="officePreviewVisible"></office-preview> <iframe-preview :file="officePreviewRow" :shareId="shareId" :sharer="sharerUsername" :status.sync="officePreviewVisible"></iframe-preview>
<el-divider v-if="!linkFailed && !showShareCode" class="grid-divider" content-position="center"><i <el-divider v-if="!linkFailed && !showShareCode" class="grid-divider" content-position="center"><i
class="el-icon-folder-opened"></i>&nbsp;{{ summaries }} class="el-icon-folder-opened"></i>&nbsp;{{ summaries }}
</el-divider> </el-divider>
@ -290,7 +290,7 @@ import VideoPreview from "@/components/preview/VideoPreview";
import AudioPreview from "@/components/preview/AudioPreview"; import AudioPreview from "@/components/preview/AudioPreview";
import fileConfig from "@/utils/file-config"; import fileConfig from "@/utils/file-config";
import Clipboard from "clipboard"; import Clipboard from "clipboard";
import OfficePreview from "@/components/preview/OfficePreview"; import IframePreview from "@/components/preview/IframePreview.vue";
import Logo from "@/components/Logo"; import Logo from "@/components/Logo";
import store from "@/store"; import store from "@/store";
import FileTree from "@/components/FileTree"; import FileTree from "@/components/FileTree";
@ -299,7 +299,7 @@ export default {
components: { components: {
FileTree, FileTree,
Logo, Logo,
OfficePreview, IconFile, BreadcrumbFilePath, AlLoading, IframePreview, IconFile, BreadcrumbFilePath, AlLoading,
AudioPreview, VideoPreview, ImageViewer, SimTextPreview AudioPreview, VideoPreview, ImageViewer, SimTextPreview
}, },
data() { data() {
@ -401,7 +401,8 @@ export default {
shareLink: '', shareLink: '',
shareFileName: '', shareFileName: '',
generateShareLinkLoading: true, generateShareLinkLoading: true,
shareId: this.$route.query.s, shareId: '',
shortId: this.$route.params.id,
currentDirName: '', currentDirName: '',
linkFailed: true, linkFailed: true,
textPreviewVisible: false, textPreviewVisible: false,
@ -417,6 +418,7 @@ export default {
showUpdateDateItem: this.$pc,// 列表模式下是否显示修改时间 showUpdateDateItem: this.$pc,// 列表模式下是否显示修改时间
showSizeItem: this.$pc,// 列表模式下是否显示文件大小 showSizeItem: this.$pc,// 列表模式下是否显示文件大小
sharer: undefined,//分享者信息, sharer: undefined,//分享者信息,
sharerUsername: undefined,
loginTitle: '', loginTitle: '',
sharerAvatarUrl: '', sharerAvatarUrl: '',
netdiskName: 'JmalCloud', netdiskName: 'JmalCloud',
@ -584,7 +586,7 @@ export default {
if (index === 0) { if (index === 0) {
this.pathList.splice(this.pathList.findIndex((v, i) => i === index + 1), this.pathList.length - (index + 1)) this.pathList.splice(this.pathList.findIndex((v, i) => i === index + 1), this.pathList.length - (index + 1))
this.getFileList(null, true); this.getFileList(null, true);
this.$router.push(`/s?s=${this.shareId}&vmode=${this.vmode}`) this.pushRouter()
} }
if (item && item.fileId) { if (item && item.fileId) {
this.accessShareOpenDir(item.fileId) this.accessShareOpenDir(item.fileId)
@ -598,8 +600,7 @@ export default {
if (this.grid) { if (this.grid) {
this.vmode = 'grid' this.vmode = 'grid'
} }
let f = '' this.pushRouter()
this.$router.push(`/s?s=${this.shareId}&vmode=${this.vmode}`)
}, },
accessShareOpenDir(fileId) { accessShareOpenDir(fileId) {
this.tableLoading = true this.tableLoading = true
@ -607,7 +608,8 @@ export default {
share: this.shareId, share: this.shareId,
fileId: fileId, fileId: fileId,
pageIndex: this.pagination.pageIndex, pageIndex: this.pagination.pageIndex,
pageSize: this.pagination.pageSize pageSize: this.pagination.pageSize,
showFolderSize: true
}).then(res => { }).then(res => {
this.isLoading = false this.isLoading = false
this.fileList = res.data this.fileList = res.data
@ -626,9 +628,10 @@ export default {
getFileList(pagination, overload) { getFileList(pagination, overload) {
this.tableLoading = true this.tableLoading = true
api.accessShare({ api.accessShare({
share: this.shareId, share: this.shortId,
pageIndex: this.pagination.pageIndex, pageIndex: this.pagination.pageIndex,
pageSize: this.pagination.pageSize pageSize: this.pagination.pageSize,
showFolderSize: true
}).then(res => { }).then(res => {
this.isLoading = false this.isLoading = false
if (Object.getPrototypeOf(res.data) === String.prototype) { if (Object.getPrototypeOf(res.data) === String.prototype) {
@ -687,14 +690,16 @@ export default {
}) })
}, },
getSharer() { getSharer() {
api.getSharer({shareId: this.shareId}).then(res => { api.getSharer({shareId: this.shortId}).then(res => {
this.sharer = res.data this.sharer = res.data
this.setLoginTitle() this.setLoginTitle()
if (res.data) { if (res.data) {
this.shareId = res.data.shareId
this.sharerAvatarUrl = window.location.origin + this.imageUrl + res.data.avatar this.sharerAvatarUrl = window.location.origin + this.imageUrl + res.data.avatar
if (this.sharer.netdiskName) { if (this.sharer.netdiskName) {
this.netdiskName = this.sharer.netdiskName this.netdiskName = this.sharer.netdiskName
} }
this.sharerUsername = this.sharer.username
if (this.sharer.netdiskLogo) { if (this.sharer.netdiskLogo) {
this.netdiskLogo = this.sharer.netdiskLogo this.netdiskLogo = this.sharer.netdiskLogo
} }
@ -1044,6 +1049,9 @@ export default {
clipboard.destroy() clipboard.destroy()
}) })
}, },
pushRouter() {
this.$router.push(`/s/${this.shortId}/?vmode=${this.vmode}`)
},
// 点击文件或文件夹 // 点击文件或文件夹
fileClick(row) { fileClick(row) {
window.shareId = this.shareId window.shareId = this.shareId
@ -1055,7 +1063,7 @@ export default {
item['index'] = this.pathList.length item['index'] = this.pathList.length
this.pathList.push(item) this.pathList.push(item)
this.pagination.pageIndex = 1 this.pagination.pageIndex = 1
this.$router.push(`?s=${this.shareId}&vmode=${this.vmode}`) this.pushRouter()
this.accessShareOpenDir(row.id) this.accessShareOpenDir(row.id)
} else { } else {
if (row.contentType.startsWith('image')) { if (row.contentType.startsWith('image')) {
@ -1131,6 +1139,13 @@ export default {
padding: 0 15px; padding: 0 15px;
} }
.share-header-prompt {
text-align: center;
font-size: 18px;
font-weight: 500;
margin-bottom: 10px;
}
.share-header { .share-header {
text-align: center; text-align: center;
font-size: 18px; font-size: 18px;

View File

@ -6,6 +6,8 @@
:sortable="sortable" :sortable="sortable"
:queryCondition="queryCondition" :queryCondition="queryCondition"
:showNavigation="true" :showNavigation="true"
:contextMenus="contextMenus"
:showUploadButton="false"
></show-file> ></show-file>
</div> </div>
</template> </template>
@ -22,7 +24,36 @@
}, },
queryCondition: { queryCondition: {
isFolder:false isFolder:false
} },
contextMenus: [
{ label: '查看', operation: 'viewMode' ,child: [
{ iconClass: this.grid?'':'menu-point', label: '列表', operation: 'vmode-list'},
{ iconClass: this.grid?'menu-point':'', label: '缩略图', operation: 'vmode-grid'},
{ label: '显示文件夹大小', operation: 'show-folder-size'},
]
},
{ label: '排列方式', operation: 'arrangement' ,child: [
{ label: '名称', operation: 'orderName', orderProp: 'name'},
{ label: '大小', operation: 'orderSize', orderProp: 'size'},
{ label: '日期', operation: 'orderUpdateDate', orderProp: 'updateDate'},
]
},
{ label: '刷新', operation: 'refresh'},
{ divider: true, operation: 'divider' },
{ label: '新建', operation: 'create' , homeDisable: true, child: [
{ label: '文件夹', operation: 'createFolder', iconClass: 'folder'},
{ divider: true, operation: 'divider' },
{ label: '文本', operation: 'createTextFile', iconClass: 'file-txt'},
{ label: '思维导图', operation: 'createMinderFile', iconClass: 'file-mind'},
{ label: '流程图', operation: 'createDrawioFile', iconClass: 'file-drawio'},
{ label: 'Word', operation: 'createWordFile', iconClass: 'file-word'},
{ label: 'Excel', operation: 'createExcelFile', iconClass: 'file-excel'},
{ label: 'PPT', operation: 'createPPTFile', iconClass: 'file-ppt'},
]
},
{ divider: true, operation: 'divider' },
{ label: '写文章', operation: 'createMarkdownFile'},
]
} }
}, },
} }

View File

@ -300,16 +300,11 @@ export default {
api.delete({ api.delete({
currentDirectory: '', currentDirectory: '',
username: this.$store.state.user.name, username: this.$store.state.user.name,
fileIds: fileIds fileIds: fileIds,
sweep: true
}).then(() => { }).then(() => {
// 移除列表 // 移除列表
this.getArticleList() this.getArticleList()
}).then(() => {
this.$notify({
title: '删除成功',
type: 'success',
duration: 1000
})
}) })
}) })
}, },

View File

@ -37,8 +37,10 @@
</div> </div>
</div> </div>
<div> <div>
<div class="token-title-desc">您可以使用这些以生成的令牌来操作 JmalCloud API。</div> <div class="setting-title-desc">
<el-divider></el-divider> <div class="setting-title-desc-text">您可以使用这些以生成的令牌来操作 JmalCloud API。</div>
<el-divider></el-divider>
</div>
<div> <div>
<ul v-if="accessTokenList.length > 0"> <ul v-if="accessTokenList.length > 0">
<li v-for="token in accessTokenList" :key="token.id"> <li v-for="token in accessTokenList" :key="token.id">
@ -144,9 +146,6 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/styles/setting"; @import "src/styles/setting";
>>>.el-divider--horizontal {
margin: 0;
}
>>>.el-alert { >>>.el-alert {
margin-bottom: 15px; margin-bottom: 15px;
.el-alert__content { .el-alert__content {
@ -156,9 +155,6 @@ export default {
font-size: 1rem; font-size: 1rem;
} }
} }
.token-title-desc {
padding: 5px 0 20px 0;
}
.token-item { .token-item {
display: flex; display: flex;
height: 80px; height: 80px;

View File

@ -44,12 +44,11 @@
<a href="https://baike.baidu.com/item/webp%E6%A0%BC%E5%BC%8F" target="_blank">什么是webp?</a> <a href="https://baike.baidu.com/item/webp%E6%A0%BC%E5%BC%8F" target="_blank">什么是webp?</a>
</span> </span>
<div class="config-itme-label">重建索引: <div class="config-itme-label">重建索引:
<el-button class="sync-button" size="mini" :loading="syncLoading" type="primary" @click="sync()"><i <el-button class="sync-button" size="mini" :loading="syncLoading" type="primary" @click="sync()"><i class="el-icon-refresh"></i></el-button>
class="el-icon-refresh"></i> <span v-show="syncPercent < 100">正在同步文件基本信息: {{ syncPercent }}%</span>
</el-button> <span v-show="indexingPercent > 0 && indexingPercent < 100">正在为文件内容创建索引: </span><span v-show="indexingPercent > 0 && indexingPercent < 100">{{ indexingPercent }}%</span>
<span v-show="syncPercent < 100">{{ syncPercent }}%</span>
</div> </div>
<span class="instruction">一般用于初始化操作, 将文件数据同步到数据库</span> <span class="instruction">重建索引分为两步: 1.同步文件基本信息, 2.为文件内容创建索引</span>
<div class="config-itme-label">重置角色、菜单: <div class="config-itme-label">重置角色、菜单:
<el-button class="sync-button" size="mini" :loading="resetLoading" type="danger" <el-button class="sync-button" size="mini" :loading="resetLoading" type="danger"
@click="resetMenuAndRole()"> @click="resetMenuAndRole()">
@ -66,60 +65,24 @@
</div> </div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="LDAP认证" name="2" class="setting-tab-panel"> <el-tab-pane label="OnlyOffice" name="2" class="setting-tab-panel">
<div v-if="activeName === '2'"> <div v-if="activeName === '2'">
<el-form :rules="rules" ref="form" :model="ldapFormData" label-width="120px" size="small" <office-config></office-config>
style="width: 450px" autocomplete="off"> </div>
<el-form-item label="功能状态" prop="enable"> </el-tab-pane>
<el-select v-model="ldapFormData.enable"> <el-tab-pane label="视频转码" name="3" class="setting-tab-panel">
<el-option <div v-if="activeName === '3'">
v-for="item in ldapStatusOptions" <transcode-config></transcode-config>
:key="item.value" </div>
:label="item.label" </el-tab-pane>
:value="item.value"> <el-tab-pane label="预览配置" name="4" class="setting-tab-panel">
</el-option> <div v-if="activeName === '4'">
</el-select> <preview-config></preview-config>
</el-form-item> </div>
<el-form-item label="LDAP 服务器" prop="ldapServer"> </el-tab-pane>
<el-input placeholder="例如: ldap.test.com:389" v-model="ldapFormData.ldapServer"></el-input> <el-tab-pane label="LDAP认证" name="5" class="setting-tab-panel">
</el-form-item> <div v-if="activeName === '5'">
<el-form-item label="Base DN" prop="baseDN"> <ldap-config></ldap-config>
<el-input placeholder="例如: dc=test,dc=com" v-model="ldapFormData.baseDN" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="账号" prop="userDN">
<el-input placeholder="管理员账号,例如: cn=admin,dc=test,dc=com" v-model="ldapFormData.userDN" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" placeholder="管理员密码" v-model="ldapFormData.password" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" :disabled="ldapTestBtn" v-loading="testLdapConfigLoading" @click="doTestLdapConfig">测试链接</el-button>
<span v-if="testLdapConfigResult === 0" class="el-icon-check" style="color: #67C23A"></span>
<span v-if="testLdapConfigResult > 0" class="el-icon-close" style="color: #F56C6C"></span>
</el-form-item>
<el-form-item label="登录名" prop="loginName">
<el-input placeholder="LDAP服务器中对应个人用户名的字段, 例如: uid" v-model="ldapFormData.loginName"></el-input>
</el-form-item>
<el-form-item label="默认角色" prop="defaultRoleList">
<el-select ref="selectRole" v-model="ldapFormData.defaultRoleList" multiple placeholder="请选择角色">
<el-option
v-for="item in roleList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :disabled="ldapSaveBtn" v-loading="saveLdapConfigLoading" @click="saveLdapConfig">保存配置</el-button>
</el-form-item>
</el-form>
</div> </div>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@ -131,23 +94,28 @@
<script> <script>
import config from '@/../package.json' import config from '@/../package.json'
import settingApi, {getSetting} from "@/api/setting-api"; import settingApi, { getSetting } from "@/api/setting-api";
import Logo from "@/components/Logo"; import Logo from "@/components/Logo";
import getPageTitle from "@/utils/get-page-title"; import getPageTitle from "@/utils/get-page-title";
import UploadImageInput from "@/components/input/UploadImageInput.vue"; import UploadImageInput from "@/components/input/UploadImageInput.vue";
import roleApi from "@/api/role"; import PreviewConfig from '@/views/setting/cloudSetting/previewConfig.vue'
import { loadLdapConfig, testLdapConfig, updateLdapConfig} from '@/api/user'
import {mapGetters, mapState} from "vuex"; import {mapGetters, mapState} from "vuex";
import store from "@/store"; import store from "@/store";
import TaskProgress from "@/components/TaskProgress/index.vue";
import LdapConfig from "@/views/setting/cloudSetting/ldapConfig.vue";
import TranscodeConfig from "@/views/setting/cloudSetting/transcodeConfig.vue";
import OfficeConfig from "@/views/setting/cloudSetting/officeConfig.vue";
export default { export default {
components: {UploadImageInput, Logo}, components: { PreviewConfig, OfficeConfig, TranscodeConfig, LdapConfig, TaskProgress, UploadImageInput, Logo},
data() { data() {
return { return {
activeName: '1', activeName: '1',
uploadUrl: `${process.env.VUE_APP_BASE_API}/user/setting/upload_logo?jmal-token=${this.$store.state.user.token}&name=${this.$store.state.user.name}`, uploadUrl: `${process.env.VUE_APP_BASE_API}/user/setting/upload_logo?jmal-token=${this.$store.state.user.token}&name=${this.$store.state.user.name}`,
title: '网盘管理', title: '网盘管理',
syncLoading: false, syncLoading: false,
clickSync: false,
indexingPercent: 100,
syncPercent: 100, syncPercent: 100,
resetLoading: false, resetLoading: false,
webpEnabled: false, webpEnabled: false,
@ -156,44 +124,7 @@ export default {
showAckBtn: false, showAckBtn: false,
inputNetdiskNameWidth: 150, inputNetdiskNameWidth: 150,
logoFileTypeList: ['image/svg+xml', 'image/jpg', 'image/png', 'image/jpeg'], logoFileTypeList: ['image/svg+xml', 'image/jpg', 'image/png', 'image/jpeg'],
ldapFormData: {
enable: true,
ldapServer: '',
defaultRoleList: [],
baseDN: '',
userDN: '',
password: '',
loginName: ''
},
currentVersion: config.version, currentVersion: config.version,
roleList: [],
ldapStatusOptions: [{
value: true,
label: '启用'
}, {
value: false,
label: '禁用'
}],
rules: {
ldapServer: [
{required: true, message: '请填写LDAP服务器', trigger: 'submit'}
],
defaultRoleList: [
{required: true, message: '请选择默认角色', trigger: 'submit'}
],
baseDN: [
{required: true, message: '请填写 Base DN', trigger: 'submit'}
],
userDN: [
{required: true, message: '请填写 管理员账号', trigger: 'submit'}
],
loginName: [
{required: true, message: '请填写登录名', trigger: 'submit'}
]
},
testLdapConfigLoading: false,
saveLdapConfigLoading: false,
testLdapConfigResult: -1,
} }
}, },
mounted() { mounted() {
@ -203,8 +134,6 @@ export default {
if (this.$route.query.tab) { if (this.$route.query.tab) {
this.activeName = this.$route.query.tab this.activeName = this.$route.query.tab
if (this.activeName === '2') { if (this.activeName === '2') {
this.getRoleList()
this.getLdapConfig()
} }
} }
}, },
@ -213,50 +142,17 @@ export default {
...mapGetters([ ...mapGetters([
'newVersion' 'newVersion'
]), ]),
ldapTestBtn() {
return !(this.ldapFormData.ldapServer.length > 0 && this.ldapFormData.baseDN.length > 0 && (this.ldapFormData.password ? this.ldapFormData.password.length > 0 : false))
},
ldapSaveBtn() {
return !(!this.ldapTestBtn && this.ldapFormData.defaultRoleList.length > 0 && this.ldapFormData.loginName.length > 0)
}
}, },
watch: { watch: {
message(msg) { message(msg) {
if (msg.event === 'msg/synced') { if (msg.event === 'msg/synced') {
this.syncPercent = msg.data.body this.updateSyncStatus(msg.data.body)
if (this.syncPercent >= 100) {
this.syncLoading = false
}
} }
} }
}, },
methods: { methods: {
doTestLdapConfig() {
this.testLdapConfigLoading = true
testLdapConfig(this.ldapFormData).then(() => {
this.testLdapConfigLoading = false
this.testLdapConfigResult = 0
this.$message.success("连接成功")
}).catch(() => {
this.testLdapConfigResult = 1
this.testLdapConfigLoading = false
});
},
saveLdapConfig() {
this.saveLdapConfigLoading = true
updateLdapConfig(this.ldapFormData).then(() => {
this.saveLdapConfigLoading = false
this.$message.success("保存成功")
}).catch(() => {
this.saveLdapConfigLoading = false
});
},
handleClick(tab) { handleClick(tab) {
this.$router.push({query: {tab: tab.name}}) this.$router.push({query: {tab: tab.name}})
if (tab.name === '2') {
this.getRoleList()
this.getLdapConfig()
}
}, },
handleAvatarSuccess(res) { handleAvatarSuccess(res) {
if (res.code === 0) { if (res.code === 0) {
@ -295,18 +191,6 @@ export default {
} }
}) })
}, },
getRoleList() {
roleApi.roleList().then(res => {
this.roleList = res.data;
})
},
getLdapConfig() {
loadLdapConfig().then(res => {
if (res.data) {
this.ldapFormData = res.data;
}
})
},
inputNetdiskName(input) { inputNetdiskName(input) {
if (input) { if (input) {
this.showAckBtn = true this.showAckBtn = true
@ -350,21 +234,36 @@ export default {
}, },
getIsSync() { getIsSync() {
settingApi.isSync({username: this.$store.state.user.name}).then((res) => { settingApi.isSync({username: this.$store.state.user.name}).then((res) => {
this.syncLoading = true; this.updateSyncStatus(res.data)
this.syncPercent = res.data
this.syncLoading = this.syncPercent < 100;
}) })
}, },
updateSyncStatus(dataPercent) {
const {syncPercent, indexingPercent} = dataPercent
if (syncPercent) {
this.syncPercent = syncPercent
} else {
this.syncPercent = 0
}
if (indexingPercent) {
this.indexingPercent = indexingPercent
} else {
this.indexingPercent = 0
}
this.syncLoading = !((this.syncPercent === 100 && this.indexingPercent === 100) || (this.syncPercent === 0 && this.indexingPercent === 0));
},
sync() { sync() {
this.$confirm('是否开始同步? ', '提示', { this.$confirm('是否开始扫描? ', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
this.syncLoading = true this.syncLoading = true
this.syncPercent = 0 this.syncPercent = 0
settingApi.sync({username: this.$store.state.user.name}).then(() => { this.clickSync = true
this.$message.success("开始同步...") settingApi.syncCloud({username: this.$store.state.user.name}).then(() => {
this.clickSync = false
}).catch(() => {
this.clickSync = false
}) })
}) })
}, },
@ -392,7 +291,7 @@ export default {
}).catch(() => { }).catch(() => {
this.webpEnabled = !this.webpEnabled this.webpEnabled = !this.webpEnabled
}) })
} },
} }
} }
</script> </script>
@ -400,7 +299,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
@import "src/styles/setting"; @import "src/styles/setting";
> > > .el-textarea { >>> .el-textarea {
max-width: 1080px; max-width: 1080px;
} }
@ -413,7 +312,7 @@ export default {
width: 220px; width: 220px;
} }
.avatar-uploader > > > .el-upload { .avatar-uploader >>> .el-upload {
display: block; display: block;
.avatar { .avatar {
@ -432,11 +331,11 @@ export default {
} }
} }
> > > .el-card__body { >>> .el-card__body {
padding: 0; padding: 0;
} }
> > > .el-tabs__header { >>> .el-tabs__header {
margin: 0; margin: 0;
@media screen and (min-width: 768px) { @media screen and (min-width: 768px) {
.el-tabs__nav { .el-tabs__nav {
@ -448,7 +347,7 @@ export default {
} }
} }
> > > .el-form-item__content { >>> .el-form-item__content {
.el-select { .el-select {
width: 100%; width: 100%;
} }
@ -462,4 +361,11 @@ export default {
} }
} }
.progress {
margin-top: 5px;
>>> .el-card__header {
padding: 8px 20px;
}
}
</style> </style>

View File

@ -0,0 +1,165 @@
<template>
<div class="container">
<el-form :rules="rules" ref="form" :model="ldapFormData" label-width="120px" size="small"
style="width: 450px" autocomplete="off">
<el-form-item label="功能状态" prop="enable">
<el-select v-model="ldapFormData.enable">
<el-option
v-for="item in ldapStatusOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="LDAP 服务器" prop="ldapServer">
<el-input placeholder="例如: ldap.test.com:389" v-model="ldapFormData.ldapServer"></el-input>
</el-form-item>
<el-form-item label="Base DN" prop="baseDN">
<el-input placeholder="例如: dc=test,dc=com" v-model="ldapFormData.baseDN" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="账号" prop="userDN">
<el-input placeholder="管理员账号,例如: cn=admin,dc=test,dc=com" v-model="ldapFormData.userDN" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" placeholder="管理员密码" v-model="ldapFormData.password" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" :disabled="ldapTestBtn" v-loading="testLdapConfigLoading" @click="doTestLdapConfig">测试链接</el-button>
<span v-if="testLdapConfigResult === 0" class="el-icon-check" style="color: #67C23A"></span>
<span v-if="testLdapConfigResult > 0" class="el-icon-close" style="color: #F56C6C"></span>
</el-form-item>
<el-form-item label="登录名" prop="loginName">
<el-input placeholder="LDAP服务器中对应个人用户名的字段, 例如: uid" v-model="ldapFormData.loginName"></el-input>
</el-form-item>
<el-form-item label="默认角色" prop="defaultRoleList">
<el-select ref="selectRole" v-model="ldapFormData.defaultRoleList" multiple placeholder="请选择角色">
<el-option
v-for="item in roleList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :disabled="ldapSaveBtn" v-loading="saveLdapConfigLoading" @click="saveLdapConfig">保存配置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import {loadLdapConfig, testLdapConfig, updateLdapConfig} from "@/api/user";
import roleApi from "@/api/role";
export default {
name: 'LdapConfig',
props: {
data: {
type: Array,
default: []
},
},
computed: {
ldapTestBtn() {
return !(this.ldapFormData.ldapServer.length > 0 && this.ldapFormData.baseDN.length > 0 && (this.ldapFormData.password ? this.ldapFormData.password.length > 0 : false))
},
ldapSaveBtn() {
return !(!this.ldapTestBtn && this.ldapFormData.defaultRoleList.length > 0 && this.ldapFormData.loginName.length > 0)
}
},
data() {
return {
ldapFormData: {
enable: true,
ldapServer: '',
defaultRoleList: [],
baseDN: '',
userDN: '',
password: '',
loginName: ''
},
ldapStatusOptions: [{
value: true,
label: '启用'
}, {
value: false,
label: '禁用'
}],
rules: {
ldapServer: [
{required: true, message: '请填写LDAP服务器', trigger: 'submit'}
],
defaultRoleList: [
{required: true, message: '请选择默认角色', trigger: 'submit'}
],
baseDN: [
{required: true, message: '请填写 Base DN', trigger: 'submit'}
],
userDN: [
{required: true, message: '请填写 管理员账号', trigger: 'submit'}
],
loginName: [
{required: true, message: '请填写登录名', trigger: 'submit'}
]
},
testLdapConfigLoading: false,
saveLdapConfigLoading: false,
testLdapConfigResult: -1,
roleList: [],
}
},
mounted() {
this.getRoleList()
this.getLdapConfig()
},
methods: {
doTestLdapConfig() {
this.testLdapConfigLoading = true
testLdapConfig(this.ldapFormData).then(() => {
this.testLdapConfigLoading = false
this.testLdapConfigResult = 0
this.$message.success("连接成功")
}).catch(() => {
this.testLdapConfigResult = 1
this.testLdapConfigLoading = false
});
},
saveLdapConfig() {
this.saveLdapConfigLoading = true
updateLdapConfig(this.ldapFormData).then(() => {
this.saveLdapConfigLoading = false
this.$message.success("保存成功")
}).catch(() => {
this.saveLdapConfigLoading = false
});
},
getRoleList() {
roleApi.roleList().then(res => {
this.roleList = res.data;
})
},
getLdapConfig() {
loadLdapConfig().then(res => {
if (res.data) {
this.ldapFormData = res.data;
}
})
},
}
}
</script>
<style lang="scss" scoped>
.container {
padding: 0 !important;
}
</style>

View File

@ -0,0 +1,81 @@
<template>
<div class="container">
<el-form ref="officeConfigForm" :model="configFormData" label-width="150px" size="small"
style="width: 520px" autocomplete="off">
<el-form-item label="OnlyOffice地址" prop="documentServer">
<el-input placeholder="OnlyOffice Server 地址, 一般不用填" v-model.number="configFormData.documentServer"></el-input>
<span class="form-instruction">使用外部OnlyOffice服务时需要填写</span>
</el-form-item>
<el-form-item label="密钥" prop="secret">
<el-input type="password" placeholder="OnlyOffice JWT (留空为不使用密钥)" v-model="configFormData.secret"></el-input>
<span class="form-instruction">建议使用密钥,防止office服务被白嫖。<a href="https://helpcenter.onlyoffice.com/installation/docs-configure-jwt.aspx" target="_blank">查看OnlyOffice文档</a></span>
</el-form-item>
<el-form-item label="回调服务地址" prop="callbackServer">
<el-input placeholder="JmalCloud Server 地址, 一般不用填" v-model.number="configFormData.callbackServer"></el-input>
<span class="form-instruction">使用外部OnlyOffice服务时需要填写</span>
</el-form-item>
<el-form-item>
<el-button type="primary" v-loading="saveConfigLoading" @click="saveConfig">保存配置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import settingApi from "@/api/setting-api";
export default {
name: 'OfficeConfig',
props: {
data: {
type: Array,
default: []
},
},
computed: {
},
data() {
return {
saveConfigLoading: false,
configFormData: {
documentServer: undefined,
secret: undefined,
tokenEnabled: false,
callbackServer: undefined
},
}
},
mounted() {
this.getOfficeConfig()
},
methods: {
getOfficeConfig() {
settingApi.getOfficeConfig().then(res => {
this.configFormData = res.data
})
},
saveConfig() {
this.saveConfigLoading = true
if (this.configFormData.documentServer === '') {
this.configFormData.documentServer = undefined
}
if (this.configFormData.callbackServer === '') {
this.configFormData.callbackServer = undefined
}
settingApi.updateOfficeConfig(this.configFormData).then(() => {
this.saveConfigLoading = false
this.$message.success('保存成功')
}).catch(() => {
this.saveConfigLoading = false
})
}
}
}
</script>
<style lang="scss" scoped>
.container {
padding: 0 !important;
}
</style>

View File

@ -0,0 +1,107 @@
<template>
<div class="container">
<el-form :rules="rules" ref="previewConfigForm" :model="configFormData" label-width="120px" size="small"
style="width: 860px" autocomplete="off">
<el-form-item label="iframe 预览" prop="iframe">
<span class="form-instruction"><a href="https://alist.nn.ci/zh/config/preview.html#iframe-%E9%A2%84%E8%A7%88" target="_blank"><svg-icon icon-class="wailian"/>参考Alist的iframe预览 </a> </span>
<el-input
type="textarea"
:autosize="{ minRows: 2}"
:placeholder="defaultIframeConfig"
v-model="configFormData.iframe">
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" v-loading="saveConfigLoading" @click="submitForm">保存配置</el-button>
<el-button type="warning" @click="resetConfig">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import settingApi from '@/api/setting-api'
export default {
name: 'PreviewConfig',
props: {
data: {
type: Array,
default: [],
},
},
computed: {},
data() {
const validateIframeFormat = async (rule, value, callback) => {
if (!value) {
callback()
} else {
try {
this.configFormData.iframe = JSON.stringify(JSON.parse(value), null, 4)
} catch (e) {
callback(new Error('json格式错误'))
}
callback()
}
}
return {
saveConfigLoading: false,
defaultIframeConfig: '{\n' +
' "pdf": {\n' +
' "PDF.js": "/pdf.js/web/viewer.html?file=$e_url"\n' +
' },\n' +
' "epub": {\n' +
' "epub.js": "/resource/epubjs/viewer.html?url=$e_url"\n' +
' }\n' +
'}',
configFormData: {
iframe: '',
},
rules: {
iframe: [
{ required: false, validator: validateIframeFormat, trigger: 'submit'}
],
}
}
},
mounted() {
this.getPreviewConfig()
},
methods: {
getPreviewConfig() {
settingApi.getPreviewConfig().then(res => {
this.configFormData = res.data
})
},
submitForm() {
this.$refs.previewConfigForm.validate((valid) => {
if (valid) {
this.saveConfig()
} else {
return false
}
});
},
resetConfig() {
this.configFormData.iframe = this.defaultIframeConfig
},
saveConfig() {
this.saveConfigLoading = true
settingApi.updatePreviewConfig(this.configFormData).then(() => {
this.saveConfigLoading = false
this.$message.success('保存成功')
this.$store.dispatch('user/getInfo')
}).catch(() => {
this.saveConfigLoading = false
})
},
},
}
</script>
<style lang="scss" scoped>
.container {
padding: 0 !important;
}
</style>

View File

@ -0,0 +1,240 @@
<template>
<div class="container">
<el-form :rules="rules" ref="transcodeConfigForm" :model="configFormData" label-width="120px" size="small"
style="width: 520px" autocomplete="off">
<el-form-item label="功能状态" prop="enable">
<el-switch
v-model="configFormData.enable"
active-text="启用"
active-color="#13ce66"
inactive-color="#ff4949"
inactive-text="禁用">
</el-switch>
</el-form-item>
<el-form-item label="最大线程数" prop="maxThreads">
<div class="form-item-flex">
<el-input-number v-model="configFormData.maxThreads" :min="1" :max="8" label="最大线程数"></el-input-number>
<div>
<el-tooltip class="item" effect="dark" content="最多同时的转码的任务数, 默认为1(可根据具体硬件配置酌情增加)" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</div>
</div>
</el-form-item>
<el-divider content-position="left">转码条件, 以下三个条件满足其一即可</el-divider>
<el-form-item label="码率(kbps)" prop="bitrateCond">
<div class="form-item-flex">
<el-input placeholder="2500" v-model.number="configFormData.bitrateCond"></el-input>
<div>
<el-tooltip class="item" effect="dark" content="转码条件: 视频码率(kbps), 默认 2500 kbps, 小于该值则不转码" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</div>
</div>
</el-form-item>
<el-form-item label="高度" prop="height">
<div class="form-item-flex">
<el-input placeholder="720" v-model.number="configFormData.heightCond"></el-input>
<div>
<el-tooltip class="item" effect="dark" content="转码条件: 视频高度(视频宽度, 默认随高度等比例缩放), 默认 720, 小于该值则不转码" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</div>
</div>
</el-form-item>
<el-form-item label="帧率" prop="height">
<div class="form-item-flex">
<el-input placeholder="30" v-model.number="configFormData.frameRateCond"></el-input>
<div>
<el-tooltip class="item" effect="dark" content="转码条件: 视频帧率, 默认 30 fps, 小于该值则不转码" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</div>
</div>
</el-form-item>
<el-divider content-position="left">转码参数</el-divider>
<el-form-item label="目标码率(kbps)" prop="bitrate">
<div class="form-item-flex">
<el-input placeholder="2500" v-model.number="configFormData.bitrate"></el-input>
<div>
<el-tooltip class="item" effect="dark" content="转码后的视频码率(kbps),默认 2500 kbps" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</div>
</div>
</el-form-item>
<el-form-item label="目标高度" prop="height">
<div class="form-item-flex">
<el-input placeholder="720" v-model.number="configFormData.height"></el-input>
<div>
<el-tooltip class="item" effect="dark" content="转码后的视频高度, 默认720(视频宽度则默认随高度等比例缩放)" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</div>
</div>
</el-form-item>
<el-form-item label="目标帧率" prop="height">
<div class="form-item-flex">
<el-input placeholder="30" v-model.number="configFormData.frameRate"></el-input>
<div>
<el-tooltip class="item" effect="dark" content="转码后的视频帧率, 默认 30 fps" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</div>
</div>
</el-form-item>
<el-form-item label="vtt缩略图数量" prop="vttThumbnailCount">
<div class="form-item-flex">
<el-input placeholder="60" v-model.number="configFormData.vttThumbnailCount"></el-input>
<div>
<el-tooltip class="item" effect="dark" content="vtt缩略图数量, 默认 60 张" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</div>
</div>
</el-form-item>
<el-form-item label="是否重新转码" prop="isReTranscode">
<div class="form-item-flex">
<el-switch v-model="configFormData.isReTranscode"></el-switch>
<div>
<el-tooltip class="item" effect="dark" content="转码参数变化后对已经转码过的视频重新转码, 默认开启" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</div>
</div>
</el-form-item>
<el-form-item>
<div class="transcode-status">
<el-button v-if="transcodeStatus.waitingTranscodingCount > 0 || transcodeStatus.transcodingCount > 0" type="warning" v-loading="cancelTaskLoading" @click="cancelTask">取消任务</el-button>
<el-button v-else type="primary" v-loading="saveConfigLoading" @click="saveConfig">保存配置</el-button>
<div>
<span v-if="transcodeStatus.waitingTranscodingCount > 0"><b>{{transcodeStatus.waitingTranscodingCount}}</b>个视频等待转码 </span>
<span v-if="transcodeStatus.transcodingCount > 0"> <b>{{transcodeStatus.transcodingCount}}</b>个视频正在转码中</span>
</div>
</div>
</el-form-item>
</el-form>
</div>
</template>
<script>
import settingApi, { getTranscodeStatus } from '@/api/setting-api'
import { mapState } from 'vuex'
export default {
name: 'TranscodeConfig',
props: {
data: {
type: Array,
default: []
},
},
computed: {
...mapState(['message'])
},
watch: {
message(msg) {
if (msg.event === 'msg/transcodeStatus') {
this.transcodeStatus = msg.data.body
}
}
},
data() {
return {
saveConfigLoading: false,
cancelTaskLoading: false,
configFormData: {
enable: true,
bitrate: undefined,
height: undefined,
frameRate: undefined,
bitrateCond: undefined,
maxThreads: undefined,
heightCond: undefined,
frameRateCond: undefined,
vttThumbnailCount: undefined,
isReTranscode: undefined
},
transcodeStatus: {
waitingTranscodingCount: 0,
transcodingCount: 0
},
configEnableOptions: [{
value: true,
label: '启用'
}, {
value: false,
label: '禁用'
}],
rules: {
bitrate: [
{type: 'number', message: '码率必须为数字值', trigger: 'blur'}
],
height: [
{type: 'number', message: '高度必须为数字值', trigger: 'blur'}
]
}
}
},
mounted() {
this.getTranscodeConfig()
getTranscodeStatus().then(res => {
this.transcodeStatus = res.data
})
},
methods: {
getTranscodeConfig() {
settingApi.getTranscodeConfig().then(res => {
this.configFormData = res.data
})
},
saveConfig() {
this.saveConfigLoading = true
settingApi.setTranscodeConfig(this.configFormData).then((res) => {
if (res.data > 0) {
this.transcodeStatus.waitingTranscodingCount = res.data
}
this.saveConfigLoading = false
this.$message.success('保存成功')
}).catch(() => {
this.saveConfigLoading = false
})
},
cancelTask() {
this.$confirm('确定要取消转码任务吗?可能导致转码后的视频不完整', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.doCancelTask()
})
},
doCancelTask() {
this.cancelTaskLoading = true
settingApi.cancelTranscodeTask().then(() => {
this.$message.success('取消成功')
this.cancelTaskLoading = false
}).catch(() => {
this.cancelTaskLoading = false
})
}
}
}
</script>
<style lang="scss" scoped>
@import "src/styles/setting";
.transcode-status {
display: flex;
justify-content: space-between;
}
.container {
padding: 0 !important;
}
</style>

View File

@ -123,7 +123,7 @@ export default {
isLoginType: this.type === 'LOGIN', isLoginType: this.type === 'LOGIN',
// 查询条件 // 查询条件
queryCondition: { queryCondition: {
excludeUsername: 'other', excludeUsername: undefined,
username: undefined, username: undefined,
ip: undefined, ip: undefined,
type: this.type, type: this.type,
@ -191,12 +191,7 @@ export default {
} }
this.queryLogList() this.queryLogList()
}, },
queryLogList() { queryLogList(val) {
if (this.queryCondition.username) {
this.queryCondition.excludeUsername = null;
} else {
this.queryCondition.excludeUsername = 'other';
}
this.$refs.tableList.initPageIndex() this.$refs.tableList.initPageIndex()
this.getLogList() this.getLogList()
}, },

View File

@ -0,0 +1,82 @@
<template>
<div class="app-container">
<el-card class="box-card">
<div slot="header">
<div class="box-card-header">
<div class="clearfix card-header-back">
<span>任务进度</span>
</div>
</div>
</div>
<div>
<div class="setting-title-desc">
<div class="title-status">
<div class="setting-title-desc-text">OCR识别任务和视频转码任务</div>
<div>
<span v-if="transcodeStatus.waitingTranscodingCount > 0"><b>{{transcodeStatus.waitingTranscodingCount}}</b>个视频等待转码 </span>
<span v-if="transcodeStatus.transcodingCount > 0"> <b>{{transcodeStatus.transcodingCount}}</b>个视频正在转码中</span>
</div>
</div>
<el-divider></el-divider>
</div>
<task-progress :data="taskProgressDataList"></task-progress>
</div>
</el-card>
</div>
</template>
<script>
import TaskProgress from "@/components/TaskProgress/index.vue";
import { getTaskProgress, getTranscodeStatus } from '@/api/setting-api'
import { mapState } from 'vuex'
export default {
components: { TaskProgress },
data() {
return {
taskProgressDataList: [],
transcodeStatus: {
waitingTranscodingCount: 0,
transcodingCount: 0
}
}
},
mounted() {
getTaskProgress().then(res => {
this.taskProgressDataList = res.data
})
getTranscodeStatus().then(res => {
this.transcodeStatus = res.data
})
},
computed: {
...mapState(['message'])
},
watch: {
message(msg) {
if (msg.event === 'msg/transcodeStatus') {
this.transcodeStatus = msg.data.body
}
}
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "src/styles/setting";
.title-status {
display: flex;
align-items: baseline;
justify-content: space-between;
}
.progress {
margin-top: 5px;
>>> .el-card__header {
padding: 8px 20px;
}
}
</style>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="container"> <div class="container">
<share-dialog :file.sync="shareDialogObject" :status.sync="shareDialogVisible" @onUpdateExpireData="onUpdateExpireData"></share-dialog> <share-dialog :file.sync="shareDialogObject" :status.sync="shareDialogVisible" @onUpdateExpireData="onUpdateExpireData" @onUpdateShareForm="onUpdateShareForm" @onCancelShare="onCancelShare"></share-dialog>
<!--list布局--> <!--list布局-->
<div v-show="fileList.length > 0" :style="{'width':'100%','height': clientHeight+'px'}"> <div v-show="fileList.length > 0" :style="{'width':'100%','height': clientHeight+'px'}">
<pl-table <pl-table
@ -138,7 +138,7 @@
header-align="left" header-align="left"
> >
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{expireInfo(scope.row)}}</span> <span :style="{color: expireColor(scope.row)}">{{expireInfo(scope.row)}}</span>
</template> </template>
</pl-table-column> </pl-table-column>
</template> </template>
@ -271,6 +271,19 @@
return '永久有效' return '永久有效'
} }
}, },
expireColor(row) {
if (row.expireDate) {
let currentTime = new Date();
let targetTime = new Date(row.expireDate);
if (currentTime > targetTime) {
return '#F56C6C'
} else {
return '#606266'
}
} else {
return '#67C23A'
}
},
// 请求之前的准备 // 请求之前的准备
beforeLoadData(onLoad){ beforeLoadData(onLoad){
if (onLoad) { if (onLoad) {
@ -461,6 +474,9 @@
}) })
}) })
}, },
onCancelShare() {
this.getFileList()
},
// 点击文件或文件夹 // 点击文件或文件夹
fileClick(row) { fileClick(row) {
this.shareDialogObject = row this.shareDialogObject = row
@ -474,6 +490,12 @@
share.expireDate = expireDate share.expireDate = expireDate
this.fileList.splice(index, 1, share) this.fileList.splice(index, 1, share)
}, },
onUpdateShareForm(shareId, isPrivacy) {
let index = this.fileList.findIndex(item => item.id === shareId)
let share = this.fileList[index]
share.isPrivacy = isPrivacy
this.fileList.splice(index, 1, share)
}
} }
} }
</script> </script>

View File

@ -7,6 +7,7 @@
:defaultSort="sortable" :defaultSort="sortable"
:queryCondition="queryCondition" :queryCondition="queryCondition"
:contextMenus="contextMenus" :contextMenus="contextMenus"
homeHidden
> >
</show-file> </show-file>
</div> </div>

72
src/views/trash/index.vue Normal file
View File

@ -0,0 +1,72 @@
<template>
<div>
<show-file
queryFileType="trash"
:defaultGrid="true"
:orderCustom="true"
:sortable="sortable"
:queryCondition="queryCondition"
:showNavigation="true"
:show-search-button="false"
:context-menus="contextMenus"
:single-menus="singleMenus"
:multiple-right-menus="multipleRightMenus"
:show-upload-button="false"
></show-file>
</div>
</template>
<script>
import ShowFile from "@/components/ShowFile/ShowFile";
export default {
components: {ShowFile},
data() {
return {
sortable: {
prop: 'updateDate', order: 'descending'
},
queryCondition: {
isFolder: false,
isTrash: true
},
contextMenus: [
{
label: '查看', operation: 'viewMode', child: [
{ iconClass: this.grid ? '' : 'menu-point', label: '列表', operation: 'vmode-list' },
{ iconClass: this.grid ? 'menu-point' : '', label: '缩略图', operation: 'vmode-grid' }
]
},
{
label: '排列方式', operation: 'arrangement', child: [
{ label: '名称', operation: 'orderName', orderProp: 'name' },
{ label: '大小', operation: 'orderSize', orderProp: 'size' },
{ label: '日期', operation: 'orderUpdateDate', orderProp: 'updateDate' },
]
},
{ label: '刷新', operation: 'refresh' },
{ divider: true, operation: 'divider' },
{ label: '清空回收站', operation: 'clearTrash' },
],
singleMenus: [
{iconClass: 'huanyuan', label: '放回原处', operation: 'restore'},
{iconClass: 'menu-remove', label: '彻底删除', operation: 'sweep'}
],
multipleRightMenus: [
{
iconClass: 'menu-deselect',
label: '取消选定',
operation: 'deselect',
},
{iconClass: 'huanyuan', label: '放回原处', operation: 'restore'},
{ iconClass: 'menu-remove', label: '彻底删除', operation: 'sweep' },
],
}
},
}
</script>
<style lang="scss" scoped>
</style>