You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

596 lines
19 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="page page-upload">
<el-row>
<el-col :span="24">
<el-card shadow="never">
<div slot="header" class="clearfix">
<strong>上传文档</strong>
</div>
<el-row :gutter="40">
<el-col :span="14" class="part-left">
<el-form
ref="form"
:model="document"
label-position="top"
label-width="80px"
>
<el-form-item
label="文档分类"
prop="category_id"
:rules="[
{
required: true,
trigger: 'blur',
message: '请选择文档分类',
},
]"
>
<el-cascader
v-model="document.category_id"
:options="categoryTrees"
:filterable="true"
:disabled="loading"
:props="{
checkStrictly: true,
expandTrigger: 'hover',
label: 'title',
value: 'id',
}"
placeholder="请选择文档分类"
></el-cascader>
</el-form-item>
<el-form-item
:label="`默认售价(${
settings.system.credit_name || '魔豆'
}`"
prop="price"
>
<el-input-number
v-model="document.price"
:min="0"
:step="1"
:disabled="loading"
></el-input-number>
</el-form-item>
<el-form-item>
<el-upload
ref="upload"
drag
multiple
:action="'/api/v1/upload/document'"
:headers="{ authorization: `bearer ${token}` }"
:show-file-list="false"
:disabled="loading || !canIUploadDocument"
:auto-upload="false"
:on-change="onChange"
:file-list="fileList"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">
将文件拖到此处,或<em>点击上传</em>
</div>
</el-upload>
<el-table
v-if="fileList.length > 0"
:data="fileList"
style="width: 100%"
max-height="480"
>
<el-table-column prop="title" label="#" width="50">
<template slot-scope="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="title" label="文件" min-width="180">
<template slot-scope="scope">
<el-input v-model="scope.row.title" :disabled="loading">
<template slot="append">{{
scope.row.ext
}}</template></el-input
>
<div v-if="scope.row.error">
<el-progress
:key="scope.row.name"
:percentage="scope.row.percentage"
status="exception"
></el-progress>
<small class="el-link el-link--danger error-tips">{{
scope.row.error
}}</small>
</div>
<el-progress
v-else-if="scope.row.percentage > 0"
:percentage="scope.row.percentage"
></el-progress>
</template>
</el-table-column>
<el-table-column prop="size" label="大小" width="100">
<template slot-scope="scope">
{{ formatBytes(scope.row.size) }}
</template>
</el-table-column>
<el-table-column
prop="price"
:label="`售价(${settings.system.credit_name || '魔豆'})`"
width="130"
>
<template slot-scope="scope">
<el-input-number
v-model="scope.row.price"
:min="0"
:step="1"
:disabled="loading"
controls-position="right"
></el-input-number>
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template slot="header">
操作 (<el-button
type="text"
size="mini"
:disabled="loading"
@click="clearAllFiles"
>清空</el-button
>)
</template>
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-delete"
:disabled="loading"
@click="handleRemove(scope.$index)"
>
移除
</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
<el-form-item style="margin-bottom: 0">
<el-button
v-if="canIUploadDocument"
type="primary"
class="btn-block"
:loading="loading"
@click="onSubmit"
>
<span v-if="loading">请勿刷新页面,文档上传中...</span>
<span v-else>确定上传</span>
</el-button>
<el-button
v-else
type="primary"
icon="el-icon-hot-water"
class="btn-block"
disabled
>
<span v-if="user.id > 0">您所在用户组暂无权限上传文档</span>
<span v-else>您未登录,请先登录</span>
</el-button>
</el-form-item>
</el-form>
</el-col>
<el-col :span="10" class="upload-tips part-right">
<div><strong>温馨提示</strong></div>
<div class="help-block">
<ul>
<li>
1. 带有
<span class="el-link el-link--danger">*</span> 为必填项。
</li>
<li>
<!-- 应该从管理后台的配置中查询 -->
2. 允许上传的最大单个文档大小为:<span
class="el-link el-link--primary"
>{{
settings.security.max_document_size.toFixed(2) ||
'50.00'
}}
MB</span
>
</li>
<li>3. 支持批量上传</li>
<!-- <li>
4.
<span class="el-link el-link--danger">同名覆盖</span>
表示相同名称的文档(含扩展名),直接用新文档文件替换,以达到更新文档文件的目的。
</li> -->
<li>
4. 目前支持的文档类型:
<div v-if="wordExt.length > 0">
<img src="/static/images/word_24.png" alt="Word文档" />
{{ wordExt.join('') }}
</div>
<div v-if="pptExt.length > 0">
<img src="/static/images/ppt_24.png" alt="PPT文档" />
{{ pptExt.join('') }}
</div>
<div v-if="excelExt.length > 0">
<img src="/static/images/excel_24.png" alt="Excel文档" />
{{ excelExt.join('') }}
</div>
<div v-if="otherExt.length > 0">
<img src="/static/images/other_24.png" alt="其他文档" />
{{ otherExt.join('') }}
</div>
<div v-if="allowExt.includes('.txt')">
<img src="/static/images/text_24.png" alt="TXT文档" />
.txt
</div>
<div v-if="allowExt.includes('.pdf')">
<img src="/static/images/pdf_24.png" alt="PDF文档" />
.pdf
</div>
</li>
<li>
5. 上传遇到问题需要帮助?请查看
<nuxt-link
to="/article/help"
class="el-link el-link--primary"
>文库帮助</nuxt-link
>
<nuxt-link
to="/article/feedback"
class="el-link el-link--primary"
>意见反馈</nuxt-link
>
</li>
<li>
6. 为营造绿色网络环境,严禁上传含有淫秽色情及低俗信息等文档
</li>
<li>
7.
对于涉嫌侵权和违法违规的文档,本站有权在不提前通知的情况下对文档进行删除,您在本站上传文档,表示认同该条款
</li>
</ul>
</div>
</el-col>
</el-row>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import { formatBytes } from '~/utils/utils'
import { createDocument } from '~/api/document'
import { canIUploadDocument } from '~/api/user'
import { uploadDocument } from '~/api/attachment'
export default {
data() {
return {
canIUploadDocument: false,
document: {
category_id: [],
price: 0,
overwrite: false,
},
maxDocumentSize: 50 * 1024 * 1024,
fileList: [],
filesMap: {},
loading: false,
percentAge: 0,
allowExt: [
'.doc',
'.docx',
'.rtf',
'.wps',
'.odt',
'.dot',
'.ppt',
'.pptx',
'.pps',
'.ppsx',
'.dps',
'.odp',
'.pot',
'.xls',
'.xlsx',
'.csv',
'.tsv',
'.et',
'.ods',
'.epub',
'.umd',
'.chm',
'.mobi',
'.txt',
'.pdf',
],
wordExtEnum: ['.doc', '.docx', '.rtf', '.wps', '.odt', '.dot'],
pptExtEnum: ['.ppt', '.pptx', '.pps', '.ppsx', '.dps', '.odp', '.pot'],
excelExtEnum: ['.xls', '.xlsx', '.csv', '.tsv', '.et', '.ods'],
otherExtEnum: ['.epub', '.umd', '.chm', '.mobi'],
wordExt: [],
pptExt: [],
excelExt: [],
otherExt: [],
totalFiles: 0, // 总个数
totalFailed: 0, // 失败个数
totalSuccess: 0, // 成功个数
totalDone: 0, // 完成个数
}
},
head() {
return {
title: '上传文档 - ' + this.settings.system.title || 'MOREDOC · 魔豆文库',
meta: [
{
hid: 'keywords',
name: 'keywords',
content: `上传文档,${this.settings.system.sitename},${this.settings.system.keywords}`,
},
{
hid: 'description',
name: 'description',
content: this.settings.system.description,
},
],
}
},
computed: {
...mapGetters('user', ['token', 'user']),
...mapGetters('category', ['categoryTrees']),
...mapGetters('setting', ['settings']),
},
async created() {
const res = await canIUploadDocument()
if (res.status === 200) {
this.canIUploadDocument = true
}
try {
this.maxDocumentSize =
(this.settings.security.max_document_size || 50) * 1024 * 1024
} catch (error) {
console.log(error)
}
try {
this.allowExt =
this.settings.security.document_allowed_ext || this.allowExt
} catch (error) {
console.log(error)
}
this.allowExt.map((ext) => {
if (this.wordExtEnum.includes(ext)) {
this.wordExt.push(ext)
} else if (this.pptExtEnum.includes(ext)) {
this.pptExt.push(ext)
} else if (this.excelExtEnum.includes(ext)) {
this.excelExt.push(ext)
} else if (this.otherExtEnum.includes(ext)) {
this.otherExt.push(ext)
}
})
},
methods: {
formatBytes,
...mapActions('user', ['getUser']),
onChange(file) {
const name = file.name.toLowerCase()
const ext = file.name.substring(file.name.lastIndexOf('.')).toLowerCase()
if (!this.allowExt.includes(ext)) {
this.$message.warning(`${file.name} 不支持的文件格式,忽略该文件`)
return
}
if (file.size > this.maxDocumentSize) {
this.$message.warning(
`${file.name} 文件大小${formatBytes(
file.size
)} 超过限制(最大${formatBytes(this.maxDocumentSize)}),忽略该文件`
)
return
}
// 文件不能大于指定的文件大小
if (
!this.filesMap[name] &&
this.allowExt.includes(ext) &&
file.size <= this.maxDocumentSize
) {
const item = {
...file,
title: file.name.substring(0, file.name.lastIndexOf('.')),
ext,
price: this.document.price || 0,
progressStatus: 'success',
error: '',
percentage: 0,
attachment_id: 0,
}
this.filesMap[name] = item
this.fileList.push(item)
this.totalFiles = this.fileList.length
}
},
handleRemove(index) {
this.filesMap[this.fileList[index].name] = null
this.fileList.splice(index, 1)
},
onSubmit() {
this.$refs.form.validate((valid) => {
if (valid) {
// 1. 验证文件是否存在
if (this.fileList.length === 0) {
this.$message.error('请先选择要上传的文档')
return
}
this.totalFiles = this.fileList.length
this.totalDone = 0
this.loading = true
try {
// 取消之前上传的请求不然一直pending新请求会没法发送
window.uploadDocumentCancel.map((c) => c())
window.uploadDocumentCancel = []
} catch (error) {}
// chrome 等浏览器同一域名下最多只能同时发起 6 个请求,所以这里将 fileList 拆分成多个数组,每个数组的长度为 2以便控制并发每次只同时上传 2 个文件
const fileList = this.fileList.reduce((prev, cur, index) => {
const i = Math.floor(index / 2)
prev[i] = prev[i] || []
prev[i].push(cur)
return prev
}, [])
fileList.reduce(async (prev, cur) => {
await prev
await Promise.all(
cur.map(async (file) => {
await this.uploadDocument(file)
})
)
}, Promise.resolve())
}
})
},
clearAllFiles() {
if (this.loading) {
return
}
this.fileList = []
this.filesMap = {}
this.$refs.upload.clearFiles()
},
async uploadDocument(file) {
if (file.percentage === 100 && file.attachment_id) {
// 不用再次上传
this.createDocument(file)
this.totalDone++
return
}
file.error = ''
file.progressStatus = 'success'
const formData = new FormData()
formData.append('file', file.raw)
try {
const res = await uploadDocument(formData, {
onUploadProgress: (progressEvent) => {
file.percentage = parseInt(
(progressEvent.loaded / progressEvent.total) * 100
)
},
// timeout: 1000 * 6,
})
if (res.status === 200) {
file.attachment_id = res.data.data.id || 0
this.createDocument(file)
this.totalSuccess++
} else {
file.progressStatus = 'exception'
file.error = res.data.message || res.statusText
this.$message.error(`《${file.name}》${file.error}`)
this.totalFailed++
}
} catch (error) {
file.progressStatus = 'exception'
file.error = '上传失败或超时,请重试'
this.$message.error(`《${file.name}》${file.error}`)
this.totalFailed++
}
this.totalDone++
if (this.totalDone === this.totalFiles) {
this.loading = false
}
},
async createDocument(doc) {
const createDocumentRequest = {
overwrite: this.document.overwrite,
category_id: this.document.category_id,
document: [
{
title: doc.title,
price: doc.price,
attachment_id: doc.attachment_id,
},
],
}
const res = await createDocument(createDocumentRequest)
if (res.status === 200) {
// 从 fileList 中剔除 attachment_id 与当前文档相同的文档
this.$message.success(`《${doc.title}》上传成功`)
this.fileList = this.fileList.filter((item) => {
return item.attachment_id !== doc.attachment_id && doc.attachment_id
})
// 过滤 filesMap 中的文档
this.filesMap = Object.keys(this.filesMap).reduce((acc, key) => {
if (this.filesMap[key].attachment_id !== doc.attachment_id) {
acc[key] = this.filesMap[key]
}
return acc
}, {})
} else {
this.$message.error(`《${doc.title}》上传失败 ` + res.data.message)
}
},
},
}
</script>
<style lang="scss">
.page-upload {
.el-table {
.el-input-number {
width: 120px;
}
}
.el-progress {
position: absolute;
width: 100%;
bottom: -1px;
}
.error-tips {
font-size: 12px;
}
.upload-tips {
line-height: 180%;
border-left: 1px dashed rgb(252, 155, 91);
ul,
li {
list-style: none;
margin: 0;
padding: 0;
}
li {
margin-bottom: 10px;
}
.el-link {
top: -2px;
}
img {
position: relative;
top: 7px;
}
}
}
@media screen and (max-width: $mobile-width) {
.page-upload {
.part-left {
width: 100% !important;
.el-upload {
display: block;
.el-upload-dragger {
width: 100% !important;
}
}
}
.part-right {
width: 100% !important;
margin-top: 20px;
li {
margin-bottom: 0;
}
}
}
}
</style>