Merge branch 'feature-analysis-RLR-renpy' of http://git.hivekion.com:3000/xiaoguangbin/AnalysisSystemForRadionuclide_vue into master-dev

This commit is contained in:
orgin 2023-10-09 20:09:28 +08:00
commit 0d39833584
8 changed files with 639 additions and 76 deletions

55
src/utils/FilePicker.js Normal file
View File

@ -0,0 +1,55 @@
/**
* 本地文件选择
*/
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 || relativePaths.length > 1) {
return false
} else {
return true
}
}
}

View File

@ -1,5 +1,6 @@
import { Modal } from 'ant-design-vue'
import { saveAs } from 'file-saver'
import JSZip from 'jszip'
/**
* 弹窗填入文件名保存文件
@ -31,3 +32,56 @@ export const showSaveFileModal = (data, ext) => {
}
})
}
/**
* 读文件
* @param {File} file
* @param { 'arrayBuffer' | 'text' | 'dataURL' | 'binaryString'} fileType
* @returns {Promise<string | ArrayBuffer}
*/
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)
}
})
}
/**
* 压缩文件
* @param {Array<File>} fileList
* @param {string} zipName
* @returns
*/
export const zipFile = async (fileList, zipName) => {
const zip = new JSZip()
const promises = []
const readFileWithName = async file => {
const data = await readFile(file, 'arrayBuffer')
return {
fileName: file.name,
data
}
}
fileList.forEach(file => {
promises.push(readFileWithName(file))
})
const result = await Promise.all(promises)
result.forEach(res => {
zip.file(res.fileName, res.data)
})
const content = await zip.generateAsync({ type: 'blob' })
return new File([content], zipName, { type: content.type })
}

182
src/utils/phdHelper.js Normal file
View File

@ -0,0 +1,182 @@
/**
* PHD 类型
*/
export const PHD_DATA_TYPE = {
QCPHD: 'QCPHD',
DETBKPHD: 'DETBKPHD',
SAMPLEPHD: 'SAMPLEPHD',
GASBKPHD: 'GASBKPHD'
}
/**
* 判断是不是sample
* @param {*} dataType
* @returns
*/
export const isSample = dataType => {
return ['SAMPLEPHD', 'SPHDP', 'SPHDF'].includes(dataType)
}
export class PHDParser {
/**
* 根据Block解析出的结果集
*/
blocks = {
baseInfo: []
}
/**
* 数据类型
*/
dataType = ''
/**
* 类型 B G
*/
fileType = ''
/**
* 质量 FULL
*/
qualify = ''
/**
* 生存时间
*/
liveTime = ''
/**
* sample 谱的文件名
*/
sampleFilePrefix = ''
/**
* 其他文件名
*/
otherFilePrefixes = []
/**
* 该文件是不是sample
*/
isSample = false
/**
* 构造函数
* @param {string} text
*/
constructor(text) {
this.splitByBlock(text)
this.dataType = this.getBaseInfoByTag('DATA_TYPE')[0]
this.isSample = isSample(this.dataType)
const headerInfo = this.getBlockInfo('Header')
const headerInfoLine1 = this.splitLineText(headerInfo[0])
this.fileType = headerInfoLine1[2]
this.qualify = headerInfoLine1[4]
const liveTime = parseFloat(this.getBlockStr('Acquisition', 0, 3)).toFixed(1)
this.liveTime = liveTime.indexOf('.0') == -1 ? liveTime : liveTime.slice(0, -2)
// 如果解析的是sample 文件,则解析相关联的文件
if (this.isSample) {
const filePrefixes = this.getFilePrefixes(headerInfo[2])
this.sampleFilePrefix = filePrefixes.splice(0, 1)[0]
this.otherFilePrefixes = filePrefixes
}
}
/**
* 根据块类型分割
* @param {string} text
*/
splitByBlock(text) {
const lines = (text = text.replace(/\r{0,1}\n/g, '\n').split('\n'))
let blockType = 'baseInfo'
for (const line of lines) {
// 如果以#开头
if (line.startsWith('#')) {
blockType = line.slice(1)
if (blockType.startsWith('Header')) {
blockType = 'Header'
}
this.blocks[blockType] = []
continue
}
this.blocks[blockType].push(line.trim())
}
}
/**
* 根据块名拿到块内容
* @param {string} blockName
* @returns {Array<string>}
*/
getBlockInfo(blockName) {
return this.blocks[blockName]
}
/**
* 在baseInfo中根据tag查找值
* @param {*} tag
* @returns {Array<string>}
*/
getBaseInfoByTag(tag) {
const baseInfo = this.getBlockInfo('baseInfo')
const map = new Map()
baseInfo.forEach(line => {
if (line.startsWith(tag)) {
map.set(tag, this.getOnymousData(line))
}
})
return map.get(tag)
}
/**
* 根据行号和位置在block中查找字符
* @param {string} blockName
* @param {number} lineNum
* @param {number} index
* @returns {string}
*/
getBlockStr(blockName, lineNum, index) {
const blockInfo = this.getBlockInfo(blockName)
const lineText = blockInfo[lineNum]
const splited = this.splitLineText(lineText)
return splited[index]
}
/**
* 获取 具名 行的数据
* @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
*/
getFilePrefixes(text) {
const unHandledfilePrefixes = this.splitLineText(text)
const filePrefixes = unHandledfilePrefixes
.filter(filePrefix => filePrefix)
.map(filePrefix => {
filePrefix = filePrefix.replace(/(\d{4})\/(\d{2})\/(\d{2})-(\d{2}):(\d{2})(:\d{2}\.\d)?/, '$1$2$3_$4$5')
return filePrefix + '_'
})
return filePrefixes
}
}

View File

@ -197,7 +197,7 @@ export default {
*/
async handleLoad() {
if (!this.selectedRowKeys.length) {
this.$message.warn('Please Select Databases To Load')
this.$message.warn('Please Select Sample To Load')
return
}
this.selectedRowKeys = []

View File

@ -1,7 +1,68 @@
<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, index">
<div
class="file-name file-ellipsis"
:title="text && text.fileName"
@dblclick="useFilePicker('sampleFileName', record, index)"
>
{{ text && text.fileName }}
</div>
</template>
<template slot="gasBkData" slot-scope="text, record, index">
<div
:class="['file-ellipsis', text && text.file ? '' : 'file-name-color']"
:title="text && text.fileName"
@dblclick="useFilePicker('gasFileName', record, index)"
>
{{ text && text.fileName }}
</div>
</template>
<template slot="detBkData" slot-scope="text, record, index">
<div
:class="['file-ellipsis', text && text.file ? '' : 'file-name-color']"
:title="text && text.fileName"
@dblclick="useFilePicker('detFileName', record, index)"
>
{{ text && text.fileName }}
</div>
</template>
<template slot="qcData" slot-scope="text, record, index">
<div
:class="['file-ellipsis', text && text.file ? '' : 'file-name-color']"
:title="text && text.fileName"
@dblclick="useFilePicker('qcFileName', record, index)"
>
{{ text && text.fileName }}
</div>
</template>
<template slot="status" slot-scope="text, record">
<template v-if="record.sampleFileName">
<span
class="status"
:class="
record.fileType == 'B' &&
!(record.gasFileName && record.gasFileName.file && record.detFileName && record.detFileName.file)
? 'status_false'
: 'status_true'
"
></span>
</template>
</template>
</a-table>
<!-- 不支持 File System Access 的情况 -->
<a-table
v-else
:data-source="list"
:columns="columns"
:loading="loading_list"
@ -13,7 +74,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>
@ -61,6 +122,7 @@
<template slot="custom-footer">
<a-space>
<a-upload
v-if="!canUseFilePicker"
accept=".PHD,.phd"
:custom-request="handleUpload"
:multiple="true"
@ -70,7 +132,7 @@
<a-button type="primary" :loading="uploading"> Upload </a-button>
</a-upload>
<a-button type="primary" @click="handleReset">Reset</a-button>
<a-button type="primary" @click="handleLoad">Load</a-button>
<a-button :loading="isUploadingZip" type="primary" @click="handleLoad">Load</a-button>
<a-button type="primary" @click="handleCancel">Cancel</a-button>
</a-space>
</template>
@ -140,10 +202,12 @@
</template>
<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, zipFile } from '@/utils/file'
import { isSample, PHDParser, PHD_DATA_TYPE } from '@/utils/phdHelper'
import ModalMixin from '@/mixins/ModalMixin'
const columns = [
{
title: 'SampleData',
@ -208,8 +272,11 @@ const columns_file = [
align: 'left',
},
]
const canUseFilePicker = FilePicker.canUse()
export default {
components: { PhdSelect },
mixins: [ModalMixin],
props: {
value: {
type: Boolean,
@ -223,7 +290,8 @@ export default {
columns_file,
loading_file: false,
loading_list: false,
list: this.getInitialList(),
isUploadingZip: false,
list: [],
ipagination: {
current: 1,
pageSize: 10,
@ -247,6 +315,8 @@ export default {
selectionRows_edit: [],
tableType: 'multiple',
searchName: '',
canUseFilePicker,
}
},
methods: {
@ -290,6 +360,161 @@ export default {
this.tableType = str === '_S_' ? 'multiple' : 'single'
this.getSpectrumFile({ pageNo: 1, pageSize: 10 })
},
//
async useFilePicker(column, record, rowIndex) {
// sampleFile
if (column !== 'sampleFileName' && (!record.sampleFileName || record.fileType !== 'B')) {
return
}
if (this.directoryHanlder) {
this.chooseFile(column, record, rowIndex)
} else {
try {
this.directoryHanlder = await FilePicker.chooseDirectory()
this.chooseFile(column, record, rowIndex)
} catch {}
}
},
async chooseFile(column, record, rowIndex) {
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')
this.directoryHanlder = undefined
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 findIndex = this.list.findIndex(
(item) => item.sampleFileName && item.sampleFileName.file && item.sampleFileName.file.name == file.name
)
if (-1 !== findIndex && findIndex !== rowIndex) {
return
}
const { sampleFilePrefix, otherFilePrefixes, qualify, liveTime } = phdParser
record.sampleFileName = {
file,
fileName: `${sampleFilePrefix}S_${qualify}_${liveTime}.PHD`,
}
// Beta Gamma
if (phdParser.fileType == 'B') {
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']
const fileTypes = ['D', 'G']
otherFilePrefixes.forEach((otherFilePrefix, index) => {
const fileType = fileTypes[index]
// sample
const findFile = fileList.find((file) => {
return file.name.includes(`${otherFilePrefix}${fileType}_${qualify}`)
})
if (findFile) {
record[nameKeys[index]] = {
file: findFile,
fileName: findFile.name,
}
} else {
record[nameKeys[index]] = {
file: undefined,
fileName: `${otherFilePrefix}${fileType}.PHD`,
}
}
})
// QC
let qcFileInfo = {
file: undefined,
fileName: `${sampleFilePrefix}Q.PHD`,
}
const qcFileList = fileList.filter((file) => file.name.includes('_Q_'))
for (const qcFile of qcFileList) {
if (qcFile.name.slice(0, 23) <= sampleFilePrefix) {
qcFileInfo = {
file: qcFile,
fileName: qcFile.name,
}
} else {
break
}
}
record.qcFileName = qcFileInfo
} else {
Object.assign(record, {
gasFileName: undefined,
detFileName: undefined,
qcFileName: undefined,
})
}
}
// sample
else {
record[column] = {
file,
fileName: file.name,
}
}
} catch (error) {
console.log(error)
}
} catch {}
},
/**
* 判断文件和列是否匹配
* @param {*} column
* @param {*} dataType
*/
fileNameAndColumnMatch(column, dataType) {
let currDataType = ''
switch (column) {
case 'sampleFileName':
return isSample(dataType)
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 })
},
@ -370,28 +595,12 @@ export default {
beforeUpload(file, fileList) {
this.fileList = fileList
},
handleUpload({ file }) {
async handleUpload() {
this.uploading = true
this.fileNum += 1
if (this.fileNum == this.fileList.length) {
this.zipFile(this.fileList)
}
},
async zipFile(fileList, onProgress) {
const zip = new JSZip()
const promises = []
fileList.forEach((file) => {
promises.push(this.readAsArrayBuffer(file))
})
Promise.all(promises).then((result) => {
result.forEach((res) => {
zip.file(res.fileName, res.data)
})
zip.generateAsync({ type: 'blob' }).then((content) => {
let file = new File([content], 'test.zip', { type: content.type })
const formData = new FormData()
formData.append('file', file)
postAction('/spectrumFile/upload', formData).then((res) => {
const file = await zipFile(this.fileList, 'test.zip')
const res = await this.uploadZipFile(file)
this.uploading = false
this.fileNum = 0
if (res.success) {
@ -399,45 +608,80 @@ export default {
} else {
this.$message.warning(res.message)
}
})
})
})
}
},
async readAsArrayBuffer(file) {
return new Promise((resolve) => {
let reader = new FileReader()
reader.readAsArrayBuffer(file)
reader.onload = (e) => {
resolve({
fileName: file.name,
data: e.target.result,
})
}
})
// zip
async uploadZipFile(file) {
const formData = new FormData()
formData.append('file', file)
const res = await postAction('/spectrumFile/upload', formData)
return res
},
handleReset() {
this.list = this.getInitialList()
},
handleLoad() {
async handleLoad() {
//
if (this.canUseFilePicker) {
const propNames = ['sampleFileName', 'gasFileName', 'detFileName', 'qcFileName']
const files = []
for (const item of this.list) {
propNames.forEach((propName) => {
const value = item[propName]
if (value && value.file) {
files.push(value.file)
}
})
}
if (!files.length) {
this.$message.warn('File is Empty ')
return
}
const zipedFiles = await zipFile(files, 'test.zip')
this.isUploadingZip = true
try {
const { success, message } = await this.uploadZipFile(zipedFiles)
if (success) {
try {
const { success, result, message } = await getAction('/spectrumAnalysis/getFilesBySampleFile', {
fileName: this.list
.filter((item) => item.sampleFileName)
.map((item) => item.sampleFileName.file.name)
.join(','),
})
if (success) {
this.visible = false
this.$emit('loadFormFile', result)
} else {
this.$message.error(message)
}
} catch (error) {
console.error(error)
} finally {
this.isUploadingZip = false
}
} else {
this.$message.error(message)
}
} catch (error) {
console.error(error)
this.isUploadingZip = false
}
} else {
this.visible = false
this.$emit('loadFormFile', this.list)
}
},
handleCancel() {
this.visible = false
},
},
computed: {
visible: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
},
beforeModalOpen() {
this.handleReset()
},
},
}
@ -451,7 +695,7 @@ export default {
border-radius: 50%;
margin-top: 8px;
background-color: #00e170;
background-color: transparent;
}
.status_true {
background-color: #00e170;
@ -525,6 +769,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;

View File

@ -16,7 +16,7 @@
<div class="nuclide-library-settings">
<div class="nuclide-library-settings-select">
<title-over-border class="select-library" title="Select Library">
<a-radio-group v-model="model.libraryName" @change="handleSearch">
<a-radio-group v-model="model.libraryName" @change="handleChangeLibraryType">
<a-radio value="UserLibrary">User Library</a-radio>
<a-radio value="FULLLibrary">Full Library</a-radio>
<a-radio value="RelevantLibrary">Relevant Library</a-radio>
@ -275,6 +275,12 @@ export default {
}
},
// Library
handleChangeLibraryType() {
this.model.nuclideName = ''
this.handleSearch()
},
//
handleSearch() {
this.getInfo(true)

View File

@ -10,7 +10,8 @@
<title-over-border title="Format">
<a-radio-group v-model="saveFormat" class="format-radio-group">
<a-radio value="txt">Save as Txt</a-radio>
<a-radio value="excel">Save as Excel</a-radio>
<a-radio value="xls">Save as Excel</a-radio>
<a-radio value="html">Save as Html</a-radio>
</a-radio-group>
</title-over-border>
</div>
@ -19,7 +20,6 @@
</template>
<script>
import { downloadFile } from '../../../../api/manage'
import TitleOverBorder from '../TitleOverBorder.vue'
export default {
components: { TitleOverBorder },
@ -41,8 +41,7 @@ export default {
},
handleOk() {
console.log('%c [ save ]-22', 'font-size:13px; background:pink; color:#bf2c9f;')
downloadFile('', 'result.' + this.saveFormat, {})
this.$emit('save', this.saveFormat)
}
},
computed: {
@ -73,7 +72,7 @@ export default {
}
.format-radio-group {
.ant-radio-wrapper:first-child {
.ant-radio-wrapper:not(:last-child) {
margin-bottom: 10px;
}
}

View File

@ -74,7 +74,7 @@
<!-- Nuclide Activity and MDC 弹窗结束 -->
<!-- Save Setting 弹窗开始 -->
<save-setting-modal v-model="saveSettingModalVisible" />
<save-setting-modal v-model="saveSettingModalVisible" @save="handleSaveResultsToFile" />
<!-- Save Setting 弹窗结束 -->
<!-- 分析-设置弹窗开始 -->
@ -156,10 +156,7 @@
<!-- Beta-Gamma 的Comments 结束 -->
<!-- Beta-Gamma 的Energy Calibration开始 -->
<beta-gamma-energy-calibration-modal
v-model="betaGammaEnergyCalibrationModalVisible"
@sendInfo="getCheckFlag"
/>
<beta-gamma-energy-calibration-modal v-model="betaGammaEnergyCalibrationModalVisible" @sendInfo="getCheckFlag" />
<!-- Beta-Gamma 的Energy Calibration结束 -->
<!-- Beta-Gamma Extrapolation 弹窗开始 -->
@ -191,7 +188,7 @@
</div>
</template>
<script>
import { postAction } from '@/api/manage'
import { downloadFile, postAction } from '@/api/manage'
import GammaAnalysis from './gamma-analysis.vue'
import BetaGammaAnalysis from './beta-gamma-analysis.vue'
import SpectraListInMenu from './components/SpectraListInMenu.vue'
@ -460,8 +457,31 @@ export default {
},
//
handleSaveResultsToFile() {
this.saveSettingModalVisible = true
async handleSaveResultsToFile(saveFormat) {
const url =
saveFormat == 'xls'
? '/spectrumAnalysis/saveToExcel'
: saveFormat == 'txt'
? '/spectrumAnalysis/saveToTxt'
: saveFormat == 'html'
? '/spectrumAnalysis/saveToHTML'
: ''
if (!this.resultDisplayFlag) {
this.$message.warn('Please Analyse Spectrum First')
return
}
this.resultDisplayFlag.forEach((item) => {
this.params_toDB[`${item.nuclideName.toLowerCase()}Flag`] = item.nidFlag
})
this.params_toDB.sampleFileName = this.newSampleData.inputFileName
this.params_toDB.gasFileName = this.newSampleData.gasFileName
this.params_toDB.detFileName = this.newSampleData.detFileName
this.params_toDB.qcFileName = this.newSampleData.qcFileName
this.params_toDB.dbName = this.newSampleData.dbName
downloadFile(url, `result.${saveFormat}`, this.params_toDB, 'post')
},
/**
@ -478,7 +498,7 @@ export default {
const hideLoading = this.$message.loading('Saving...', 0)
try {
const { success, message } = await getAction('/gamma/saveToDB', {
fileName: this.sampleData.inputFileName
fileName: this.sampleData.inputFileName,
})
if (success) {
this.$message.success('Save Success')
@ -727,7 +747,7 @@ export default {
},
on: {
menuClick: () => {
this.handleSaveResultsToFile()
this.saveSettingModalVisible = true
},
submenuClick: ({ item, child }) => {
if (item.title == 'Save Results to DB') {