diff --git a/package.json b/package.json
index 9494fd4..fa321e8 100644
--- a/package.json
+++ b/package.json
@@ -44,6 +44,7 @@
"jszip": "^3.7.1",
"moment": "^2.27.0",
"node-polyfill-webpack-plugin": "^2.0.1",
+ "streamsaver": "^2.0.6",
"node-sass": "^4.14.1",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
diff --git a/public/mitm.html b/public/mitm.html
new file mode 100644
index 0000000..508a9c3
--- /dev/null
+++ b/public/mitm.html
@@ -0,0 +1,167 @@
+
+
diff --git a/public/sw.js b/public/sw.js
new file mode 100644
index 0000000..dd75c1a
--- /dev/null
+++ b/public/sw.js
@@ -0,0 +1,130 @@
+/* global self ReadableStream Response */
+
+self.addEventListener('install', () => {
+ self.skipWaiting()
+})
+
+self.addEventListener('activate', event => {
+ event.waitUntil(self.clients.claim())
+})
+
+const map = new Map()
+
+// This should be called once per download
+// Each event has a dataChannel that the data will be piped through
+self.onmessage = event => {
+ // We send a heartbeat every x second to keep the
+ // service worker alive if a transferable stream is not sent
+ if (event.data === 'ping') {
+ return
+ }
+
+ const data = event.data
+ const downloadUrl = data.url || self.registration.scope + Math.random() + '/' + (typeof data === 'string' ? data : data.filename)
+ const port = event.ports[0]
+ const metadata = new Array(3) // [stream, data, port]
+
+ metadata[1] = data
+ metadata[2] = port
+
+ // Note to self:
+ // old streamsaver v1.2.0 might still use `readableStream`...
+ // but v2.0.0 will always transfer the stream through MessageChannel #94
+ if (event.data.readableStream) {
+ metadata[0] = event.data.readableStream
+ } else if (event.data.transferringReadable) {
+ port.onmessage = evt => {
+ port.onmessage = null
+ metadata[0] = evt.data.readableStream
+ }
+ } else {
+ metadata[0] = createStream(port)
+ }
+
+ map.set(downloadUrl, metadata)
+ port.postMessage({ download: downloadUrl })
+}
+
+function createStream (port) {
+ // ReadableStream is only supported by chrome 52
+ return new ReadableStream({
+ start (controller) {
+ // When we receive data on the messageChannel, we write
+ port.onmessage = ({ data }) => {
+ if (data === 'end') {
+ return controller.close()
+ }
+
+ if (data === 'abort') {
+ controller.error('Aborted the download')
+ return
+ }
+
+ controller.enqueue(data)
+ }
+ },
+ cancel (reason) {
+ console.log('user aborted', reason)
+ port.postMessage({ abort: true })
+ }
+ })
+}
+
+self.onfetch = event => {
+ const url = event.request.url
+
+ // this only works for Firefox
+ if (url.endsWith('/ping')) {
+ return event.respondWith(new Response('pong'))
+ }
+
+ const hijacke = map.get(url)
+
+ if (!hijacke) return null
+
+ const [ stream, data, port ] = hijacke
+
+ map.delete(url)
+
+ // Not comfortable letting any user control all headers
+ // so we only copy over the length & disposition
+ const responseHeaders = new Headers({
+ 'Content-Type': 'application/octet-stream; charset=utf-8',
+
+ // To be on the safe side, The link can be opened in a iframe.
+ // but octet-stream should stop it.
+ 'Content-Security-Policy': "default-src 'none'",
+ 'X-Content-Security-Policy': "default-src 'none'",
+ 'X-WebKit-CSP': "default-src 'none'",
+ 'X-XSS-Protection': '1; mode=block',
+ 'Cross-Origin-Embedder-Policy': 'require-corp'
+ })
+
+ let headers = new Headers(data.headers || {})
+
+ if (headers.has('Content-Length')) {
+ responseHeaders.set('Content-Length', headers.get('Content-Length'))
+ }
+
+ if (headers.has('Content-Disposition')) {
+ responseHeaders.set('Content-Disposition', headers.get('Content-Disposition'))
+ }
+
+ // data, data.filename and size should not be used anymore
+ if (data.size) {
+ console.warn('Depricated')
+ responseHeaders.set('Content-Length', data.size)
+ }
+
+ let fileName = typeof data === 'string' ? data : data.filename
+ if (fileName) {
+ console.warn('Depricated')
+ // Make filename RFC5987 compatible
+ fileName = encodeURIComponent(fileName).replace(/['()]/g, escape).replace(/\*/g, '%2A')
+ responseHeaders.set('Content-Disposition', "attachment; filename*=UTF-8''" + fileName)
+ }
+
+ event.respondWith(new Response(stream, { headers: responseHeaders }))
+
+ port.postMessage({ debug: 'Download started' })
+}
diff --git a/src/api/trials.js b/src/api/trials.js
index f0b2689..0564097 100644
--- a/src/api/trials.js
+++ b/src/api/trials.js
@@ -3610,4 +3610,21 @@ export function getAuthorizationCodeInfo(params) {
method: 'get',
params
})
+}
+
+// 获取下载信息
+export function getDownloadSubjectVisitStudyInfo(params) {
+ return request({
+ url: `/Patient/getDownloadSubjectVisitStudyInfo`,
+ method: 'get',
+ params
+ })
+}
+// 下载成功回调
+export function downloadImageSuccess(params) {
+ return request({
+ url: `/Patient/downloadImageSuccess`,
+ method: 'get',
+ params
+ })
}
\ No newline at end of file
diff --git a/src/utils/stream.js b/src/utils/stream.js
new file mode 100644
index 0000000..88f7d59
--- /dev/null
+++ b/src/utils/stream.js
@@ -0,0 +1,106 @@
+import streamSaver from "streamsaver";
+import "streamsaver/examples/zip-stream.js";
+// import store from '@/store'
+streamSaver.mitm = `${window.location.origin}/mitm.html?version=2.0.0`
+// 下载文件并压缩
+function zipFiles(zipName, files) {
+ return new Promise((resolve) => {
+ try {
+ console.log("同步下载打包开始时间:" + new Date());
+ // store.dispatch('trials/setUnLock', true)
+ files = formatFiles(files)
+ // 创建压缩文件输出流
+ const zipFileOutputStream = streamSaver.createWriteStream(zipName);
+ // 创建下载文件流
+ const fileIterator = files.values();
+ const readableZipStream = new ZIP({
+ async pull(ctrl) {
+ const fileInfo = fileIterator.next();
+ if (fileInfo.done) {//迭代终止
+ ctrl.close();
+ } else {
+ let { name, url } = fileInfo.value;
+ url = decodeUtf8(url);
+ return fetch(url).then(res => {
+ ctrl.enqueue({
+ name,
+ stream: () => res.body
+ });
+ })
+ }
+ }
+ });
+ if (window.WritableStream && readableZipStream.pipeTo) {
+ // 开始下载
+ readableZipStream
+ .pipeTo(zipFileOutputStream)
+ .then(() => {
+ console.log("同步下载打包结束时间:" + new Date());
+ // store.dispatch('trials/setUnLock', false)
+ resolve(true)
+ }).catch(err => {
+ console.log(err);
+ resolve(false)
+ });
+ } else {
+ resolve(false)
+ }
+ } catch (err) {
+ console.log(err);
+ resolve(false)
+ }
+ })
+
+}
+// 下载文件并修改名称
+async function updateFile(file, name) {
+ return new Promise(async resolve => {
+ try {
+ // store.dispatch('trials/setUnLock', true)
+ const fileOutputStream = streamSaver.createWriteStream(name);
+ file = decodeUtf8(file);
+ let res = await fetch(file);
+ res.body.pipeTo(fileOutputStream).then(() => {
+ // store.dispatch('trials/setUnLock', true)
+ resolve(true)
+ }).catch(err => {
+ console.log(err)
+ resolve(false)
+ });
+ } catch (err) {
+ console.log(err)
+ resolve(false)
+ }
+ })
+}
+// 同名文件修改名称
+function formatFiles(files) {
+ let fileObj = {};
+ files.forEach(file => {
+ let arr = Object.keys(fileObj);
+ if (!~arr.indexOf(file.name)) {
+ fileObj[file.name] = 1;
+ } else {
+ let name = file.name;
+ file.name = name.split(".")[0] + `(${fileObj[name]})` + name
+ .substring(name.lastIndexOf('.'))
+ .toLocaleLowerCase()
+ fileObj[name]++;
+ }
+ })
+ return files;
+}
+function decodeUtf8(bytes) {
+ let str = bytes.split('?');
+ let str2 = str[0].split('/');
+ let name = str2[str2.length - 1];
+ name = encodeURIComponent(name);
+ str.shift();
+ str2.pop();
+ return str2.join("/") + '/' + name;
+}
+export async function downLoadFile(file, name, type = 'file') {
+ if (type === 'zip') return await zipFiles(name, file);
+ return await updateFile(file, name)
+
+}
\ No newline at end of file
diff --git a/src/views/trials/trials-panel/hirVisit/index.vue b/src/views/trials/trials-panel/hirVisit/index.vue
index e1e4819..bfa762f 100644
--- a/src/views/trials/trials-panel/hirVisit/index.vue
+++ b/src/views/trials/trials-panel/hirVisit/index.vue
@@ -83,7 +83,7 @@