feat: 增加Load From File调用File System Access功能
This commit is contained in:
parent
8c974aaf35
commit
85f4ccb995
56
src/utils/FilePicker.js
Normal file
56
src/utils/FilePicker.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* 本地文件选择
|
||||
*/
|
||||
export class FilePicker {
|
||||
/**
|
||||
* 接口是否可用
|
||||
* @returns { Boolean }
|
||||
*/
|
||||
static canUse() {
|
||||
return !!(window.showDirectoryPicker && window.showOpenFilePicker)
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择一个目录
|
||||
* @returns { Promise<FileSystemDirectoryHandle> }
|
||||
*/
|
||||
static chooseDirectory() {
|
||||
if (!this.canUse()) {
|
||||
throw new Error('Not Support showDirectoryPicker')
|
||||
}
|
||||
return window.showDirectoryPicker()
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择一个文件
|
||||
* @param { Boolean } multiple
|
||||
* @param { { description?: string; accept?: { [key: string]: string[]; } } } types
|
||||
* @returns { Promise<FileSystemFileHandle[]> }
|
||||
*/
|
||||
static chooseFile(multiple, types) {
|
||||
if (!this.canUse()) {
|
||||
throw new Error('Not Support showOpenFilePicker')
|
||||
}
|
||||
const pickerOpts = {
|
||||
multiple,
|
||||
types,
|
||||
excludeAcceptAllOption: true
|
||||
}
|
||||
return window.showOpenFilePicker(pickerOpts)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断一个文件是否在某个目录下
|
||||
* @param {FileSystemDirectoryHandle} directoryHandle
|
||||
* @param {FileSystemFileHandle} fileHandle
|
||||
*/
|
||||
static async isFileInDirectory(directoryHandle, fileHandle) {
|
||||
const relativePaths = await directoryHandle.resolve(fileHandle)
|
||||
|
||||
if (relativePaths === null) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,3 +31,27 @@ export const showSaveFileModal = (data, ext) => {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 读文件
|
||||
* @param {File} file
|
||||
* @param { 'arrayBuffer' | 'text' | 'dataURL' | 'binaryString'} fileType
|
||||
*/
|
||||
export const readFile = (file, fileType = 'text') => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileReader = new FileReader()
|
||||
const category = {
|
||||
arrayBuffer: 'readAsArrayBuffer',
|
||||
text: 'readAsText',
|
||||
dataURL: 'readAsDataURL',
|
||||
binaryString: 'readAsBinaryString'
|
||||
}
|
||||
fileReader[category[fileType]](file)
|
||||
fileReader.onload = event => {
|
||||
resolve(event.target.result)
|
||||
}
|
||||
fileReader.onerror = error => {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
117
src/utils/phdHelper.js
Normal file
117
src/utils/phdHelper.js
Normal file
|
@ -0,0 +1,117 @@
|
|||
export class PHDParser {
|
||||
/**
|
||||
* 数据类型
|
||||
*/
|
||||
dataType = ''
|
||||
|
||||
/**
|
||||
* 类型 B G 等
|
||||
*/
|
||||
fileType = ''
|
||||
|
||||
/**
|
||||
* 质量 FULL 等
|
||||
*/
|
||||
qualify = ''
|
||||
|
||||
/**
|
||||
* 生存时间
|
||||
*/
|
||||
|
||||
liveTime = ''
|
||||
|
||||
/**
|
||||
* sample 谱的文件名
|
||||
*/
|
||||
sampleFileName = ''
|
||||
|
||||
/**
|
||||
* 其他文件名
|
||||
*/
|
||||
otherFileNames = []
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {string} text
|
||||
*/
|
||||
constructor(text) {
|
||||
const getLine = this.readTextLine(text)
|
||||
const dataTypeLine = getLine(4)
|
||||
const fileTypeLine = getLine(6)
|
||||
const fileNameLine = getLine(8)
|
||||
const liveTimeLine = getLine(18)
|
||||
|
||||
this.dataType = this.getOnymousData(dataTypeLine)[0]
|
||||
const fileType = this.splitLineText(fileTypeLine)
|
||||
this.fileType = fileType[2]
|
||||
this.qualify = fileType[4]
|
||||
|
||||
const liveTime = parseFloat(this.splitLineText(liveTimeLine)[3]).toFixed(1)
|
||||
this.liveTime = liveTime.indexOf('.0') == -1 ? liveTime : liveTime.slice(0, -2)
|
||||
|
||||
// 如果是 Beta 谱
|
||||
if (this.fileType == 'B') {
|
||||
// 如果解析的是sample 文件,则获取其他三个文件
|
||||
if (this.dataType == PHD_DATA_TYPE.SAMPLEPHD) {
|
||||
const fileNames = this.getFileNames(fileNameLine)
|
||||
this.sampleFileName = fileNames.splice(0, 1)[0]
|
||||
this.otherFileNames = fileNames
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 将文本按行分割
|
||||
* @param {string} text
|
||||
* @returns { (line: number) => string }
|
||||
*/
|
||||
readTextLine(text) {
|
||||
const splited = text.split('\n')
|
||||
return line => {
|
||||
return splited[line - 1]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 具名 行的数据
|
||||
* @param {string} text
|
||||
* @example DATA_TYPE SAMPLEPHD 返回['SAMPLEPHD']
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getOnymousData(text) {
|
||||
return text.split(' ').slice(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一行中的文本按空格切割
|
||||
* @param {string} text
|
||||
*/
|
||||
splitLineText(text) {
|
||||
return text.replace(/\s+/g, ',').split(',')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全部文件名
|
||||
* @param {string} text
|
||||
*/
|
||||
getFileNames(text) {
|
||||
const unHandledfileNames = this.splitLineText(text)
|
||||
const fileTypes = ['S', 'D', 'G']
|
||||
const fileNames = unHandledfileNames
|
||||
.filter(fileName => fileName)
|
||||
.map((fileName, index) => {
|
||||
fileName = fileName.replace(/(\d{4})\/(\d{2})\/(\d{2})-(\d{2}):(\d{2})/, '$1$2$3_$4$5')
|
||||
return `${fileName}_${fileTypes[index]}_${this.qualify}`
|
||||
})
|
||||
return fileNames
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PHD 类型
|
||||
*/
|
||||
export const PHD_DATA_TYPE = {
|
||||
QCPHD: 'QCPHD',
|
||||
DETBKPHD: 'DETBKPHD',
|
||||
SAMPLEPHD: 'SAMPLEPHD',
|
||||
GASBKPHD: 'GASBKPHD'
|
||||
}
|
|
@ -1,7 +1,67 @@
|
|||
<template>
|
||||
<div>
|
||||
<custom-modal v-model="visible" :width="1200" title="Load Data From File">
|
||||
<!-- 支持 File System Access 的情况 -->
|
||||
<a-table
|
||||
v-if="canUseFilePicker"
|
||||
:dataSource="list"
|
||||
:columns="columns"
|
||||
:pagination="false"
|
||||
bordered
|
||||
:scroll="{ y: 450 }"
|
||||
>
|
||||
<template slot="sampleData" slot-scope="text, record">
|
||||
<div
|
||||
class="file-name file-ellipsis"
|
||||
:title="text && text.fileName"
|
||||
@dblclick="useFilePicker('sampleFileName', record)"
|
||||
>
|
||||
{{ text && text.fileName }}
|
||||
</div>
|
||||
</template>
|
||||
<template slot="gasBkData" slot-scope="text, record">
|
||||
<div
|
||||
:class="['file-ellipsis', !record.gasFileName ? 'file-name-color' : '']"
|
||||
:title="text && text.fileName"
|
||||
@dblclick="useFilePicker('gasFileName', record)"
|
||||
>
|
||||
{{ text && text.fileName }}
|
||||
</div>
|
||||
</template>
|
||||
<template slot="detBkData" slot-scope="text, record">
|
||||
<div
|
||||
:class="['file-ellipsis', !record.detFileName ? 'file-name-color' : '']"
|
||||
:title="text && text.fileName"
|
||||
@dblclick="useFilePicker('detFileName', record)"
|
||||
>
|
||||
{{ text && text.fileName }}
|
||||
</div>
|
||||
</template>
|
||||
<template slot="qcData" slot-scope="text, record">
|
||||
<div
|
||||
:class="['file-ellipsis', !record.qcFileName ? 'file-name-color' : '']"
|
||||
:title="text && text.fileName"
|
||||
@dblclick="useFilePicker('qcFileName', record)"
|
||||
>
|
||||
{{ text && text.fileName }}
|
||||
</div>
|
||||
</template>
|
||||
<template slot="status" slot-scope="text, record">
|
||||
<span
|
||||
:class="[
|
||||
record.sampleFileName
|
||||
? record.fileType == 'B' && !(record.gasFileName && record.detFileName)
|
||||
? 'status_false'
|
||||
: 'status_true'
|
||||
: '',
|
||||
'status',
|
||||
]"
|
||||
></span>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 不支持 File System Access 的情况 -->
|
||||
<a-table
|
||||
v-else
|
||||
:data-source="list"
|
||||
:columns="columns"
|
||||
:loading="loading_list"
|
||||
|
@ -13,7 +73,7 @@
|
|||
<div class="file-name file-ellipsis" :title="text" @dblclick="handleFileSelect('_S_', record)">
|
||||
{{ text }}
|
||||
</div>
|
||||
<!-- <phd-select type="file" @change="handleFileChange(record, 'sampleData', $event)" @select="handleFileSelect" :title="text && text.name">
|
||||
<!-- <phd-select type="file" @change="handleFileChange(record, 'sampleData', $event)" @select="handleFileSelect" :title="text && text.fileName">
|
||||
{{ text }}
|
||||
</phd-select> -->
|
||||
</template>
|
||||
|
@ -141,9 +201,12 @@
|
|||
|
||||
<script>
|
||||
import JSZip from 'jszip'
|
||||
import FileSaver from 'file-saver'
|
||||
import PhdSelect from '../PHDSelect.vue'
|
||||
import { getAction, postAction } from '../../../../api/manage'
|
||||
import { FilePicker } from '@/utils/FilePicker'
|
||||
import { readFile } from '@/utils/file'
|
||||
import { PHDParser, PHD_DATA_TYPE } from '@/utils/phdHelper'
|
||||
import ModalMixin from '@/mixins/ModalMixin'
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'SampleData',
|
||||
|
@ -208,8 +271,11 @@ const columns_file = [
|
|||
align: 'left',
|
||||
},
|
||||
]
|
||||
|
||||
const canUseFilePicker = FilePicker.canUse()
|
||||
|
||||
export default {
|
||||
components: { PhdSelect },
|
||||
mixins: [ModalMixin],
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
|
@ -223,7 +289,7 @@ export default {
|
|||
columns_file,
|
||||
loading_file: false,
|
||||
loading_list: false,
|
||||
list: this.getInitialList(),
|
||||
list: [],
|
||||
ipagination: {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
|
@ -247,6 +313,8 @@ export default {
|
|||
selectionRows_edit: [],
|
||||
tableType: 'multiple',
|
||||
searchName: '',
|
||||
|
||||
canUseFilePicker,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -290,6 +358,137 @@ export default {
|
|||
this.tableType = str === '_S_' ? 'multiple' : 'single'
|
||||
this.getSpectrumFile({ pageNo: 1, pageSize: 10 })
|
||||
},
|
||||
|
||||
// 用新的文件操作接口
|
||||
async useFilePicker(column, record) {
|
||||
// 如果没有选择sampleFile,则不允许选其他的
|
||||
if (column !== 'sampleFileName' && (!record.sampleFileName || record.fileType !== 'B')) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.directoryHanlder) {
|
||||
this.chooseFile(column, record)
|
||||
} else {
|
||||
try {
|
||||
this.directoryHanlder = await FilePicker.chooseDirectory()
|
||||
this.chooseFile(column, record)
|
||||
} catch {}
|
||||
}
|
||||
},
|
||||
|
||||
async chooseFile(column, record) {
|
||||
try {
|
||||
const [fileHandle] = await FilePicker.chooseFile(false, [{ accept: { 'text/phd': ['.phd'] } }])
|
||||
try {
|
||||
const isFileInDirectory = await FilePicker.isFileInDirectory(this.directoryHanlder, fileHandle)
|
||||
if (!isFileInDirectory) {
|
||||
this.$message.warn('File and Directory Not in Same')
|
||||
return
|
||||
}
|
||||
|
||||
const file = await fileHandle.getFile()
|
||||
const text = await readFile(file)
|
||||
const phdParser = new PHDParser(text)
|
||||
console.log('%c [ phdParser ]-313', 'font-size:13px; background:pink; color:#bf2c9f;', phdParser)
|
||||
const match = this.fileNameAndColumnMatch(column, phdParser.dataType)
|
||||
if (!match) {
|
||||
this.$message.warn('File Type Error')
|
||||
return
|
||||
}
|
||||
|
||||
record.fileType = phdParser.fileType
|
||||
|
||||
// 如果是sample
|
||||
if (column == 'sampleFileName') {
|
||||
const { sampleFileName, otherFileNames, liveTime } = phdParser
|
||||
record.sampleFileName = {
|
||||
file,
|
||||
fileName: `${sampleFileName}_${liveTime}.PHD`,
|
||||
}
|
||||
|
||||
const iter = await this.directoryHanlder.values()
|
||||
const fileList = []
|
||||
|
||||
let result = await iter.next()
|
||||
while (!result.done) {
|
||||
const fileHandle = result.value
|
||||
const file = await fileHandle.getFile()
|
||||
fileList.push(file)
|
||||
result = await iter.next()
|
||||
}
|
||||
|
||||
const nameKeys = ['detFileName', 'gasFileName']
|
||||
|
||||
otherFileNames.forEach((otherFileName, index) => {
|
||||
// 在所有文件中查找 名字含有 从sample文件解析出的文件名 的文件
|
||||
const findFile = fileList.find((file) => {
|
||||
return file.name.includes(otherFileName)
|
||||
})
|
||||
|
||||
if (findFile) {
|
||||
record[nameKeys[index]] = {
|
||||
file: findFile,
|
||||
fileName: findFile.name,
|
||||
}
|
||||
} else {
|
||||
record[columns[index]] = undefined
|
||||
}
|
||||
})
|
||||
|
||||
// 查找 QC 文件
|
||||
let qcFileInfo = undefined
|
||||
const qcFileList = fileList.filter((file) => file.name.includes('_Q_'))
|
||||
const compareFileName = sampleFileName.slice(0, 23)
|
||||
for (const qcFile of qcFileList) {
|
||||
if (qcFile.name.slice(0, 23) <= compareFileName) {
|
||||
qcFileInfo = {
|
||||
file: qcFile,
|
||||
fileName: qcFile.name,
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
record.qcFileName = qcFileInfo
|
||||
}
|
||||
// 非sample
|
||||
else {
|
||||
record[column] = {
|
||||
file,
|
||||
fileName: file.name,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
} catch {}
|
||||
},
|
||||
|
||||
/**
|
||||
* 判断文件和列是否匹配
|
||||
* @param {*} fileType
|
||||
* @param {*} dataType
|
||||
*/
|
||||
fileNameAndColumnMatch(column, dataType) {
|
||||
let currDataType = ''
|
||||
switch (column) {
|
||||
case 'sampleFileName':
|
||||
currDataType = PHD_DATA_TYPE.SAMPLEPHD
|
||||
break
|
||||
case 'gasFileName':
|
||||
currDataType = PHD_DATA_TYPE.GASBKPHD
|
||||
break
|
||||
case 'detFileName':
|
||||
currDataType = PHD_DATA_TYPE.DETBKPHD
|
||||
break
|
||||
case 'qcFileName':
|
||||
currDataType = PHD_DATA_TYPE.QCPHD
|
||||
break
|
||||
}
|
||||
|
||||
return currDataType == dataType
|
||||
},
|
||||
|
||||
onSearchFileName() {
|
||||
this.getSpectrumFile({ pageNo: 1, pageSize: 10 })
|
||||
},
|
||||
|
@ -429,15 +628,9 @@ export default {
|
|||
handleCancel() {
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
visible: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('input', val)
|
||||
},
|
||||
|
||||
beforeModalOpen() {
|
||||
this.handleReset()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -451,7 +644,7 @@ export default {
|
|||
border-radius: 50%;
|
||||
margin-top: 8px;
|
||||
|
||||
background-color: #00e170;
|
||||
background-color: transparent;
|
||||
}
|
||||
.status_true {
|
||||
background-color: #00e170;
|
||||
|
@ -525,6 +718,9 @@ export default {
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
}
|
||||
/deep/.ant-table-tbody .ant-table-row td {
|
||||
cursor: pointer;
|
||||
|
|
Loading…
Reference in New Issue
Block a user