feat:分析

This commit is contained in:
houjunxiang
2025-11-11 09:58:12 +08:00
parent 5916b8c833
commit fb41fa9a03
9 changed files with 311 additions and 149 deletions

View File

@@ -9,28 +9,30 @@
<u-row gutter="8">
<u-col span="3">
<view class="content-title-name">
<text>待化验样品</text>
</view>
<up-tabs
:current="activeAssayTypeIndex"
:list="assayGroups"
lineColor="#5ac725"
:activeStyle="{
fontWeight: 'bold',
color: '#0055a2'
}"
:itemStyle="{
height: '35px'
}"
@change="handleAssayTypeChange"
></up-tabs>
<u-gap height="5" bg-color="#0055A2"></u-gap>
</u-col>
<u-col span="6">
<view class="content-title-name">
<view class="current-sample-code">{{ currentSample.sampleCode }}</view>
<view class="current-sample-code">{{ currentSampleData.sampleCode }}</view>
<view>数据采集或录入</view>
</view>
<u-gap height="5" bg-color="#0055A2"></u-gap>
</u-col>
<u-col span="3">
<view class="content-title-name">
<text>样品详情</text>
</view>
<u-gap height="5" bg-color="#0055A2"></u-gap>
</u-col>
</u-row>
<u-row gutter="8" align="top">
<u-col span="3">
<u-dropdown>
<u-dropdown style="height: 35px">
<u-dropdown-item
v-model="curParameterClassify"
:title="curParameterTitle"
@@ -39,9 +41,14 @@
@change="parameterClassifyChange"
></u-dropdown-item>
</u-dropdown>
<u-gap height="5" bg-color="#0055A2"></u-gap>
</u-col>
</u-row>
<u-row gutter="8" align="top">
<u-col span="3">
<scroll-view class="content-left-scroll" scroll-with-animation scroll-y :scroll-top="scrollTop">
<view
v-for="(sample, index) in leftList"
v-for="(sample, index) in sampleDataList"
:key="index"
class="u-tab-item"
:class="currentSampleIndex === index ? 'u-tab-item-active' : ''"
@@ -63,10 +70,10 @@
<view class="field-name" v-html="selectedField.title" />
<zzjc-num-keyboard
ref="myKeyboard"
v-show="selectedField.fillingWay == '1' && selectedField.type != 'select'"
v-show="selectedField.fillingWay == 'keyboard' && selectedField.type != 'select'"
:numKeyboardParam="numKeyboardParam"
></zzjc-num-keyboard>
<view v-if="selectedField.fillingWay == '2'" class="y-f">
<view v-if="selectedField.fillingWay == 'collect'" class="y-f">
<view class="auncel" @click="selectAuncel">
<view class="auncel-title"> {{ currentAuncel.code }}</view>
<view class="auncel-weight">
@@ -103,11 +110,10 @@
<view>
<scroll-view class="content-right-scroll" scroll-y scroll-with-animation>
<view>
<u-form :model="curSample" ref="uForm" label-width="140">
<template v-for="(fields, groupIndex) in fieldGroup" :key="'group_' + groupIndex">
<view>
<!-- 组名 -->
<view class="my-collapse" @click="fields.open = !fields.open">
<!-- <template v-for="(fields, groupIndex) in fieldGroup" :key="'group_' + groupIndex"> -->
<!-- <view> -->
<!-- 组名 -->
<!-- <view class="my-collapse" @click="fields.open = !fields.open">
<text class="title">{{ fields.title }}</text>
<u-icon :name="fields.open ? 'arrow-up' : 'arrow-down'"></u-icon>
</view>
@@ -115,37 +121,39 @@
class="content"
:id="'elId' + groupIndex"
:style="{ height: fields.open ? collaHeights[groupIndex] + 'px' : '0' }"
>
<view
class="form-item-my"
v-for="(field, fieldIndex) in fields.fields"
@click="fieldClick(field, groupIndex + '-' + fieldIndex)"
:key="groupIndex + '-' + fieldIndex"
v-show="field.hidden != 1"
:class="{
'selected-field': groupFieldIndex === groupIndex + '-' + fieldIndex,
'disabled-field': !field.isEdit
}"
>
<view
:class="['label-my', { 'label-high-light': field.highlight == 1 }]"
v-html="field.title"
></view>
<view class="content-my">
<!--
> -->
<up-collapse ref="collapseRef" :value="activeCollapses">
<up-collapse-item v-for="(fields, groupIndex) in fieldGroup" :title="fields.title">
<view
class="form-item-my"
v-for="(field, fieldIndex) in fields.fields"
@click="fieldClick(field, groupIndex + '-' + fieldIndex)"
:key="groupIndex + '-' + fieldIndex"
v-show="field.hidden != 1"
:class="{
'selected-field': groupFieldIndex === groupIndex + '-' + fieldIndex,
'disabled-field': !field.isEdit
}"
>
<view
:class="['label-my', { 'label-high-light': field.highlight == 1 }]"
v-html="field.title"
></view>
<view class="content-my">
<!--
如果是select渲染2个组件1个input1个picker.
field.valueText用于显示picker选中的文本
1键盘输入2天平3自动计算4文本输入
-->
<u-input
border="bottom"
style="width: 120px"
v-if="field.fillingWay == 4"
v-model="field.value"
placeholder="请输入"
/>
<u-input
border="bottom"
style="width: 120px"
v-if="field.fillingWay == 'input'"
v-model="field.value"
placeholder="请输入"
/>
<!-- <view v-if="field.type === 'select'" class="x-bc select-my" @click="field.showPicker = true">
<!-- <view v-if="field.type === 'select'" class="x-bc select-my" @click="field.showPicker = true">
<text v-if="field.valueText">{{ field.valueText }}</text>
<text v-else>请选择</text>
<u-icon name="arrow-down" size="20"></u-icon>
@@ -158,23 +166,22 @@
@cancel="field.showPicker = false"
@confirm="event => dicPickerConfirm(event, field)"
/> -->
<!--普通输入框 使用文本显示-->
<view class="content-my-text" v-if="field.dataType != 'select' && field.fillingWay != 4">
<text v-if="!field.value" class="content-my-text-placeholder">{{
!field.fillingWay || field.fillingWay == 3 ? '计算值' : '请输入'
}}</text>
<text
v-else
:class="['content-my-text-value', { 'field-high-light': field.highlight == 1 }]"
>{{ field.value }}</text
>
</view>
</view>
<!--普通输入框 使用文本显示-->
<view class="content-my-text" v-if="field.dataType != 'select' && field.fillingWay != 'input'">
<text v-if="!field.value" class="content-my-text-placeholder">{{
!field.fillingWay || field.fillingWay == 'calculate' ? '计算值' : '请输入'
}}</text>
<text v-else :class="['content-my-text-value', { 'field-high-light': field.highlight == 1 }]">{{
field.value
}}</text>
</view>
</view>
</view>
</template>
</u-form>
</up-collapse-item>
</up-collapse>
<!-- </view>
</view> -->
<!-- </template> -->
</view>
</scroll-view>
<u-button class="btn-operation" type="success" @click="saveDetail()">保存样品数据</u-button>
@@ -192,10 +199,10 @@
</template>
<script setup>
import { ref, reactive, computed, nextTick, getCurrentInstance } from 'vue'
import { ref, reactive, computed, nextTick, watch, getCurrentInstance } from 'vue'
import { onLoad, onBackPress, onShow, onHide, onUnload } from '@dcloudio/uni-app'
import request from '@/nx/request'
import { calcAnalysisValue } from '@/nx/helper/calcAnalysisValue'
import { calcAnalysisValue, handleRoundFiveNumber, calcRowAnalysisValue, math } from '@/nx/helper/calcAnalysisValue'
import { number } from 'mathjs'
import AuncelSelectPopup from '@/components/sample/auncel-select-popup.vue'
import { getTenantId } from '@/defaultBaseUrl'
@@ -212,9 +219,8 @@ const scrollTop = ref(0) //tab标题的滚动条位置
const menuHeight = ref(0) // 左边菜单的高度
const menuItemHeight = ref(0) // 左边菜单item的高度
const weightDataIsToZero = ref(false) //重量数据是否归零
const currentSample = ref({}) //当前样品
const currentSampleIndex = ref(0) // 预设当前项的值
let sampleDataList = []
let sampleDataList = ref([])
const title = ref('')
const numKeyboardParam = reactive({
decimal: '-1' //数字键盘小数位数,-1为不限制
@@ -230,7 +236,6 @@ const currentAuncel = ref({
})
let selectedField = ref({})
const groupFieldIndex = ref('') //分组的索引
const leftList = ref([])
const curSample = ref({})
const curParameterTitle = ref('选择字段分类')
const curParameterKey = ref('')
@@ -249,7 +254,7 @@ const auncelSelector = ref(null)
// 计算属性
const confirmWeightDisabled = computed(() => {
if (
currentSample.value.sampleCode &&
currentSampleData.value.sampleCode &&
currentAuncel.value.weightStable === 1 &&
Number(currentAuncel.value.weightData) > 0
) {
@@ -257,12 +262,12 @@ const confirmWeightDisabled = computed(() => {
}
return true
})
// 当前样品数据索引
const currentSampleDataIndex = computed(() => {
if (sampleDataList.length > 0) {
return sampleDataList.findIndex(item => item.businessAssayTaskDataId === currentSample.value.id)
// 当前样品数据
const currentSampleData = computed(() => {
if (sampleDataList.value.length > 0) {
return sampleDataList.value[currentSampleIndex.value]
}
return -1
return {}
})
// 当前操作字段索引
const currentFieldKey = computed(() => {
@@ -316,15 +321,17 @@ const parameterClassifyChange = (v, a) => {
}
const fieldClick = (field, key) => {
myKeyboard.value.clearNum()
if (field.fillingWay == 4 || !field.fillingWay || field.fillingWay == 3) return
if (field.fillingWay == 'input' || !field.fillingWay || field.fillingWay == 'calculate') return
selectedField.value = field
groupFieldIndex.value = key
if (myKeyboard.value) {
myKeyboard.value.clearNum()
}
//判断小数位数
let decimalPosition = field.decimalPosition
if (decimalPosition == null || decimalPosition < -1) decimalPosition = -1
numKeyboardParam.decimal = decimalPosition
if (field.fillingWay == 2) {
if (field.fillingWay == 'keyboard') {
listenDeviceData()
} else {
closeDeviceListener()
@@ -350,7 +357,7 @@ const autoNextField = () => {
//自动切换到下一个样品
const autoNextSample = () => {
if (leftList.value.length <= currentSampleIndex.value + 1) return
if (sampleDataList.value.length <= currentSampleIndex.value + 1) return
const index = currentSampleIndex.value + 1
groupFieldIndex.value = ''
selectedField.value = {}
@@ -376,7 +383,6 @@ const switchSample = async (index, autoFlag) => {
// groupFieldIndex.value = groupFieldIndex.value.split('-')[0] + '-'
// }
uni.showLoading({ title: '加载中...' })
currentSample.value = leftList.value[index]
setValueToField()
autoGenerateCupNum()
setTimeout(() => {
@@ -413,7 +419,7 @@ const getElRect = (elClass, maxRetry = 50) => {
//读取样品明细字段
const getDetailFieldsAndStatus = autoSelectNextField => {
const taskDetailId = currentSample.value.id
const taskDetailId = currentSampleData.value.id
//读取回收率配置
loadConRecoveryList()
optionParameterClassify.value = arr //字段分类
@@ -619,29 +625,96 @@ const saveAuncelData = () => {
autoNextField()
}, 100)
}
const dynamicFormData = {}
const saveDetail = async () => {
//检查杯号
if (!checkBh()) return
let params = {
businessAssayTaskId: taskId.value,
datas: sampleDataList
}
setValueToSample()
await nx.$api.assayTask.saveDetailValue(params)
let params = {
businessAssayTaskId: taskId.value
}
// 如果是空白样和标样就需要根据配置信息来计算质控样、分析样
if (activeAssayTypeKey.value === 'kby' || activeAssayTypeKey.value === 'by') {
// 处理其他页签的输入变化
const configInfomation = currentAssayType.value.configQCSampleMethodInfo
const row = sampleDataList.value[currentSampleIndex.value]
configInfomation.forEach(item => {
const sourceKey = item.source
const sourceValue = row[sourceKey]
if (sourceValue !== undefined) {
if (item.calcMethod === 'calculateAverageValue') {
item.value = calcAverageValue(sourceKey, currentAssayType.value.tableData)
} else {
item.value = handleRoundFiveNumber(row[sourceKey].value, row.decimalPosition)
}
// 如果处理后的值不为空,重新赋值该字段到其他样品类型下的样品上,并触发每一个样品的计算
if (item.value !== null) {
dynamicFormData[item.target] = item.value
updateTableDataByConfigFields()
}
}
})
params.formValue = JSON.stringify(dynamicFormData)
params.assayTaskAnalysisDataList = assayGroups.value.map(item => ({
datas: item.tableData,
analysisType: item.value
}))
} else {
const datas = sampleDataList.value[currentSampleIndex.value]
params.assayTaskAnalysisDataList = [{ datas, analysisType: activeAssayTypeKey.value }]
}
await nx.$api.assayTask.saveBatchSmpleAndQcAnalysis(params)
getSampleAnalysisByTaskId()
autoNextSample()
}
// 计算表格列平均值
function calcAverageValue(fieldIndex, tableData) {
const rows = tableData.map(row => row[fieldIndex])
const decimalPosition = rows[0].decimalPosition
const values = rows.map(row => Number(row.value))
if (values.some(item => item == null)) return null
return handleRoundFiveNumber(math.mean(values), decimalPosition)
}
// 表格数据更新后重新计算
function updateTableDataByConfigFields() {
let needCalcTabs = []
needCalcTabs = assayGroups.value.filter(t => t.value !== 'by' && t.value !== 'kby')
if (needCalcTabs.length === 0) return
for (const key in dynamicFormData) {
for (const tab of needCalcTabs) {
const columnObj = tab.columns.find(c => {
if (c.formula) {
let FromKey = c.formula.split(':')
return FromKey[1] === key
}
return false
})
if (!columnObj) continue
tab.tableData.forEach(row => {
// 赋值配置列参与计算
row[columnObj.fieldIndex].value = dynamicFormData[key]
calcRowAnalysisValue(row, columnObj, tab.columns)
})
}
}
}
// 把字段值保存回样品数据中
function setValueToSample() {
let fields = fieldGroup.value.flatMap(item => item.fields)
fields.forEach(item => {
if (sampleDataList[currentSampleDataIndex.value].hasOwnProperty(item.fieldIndex)) {
sampleDataList[currentSampleDataIndex.value][item.fieldIndex].value = item.value
if (
sampleDataList.value[currentSampleIndex.value].hasOwnProperty(item.fieldIndex) &&
nx.$test.object(sampleDataList.value[currentSampleIndex.value][item.fieldIndex])
) {
sampleDataList.value[currentSampleIndex.value][item.fieldIndex].value = item.value
}
// 初始化的时候保存杯号到样品中后续自动生成杯号用
if (item.title === cupNumKey && currentSampleIndex.value == 0) {
leftList.value[currentSampleIndex.value].cupNum = item.value
sampleDataList.value[currentSampleIndex.value].cupNum = item.value
}
})
}
@@ -676,14 +749,14 @@ const autoGenerateCupNum = () => {
//第一杯
if (current === 0) return
//取上一个样品的杯号
const sample = leftList.value[current - 1]
const sample = sampleDataList.value[current - 1]
cupNum = sample.cupNum
//杯号赋值到当前样品
putCupNum(Number(cupNum) + 1, current, Number(cupNum))
}
const putCupNum = (cupNum, sampleIndex, lastCupNum) => {
leftList.value[sampleIndex].cupNum = cupNum
sampleDataList.value[sampleIndex].cupNum = cupNum
for (const fields of fieldGroup.value) {
if (typeof fields === 'undefined') continue
//杯号
@@ -727,7 +800,7 @@ const putCupNum = (cupNum, sampleIndex, lastCupNum) => {
}
const loadConRecoveryList = () => {
const conBaseSampleId = currentSample.value.conBaseSampleId
const conBaseSampleId = currentSampleData.value.conBaseSampleId
const rParam = {
pageNo: 1,
pageSize: -1,
@@ -754,29 +827,60 @@ const loadConRecoveryList = () => {
})
}
const getAssayTaskSampleList = businessAssayTaskId => {
leftList.value = []
nx.$api.assayTask
.getAssayTaskDataList({ businessAssayTaskId })
.then(res => {
leftList.value = res
currentSampleIndex.value = 0
currentSample.value = leftList.value[0]
})
.catch(err => {
console.error(err)
})
// 分析样品组
let assayGroups = ref([])
const activeAssayTypeKey = ref('')
const activeAssayTypeIndex = ref(0)
//元素结果范围
let conRangeElementAnalysisList = []
const currentAssayType = computed(() => {
return assayGroups.value.find(item => item.value === activeAssayTypeKey.value)
})
watch(
() => currentAssayType.value,
() => {
sampleDataList.value = currentAssayType.value.tableData
fieldGroup.value = [{ open: true, fields: currentAssayType.value.columns, title: '样品分析' }]
setValueToField()
// getDomHeight()
activeCollapses.value = fieldGroup.value.map((_, index) => index)
}
)
const collapseRef = ref()
const activeCollapses = ref([])
function handleAssayTypeChange({ index, value }) {
activeAssayTypeKey.value = value
activeAssayTypeIndex.value = index
currentSampleIndex.value = 0
groupFieldIndex.value = ''
collapseRef.value.init()
}
// 获取任务指派单数据
async function getSampleAnalysisByTaskId() {
const { assayTaskAnalysisDataList, configAssayMethodProjectRangeList, businessAssayTasNo } =
await nx.$api.assayTask.batchSampleAndQcAnalysisByTaskId(taskId.value)
title.value = '样品分析-任务指派单:' + businessAssayTasNo
// 处理分析数据
assayGroups.value = assayTaskAnalysisDataList.map(group => {
// 必须深拷贝 datas防止多个表格共享引用
const tableData = JSON.parse(JSON.stringify(group.datas || []))
const columns = group.columns || []
async function getSampleAnalysisByTaskId(businessAssayTaskId) {
const data = await nx.$api.assayTask.getSampleAnalysisByTaskId(businessAssayTaskId)
sampleDataList = data.datas
let columns = data.columns.filter(item => item.paramNo)
fieldGroup.value = [{ open: true, fields: columns, title: '样品分析' }]
setValueToField()
title.value = '样品分析-任务指派单:' + data.businessAssayTasNo
getDomHeight()
autoNextField()
return {
value: group.analysisType,
name: group.analysisName,
columns,
tableData,
configQCSampleMethodInfo: group.configQCSampleMethod?.configInfomation
? JSON.parse(group.configQCSampleMethod['configInfomation'])['set']
: []
}
})
// 默认激活第一个 类型
if (!activeAssayTypeKey.value) {
activeAssayTypeKey.value = assayGroups.value.length > 0 ? assayGroups.value[0].value : ''
}
conRangeElementAnalysisList = configAssayMethodProjectRangeList || []
}
// 设置字段值
function setValueToField() {
@@ -788,7 +892,7 @@ function setValueToField() {
}
}
function getFieldValue(field) {
const fieldValue = sampleDataList[currentSampleDataIndex.value][field.fieldIndex].value
const fieldValue = sampleDataList.value[currentSampleIndex.value][field.fieldIndex]?.value
if (fieldValue) {
return fieldValue
} else {
@@ -1008,8 +1112,8 @@ onLoad(param => {
lockOrientation('landscape')
if (param.currentTaskId) {
taskId.value = param.currentTaskId
getAssayTaskSampleList(taskId.value)
getSampleAnalysisByTaskId(taskId.value)
// getAssayTaskSampleList(taskId.value)
getSampleAnalysisByTaskId()
}
loadFieldApiData(fieldGroup.value)
listenNumKeyboard()
@@ -1112,7 +1216,7 @@ onBackPress(() => {
color: #c0c4cc;
}
.content-left-scroll {
height: 60vh;
height: 68vh;
}
.content-right-scroll {
height: 68vh;