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'
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
```
#### 清理旧镜像
```shell
docker images --filter=reference='jmal/jmalcloud*' --filter "dangling=true" -q | xargs -r docker rmi
```
## Changes in this release:
$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:
workflow_dispatch:
@ -12,10 +12,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Node.js 16.20.2
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: 16.20.2
@ -31,23 +31,30 @@ jobs:
echo "PROJECT_VERSION=$VERSION" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
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
uses: docker/build-push-action@v2
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
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
build-args: |
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:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: release-drafter/release-drafter@v5
with:
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:
push:
@ -11,10 +11,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Node.js 16.20.2
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: 16.20.2
@ -30,23 +30,30 @@ jobs:
echo "PROJECT_VERSION=$VERSION" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
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
uses: docker/build-push-action@v2
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
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
build-args: |
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.template
COPY dist /var/www/public
COPY config.js.template /var/www/public/config.js.template
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh

View File

@ -21,7 +21,7 @@ JmalCloud 是一款私有云存储网盘项目,能够简单安全管理您的
### 部署
- 必须安装:`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
docker compose up -d
@ -44,7 +44,7 @@ docker restart jmalcloud_server
```
#### 恢复数据库
```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

View File

@ -16,13 +16,13 @@ JmalCloud It's a private cloud storage project that makes it simple and secure t
✅ supports WebDAV
### Online Demo:
[View Demo](https://jmal.cc/demo) , username:demo, password:demo1234
[Live Demo](https://jmal.cc/demo) , username:demo, password:demo1234
### Deploy
- 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
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
set -e
# 使用envsubst命令替换nginx.conf.template文件中的环境变量
envsubst '$API_BASE_URL $API_DRAWIO_URL $API_OFFICE_URL' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
# 启动Nginx
envsubst '${API_URL} ${API_OFFICE_URL}' < /var/www/public/config.js.template > /var/www/public/config.js
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;
}
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/ {
proxy_pass http://jmalcloud:8088/;
proxy_http_version 1.1;
@ -179,11 +209,29 @@ http {
}
location /drawio/webapp/ {
proxy_pass http://drawio-webapp:8080/;
proxy_pass http://localhost:8080/;
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/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-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;

View File

@ -1,6 +1,6 @@
{
"name": "jmal-cloud-view",
"version": "2.8.3",
"version": "2.9.2",
"description": "jmalcloud",
"author": "Jmal <zhushilun084@163.com>",
"license": "MIT",
@ -16,7 +16,6 @@
},
"dependencies": {
"ant-design-vue": "^1.6.5",
"artplayer": "^5.1.1",
"clipboard": "^2.0.6",
"cropperjs": "^1.5.6",
"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-->
<script src="<%= htmlWebpackPlugin.options.path %>/resource/axios@0.21.1.js"></script>
<!--vant.js-->
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.path %>/resource/vant@2.12.css"/>
<script src="<%= htmlWebpackPlugin.options.path %>/resource/vant@2.12.js"></script>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.path %>/resource/vant@2.12.54.css"/>
<script src="<%= htmlWebpackPlugin.options.path %>/resource/vant@2.12.54.js"></script>
<!--element-ui.js-->
<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>
@ -35,7 +35,9 @@
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= webpackConfig.name %></title>
<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>
<body>
<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>
<script>
const pcList = ['/','/s']
const mobileList = ['/_m','/_m/','/s_m','/s_m/']
const pcList = ['/', '/s']
const mobileList = ['/_m', '/_m/']
export default {
name: 'App',
components: {},
watch: {
$route: function(to) {
const path = to.path
if(pcList.includes(path) && !this.$pc){
this.$router.push({path: path+'_m', query: to.query})
if (pcList.includes(path) && !this.$pc) {
this.$router.push({ path: path + '_m', query: to.query })
}
if(mobileList.includes(path) && this.$pc){
this.$router.push({path: path.split('_m')[0], query: to.query})
if (mobileList.includes(path) && this.$pc) {
this.$router.push({ path: path.split('_m')[0], query: to.query })
}
}
}
},
},
}
</script>

View File

@ -16,11 +16,11 @@ export default {
})
},
// 检查文件是否存在
checkExist: function (params) {
checkExist: function (data) {
return request({
url: "checkExist",
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) {
return request({
@ -189,6 +218,14 @@ export default {
data: data
})
},
// 生成shareToken
generateShareToken: function(params) {
return request({
url: '/share/generate/share-token',
method: 'get',
params
})
},
// 取消分享
cancelShareLink: function(params) {
return request({
@ -241,9 +278,9 @@ export default {
})
},
// 获取分享信息
getShare: function(params) {
getShareByFileId: function(params) {
return request({
url: '/get/share',
url: '/get/share/by/fileId',
method: 'get',
params
})
@ -338,5 +375,22 @@ export default {
method: 'get',
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'
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({
url: '/user/setting/sync',
method: 'get',
params
})
},
syncCloud(params) {
return request({
url: '/cloud/setting/sync',
method: 'get',
params
})
},
isSync(params) {
return request({
url: '/user/setting/isSync',
@ -62,6 +104,29 @@ export default {
url: '/user/setting/resetMenuAndRole',
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
})
}
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) {
return request({
url: 'user/userList',

View File

@ -35,7 +35,7 @@
</div>
</template>
<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/modules/jquery.fancytree.edit';
import fancytree from 'jquery.fancytree';
@ -450,34 +450,7 @@
})
},
icon(event,data) {
return { html: this.loadSvg(this.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'
return { html: this.loadSvg(findIconClass(data.node.data)) }
},
loadSvg(svgName){
if(svgName){

View File

@ -5,18 +5,17 @@
import { Icon } from 'ant-design-vue'
const IconFont = Icon.createFromIconfontCN({
scriptUrl: document.location.protocol + '/resource/font_2024484_ziwmm4cu4r.js'
scriptUrl: document.location.protocol + '/resource/font_2024484_jfax4gppvg.js'
})
// 把此段代码贴到iconfont项目页面的控制台中, 以获取图标列表
// 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-',''))
// console.log(iconNames.slice(0,80))
// console.log(iconNames.slice(80,160))
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"]
}

View File

@ -1,5 +1,5 @@
<template>
<span>
<div :style="{position: grid ? 'relative': 'unset'}">
<div v-if="item.isFavorite && !public">
<div v-if="pc">
<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">
<svg-icon icon-class="play1"/>
</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)">
<div slot="error" class="image-slot">
<div slot="error" class="image-slot-error">
<svg-icon icon-class="video"/>
</div>
</el-image>
@ -75,9 +75,9 @@
<div v-else-if="item.contentType && item.contentType.indexOf('audio') > -1">
<div v-if="item.music !== undefined">
<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)">
<div slot="error" class="image-slot">
<div slot="error" class="image-slot-error">
<svg-icon icon-class="audio"/>
</div>
</el-image>
@ -94,7 +94,7 @@
<div v-else-if="item.contentType && item.contentType.startsWith('image')">
<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)">
<div slot="error" class="image-slot">
<div slot="error" class="image-slot-error">
<svg-icon icon-class="image"/>
</div>
</el-image>
@ -102,12 +102,8 @@
<el-avatar v-if="!grid" shape="square"
:src="item.fileId ? (imageUrl+item.fileId) : (imageUrl+item.id)"></el-avatar>
</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"/>
</span>
</div>
</template>
<script>
import {iconClass} from '@/utils/file-type'
@ -191,6 +187,9 @@ export default {
line-height: 35px;
}
>>> .image-slot-error {
}
.icon-favorite {
position: absolute;
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="1" v-if="!shareOptionConfig.shared">&nbsp</el-col>
<el-col v-if="shareOption.expiresDateOption === 2" :span="12" class="share-expires-data">
<el-form-item prop="expiresDate">
<el-date-picker
ref="expiresDatePicker"
size="small"
:clearable="false"
v-model="shareOption.expiresDate"
type="date"
:placeholder="!shareOption.expiresDate ? '永久有效' : '选择到期时间'"
align="right"
format="yyyy-MM-dd HH:mm"
value-format="timestamp"
@change="pickerChange"
:picker-options="pickerOptions">
</el-date-picker>
</el-form-item>
<el-date-picker
ref="expiresDatePicker"
size="small"
:clearable="false"
v-model="shareOption.expiresDate"
type="date"
:placeholder="!shareOption.expiresDate ? '永久有效' : '选择到期时间'"
align="right"
format="yyyy-MM-dd HH:mm"
value-format="timestamp"
@change="pickerChange"
:picker-options="pickerOptions">
</el-date-picker>
</el-col>
</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-col :span="20">
<el-select size="small" v-model="shareOption.isPrivacy" style="width: 100%">
@ -77,7 +86,7 @@
<el-collapse-item title="操作权限" name="1">
<el-checkbox-group v-model="shareOption.operationPermissionList" @change="permissionActionChange">
<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-group>
</el-collapse-item>
@ -89,6 +98,7 @@
</div>
</div>
<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"
v-loading="generateShareLinkLoading">创建分享
</el-button>
@ -202,7 +212,8 @@ export default {
shared: false,
linkLabel: "选择有效期",
},
filename: '',// 文件名
filename: '',
shareId: ''
}
},
computed: {
@ -221,23 +232,17 @@ export default {
}).then(res => {
this.filename = res.data.path + this.file.name
})
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
this.showSharedPage(this.file)
} else {
this.shareOptionConfig.shared = false
this.shareOptionConfig.linkLabel = '选择有效期'
this.shareOption.isPrivacy = false
this.shareOption.expiresDate = null
this.shareOption.expiresDateOption = 1
if (this.file.shareBase) {
api.getShareByFileId({fileId: this.file.fileId}).then(res => {
const shareObject = res.data
shareObject.shareId = res.data.id
this.showSharedPage(shareObject)
})
} else {
this.showNotSharedPage()
}
}
}
}
@ -256,6 +261,26 @@ export default {
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() {
this.shareOptionConfig.shared = false
this.$emit('update:status', this.shareDialogVisible)
@ -272,9 +297,34 @@ export default {
this.shareOption.expiresDate = null
}
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() {
this.$refs.form.validate((valid) => {
if (valid) {
@ -289,8 +339,11 @@ export default {
timeFormat(time) {
return moment(time).format("yyyy-MM-DD HH:mm")
},
getShareLink(shareId) {
return window.location.origin + '/s?s=' + shareId
getShareLink(shareId, shortId) {
if (!shortId) {
shortId = shareId
}
return `${window.location.origin}/s/${shortId}`
},
createShare(update) {
this.generateShareLinkLoading = true
@ -303,6 +356,9 @@ export default {
break
case 2:
if (this.shareOption.expiresDate) {
if (typeof this.shareOption.expiresDate === 'string') {
this.shareOption.expiresDate = Number(this.shareOption.expiresDate)
}
expireDate = this.timeFormat(this.shareOption.expiresDate)
}
}
@ -315,8 +371,9 @@ export default {
operationPermissionList: this.shareOption.operationPermissionList
}).then(res => {
if (res.data) {
let {shareId, extractionCode, operationPermissionList} = res.data
this.setShareLink(this.getShareLink(shareId))
let {shareId, shortId, extractionCode, operationPermissionList} = res.data
this.setShareLink(this.getShareLink(shareId, shortId))
this.shareId = shareId
this.generateShareLinkLoading = false
this.shareOptionConfig.shared = true
this.shareOption.expiresDateOption = 2
@ -324,23 +381,38 @@ export default {
this.extractionCode = extractionCode
this.shareOption.operationPermissionList = operationPermissionList || []
this.$emit('onSuccess', shareId)
if (update) {
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>'
});
if (update === 'pickerChange') {
this.afterPickerChange(shareId, expireDate)
}
if (update === 'shareFormChange') {
this.afterShareFormChange(shareId)
}
}
}).catch(() => {
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) {
let clipboard = new Clipboard('.tag-share-link')
@ -370,7 +442,7 @@ export default {
<style lang="scss" scoped>
> > > .el-dialog {
>>> .el-dialog {
max-width: 460px;
.el-dialog__title {
@ -395,7 +467,7 @@ export default {
display: none;
}
.share-icon-font > > > .svg-icon {
.share-icon-font >>> .svg-icon {
font-size: 8rem;
}
}
@ -403,26 +475,27 @@ export default {
.share-filename {
margin: 25px 0 50px 0;
text-align: center;
word-break: break-word;
}
.share-option {
> > > .el-form-item--mini.el-form-item {
>>> .el-form-item--mini.el-form-item {
margin-bottom: 10px;
}
.share-expires-data {
> > > .el-input__inner {
>>> .el-input__inner {
cursor: pointer;
padding-right: 0;
}
> > > .el-date-editor.el-input {
>>> .el-date-editor.el-input {
width: 100%;
}
}
> > > .el-form-item__label {
>>> .el-form-item__label {
text-align: left;
line-height: 32px;
}
@ -434,21 +507,20 @@ export default {
}
}
> > > .dialog-footer {
>>> .dialog-footer {
.el-loading-spinner .circular {
width: 25px !important;
}
}
.share-link {
margin-top: -10px;
margin-bottom: 25px;
.share-link-code {
width: 130px;
margin-top: 10px;
> > > .el-input-group__prepend {
>>> .el-input-group__prepend {
padding: 0 10px;
font-weight: 500;
}
@ -462,7 +534,7 @@ export default {
font-size: 1.5rem;
}
> > > .el-input__inner {
>>> .el-input__inner {
background-color: #84858d14;
text-align: center;
padding: 0 10px;

View File

@ -17,7 +17,7 @@
>
<uploader-unsupport></uploader-unsupport>
<uploader-drop v-if="dragover && enableDragUplaod" class="uploader-drop">
<uploader-drop v-if="dragover && enableDragUpload" class="uploader-drop">
<span>上传文件到当前目录下</span>
</uploader-drop>
@ -112,7 +112,6 @@
<script>
// import { ACCEPT_CONFIG } from '@/assets/js/config'
import store from '@/store'
import $ from 'jquery'
import SparkMD5 from 'spark-md5'
@ -129,11 +128,11 @@ export default {
username: this.$store.state.user.name,
options: {
target: api.simpleUploadURL,
chunkSize: 5 * 1024 * 1024,
chunkSize: localStorage.getItem('uploader_chunk_size') || 1024 * 1024,
// speedSmoothingFactor: 0.1,
// progressCallbacksInterval: 500,
maxChunkRetries: 3, // 最大重试次数
simultaneousUploads: 5, // 并发上传数
simultaneousUploads: 3, // 并发上传数
testChunks: true, // 是否开启服务器分片校验
// 服务器分片校验函数,秒传及断点续传基础
checkChunkUploadedByResponse: function (chunk, message) {
@ -190,7 +189,7 @@ export default {
fileListScrollTop: 0,
dragoverLoop: null,
successMsg: null,
enableDragUplaod: false,// 是否启用拖拽上传
enableDragUpload: false,// 是否启用拖拽上传
uploader: null,
}
},
@ -200,12 +199,12 @@ export default {
watch: {
$route(route) {
// 只有首页才启用拖拽上传
this.enableDragUplaod = route.path === '/'
this.enableDragUpload = route.path === '/'
},
message(msg) {
switch (msg.event) {
case 'storageTypeChange':
this.onStorageTypeChange(msg.data)
case 'uploaderChunkSize':
this.onStorageTypeChange(msg.data.body)
break
case 'fileListScrollTop':
this.fileListScrollTop = msg.data
@ -234,12 +233,12 @@ export default {
}
},
mounted() {
this.enableDragUplaod = this.$route.path === '/'
this.enableDragUpload = this.$route.path === '/'
let that = this
let dropbox = document.body
document.body.ondragstart = function (e) {
if (that.enableDragUplaod) {
if (that.enableDragUpload) {
if (e.target.slot === 'jmal') {
that.isDragStart = true
}
@ -281,17 +280,10 @@ export default {
destroyed() {
},
methods: {
onStorageTypeChange(storageType) {
if (storageType === 'File') {
if (this.options.chunkSize === 1024 * 1024) {
return
}
this.updateChunkSize(1024 * 1024)
} else {
if (this.options.chunkSize === 5 * 1024 * 1024) {
return
}
this.updateChunkSize(5 * 1024 * 1024)
onStorageTypeChange(chunkSize) {
localStorage.setItem('uploader_chunk_size', chunkSize)
if (this.options.chunkSize !== chunkSize && !this.panelShow) {
this.updateChunkSize(chunkSize)
}
},
initUploader() {
@ -351,12 +343,23 @@ export default {
}).then(() => {
this.doUploadBefore(files)
}).catch(() => {
this.uploader.cancel()
this.uploaderCancel()
})
} else {
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) {
this.fileListLength = this.uploader.fileList.length
const filePaths = this.uploader.filePaths
@ -374,15 +377,12 @@ export default {
currentDirectory: encodeIfNeeded(this.params.currentDirectory),
username: this.params.username,
userId: this.params.userId
}).then(() => {
this.getFileList()
}).catch(e => {
})
})
}
if (window.pc) {
this.pc = true
this.panelShow = true
this.displayPanel(true)
} else {
this.pc = false
this.shrink()
@ -400,14 +400,20 @@ export default {
this.uploader.resume()
})
},
onFileProgress(rootFile, file, chunk) {
this.netSpeed = formatNetSpeed(file.currentSpeed)
this.process = Math.trunc(window.uploader.progress() * 100)
setPageTitle() {
if (this.process === -10 || this.process === 100 || this.fileListLength === 0) {
document.title = `${this.$route.meta.title}`
} else {
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) {
window.onbeforeunload = function () {
return "还有文件正在上传, 确定退出吗?";
@ -425,7 +431,6 @@ export default {
message: res.message,
type: 'error'
})
this.uploader.cancel()
this.close()
return;
}
@ -489,8 +494,7 @@ export default {
}
if (this.process === -10 || this.process === 100 || this.fileListLength === 0) {
this.uploader.cancel()
this.panelShow = false
this.uploaderCancel()
}
},
onFileError(rootFile, file, response) {
@ -640,16 +644,14 @@ export default {
},
close() {
if (this.process === -10 || this.process === 100 || this.fileListLength === 0) {
this.uploader.cancel()
this.panelShow = false
this.uploaderCancel()
} else {
this.$confirm('还有文件正在上传, 确定要关闭吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.uploader.cancel()
this.panelShow = false
this.uploaderCancel()
})
}
},
@ -659,8 +661,12 @@ export default {
* @param id
* @param status
*/
statusSet(id, status) {
statusSet(id, status, progressText) {
const statusMap = {
progress: {
text: progressText,
bgc: '#ffffff00'
},
md5: {
text: '校验MD5',
bgc: '#fff'
@ -683,6 +689,11 @@ export default {
}
}
if (status === 'progress') {
$('.uploader-file-status').find('span:eq(1) em').text(progressText)
return
}
this.$nextTick(() => {
$(`<p class="myStatus_${id}"></p>`).appendTo(`.file_${id} .uploader-file-status`).css({
'position': 'absolute',
@ -812,7 +823,7 @@ export default {
padding: 0;
margin: 0;
> li {
>>> li {
background-color: #fff;
}
}
@ -832,7 +843,7 @@ export default {
font-size: 16px;
}
> .uploader-file-icon {
>>> .uploader-file-icon {
&:before {
content: "" !important;
}
@ -860,21 +871,21 @@ export default {
}
}
> .uploader-file-actions > span {
>>> .uploader-file-actions > span {
margin-right: 6px;
}
}
> .uploader-file-status {
>>> .uploader-file-status {
width: 32%;
text-indent: 20px;
}
> .uploader-file-meta {
>>> .uploader-file-meta {
width: 0;
}
> .uploader-file-icon {
>>> .uploader-file-icon {
width: 32px;
height: 32px;
display: inline-block;
@ -883,7 +894,7 @@ export default {
margin-right: 8px;
}
> .uploader-file-actions > span {
>>> .uploader-file-actions > span {
margin-right: 6px;
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 theme = 'kennedy'
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}`)
},
mounted() {
@ -322,7 +322,7 @@ export default {
top: 2.5rem;
left: 0;
width: 100%;
height: 100%;
height: calc(100% - 2.5rem);
>>>.dark-button {
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>
import api from "@/api/file-api"
import settingApi from "@/api/setting-api"
import fileConfig from "@/utils/file-config";
import SparkMD5 from 'spark-md5'
import HistoryPopover from "@/components/HistoryPopover/index.vue";
@ -49,6 +50,10 @@ export default {
type: String,
default: ''
},
sharer: {
type: String,
default: undefined
},
file: {
type: Object,
default: function () {
@ -75,7 +80,12 @@ export default {
historyPageIndex: 1,
historyPageSize: 50,
viewHistory: false,
historyOperationLoading: false
historyOperationLoading: false,
officeServerConfig: {
documentServer: undefined,
callbackServer: undefined,
tokenEnabled: false
}
}
},
beforeDestroy() {
@ -93,25 +103,18 @@ export default {
if (!id) {
return
}
let officeApiUrl = fileConfig.officeApiUrl()
$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()
})
}
})
if (window.shareId) {
settingApi.getPublicOfficeConfig({shareId: window.shareId, shareToken: this.$store.getters.shareToken}).then(res => {
this.officeServerConfig = res.data
this.loadOfficeApi()
})
} else {
settingApi.getOfficeConfig().then(res => {
this.officeServerConfig = res.data
this.loadOfficeApi()
})
}
},
immediate: true,
},
@ -134,6 +137,27 @@ export default {
destroyed() {
},
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}) {
const historyUrl = window.location.origin + fileConfig.previewHistoryUrl(historyInfo.id, this.$store.state.user.name, this.$store.state.user.token)
this.onRequestHistoryData(null, historyInfo, historyUrl)
@ -188,11 +212,15 @@ export default {
this.docEditor = new DocsAPI.DocEditor(this.id, this.docEditorConfig)
},
onRequestHistoryData(event, historyInfo, url) {
this.docEditor.setHistoryData({
let historyConfig = {
"fileType": this.fileType,
"key": `${new Date(historyInfo.metadata.time).getTime()}-${SparkMD5.hash(this.file.id)}`,
"url": url,
"version": historyInfo.metadata.time,
}
api.getOfficeJwt(historyConfig).then(res => {
historyConfig.token = res.data
this.docEditor.setHistoryData(historyConfig)
})
},
onRequestHistory() {
@ -221,14 +249,25 @@ export default {
this.$refs.historyPopover.loadHistoryList(this.file.id)
})
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) {
this.fileUrl = window.location.origin + fileConfig.publicPreviewUrl(this.file, window.shareId, this.$store.getters.shareToken)
const file_username = this.sharer ? this.sharer : this.$store.state.user.name
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)}`
let callbackUrl = fileConfig.officeCallBackUrl(this.$store.getters.token, this.$store.getters.name, 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)}`
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 = {
document: {
fileType: this.fileType,
@ -241,7 +280,8 @@ export default {
lang: "zh",
user: {
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: {
autosave: false,
@ -260,6 +300,28 @@ export default {
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) {
this.docEditorConfig.type = 'mobile'
}
@ -276,6 +338,7 @@ export default {
this.docEditorConfig.editorConfig.user.name = "Visitor_" + visitor
}
}
this.docEditorConfig.events = {
"onAppReady": this.onAppReady,
"onDocumentReady": this.onDocumentReady,
@ -316,6 +379,7 @@ export default {
if (this.hasHistoryVersion && !doc.getElementById('box-doc-name-history-btn')) {
const toolbar = doc.getElementById('box-doc-name')
toolbar.style.justifyContent = 'right'
toolbar.style.padding = '0'
let versionButton = document.createElement("button")
versionButton.classList.add('btn', 'btn-header')
versionButton.setAttribute('id', 'box-doc-name-history-btn')

View File

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

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"/>
</div>
<div class="wrapper">
<div>{{fileType}}</div>
<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>
<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>
@ -28,17 +31,18 @@
<script>
import ModelPreview from '@/components/office/ModelPreview.vue'
import OnlyOfficeEditor from "@/components/office/OnlyOfficeEditor";
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 PdfPreview from "@/components/office/PdfPreview";
import Drawio from "@/components/office/Drawio";
import MyMindEditor from "@/components/Minder/minder"
export default {
name: 'OfficePreview',
components: {Drawio, PdfPreview, MessageDialog, OnlyOfficeEditor, MyMindEditor},
name: 'IframePreview',
components: { IframeContentPreview, ModelPreview, Drawio, PdfPreview, MessageDialog, OnlyOfficeEditor, MyMindEditor},
props: {
id: {
type: String,
@ -52,10 +56,18 @@ export default {
return {}
}
},
fileHandler: {
type: Object,
default: {}
},
shareId: {
type: String,
default: undefined
},
sharer: {
type: String,
default: undefined
},
status: {
type: Boolean,
default: false
@ -115,6 +127,12 @@ export default {
}
},
getType(suffix) {
if (Object.keys(this.fileHandler).length > 0) {
return 'other'
}
if (this.file.contentType.indexOf('office') > -1) {
return 'office'
}
switch (suffix) {
case 'drawio':
return 'drawio'
@ -122,8 +140,14 @@ export default {
return 'pdf'
case 'mind':
return 'mind'
default:
case 'gltf':
case 'glb':
return 'glTF/GLB'
case 'office':
case 'csv':
return 'office'
default:
return 'other'
}
},
/**
@ -207,7 +231,7 @@ export default {
this.$message.info('请手动保存')
},
/**
* 检查要预览调文件是否为只读
* 检查要预览的文件是否为只读
* @param file userId
*/
checkReadOnly(file){

View File

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

View File

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

View File

@ -16,6 +16,7 @@
<script>
import fileConfig from '@/utils/file-config'
import Artplayer from "@/components/preview/Artplayer.vue";
import artplayerPluginVttThumbnail from './artplayer-plugin-vtt-thumbnail';
import Hls from "hls.js";
import FlvJs from "flv.js";
@ -45,6 +46,7 @@ export default {
return {
pc: this.$pc,
art: null,
switchQuality: false,
option: {
url: '',
title: '', // 视频标题,目前会出现在 视频截图 和 迷你模式 下
@ -78,7 +80,7 @@ export default {
style: {
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,
},
{
@ -88,7 +90,7 @@ export default {
style: {
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,
},
{
@ -101,7 +103,7 @@ export default {
},
click: () => this.copyToClipboard(this.videoLink),
}
],
]
},
videoLink: '',
style: {
@ -124,6 +126,12 @@ export default {
watch: {
status: function (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)
if (this.shareId) {
videoUrl = fileConfig.publicPreviewUrl(this.file, window.shareId, this.$store.getters.shareToken)
@ -137,8 +145,9 @@ export default {
}
}
this.option.url = videoUrl
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())) {
this.option.quality = [
{
@ -155,17 +164,30 @@ export default {
this.option.customType = {
m3u8: this.playM3u8
}
this.option.plugins = [
artplayerPluginVttThumbnail({
vtt: videoUrl.replace(/m3u8/g, 'vtt'),
})
]
}
if (this.file.contentType.indexOf('flv') > -1) {
this.option.url = originUrl
this.option.quality = []
this.option.customType = {
flv: this.playFlv
}
}
this.videoLink = window.location.origin + videoUrl
this.videoLink = window.location.origin + originUrl
this.title = this.file.name
this.option.id = this.file.id
this.show = true
this.switchQuality = true
} else {
this.option.url = ''
this.videoLink = ''
this.option.quality = []
}
}
},
@ -197,15 +219,24 @@ export default {
getInstance(art) {
this.art = art
let doc = this.art.controls.$parent
// iina
const iina = doc.querySelector('[data-index="1"]').querySelector('a');
iina.href = `iina://weblink?url=${this.videoLink}`
// 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}`
if (this.$pc) {
// iina
const iina = doc.querySelector('[data-index="1"]').querySelector('a');
iina.href = `iina://weblink?url=${this.videoLink}`
// 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}`
} 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) {
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) {
this.$router.push(`/tag?tagId=${tag.id}`);
},
mouseleave() {
},
onmousemove() {
},
progressFormat() {
if (this.takeUpSpace === 0) {
this.takeUpSpace = this.userInfo.takeUpSpace

View File

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

View File

@ -7,7 +7,7 @@ import getPageTitle from '@/utils/get-page-title'
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) => {
// 开始进度条
@ -51,7 +51,7 @@ router.beforeEach(async(to, from, next) => {
}
} else {
// 没有登录的情况
if (whiteList.indexOf(to.path) !== -1) {
if (hasWhiteList(to.path)) {
// 没有token但是在白名单中,直接进入
next()
} 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){
if(store.getters.menuList.length === 0){
store.dispatch('user/setMenuList').then((res) => {

View File

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

View File

@ -1,88 +1,113 @@
import Bus from "@/assets/js/bus";
import _ from "lodash";
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 timer = null;
let lastHeartbeat = new Date().getTime();
const uuid = generateUUID();
const throttledConnectSSE = _.throttle(username => {
if (typeof (EventSource) !== 'undefined') {
const url = `/api/events?username=${username}&uuid=${uuid}`;
eventSource = new EventSource(url);
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()
const throttledConnectSSE = _.throttle((username) => {
if (typeof EventSource !== 'undefined') {
// 判断cookie中是否有token
if (store.getters.name) {
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 {
console.error('EventSource is not supported by the browser');
}
}, 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);
}
function onMessage(msg) {
const url = msg.url
if (url === 'synced') {
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 clearExistingTimer() {
if (timer) {
clearInterval(timer);
}
}
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,
showName: state => state.user.showName,
newVersion: state => state.user.newVersion,
iframePreviewConfig: state => state.user.iframePreviewConfig,
userId: state => state.user.userId,
userInfo: state => state.user.userInfo,
menuList: state => state.user.menuList,

View File

@ -33,7 +33,8 @@ const getDefaultState = () => {
menuList: [],
netdiskName: '',
netdiskLogo: '',
newVersion: ''
newVersion: '',
iframePreviewConfig: {}
}
}
@ -75,6 +76,9 @@ const mutations = {
},
SET_NEW_VERSION:(state,newVersion) => {
state.newVersion = newVersion;
},
SET_IFRAME_PREVIEW:(state, iframePreviewConfig) => {
state.iframePreviewConfig = iframePreviewConfig;
}
}
@ -147,7 +151,7 @@ const actions = {
// 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_SHOW_NAME', showName)
@ -156,6 +160,11 @@ const actions = {
commit('SET_USER_INFO', data)
commit('SET_NETDISK_NAME', netdiskName)
commit('SET_NETDISK_LOGO', netdiskLogo)
if (iframe) {
commit('SET_IFRAME_PREVIEW', JSON.parse(iframe))
} else {
commit('SET_IFRAME_PREVIEW', {})
}
if (newVersion) {
commit('SET_NEW_VERSION', newVersion)
} else {

View File

@ -285,7 +285,7 @@
}
>>> .van-grid {
padding: 0;
padding-left: 10px;
}
>>> .van-grid-item__content .svg-icon{
@ -293,6 +293,21 @@
}
>>> .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;
text-align: center;
color: #606266;
@ -306,6 +321,7 @@
padding: 0 5px 0 5px;
bottom: 5px;
left: 0;
z-index: 1;
textarea {
overflow: hidden;
resize:none;
@ -330,20 +346,16 @@
}
>>> .grid-item {
border-radius: 5px;
padding: 0 8px;
border: solid 1px #ccc0;
.grid-item-icon{
text-align: center;
position: absolute;
top: 5px;
//margin: 9px auto 0;
//width: 84px;
//height: 84px;
//text-align: center;
//position: absolute;
//top: 0;
}
.grid-item-icon-image {
width: 100%;
height: 100%;
top: 0px;
top: 0;
border-radius: 5px;
margin: 0;
-webkit-backdrop-filter: contrast(0.5);

View File

@ -226,3 +226,16 @@ input {
.dark-file-history-list.el-table::before {
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}`
},
// office api url
officeApiUrl: function() {
return `${window.location.origin}/office/web-apps/apps/api/documents/api.js`
officeApiUrl: function(documentServer) {
let serverUrl = documentServer || `${window.location.origin}/office`
if (!serverUrl.endsWith('/')) {
serverUrl = serverUrl + "/"
}
return serverUrl + "web-apps/apps/api/documents/api.js"
},
// office回调url
officeCallBackUrl: function(token, username, fileId) {
return `http://jmalcloud:8088/office/track?jmal-token=${token}&name=${username}&fileId=${fileId}`
officeCallBackUrl: function(callbackServer, token, username, fileId) {
let callbackServerUrl = callbackServer || 'http://jmalcloud:8088'
if (!callbackServerUrl.endsWith('/')) {
callbackServerUrl = callbackServerUrl + "/"
}
return `${callbackServerUrl}office/track?jmal-token=${token}&name=${username}&fileId=${fileId}`
},
// 预览文件的url
previewUrl: function(username, file, token, shareToken) {
previewUrl: function(username, file, token, shareToken, serverUrl) {
let owner = null
if (username !== store.getters.name || localStorage.getItem('mountFileOwner') !== null) {
owner = localStorage.getItem('mountFileOwner')
@ -30,8 +38,10 @@ export default {
if (owner == null) {
owner = store.getters.name
}
let fileUrl = `${this.baseUrl}/file/${owner}${encodeURI(file.path)}${encodeURI(file.name)}`
fileUrl = fileUrl.replace(/%5C/g, '/')
let baseUrl = serverUrl || this.baseUrl
let fileUrl = `${baseUrl}/file/${owner}${encodeURI(file.path)}${encodeURI(file.name)}`
fileUrl = fileUrl.replaceAll('#', '%23')
fileUrl = fileUrl.replaceAll(/%5C/g, '/')
if (token) {
return `${fileUrl}?jmal-token=${token}&name=${username}`
}
@ -64,7 +74,8 @@ export default {
// 打包下载文件
packageDownload: function(fileIds, token, username) {
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')
},
// 共享文件预览Url
publicPreviewUrl: function(file, shareId, shareToken) {
publicPreviewUrl: function(file, shareId, shareToken, serverUrl) {
if (!shareToken) {
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) {
@ -96,6 +108,13 @@ export default {
},
// 共享文件打包下载Url
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'
],
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()
iconClass.set('php','file-php')
iconClass.set('c','file-c')
iconClass.set('js','file-js')
iconClass.set('vue','file-vue')
iconClass.set('xml','file-xml')
@ -64,10 +65,12 @@ iconClass.set('pdf','file-pdf')
iconClass.set('mind','file-mind')
iconClass.set('zip','zip')
iconClass.set('rar','zip')
iconClass.set('7z','zip')
iconClass.set('iml','file-idea')
iconClass.set('key','file-key')
iconClass.set('pem','file-pem')
iconClass.set('gz','file-gzip')
iconClass.set('tar.gz','file-gzip')
iconClass.set('json','file-json')
iconClass.set('tar','file-tar')
iconClass.set('sh','file-shell')
@ -83,5 +86,36 @@ iconClass.set('gradle','file-gradle')
iconClass.set('bat','file-bat')
iconClass.set('swift','file-swift')
iconClass.set('yml','file-yml')
iconClass.set('yaml','file-yml')
iconClass.set('plist','file-plist')
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
* @param {*} size
* @param space
*/
export function formatNetSpeed(size) {
export function formatNetSpeed(size, space) {
if (size === 0) {
return '0B/s'
return space ? '0 B / s' : '0B/s'
} else if (size < 1024) {
return size + 'B/s'
return size + (space ? ' B / s' : 'B/s')
} 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) {
return (size/(1024 * 1024)).toFixed(2) + 'MB/s'
return (size/(1024 * 1024)).toFixed(2) + (space ? ' MB / s' : 'MB/s')
} 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 store from '@/store'
import _ from "lodash";
import {getShareId, getShareToken, getToken, getUsername} from '@/utils/auth'
// 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
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
*/
/**
* Determine the request status by custom code
@ -56,7 +69,7 @@ service.interceptors.response.use(
// to re-login
setTimeout(function () {
const hasToken = getToken()
if(hasToken){
if (hasToken) {
Vue.prototype.$confirm('登录已失效,请重新登录', '确认登出', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
@ -67,7 +80,7 @@ service.interceptors.response.use(
})
})
}
},200)
}, 200)
} else {
Vue.prototype.$message({
message: res.message || '服务器开小差了...',
@ -78,10 +91,10 @@ service.interceptors.response.use(
return Promise.reject()
} else {
let message = ''
if(res.message){
if (res.message) {
message = res.message.toString()
}
if(message !== 'true'){
if (message !== 'true') {
Vue.prototype.$message({
message: res.message,
type: 'success',
@ -92,15 +105,7 @@ service.interceptors.response.use(
}
},
error => {
let msg = '哎呀!服务器出错了,请稍后再试!'
if (error.response && error.response.status === 403) {
msg = '您的请求被服务器拒绝!'
}
Vue.prototype.$message({
message: msg,
type: 'error',
duration: 5 * 1000
})
responseErr(error)
return Promise.reject(error)
}
)

View File

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

View File

@ -8,6 +8,8 @@
:queryCondition="queryCondition"
singleFileType="音乐"
emptyStatus="还没有音乐呢~"
:showUploadButton="false"
:contextMenus="contextMenus"
></show-file>
</div>
</template>
@ -24,7 +26,36 @@ export default {
},
queryCondition: {
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"
:queryCondition="queryCondition"
emptyStatus="还没有文档呢~"
:showUploadButton="false"
:contextMenus="contextMenus"
>
</show-file>
</div>
@ -24,7 +26,36 @@ export default {
},
queryCondition: {
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"
:queryCondition="queryCondition"
emptyStatus="还没有图片呢~"
:showUploadButton="false"
:contextMenus="contextMenus"
>
</show-file>
</div>
@ -23,7 +25,36 @@ export default {
},
queryCondition: {
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"
:queryCondition="queryCondition"
emptyStatus="还没有视频呢~"
:showUploadButton="false"
:contextMenus="contextMenus"
>
</show-file>
</div>
@ -24,7 +26,36 @@ export default {
},
queryCondition: {
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>
<image-viewer :fileList="fileList" :file="imagePreviewRow" :status.sync="imagePreviewVisible"></image-viewer>
<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>
</template>
<script>
@ -283,14 +283,12 @@ import ImageViewer from "@/components/preview/ImageViewer";
import VideoPreview from "@/components/preview/VideoPreview";
import SimTextPreview from "@/components/preview/SimTextPreview";
import {suffix} from "@/utils/file-type";
import OfficePreview from "@/components/preview/OfficePreview";
import IframePreview from "@/components/preview/IframePreview.vue";
import Clipboard from "clipboard";
let pinyin = require("pinyin");
export default {
components: {
OfficePreview,
IframePreview,
SimTextPreview,
VideoPreview,
ImageViewer, IconFile,
@ -409,11 +407,7 @@ export default {
// 加载布局
if (this.$route.query.vmode) {
this.vmode = this.$route.query.vmode
if (this.vmode === 'list') {
this.grid = false
} else {
this.grid = true
}
this.grid = this.vmode !== 'list';
}
// 加载url上的path
if (this.$route.query.path !== '/') {
@ -676,7 +670,8 @@ export default {
api.delete({
currentDirectory: this.rowContextData.path,
username: this.$store.state.user.name,
fileIds: fileIds
fileIds: fileIds,
sweep: true
}).then(() => {
// 刷新列表
this.getFileList()
@ -929,7 +924,6 @@ export default {
this.searchFile(this.searchValue, onLoad)
}
} else {
this.beforeLoadData(onLoad)
api.fileList({
username: this.$store.state.user.name,
userId: this.$store.state.user.userId,

View File

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

View File

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

View File

@ -6,6 +6,8 @@
:sortable="sortable"
:queryCondition="queryCondition"
:showNavigation="true"
:contextMenus="contextMenus"
:showUploadButton="false"
></show-file>
</div>
</template>
@ -22,7 +24,36 @@
},
queryCondition: {
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({
currentDirectory: '',
username: this.$store.state.user.name,
fileIds: fileIds
fileIds: fileIds,
sweep: true
}).then(() => {
// 移除列表
this.getArticleList()
}).then(() => {
this.$notify({
title: '删除成功',
type: 'success',
duration: 1000
})
})
})
},

View File

@ -37,8 +37,10 @@
</div>
</div>
<div>
<div class="token-title-desc">您可以使用这些以生成的令牌来操作 JmalCloud API。</div>
<el-divider></el-divider>
<div class="setting-title-desc">
<div class="setting-title-desc-text">您可以使用这些以生成的令牌来操作 JmalCloud API。</div>
<el-divider></el-divider>
</div>
<div>
<ul v-if="accessTokenList.length > 0">
<li v-for="token in accessTokenList" :key="token.id">
@ -144,9 +146,6 @@ export default {
<style lang="scss" scoped>
@import "src/styles/setting";
>>>.el-divider--horizontal {
margin: 0;
}
>>>.el-alert {
margin-bottom: 15px;
.el-alert__content {
@ -156,9 +155,6 @@ export default {
font-size: 1rem;
}
}
.token-title-desc {
padding: 5px 0 20px 0;
}
.token-item {
display: flex;
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>
</span>
<div class="config-itme-label">重建索引:
<el-button class="sync-button" size="mini" :loading="syncLoading" type="primary" @click="sync()"><i
class="el-icon-refresh"></i>
</el-button>
<span v-show="syncPercent < 100">{{ syncPercent }}%</span>
<el-button class="sync-button" size="mini" :loading="syncLoading" type="primary" @click="sync()"><i class="el-icon-refresh"></i></el-button>
<span v-show="syncPercent < 100">正在同步文件基本信息: {{ syncPercent }}%</span>
<span v-show="indexingPercent > 0 && indexingPercent < 100">正在为文件内容创建索引: </span><span v-show="indexingPercent > 0 && indexingPercent < 100">{{ indexingPercent }}%</span>
</div>
<span class="instruction">一般用于初始化操作, 将文件数据同步到数据库</span>
<span class="instruction">重建索引分为两步: 1.同步文件基本信息, 2.为文件内容创建索引</span>
<div class="config-itme-label">重置角色、菜单:
<el-button class="sync-button" size="mini" :loading="resetLoading" type="danger"
@click="resetMenuAndRole()">
@ -66,60 +65,24 @@
</div>
</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'">
<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>
<office-config></office-config>
</div>
</el-tab-pane>
<el-tab-pane label="视频转码" name="3" class="setting-tab-panel">
<div v-if="activeName === '3'">
<transcode-config></transcode-config>
</div>
</el-tab-pane>
<el-tab-pane label="预览配置" name="4" class="setting-tab-panel">
<div v-if="activeName === '4'">
<preview-config></preview-config>
</div>
</el-tab-pane>
<el-tab-pane label="LDAP认证" name="5" class="setting-tab-panel">
<div v-if="activeName === '5'">
<ldap-config></ldap-config>
</div>
</el-tab-pane>
</el-tabs>
@ -131,23 +94,28 @@
<script>
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 getPageTitle from "@/utils/get-page-title";
import UploadImageInput from "@/components/input/UploadImageInput.vue";
import roleApi from "@/api/role";
import { loadLdapConfig, testLdapConfig, updateLdapConfig} from '@/api/user'
import PreviewConfig from '@/views/setting/cloudSetting/previewConfig.vue'
import {mapGetters, mapState} from "vuex";
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 {
components: {UploadImageInput, Logo},
components: { PreviewConfig, OfficeConfig, TranscodeConfig, LdapConfig, TaskProgress, UploadImageInput, Logo},
data() {
return {
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}`,
title: '网盘管理',
syncLoading: false,
clickSync: false,
indexingPercent: 100,
syncPercent: 100,
resetLoading: false,
webpEnabled: false,
@ -156,44 +124,7 @@ export default {
showAckBtn: false,
inputNetdiskNameWidth: 150,
logoFileTypeList: ['image/svg+xml', 'image/jpg', 'image/png', 'image/jpeg'],
ldapFormData: {
enable: true,
ldapServer: '',
defaultRoleList: [],
baseDN: '',
userDN: '',
password: '',
loginName: ''
},
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() {
@ -203,8 +134,6 @@ export default {
if (this.$route.query.tab) {
this.activeName = this.$route.query.tab
if (this.activeName === '2') {
this.getRoleList()
this.getLdapConfig()
}
}
},
@ -213,50 +142,17 @@ export default {
...mapGetters([
'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: {
message(msg) {
if (msg.event === 'msg/synced') {
this.syncPercent = msg.data.body
if (this.syncPercent >= 100) {
this.syncLoading = false
}
this.updateSyncStatus(msg.data.body)
}
}
},
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) {
this.$router.push({query: {tab: tab.name}})
if (tab.name === '2') {
this.getRoleList()
this.getLdapConfig()
}
},
handleAvatarSuccess(res) {
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) {
if (input) {
this.showAckBtn = true
@ -350,21 +234,36 @@ export default {
},
getIsSync() {
settingApi.isSync({username: this.$store.state.user.name}).then((res) => {
this.syncLoading = true;
this.syncPercent = res.data
this.syncLoading = this.syncPercent < 100;
this.updateSyncStatus(res.data)
})
},
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() {
this.$confirm('是否开始同步? ', '提示', {
this.$confirm('是否开始扫描? ', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.syncLoading = true
this.syncPercent = 0
settingApi.sync({username: this.$store.state.user.name}).then(() => {
this.$message.success("开始同步...")
this.clickSync = true
settingApi.syncCloud({username: this.$store.state.user.name}).then(() => {
this.clickSync = false
}).catch(() => {
this.clickSync = false
})
})
},
@ -392,7 +291,7 @@ export default {
}).catch(() => {
this.webpEnabled = !this.webpEnabled
})
}
},
}
}
</script>
@ -400,7 +299,7 @@ export default {
<style lang="scss" scoped>
@import "src/styles/setting";
> > > .el-textarea {
>>> .el-textarea {
max-width: 1080px;
}
@ -413,7 +312,7 @@ export default {
width: 220px;
}
.avatar-uploader > > > .el-upload {
.avatar-uploader >>> .el-upload {
display: block;
.avatar {
@ -432,11 +331,11 @@ export default {
}
}
> > > .el-card__body {
>>> .el-card__body {
padding: 0;
}
> > > .el-tabs__header {
>>> .el-tabs__header {
margin: 0;
@media screen and (min-width: 768px) {
.el-tabs__nav {
@ -448,7 +347,7 @@ export default {
}
}
> > > .el-form-item__content {
>>> .el-form-item__content {
.el-select {
width: 100%;
}
@ -462,4 +361,11 @@ export default {
}
}
.progress {
margin-top: 5px;
>>> .el-card__header {
padding: 8px 20px;
}
}
</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',
// 查询条件
queryCondition: {
excludeUsername: 'other',
excludeUsername: undefined,
username: undefined,
ip: undefined,
type: this.type,
@ -191,12 +191,7 @@ export default {
}
this.queryLogList()
},
queryLogList() {
if (this.queryCondition.username) {
this.queryCondition.excludeUsername = null;
} else {
this.queryCondition.excludeUsername = 'other';
}
queryLogList(val) {
this.$refs.tableList.initPageIndex()
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>
<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布局-->
<div v-show="fileList.length > 0" :style="{'width':'100%','height': clientHeight+'px'}">
<pl-table
@ -138,7 +138,7 @@
header-align="left"
>
<template slot-scope="scope">
<span>{{expireInfo(scope.row)}}</span>
<span :style="{color: expireColor(scope.row)}">{{expireInfo(scope.row)}}</span>
</template>
</pl-table-column>
</template>
@ -271,6 +271,19 @@
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){
if (onLoad) {
@ -461,6 +474,9 @@
})
})
},
onCancelShare() {
this.getFileList()
},
// 点击文件或文件夹
fileClick(row) {
this.shareDialogObject = row
@ -474,6 +490,12 @@
share.expireDate = expireDate
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>

View File

@ -7,6 +7,7 @@
:defaultSort="sortable"
:queryCondition="queryCondition"
:contextMenus="contextMenus"
homeHidden
>
</show-file>
</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>