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>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<custom-modal v-model="visible" :width="1200" title="Load Data From File">
|
<custom-modal v-model="visible" :width="1200" title="Load Data From File">
|
||||||
|
<!-- 支持 File System Access 的情况 -->
|
||||||
<a-table
|
<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"
|
:data-source="list"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:loading="loading_list"
|
:loading="loading_list"
|
||||||
|
@ -13,7 +73,7 @@
|
||||||
<div class="file-name file-ellipsis" :title="text" @dblclick="handleFileSelect('_S_', record)">
|
<div class="file-name file-ellipsis" :title="text" @dblclick="handleFileSelect('_S_', record)">
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</div>
|
</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 }}
|
{{ text }}
|
||||||
</phd-select> -->
|
</phd-select> -->
|
||||||
</template>
|
</template>
|
||||||
|
@ -141,9 +201,12 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import JSZip from 'jszip'
|
import JSZip from 'jszip'
|
||||||
import FileSaver from 'file-saver'
|
|
||||||
import PhdSelect from '../PHDSelect.vue'
|
|
||||||
import { getAction, postAction } from '../../../../api/manage'
|
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 = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: 'SampleData',
|
title: 'SampleData',
|
||||||
|
@ -208,8 +271,11 @@ const columns_file = [
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const canUseFilePicker = FilePicker.canUse()
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { PhdSelect },
|
mixins: [ModalMixin],
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -223,7 +289,7 @@ export default {
|
||||||
columns_file,
|
columns_file,
|
||||||
loading_file: false,
|
loading_file: false,
|
||||||
loading_list: false,
|
loading_list: false,
|
||||||
list: this.getInitialList(),
|
list: [],
|
||||||
ipagination: {
|
ipagination: {
|
||||||
current: 1,
|
current: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
|
@ -247,6 +313,8 @@ export default {
|
||||||
selectionRows_edit: [],
|
selectionRows_edit: [],
|
||||||
tableType: 'multiple',
|
tableType: 'multiple',
|
||||||
searchName: '',
|
searchName: '',
|
||||||
|
|
||||||
|
canUseFilePicker,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -290,6 +358,137 @@ export default {
|
||||||
this.tableType = str === '_S_' ? 'multiple' : 'single'
|
this.tableType = str === '_S_' ? 'multiple' : 'single'
|
||||||
this.getSpectrumFile({ pageNo: 1, pageSize: 10 })
|
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() {
|
onSearchFileName() {
|
||||||
this.getSpectrumFile({ pageNo: 1, pageSize: 10 })
|
this.getSpectrumFile({ pageNo: 1, pageSize: 10 })
|
||||||
},
|
},
|
||||||
|
@ -429,15 +628,9 @@ export default {
|
||||||
handleCancel() {
|
handleCancel() {
|
||||||
this.visible = false
|
this.visible = false
|
||||||
},
|
},
|
||||||
},
|
|
||||||
computed: {
|
beforeModalOpen() {
|
||||||
visible: {
|
this.handleReset()
|
||||||
get() {
|
|
||||||
return this.value
|
|
||||||
},
|
|
||||||
set(val) {
|
|
||||||
this.$emit('input', val)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -451,7 +644,7 @@ export default {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
|
||||||
background-color: #00e170;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
.status_true {
|
.status_true {
|
||||||
background-color: #00e170;
|
background-color: #00e170;
|
||||||
|
@ -525,6 +718,9 @@ export default {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
user-select: none;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
}
|
}
|
||||||
/deep/.ant-table-tbody .ant-table-row td {
|
/deep/.ant-table-tbody .ant-table-row td {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user