新增文件预览功能

This commit is contained in:
kuaifan 2021-12-10 17:05:16 +08:00
parent a303a60c4e
commit aa9a3238a2
11 changed files with 265 additions and 136 deletions

View File

@ -407,7 +407,8 @@ class FileController extends AbstractController
$path = 'uploads/office/' . date("Ym") . '/u' . $user->userid . '/'; $path = 'uploads/office/' . date("Ym") . '/u' . $user->userid . '/';
$data = Base::upload([ $data = Base::upload([
"file" => Request::file('files'), "file" => Request::file('files'),
"type" => 'office', "type" => 'more',
"autoThumb" => false,
"path" => $path, "path" => $path,
]); ]);
if (Base::isError($data)) { if (Base::isError($data)) {
@ -415,36 +416,36 @@ class FileController extends AbstractController
} }
$data = $data['data']; $data = $data['data'];
// //
$type = ""; $type = match ($data['ext']) {
switch ($data['ext']) { 'doc', 'docx' => "word",
case 'doc': 'xls', 'xlsx' => "excel",
case 'docx': 'ppt', 'pptx' => "ppt",
$type = "word"; 'txt', 'html', 'htm', 'asp', 'jsp', 'xml', 'json', 'properties', 'md', 'gitignore', 'log', 'java', 'py', 'c', 'cpp', 'sql', 'sh', 'bat', 'm', 'bas', 'prg', 'cmd' => "text",
break; 'jpg', 'jpeg', 'png', 'gif' => 'image',
case 'xls': 'zip', 'rar', 'jar', 'tar', 'gzip' => 'compress',
case 'xlsx': 'mp3', 'wav', 'mp4', 'flv' => 'media',
$type = "excel"; 'pdf' => 'pdf',
break; 'dwg' => 'cad',
case 'ppt': default => "",
case 'pptx': };
$type = "ppt";
break;
}
$file = File::createInstance([ $file = File::createInstance([
'pid' => $pid, 'pid' => $pid,
'name' => Base::rightDelete($data['name'], '.' . $data['ext']), 'name' => Base::rightDelete($data['name'], '.' . $data['ext']),
'type' => $type, 'type' => $type,
'ext' => $data['ext'],
'userid' => $userid, 'userid' => $userid,
'created_id' => $user->userid, 'created_id' => $user->userid,
]); ]);
// 开始创建 // 开始创建
return AbstractModel::transaction(function () use ($user, $data, $file) { return AbstractModel::transaction(function () use ($type, $user, $data, $file) {
$file->save(); $file->save();
// //
$content = FileContent::createInstance([ $content = FileContent::createInstance([
'fid' => $file->id, 'fid' => $file->id,
'content' => [ 'content' => [
'from' => '', 'from' => '',
'type' => $type,
'ext' => $data['ext'],
'url' => $data['path'] 'url' => $data['path']
], ],
'text' => '', 'text' => '',

View File

@ -10,18 +10,18 @@ use Illuminate\Database\Eloquent\SoftDeletes;
use Request; use Request;
/** /**
* Class File * App\Models\File
* *
* @package App\Models
* @property int $id * @property int $id
* @property int|null $pid 上级ID * @property int|null $pid 上级ID
* @property int|null $cid 复制ID * @property int|null $cid 复制ID
* @property string|null $name 名称 * @property string|null $name 名称
* @property string|null $type 类型 * @property string|null $type 类型
* @property string|null $ext 后缀名
* @property int|null $size 大小(B) * @property int|null $size 大小(B)
* @property int|null $userid 拥有者ID * @property int|null $userid 拥有者ID
* @property int|null $share 是否共享(1:共享所有人,2:指定成员) * @property int|null $share 是否共享
* @property int|null $created_id 创建者ID * @property int|null $created_id 创建者
* @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at * @property \Illuminate\Support\Carbon|null $deleted_at
@ -33,6 +33,7 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|File whereCreatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereCreatedId($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereCreatedId($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereDeletedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereDeletedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereExt($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereId($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereName($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|File wherePid($value) * @method static \Illuminate\Database\Eloquent\Builder|File wherePid($value)

View File

@ -57,26 +57,28 @@ class FileContent extends AbstractModel
return Response::download(public_path($content['url'])); return Response::download(public_path($content['url']));
} }
if (empty($content)) { if (empty($content)) {
switch ($type) { $content = match ($type) {
case 'document': 'document' => [
$content = [
"type" => "md", "type" => "md",
"content" => "", "content" => "",
]; ],
break; 'sheet' => [
case 'sheet':
$content = [
[ [
"name" => "Sheet1", "name" => "Sheet1",
"config" => json_decode('{}'), "config" => json_decode('{}'),
] ]
]; ],
break; default => json_decode('{}'),
};
default: } else {
$content = json_decode('{}'); $content['preview'] = false;
break; if ($content['ext'] && !in_array($content['ext'], ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'])) {
$url = 'http://' . env('APP_IPPR') . '.3/' . $content['url'];
if ($type == 'image') {
$url = Base::fillUrl($content['url']);
}
$content['url'] = base64_encode($url);
$content['preview'] = true;
} }
} }
return Base::retSuccess('success', [ 'content' => $content ]); return Base::retSuccess('success', [ 'content' => $content ]);

View File

@ -2218,17 +2218,22 @@ class Base
case 'file': case 'file':
$type = ['jpg', 'jpeg', 'png', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'esp', 'pdf', 'rar', 'zip', 'gz']; $type = ['jpg', 'jpeg', 'png', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'esp', 'pdf', 'rar', 'zip', 'gz'];
break; break;
case 'office':
$type = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
break;
case 'firmware': case 'firmware':
$type = ['img', 'tar', 'bin']; $type = ['img', 'tar', 'bin'];
break; break;
case 'md': case 'md':
$type = ['md']; $type = ['md'];
break; break;
case 'node_template': case 'more':
$type = ['csv']; $type = [
'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
'txt', 'html', 'htm', 'asp', 'jsp', 'xml', 'json', 'properties', 'md', 'gitignore', 'log', 'java', 'py', 'c', 'cpp', 'sql', 'sh', 'bat', 'm', 'bas', 'prg', 'cmd',
'jpg', 'jpeg', 'png', 'gif',
'zip', 'rar', 'jar', 'tar', 'gzip',
'mp3', 'wav', 'mp4', 'flv',
'pdf',
'dwg'
];
break; break;
default: default:
return Base::retError('错误的类型参数'); return Base::retError('错误的类型参数');

View File

@ -0,0 +1,47 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class FilesAddExt extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$isAdd = false;
Schema::table('files', function (Blueprint $table) use (&$isAdd) {
if (!Schema::hasColumn('files', 'ext')) {
$isAdd = true;
$table->string('ext', 20)->nullable()->default('')->after('type')->comment('后缀名');
}
});
if ($isAdd) {
// 更新数据
\App\Models\File::chunkById(100, function ($lists) {
foreach ($lists as $item) {
if (in_array($item->type, ['word', 'excel', 'ppt'])) {
$item->ext = str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $item->type);
$item->save();
}
}
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('files', function (Blueprint $table) {
$table->dropColumn("ext");
});
}
}

View File

@ -41,11 +41,10 @@ services:
networks: networks:
extnetwork: extnetwork:
ipv4_address: "${APP_IPPR}.3" ipv4_address: "${APP_IPPR}.3"
depends_on:
- php
links: links:
- php - php
- office - office
- fileview
restart: unless-stopped restart: unless-stopped
redis: redis:
@ -95,6 +94,17 @@ services:
ipv4_address: "${APP_IPPR}.6" ipv4_address: "${APP_IPPR}.6"
restart: unless-stopped restart: unless-stopped
fileview:
container_name: "dootask-fileview-${APP_ID}"
image: "kuaifan/fileview:4.1.0"
environment:
TZ: "Asia/Shanghai"
KK_CONTEXT_PATH: "/fileview"
networks:
extnetwork:
ipv4_address: "${APP_IPPR}.7"
restart: unless-stopped
networks: networks:
extnetwork: extnetwork:
name: "dootask-networks-${APP_ID}" name: "dootask-networks-${APP_ID}"

View File

@ -10,6 +10,10 @@ upstream office {
server office weight=5 max_fails=3 fail_timeout=30s; server office weight=5 max_fails=3 fail_timeout=30s;
keepalive 16; keepalive 16;
} }
upstream fileview {
server fileview:8012 weight=5 max_fails=3 fail_timeout=30s;
keepalive 16;
}
server { server {
listen 80; listen 80;
@ -32,26 +36,12 @@ server {
allow all; allow all;
} }
location ~* ^/(6.3.1-32|cache/files|web-apps/apps)/ {
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-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;
proxy_pass http://office;
}
location =/ws { location =/ws {
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-PORT $remote_port; proxy_set_header X-Real-PORT $remote_port;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme; proxy_set_header Scheme $scheme;
@ -69,6 +59,8 @@ server {
proxy_set_header Connection ""; proxy_set_header Connection "";
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-PORT $remote_port; proxy_set_header X-Real-PORT $remote_port;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme; proxy_set_header Scheme $scheme;
@ -78,6 +70,41 @@ server {
proxy_set_header Server-Port $server_port; proxy_set_header Server-Port $server_port;
proxy_pass http://service; proxy_pass http://service;
} }
location /office/ {
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 $http_host/office;
proxy_set_header X-Forwarded-Proto $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;
proxy_pass http://office/;
}
location /fileview {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-PORT $remote_port;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto $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_pass http://fileview;
}
} }
include /etc/nginx/conf.d/conf.d/*.conf; include /etc/nginx/conf.d/conf.d/*.conf;

View File

@ -85,7 +85,7 @@ export default {
if (!url) { if (!url) {
return; return;
} }
$A.loadScript(this.$store.state.method.apiUrl("../web-apps/apps/api/documents/api.js"), () => { $A.loadScript(this.$store.state.method.apiUrl("../office/web-apps/apps/api/documents/api.js"), () => {
this.loadFile() this.loadFile()
}) })
}, },

View File

@ -1,5 +1,7 @@
<template> <template>
<div class="file-content"> <div class="file-content">
<iframe v-if="isPreview" ref="myPreview" class="preview-iframe" :src="previewUrl"></iframe>
<template v-else>
<div v-show="!['word', 'excel', 'ppt'].includes(file.type)" class="edit-header"> <div v-show="!['word', 'excel', 'ppt'].includes(file.type)" class="edit-header">
<div class="header-title"> <div class="header-title">
<EPopover v-if="!equalContent" v-model="unsaveTip" class="file-unsave-tip"> <EPopover v-if="!equalContent" v-model="unsaveTip" class="file-unsave-tip">
@ -59,6 +61,7 @@
<LuckySheet v-else-if="file.type=='sheet'" ref="mySheet" v-model="contentDetail"/> <LuckySheet v-else-if="file.type=='sheet'" ref="mySheet" v-model="contentDetail"/>
<OnlyOffice v-else-if="['word', 'excel', 'ppt'].includes(file.type)" v-model="contentDetail"/> <OnlyOffice v-else-if="['word', 'excel', 'ppt'].includes(file.type)" v-model="contentDetail"/>
</div> </div>
</template>
<div v-if="loadContent > 0" class="content-load"><Loading/></div> <div v-if="loadContent > 0" class="content-load"><Loading/></div>
</div> </div>
</template> </template>
@ -166,6 +169,18 @@ export default {
equalContent() { equalContent() {
return this.contentBak == $A.jsonStringify(this.contentDetail); return this.contentBak == $A.jsonStringify(this.contentDetail);
}, },
isPreview() {
return this.contentDetail && this.contentDetail.preview === true;
},
previewUrl() {
if (this.isPreview) {
return this.$store.state.method.apiUrl("../fileview/onlinePreview?url=" + encodeURIComponent(this.contentDetail.url))
} else {
return '';
}
},
}, },
methods: { methods: {

View File

@ -85,7 +85,7 @@
@on-enter="onEnter(item)"/> @on-enter="onEnter(item)"/>
<div v-if="item._load" class="file-load"><Loading/></div> <div v-if="item._load" class="file-load"><Loading/></div>
</div> </div>
<div v-else class="file-name" :title="item.name">{{formatName(item.name, item.type)}}</div> <div v-else class="file-name" :title="item.name">{{formatName(item)}}</div>
</li> </li>
</ul> </ul>
</div> </div>
@ -199,7 +199,7 @@
v-model="editShow" v-model="editShow"
class="page-file-drawer" class="page-file-drawer"
:mask-closable="false"> :mask-closable="false">
<FileContent v-if="editShowNum > 0" :parent-show="editShow" :file="editInfo"/> <FileContent v-if="editNum > 0" :parent-show="editShow" :file="editInfo"/>
</DrawerOverlay> </DrawerOverlay>
</div> </div>
@ -290,13 +290,21 @@ export default {
shareLoad: 0, shareLoad: 0,
editShow: false, editShow: false,
editShowNum: 0, editNum: 0,
editInfo: {}, editInfo: {},
uploadDir: false, uploadDir: false,
uploadIng: 0, uploadIng: 0,
uploadFormat: ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'], uploadFormat: [
uploadAccept: ['.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx'].join(","), 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
'txt', 'html', 'htm', 'asp', 'jsp', 'xml', 'json', 'properties', 'md', 'gitignore', 'log', 'java', 'py', 'c', 'cpp', 'sql', 'sh', 'bat', 'm', 'bas', 'prg', 'cmd',
'jpg', 'jpeg', 'png', 'gif',
'zip', 'rar', 'jar', 'tar', 'gzip',
'mp3', 'wav', 'mp4', 'flv',
'pdf',
'dwg'
],
uploadAccept: '',
maxSize: 204800, maxSize: 204800,
contextMenuItem: {}, contextMenuItem: {},
@ -310,6 +318,9 @@ export default {
mounted() { mounted() {
this.tableHeight = window.innerHeight - 160; this.tableHeight = window.innerHeight - 160;
this.uploadAccept = this.uploadFormat.map(item => {
return '.' + item
}).join(",");
}, },
activated() { activated() {
@ -381,7 +392,7 @@ export default {
editShow(val) { editShow(val) {
if (val) { if (val) {
this.editShowNum++; this.editNum++;
this.$store.dispatch("websocketPath", "file/content/" + this.editInfo.id); this.$store.dispatch("websocketPath", "file/content/" + this.editInfo.id);
} else { } else {
this.$store.dispatch("websocketPath", "file"); this.$store.dispatch("websocketPath", "file");
@ -397,7 +408,6 @@ export default {
title: this.$L('文件名'), title: this.$L('文件名'),
key: 'name', key: 'name',
minWidth: 200, minWidth: 200,
resizable: true,
sortable: true, sortable: true,
render: (h, {row}) => { render: (h, {row}) => {
let array = []; let array = [];
@ -467,7 +477,7 @@ export default {
} }
} }
}, [ }, [
h('AutoTip', this.formatName(row.name, row.type)) h('AutoTip', this.formatName(row))
])); ]));
// //
const iconArray = []; const iconArray = [];
@ -556,13 +566,9 @@ export default {
] ]
}, },
formatName(name, type) { formatName({name, ext}) {
if (type == 'word') { if (ext != '') {
name += ".docx"; name += "." + ext;
} else if (type == 'excel') {
name += ".xlsx";
} else if (type == 'ppt') {
name += ".pptx";
} }
return name; return name;
}, },

View File

@ -9,6 +9,21 @@
border-radius: 18px 18px 0 0; border-radius: 18px 18px 0 0;
overflow: hidden; overflow: hidden;
.preview-iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: 0 0;
border: 0;
float: none;
margin: -1px 0 0;
max-width: none;
outline: 0;
padding: 0;
}
.edit-header { .edit-header {
display: flex; display: flex;
flex-direction: row; flex-direction: row;