AppendFileUpload copy 3.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. <template>
  2. <el-dialog :title="isPreview ? '预览附件' : '上传附件'" center :visible.sync="fileUploadVisible" width="600px"
  3. :before-close="dialogBeforeClose" append-to-body :close-on-press-escape="false" :close-on-click-modal="false">
  4. <div class="dialog-box scroll-bar" v-loading="loading">
  5. <el-upload v-if="!isPreview" ref="upload" action="#" :multiple="true" :limit="5" :http-request="oss_upload"
  6. :headers="$xtoken" :accept="acceptFile" :auto-upload="false" :on-change="handleChange"
  7. :on-preview="onFilePreView" :on-remove="handleRemove" :on-success="handleSuccess"
  8. :before-upload="beforeFilesUpload" :file-list="fileList">
  9. <span v-if="fileList.length >= 5" class="fontColorC">
  10. <strong style="color: red;">*</strong>
  11. (附件上传数量已经达到上限)
  12. </span>
  13. <el-button v-if="fileList.length < 5 && isOwner" slot="trigger" size="small"
  14. type="primary">选取文件</el-button>
  15. </el-upload>
  16. <template v-else>
  17. <div v-if="members && members.length > 0">
  18. <div class="member-item" v-for="member in members" :key="member.employeeId">
  19. <div class="employee-name flex-box-ce" style="justify-content: space-between;">
  20. <span>
  21. <i class="el-icon-user"></i>
  22. {{ employeeMap[member.employeeId].name }}
  23. </span>
  24. &nbsp;
  25. &nbsp;
  26. <span>
  27. <i class="el-icon-time"></i>
  28. {{ member.updateTime }}
  29. </span>
  30. </div>
  31. <div class="file-item" v-for="file in member.files" :key="file">
  32. <el-link type="primary" @click="onFilePreView2(file)">
  33. <i class="el-icon-document"></i>
  34. {{ parseUrlFile(file).fileName + parseUrlFile(file).ext }}
  35. </el-link>
  36. </div>
  37. </div>
  38. </div>
  39. <div v-else class="fontColorC">暂无附件,去上传</div>
  40. </template>
  41. </div>
  42. <el-dialog title="文件预览" :visible.sync="fileViewerDialogVisible" fullscreen append-to-body @open="onDialogOpen"
  43. :before-close="fileViewerDialogBeforeClose">
  44. <previewFile v-if="fileViewerDialogVisible" :file="currentFile" />
  45. </el-dialog>
  46. <div slot="footer">
  47. <el-button type="primary" size="small" @click="comfirm()">确 定</el-button>
  48. <!-- <el-button size="small" @click="cancel()">取 消</el-button> -->
  49. </div>
  50. </el-dialog>
  51. </template>
  52. <script>
  53. import axiosUpload from '@/utils/axiosUpload'
  54. import uploadOss from '@/components/upload';
  55. import { mapGetters } from 'vuex';
  56. import { _debounce } from '@/utils/auth';
  57. import axios from 'axios' // axios 兼容 IE;如不需要可换回 fetch
  58. import previewFile from './previewFile.vue';
  59. import moment from 'moment'
  60. export default {
  61. components: {
  62. uploadOss,
  63. previewFile
  64. },
  65. model: {
  66. prop: 'fileUploadVisible',
  67. event: 'close-dialog'
  68. },
  69. props: {
  70. goalId: {
  71. type: Number | String,
  72. default: 0
  73. },
  74. fileUploadVisible: {
  75. type: Boolean,
  76. default: false
  77. },
  78. files: {
  79. type: Array,
  80. default: () => []
  81. },
  82. members: {
  83. type: Array,
  84. default: () => []
  85. },
  86. isAllowUpload: {
  87. type: Boolean,
  88. default: true
  89. },
  90. isPreview: {
  91. type: Boolean,
  92. default: false
  93. },
  94. ownerId: {
  95. type: String | Number,
  96. default: ''
  97. },
  98. },
  99. data() {
  100. return {
  101. employeeMap: this.$getEmployeeMap(),
  102. loading: false,
  103. fileList: [],
  104. uploadFileList: [],
  105. imgUrl: '',
  106. uploadData: {
  107. ...this.$xtoken
  108. },
  109. initFiles: [],
  110. processLength: 0,
  111. showProcess: false,
  112. currentRow: null,
  113. acceptFile: '.jpg, .jpeg, .png, .gif, .bmp, .pdf, .JPG, .JPEG, .PBG, .GIF, .BMP, .PDF, .doc, .docx, .xls, .XLSX',
  114. currentFile: null,
  115. fileViewerDialogVisible: false,
  116. config: null,
  117. lastAction: "",
  118. }
  119. },
  120. computed: {
  121. ...mapGetters(['user_info']),
  122. isOwner() {
  123. return this.ownerId == this.user_info.id
  124. }
  125. },
  126. mounted() {
  127. if (this.fileUploadVisible) {
  128. this.initFiles = this.files
  129. this.uploadFileList = this.files
  130. if (this.files.length > 0) {
  131. this.fileList = this.files.map(file => ({
  132. uid: file,
  133. url: file,
  134. name: this.parseUrlFile(file).fileName + this.parseUrlFile(file).ext,
  135. type: this.parseUrlFile(file).ext.replace(".", ""),
  136. }));
  137. console.log(this.fileList)
  138. }
  139. // this.batchLoad();
  140. }
  141. },
  142. methods: {
  143. parseUrlFile(url) {
  144. const [, fileName, ext] = url.match(/\/([^/?#]+)(\.\w+)(?:[?#]|$)/) || [];
  145. return { fileName, ext };
  146. },
  147. onFilePreView(file) {
  148. this.currentFile = null;
  149. let index = file.name.indexOf(".");
  150. let suffix = file.name.substr(index + 1, file.url.length - 1); //文件后缀名
  151. let imgFiles = ['BMP', 'GIF', 'PNG', 'JPEG', 'JPG', 'bmp', 'gif', 'png', 'jpeg', 'jpg'];
  152. if (imgFiles.includes(suffix)) {
  153. this.imgUrl = ''
  154. this.imgUrl = file.url;
  155. this.$viewerApi({
  156. images: [this.imgUrl]
  157. })
  158. } else {
  159. let currentFile = {
  160. name: file.name,
  161. url: file.url,
  162. type: suffix
  163. }
  164. this.currentFile = currentFile
  165. this.fileViewerDialogVisible = true
  166. // window.open(file.url, '_blank');
  167. }
  168. },
  169. onFilePreView2(fileUrl) {
  170. this.currentFile = null;
  171. let fileObj = this.parseUrlFile(fileUrl);
  172. let type = fileObj.ext.replace(".", "")
  173. let imgFiles = ['BMP', 'GIF', 'PNG', 'JPEG', 'JPG', 'bmp', 'gif', 'png', 'jpeg', 'jpg'];
  174. if (imgFiles.includes(type)) {
  175. this.imgUrl = ''
  176. this.imgUrl = fileUrl;
  177. this.$viewerApi({
  178. images: [this.imgUrl]
  179. })
  180. } else {
  181. let currentFile = {
  182. name: fileObj.fileName + fileObj.ext,
  183. url: fileUrl,
  184. type
  185. }
  186. this.currentFile = currentFile;
  187. this.fileViewerDialogVisible = true;
  188. }
  189. },
  190. handleSuccess: _debounce(function (response, file, fileList) {
  191. this.uploadFileList = this.fileList.map(item => {
  192. return item.url;
  193. });
  194. if (this.isAllowUpload) {
  195. this.comfirmUploadFiles(this.uploadFileList)
  196. } else {
  197. this.$emit('close-dialog', false)
  198. this.$emit('confirm', this.uploadFileList)
  199. }
  200. // this.isAllowUpload && this.comfirmUploadFiles(this.uploadFileList);
  201. }),
  202. /* 新增文件(选择后) */
  203. handleChange(file, fileList) {
  204. console.log("文件改变了")
  205. const $ext_list = ['BMP', 'GIF', 'PNG', 'JPEG', 'JPG', 'bmp', 'gif', 'png', 'jpeg', 'jpg', 'xlsx', 'xls', 'doc', 'docx', 'pdf', 'XLSX', 'XLS', 'DOC', 'DOCX', 'PDF'];
  206. const isLt2M = file.size / 1024 / 1024 < 5;
  207. let len = file.name.split('.').length - 1;
  208. const $ext_name = file.name.split('.')[len];
  209. let isFile = $ext_list.indexOf($ext_name) != -1;
  210. if (!isLt2M) {
  211. return this.$message.error('文件大小不能超过 5MB!');
  212. }
  213. if (!isFile) {
  214. return this.$message.warning('文件格式上传错误,仅支持上传xlsx,xls,doc,docx,pdf)');
  215. }
  216. this.lastAction = 'hasChange'
  217. const url = URL.createObjectURL(file.raw)
  218. file.url = url
  219. // this.fileList = fileList
  220. // this.uploadFileList = this.fileList.map(item => {
  221. // return item.url;
  222. // });
  223. },
  224. handleRemove(file, fileList) {
  225. this.lastAction = 'hasChange'
  226. URL.revokeObjectURL(file.url) // 释放内存
  227. this.fileList = fileList; // 用来显示的文件列表
  228. this.uploadFileList = this.fileList.map(item => {
  229. return item.url;
  230. });
  231. // this.isAllowUpload && this.comfirmUploadFiles(this.uploadFileList);
  232. },
  233. beforeFilesUpload(file) {
  234. const $ext_list = ['BMP', 'GIF', 'PNG', 'JPEG', 'JPG', 'bmp', 'gif', 'png', 'jpeg', 'jpg', 'xlsx', 'xls', 'doc', 'docx', 'pdf', 'XLSX', 'XLS', 'DOC', 'DOCX', 'PDF'];
  235. const isLt2M = file.size / 1024 / 1024 < 5;
  236. let len = file.name.split('.').length - 1;
  237. const $ext_name = file.name.split('.')[len];
  238. let isFile = $ext_list.indexOf($ext_name) != -1;
  239. if (!isLt2M) {
  240. this.$message.error('文件大小不能超过 5MB!');
  241. }
  242. if (!isFile) {
  243. this.$message.warning('文件格式上传错误,仅支持上传xlsx,xls,doc,docx,pdf)');
  244. }
  245. return isFile && isLt2M;
  246. },
  247. // 手动上传
  248. submitUpload() {
  249. this.$refs.upload.submit();
  250. },
  251. get_sign(callback) {
  252. // 测试添加 'https://intesys.cms.g107.com'
  253. axiosUpload('get', 'https://intesys.cms.g107.com/integral.php/Api/get_signature').then(res => {
  254. this.config = res.data.data
  255. callback()
  256. })
  257. },
  258. beforeFilesUpload(file) {
  259. const $ext_list = ['BMP', 'GIF', 'PNG', 'JPEG', 'JPG', 'bmp', 'gif', 'png', 'jpeg', 'jpg', 'xlsx', 'xls', 'doc', 'docx', 'pdf', 'XLSX', 'XLS', 'DOC', 'DOCX', 'PDF'];
  260. const isLt2M = file.size / 1024 / 1024 < 5;
  261. let len = file.name.split('.').length - 1;
  262. const $ext_name = file.name.split('.')[len];
  263. let isFile = $ext_list.indexOf($ext_name) != -1;
  264. if (!isLt2M) {
  265. this.$message.error('文件大小不能超过 5MB!');
  266. }
  267. if (!isFile) {
  268. this.$message.warning('文件格式上传错误,仅支持上传xlsx,xls,doc,docx,pdf)');
  269. }
  270. return isFile && isLt2M;
  271. },
  272. oss_upload(upload_obj) {
  273. this.get_sign(() => {
  274. // this.beforeUpload_all(upload_obj.file).then(res=>{
  275. // this.upload(res)
  276. // });
  277. this.upload(upload_obj.file)
  278. })
  279. },
  280. upload(item) {
  281. let self = this
  282. const photo = item // 获取图片对象
  283. const photoName = item.name // 原图片的名称
  284. const url = 'https://integralsys.oss-cn-shenzhen.aliyuncs.com'
  285. let date = moment().format('YYYY/MM/DD')
  286. let param = new FormData()
  287. let site_id
  288. if (this.coursePath) {
  289. site_id = this.coursePath
  290. } else {
  291. site_id = this.$getCache('site_info').id
  292. }
  293. let randomStr = this.random_string(32)
  294. let key = 'intesys/' + site_id + '/' + date + '/' + randomStr + '/' + photoName
  295. // let loadingInstance = Loading.service({});
  296. param.append('Filename', photoName)
  297. param.append('key', key)
  298. param.append('policy', this.config.policy)
  299. param.append('OSSAccessKeyId', this.config.accessid)
  300. param.append('success_action_status', '200') // 不要问为什么,照做
  301. param.append('callback', this.config.callback)
  302. param.append('signature', this.config.signature)
  303. param.append('file', photo) // 这个**切记**一定要放到最后去 append ,不然阿里云会一直报 key 的错误
  304. axios.post(url, param, {
  305. headers: {
  306. 'Content-Type': 'multipart/form-data'
  307. }
  308. }).then(response => {
  309. if (response.data.Status == 'Ok') {
  310. self.fileList.push({
  311. name: randomStr + photoName, url: 'https://integralsys.oss-cn-shenzhen.aliyuncs.com/' + key, name: item.name, response: {
  312. url: 'https://integralsys.oss-cn-shenzhen.aliyuncs.com/' + key
  313. }
  314. })
  315. self.handleSuccess({ status: 1, url: 'https://integralsys.oss-cn-shenzhen.aliyuncs.com/' + key, file_name: randomStr + photoName }, item, self.fileList)
  316. }
  317. })
  318. this.showProcess = false
  319. },
  320. random_string(len) {
  321. len = len || 32
  322. var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
  323. var maxPos = chars.length
  324. var pwd = ''
  325. for (let i = 0; i < len; i++) {
  326. pwd += chars.charAt(Math.floor(Math.random() * maxPos))
  327. }
  328. return pwd
  329. },
  330. comfirmUploadFiles(files) {
  331. let url = `/okr/og/goals/files/${this.user_info.site_id}`
  332. let data = {
  333. goalId: this.goalId,
  334. files
  335. }
  336. this.$http.post(url, data).then(res => {
  337. let { code } = res
  338. if (code !== 1)
  339. return this.$message.error(res.message || "上传附件失败")
  340. else {
  341. this.$emit('close-dialog', false)
  342. this.$emit('confirm', this.uploadFileList)
  343. }
  344. this.currentRow = res.data
  345. })
  346. },
  347. cancel() {
  348. this.$emit('close-dialog', false)
  349. },
  350. comfirm() {
  351. console.log(this.fileList)
  352. // if (this.lastAction === 'hasChange') {
  353. // this.submitUpload()
  354. // // if (this.isAllowUpload) {
  355. // // this.comfirmUploadFiles(this.uploadFileList)
  356. // // } else {
  357. // // this.$emit('close-dialog', false)
  358. // // this.$emit('confirm', this.uploadFileList)
  359. // // }
  360. // }
  361. // else
  362. // this.$emit('close-dialog', false)
  363. },
  364. dialogBeforeClose() {
  365. this.$emit('close-dialog', false)
  366. },
  367. /* 主函数:接收 url 数组 → 批量回显 */
  368. async batchLoad() {
  369. const urlArr = this.files || []; // 已成功上传的文件列表
  370. if (!urlArr.length) return
  371. /* 并发下载并转成 File */
  372. const taskList = urlArr.map(u => this.url2File(u))
  373. try {
  374. const files = await Promise.all(taskList)
  375. this.fileList = files // 一次性塞给 el-upload
  376. // this.$message.success(`已回显 ${files.length} 个文件`)
  377. } catch (e) {
  378. this.$message.error('下载失败:' + e.message)
  379. }
  380. },
  381. /* 单条 url → File → el-upload 对象 */
  382. async url2File(url) {
  383. // this.loading = true
  384. // 1. 下载成 blob(axios 写法,IE 可用)
  385. const { data: blob, headers } = await axios.get(url, {
  386. responseType: 'blob'
  387. })
  388. // 2. 从 Content-Disposition 或 url 取文件名
  389. let fileName = 'unknown'
  390. const disposition = headers['content-disposition']
  391. if (disposition && disposition.includes('filename=')) {
  392. fileName = decodeURIComponent(disposition.split('filename=')[1].replace(/"/g, ''))
  393. } else {
  394. const temp = url.split('/').pop().split('?')[0]
  395. fileName = decodeURIComponent(temp)
  396. }
  397. // 3. 生成 File 对象
  398. const file = new File([blob], fileName, { type: blob.type })
  399. // this.loading = false
  400. // 4. 返回 el-upload 需要的格式
  401. return {
  402. name: fileName,
  403. url: url, // 预览图
  404. raw: file, // File 对象
  405. status: 'success'
  406. }
  407. },
  408. /* 拦截浏览器返回键 */
  409. blockPopstate(e) {
  410. // 阻止默认返回行为
  411. e.preventDefault();
  412. // 可以选择关闭 dialog
  413. this.fileViewerDialogBeforeClose();
  414. // 重新插入一条记录,防止再次返回
  415. history.pushState(null, null, location.href);
  416. },
  417. fileViewerDialogBeforeClose() {
  418. this.currentFile = null
  419. this.fileViewerDialogVisible = false
  420. },
  421. /* 开启 dialog 时:禁用返回键 */
  422. onDialogOpen() {
  423. // 立即往 history 里插一条空记录,拦截返回
  424. history.pushState(null, null, location.href);
  425. // 监听 popstate
  426. window.addEventListener('popstate', this.blockPopstate, false);
  427. },
  428. }
  429. }
  430. </script>
  431. <style scoped lang="scss">
  432. .dialog-box {
  433. width: 100%;
  434. max-height: 600px;
  435. overflow-y: auto;
  436. }
  437. .member-item {
  438. padding: 10px;
  439. box-sizing: border-box;
  440. border-bottom: 1px solid #f1f1f1;
  441. .employee-name {
  442. font-size: 14px;
  443. margin-bottom: 10px;
  444. }
  445. .file-item {
  446. margin-bottom: 10px;
  447. }
  448. }
  449. </style>