Files
zgty-mas-m/pages/analysis/sample/sample-work-detail.vue
houjunxiang fb41fa9a03 feat:分析
2025-11-11 09:58:12 +08:00

1351 lines
37 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view>
<navbar-back :autoBack="false" titleWidth="800" :title="title" @leftClick="customBack">
<view class="navbar-right" @click="navRightClick">
<text>填写指派单</text>
<u-icon color="" name="edit-pen" size="19"></u-icon>
</view>
</navbar-back>
<u-row gutter="8">
<u-col span="3">
<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">{{ currentSampleData.sampleCode }}</view>
<view>数据采集或录入</view>
</view>
<u-gap height="5" bg-color="#0055A2"></u-gap>
</u-col>
<u-col span="3">
<u-dropdown style="height: 35px">
<u-dropdown-item
v-model="curParameterClassify"
:title="curParameterTitle"
height="70vh"
:options="optionParameterClassify"
@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 sampleDataList"
:key="index"
class="u-tab-item"
:class="currentSampleIndex === index ? 'u-tab-item-active' : ''"
:data-current="index"
@tap.stop="switchSample(index, false)"
>
<u-badge type="warning" :value="index + 1"></u-badge>
<view class="ml20">
<view>
{{ sample.sampleCode }}
</view>
<view> {{ getDataSourceTypeShow(sample.dataSourceType) }}{{ sample.sampleName }} </view>
</view>
</view>
</scroll-view>
<u-button class="btn-operation" type="primary" @click="submitTask()">提交指派单</u-button>
</u-col>
<u-col span="6">
<view class="field-name" v-html="selectedField.title" />
<zzjc-num-keyboard
ref="myKeyboard"
v-show="selectedField.fillingWay == 'keyboard' && selectedField.type != 'select'"
:numKeyboardParam="numKeyboardParam"
></zzjc-num-keyboard>
<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">
<view class="weight">
<view
:style="{ textAlign: !currentAuncel.isConnected ? 'center' : 'right' }"
:class="
currentAuncel.weightStable === 0
? 'weight-data-yellow'
: currentAuncel.weightStable === 1
? 'weight-data'
: 'weight-data-warning'
"
>
{{ currentAuncel.weightData }}
</view>
</view>
<view class="weight-unit">{{ currentAuncel.weightUnit }}</view>
</view>
</view>
<u-button
class="btn-operation"
type="success"
:disabled="confirmWeightDisabled"
shape="circle"
@click="saveAuncelData"
>
确认采集
</u-button>
</view>
</u-col>
<u-col span="3">
<view>
<scroll-view class="content-right-scroll" scroll-y scroll-with-animation>
<view>
<!-- <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>
<view
class="content"
:id="'elId' + groupIndex"
:style="{ height: fields.open ? collaHeights[groupIndex] + 'px' : '0' }"
> -->
<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 == 'input'"
v-model="field.value"
placeholder="请输入"
/>
<!-- <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>
</view>
<u-picker
v-if="field.type === 'select'"
:show="field.showPicker"
:columns="[field.options]"
keyName="dictValue"
@cancel="field.showPicker = false"
@confirm="event => dicPickerConfirm(event, field)"
/> -->
<!--普通输入框 使用文本显示-->
<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>
</up-collapse-item>
</up-collapse>
<!-- </view>
</view> -->
<!-- </template> -->
</view>
</scroll-view>
<u-button class="btn-operation" type="success" @click="saveDetail()">保存样品数据</u-button>
</view>
</u-col>
</u-row>
<!-- 天平选择-->
<auncel-select-popup
ref="auncelSelector"
v-model:showAuncelSelector="showAuncelSelector"
:previousAuncelId="currentAuncel.id"
@doSelect="auncelDoSelect"
></auncel-select-popup>
</view>
</template>
<script setup>
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, handleRoundFiveNumber, calcRowAnalysisValue, math } from '@/nx/helper/calcAnalysisValue'
import { number } from 'mathjs'
import AuncelSelectPopup from '@/components/sample/auncel-select-popup.vue'
import { getTenantId } from '@/defaultBaseUrl'
import { useScreenOrientation } from '@/nx/hooks/useScreenOrientation'
import nx from '@/nx'
import { getDataSourceTypeShow } from '../common'
const { proxy } = getCurrentInstance()
// 响应式数据定义
const taskId = ref('')
const elId = nx.$helper.uuid()
const scrollTop = ref(0) //tab标题的滚动条位置
const menuHeight = ref(0) // 左边菜单的高度
const menuItemHeight = ref(0) // 左边菜单item的高度
const weightDataIsToZero = ref(false) //重量数据是否归零
const currentSampleIndex = ref(0) // 预设当前项的值
let sampleDataList = ref([])
const title = ref('')
const numKeyboardParam = reactive({
decimal: '-1' //数字键盘小数位数,-1为不限制
})
const showAuncelSelector = ref(false) //显示天平选择框
const currentAuncel = ref({
id: '',
name: '',
code: '',
weightStable: 0,
weightData: '请选天平',
weightUnit: ''
})
let selectedField = ref({})
const groupFieldIndex = ref('') //分组的索引
const curSample = ref({})
const curParameterTitle = ref('选择字段分类')
const curParameterKey = ref('')
const curParameterClassify = ref('')
const conAssayTaskId = ref('')
const busSubCSampleId = ref('')
const optionParameterClassify = ref([])
let fieldGroup = ref([])
const collaHeights = ref([])
const cupNumKey = '杯号'
// refs
const myKeyboard = ref(null)
const auncelSelector = ref(null)
// 计算属性
const confirmWeightDisabled = computed(() => {
if (
currentSampleData.value.sampleCode &&
currentAuncel.value.weightStable === 1 &&
Number(currentAuncel.value.weightData) > 0
) {
return false
}
return true
})
// 当前样品数据
const currentSampleData = computed(() => {
if (sampleDataList.value.length > 0) {
return sampleDataList.value[currentSampleIndex.value]
}
return {}
})
// 当前操作字段索引
const currentFieldKey = computed(() => {
return selectedField.value.fieldIndex
})
const userInfo = computed(() => nx.$store('user').userInfo)
// 方法定义
const getDomHeight = async () => {
await nextTick() // 等待 DOM 更新完成
collaHeights.value = []
if (fieldGroup.value.length) {
fieldGroup.value.forEach((item, index) => {
proxy.$u.getRect('#elId' + index).then(res => {
collaHeights.value.push(res.height)
})
})
}
}
//返回上一页
const customBack = () => {
uni.redirectTo({
url: '/pages/analysis/sample/sample-work-list'
})
}
const navRightClick = () => {
let url = '/pages/analysis/sample/sample-work-edit-task'
url += '?currentTaskId=' + taskId.value
uni.navigateTo({
url: url
})
}
const parameterClassifyChange = (v, a) => {
const groupIndex = ref(0)
curParameterKey.value = v
optionParameterClassify.value.forEach((item, index) => {
if (item.value === v) {
groupIndex.value = index
curParameterTitle.value = item.label
}
})
if (v === '') groupIndex.value = 0
if (groupIndex.value > 0) groupIndex.value--
//自动选中字段
groupFieldIndex.value = groupIndex.value + '-'
autoNextField()
}
const fieldClick = (field, key) => {
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 == 'keyboard') {
listenDeviceData()
} else {
closeDeviceListener()
}
}
//自动切换到下一个字段
const autoNextField = () => {
let groupIndex = 0
let fieldIndex = 0
if (typeof groupFieldIndex.value === 'undefined' || groupFieldIndex.value === '') groupFieldIndex.value = '0-'
const indexV = groupFieldIndex.value.split('-')
groupIndex = number(indexV[0])
fieldIndex = indexV[1] === '' ? -1 : number(indexV[1])
const group = fieldGroup.value[groupIndex]
const fields = group.fields
if (fields.length > fieldIndex + 1) {
//切换到下一个字段
const key = groupIndex + '-' + (fieldIndex + 1)
fieldClick(fields[fieldIndex + 1], key)
}
}
//自动切换到下一个样品
const autoNextSample = () => {
if (sampleDataList.value.length <= currentSampleIndex.value + 1) return
const index = currentSampleIndex.value + 1
groupFieldIndex.value = ''
selectedField.value = {}
switchSample(index, true)
}
//手动切换样品
const switchSample = async (index, autoFlag) => {
//重置天平归0
weightDataIsToZero.value = false
if (index === currentSampleIndex.value) return
setValueToSample()
currentSampleIndex.value = index
// 如果为0意味着尚未初始化
if (menuHeight.value === 0 || menuItemHeight.value === 0) {
const rect = await getElRect('u-tab-item')
menuItemHeight.value = rect.height
}
// 将菜单菜单活动item垂直居中
scrollTop.value = index * menuItemHeight.value + menuItemHeight.value / 2 - menuHeight.value / 2 - 50
//修改当前选中的groupFieldIndex
// if (typeof groupFieldIndex.value !== 'undefined' && groupFieldIndex.value.indexOf('-') > 0) {
// groupFieldIndex.value = groupFieldIndex.value.split('-')[0] + '-'
// }
uni.showLoading({ title: '加载中...' })
setValueToField()
autoGenerateCupNum()
setTimeout(() => {
uni.hideLoading()
}, 500)
}
// 获取一个目标元素的高度
const getElRect = (elClass, maxRetry = 50) => {
return new Promise((resolve, reject) => {
let retryCount = 0
const execQuery = () => {
uni
.createSelectorQuery()
.in(proxy)
.select('.' + elClass)
.fields({ size: true, rect: true })
.exec(res => {
const result = res[0] // exec 返回数组,取第一个
if (result) {
resolve(result)
} else if (retryCount < maxRetry) {
retryCount++
setTimeout(execQuery, 10)
} else {
reject(new Error(`无法获取元素 .${elClass} 的尺寸,已重试 ${maxRetry}`))
}
})
}
execQuery()
})
}
//读取样品明细字段
const getDetailFieldsAndStatus = autoSelectNextField => {
const taskDetailId = currentSampleData.value.id
//读取回收率配置
loadConRecoveryList()
optionParameterClassify.value = arr //字段分类
conAssayTaskId.value = res.additionalProperties.conAssayTaskId
busSubCSampleId.value = res.additionalProperties.busSubCSampleId
const detail = res.additionalProperties.taskDetail
//处理硫值、硫量:未保存过的数据,读取接口返回的硫值、硫量
loadSValue(detail)
calAndCheck()
}
const checkLoadSValue = () => {
const vKey = 'sRange'
const vF = getFieldByKey(vKey)
if (vF === null) return false
const v = vF.value
if (v === null || v === '') return true
try {
if (v === 0 || number(v) === 0) return true
} catch (e) {}
return false
}
//读取硫值、硫量
const loadSValue = detail => {
let flag = checkLoadSValue()
if (!flag) return
const vKey = 'sValue'
const rKey = 'sRange'
const vF = getFieldByKey(vKey)
if (vF !== null) {
vF.value = detail.svalue
}
const rF = getFieldByKey(rKey)
if (rF !== null) {
rF.value = detail.srange
}
}
const getFieldByKey = key => {
const group = fieldGroup.value
let field = null
for (let g of group) {
for (let f of g.fields) {
const dicKey = f.dicKey
if (dicKey && dicKey === key) {
field = f
break
}
}
}
return field
}
//按公式计算值,并检查原数据与计算后的数据是否一致
const calAndCheck = () => {
//提取原本数值
const oldVal = fitUpValues()
calcAnalysisValue(fieldGroup.value)
if (checkDataNew(oldVal)) return
const newVal = fitUpValues(oldVal)
//检查是否一致
let modifyFlag = false
for (const o in oldVal) {
let old = oldVal[o]
if (old === null) old = 0
//判断是否number
try {
if (number(old) !== number(newVal[o])) {
modifyFlag = true
}
} catch (e) {
if (old !== newVal[o]) modifyFlag = true
}
}
if (modifyFlag)
uni.showToast({
title: '数据有修改,请检查所有样品,并重新保存!',
duration: 2000,
icon: 'error'
})
}
//检查是否是新数据
const checkDataNew = data => {
for (const o in data) {
if (data[o] !== null && data[o] !== '0') return false
}
return true
}
//组装表单值,用于数据比对
const fitUpValues = () => {
const val = {}
for (const g of fieldGroup.value) {
for (const f of g.fields) {
const detailId = f.detailId
if (!detailId) continue
val[detailId] = f.value
}
}
return val
}
const clearFieldVal = () => {
uni.showModal({
title: '提示',
content: '清空当前字段的值,是否继续?',
cancelColor: '#0055A2',
confirmColor: '#0055A2',
success: res => {
if (res.cancel) {
return
}
selectedField.value.value = ''
//重新计算
calcAnalysisValue(fieldGroup.value)
}
})
}
//自动填写筛上杯号
const autoFillBhUp = val => {
const bh = Number(val) + 1
for (const g of fieldGroup.value) {
for (const f of g.fields) {
if (f.dicKey && f.dicKey === 'bh_up') {
f.value = bh + ''
return
}
}
}
}
const checkBh = () => {
//检查杯号
let hasHb = true
for (const g of fieldGroup.value) {
for (const f of g.fields) {
if (f.title && f.title.indexOf(cupNumKey) >= 0) {
if (typeof f.value === 'undefined' || f.value === null || f.value === '') hasHb = false
}
}
}
if (!hasHb) {
nx.$helper.showToast({
title: '杯号不能为空,请填写杯号!'
})
return false
}
return true
}
const saveAuncelData = () => {
//保存数据
if (!weightDataIsToZero.value) {
nx.$helper.showToast({
title: '天平必须先回零!'
})
return
}
//获取当前重量
let weight = currentAuncel.value.weightData
selectedField.value.value = weight
const curFieldDicKey = selectedField.dicKey
let upDownFlag = ''
if (curFieldDicKey && curFieldDicKey !== '' && curFieldDicKey.indexOf('_') > 0) {
upDownFlag = curFieldDicKey.substring(curFieldDicKey.lastIndexOf('_') + 1)
}
//这里不修改weightDataIsToZero在切换样品是修改
// weightDataIsToZero.value = false;
//保存天平编号、天平名称、称重时间。支持筛上、筛下
if (curFieldDicKey && curFieldDicKey.indexOf('sampleWeight') >= 0) {
let auncelNoFieldKey = 'auncelNo'
let measureTimeFieldKey = 'measureTime'
if (upDownFlag && upDownFlag !== '') {
auncelNoFieldKey += '_' + upDownFlag
measureTimeFieldKey += '_' + upDownFlag
}
const auncelNoField = getFieldByKey(auncelNoFieldKey)
if (auncelNoField !== null) {
auncelNoField.value = currentAuncel.value.code
}
const measureTimeField = getFieldByKey(measureTimeFieldKey)
if (measureTimeField !== null) {
measureTimeField.value = nx.$dayjs().format('YYYY-MM-DD HH:mm:ss')
}
}
//指派单明细上的称重时间
curSample.value.measureTime = nx.$dayjs().format('YYYY-MM-DD HH:mm:ss')
//计算
try {
calcAnalysisValue(fieldGroup.value)
} catch (e) {
console.error(e)
}
//自动跳转下一个字段
setTimeout(() => {
autoNextField()
}, 100)
}
const dynamicFormData = {}
const saveDetail = async () => {
//检查杯号
if (!checkBh()) return
setValueToSample()
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.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) {
sampleDataList.value[currentSampleIndex.value].cupNum = item.value
}
})
}
//提交任务指派单
const submitTask = () => {
const params = {
businessAssayTaskId: taskId.value
}
const msg = '请确认所有样品数据都已保存,然后再提交指派单!是否继续?'
uni.showModal({
title: '提示',
content: msg,
cancelColor: '#0055A2',
confirmColor: '#0055A2',
success: res => {
if (res.cancel) return
nx.$api.assayTask.submitTask(params).then(res => {
uni.navigateTo({
url: '/pages/analysis/sample/sample-report'
})
})
}
})
}
//自动生成杯号(仅顺序称重):第一杯:手填,后续杯 = 上一杯 + 1
const autoGenerateCupNum = () => {
let cupNum = 0
let current = currentSampleIndex.value
//第一杯
if (current === 0) return
//取上一个样品的杯号
const sample = sampleDataList.value[current - 1]
cupNum = sample.cupNum
//杯号赋值到当前样品
putCupNum(Number(cupNum) + 1, current, Number(cupNum))
}
const putCupNum = (cupNum, sampleIndex, lastCupNum) => {
sampleDataList.value[sampleIndex].cupNum = cupNum
for (const fields of fieldGroup.value) {
if (typeof fields === 'undefined') continue
//杯号
for (const field of fields.fields) {
if (field.title === cupNumKey) {
if (typeof field.value === 'undefined' || field.value === null || field.value === '') {
field.value = cupNum
return true
}
}
}
}
//处理筛上筛下
if (lastCupNum && lastCupNum === sampleIndex) {
//上一个杯号 == 默认杯号
cupNum = sampleIndex * 2 + 1
}
for (const fields of fieldGroup.value) {
if (typeof fields === 'undefined') continue
//杯号-筛下
for (const field of fields.fields) {
if (field.dicKey === cupNumKey + '_down') {
if (typeof field.value === 'undefined' || field.value === null || field.value === '') {
field.value = cupNum
cupNum++
break
}
}
}
//杯号-筛上
for (const field of fields.fields) {
if (field.dicKey === cupNumKey + '_up') {
if (typeof field.value === 'undefined' || field.value === null || field.value === '') {
field.value = cupNum
return true
}
}
}
}
return false
}
const loadConRecoveryList = () => {
const conBaseSampleId = currentSampleData.value.conBaseSampleId
const rParam = {
pageNo: 1,
pageSize: -1,
conBaseSampleId: conBaseSampleId
}
const storageKey = 'ConRecoveryRateList'
nx.$api.assayTask
.queryConRecoveryRateList(rParam)
.then(res => {
const result = res.result
if (typeof result === 'undefined' || result === null || !result) {
uni.setStorage(storageKey, [])
return
}
const records = result.records
if (!records || records.length === 0) {
uni.setStorage(storageKey, [])
return
}
uni.setStorageSync(storageKey, records)
})
.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 || []
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() {
for (const group of fieldGroup.value) {
group.fields.forEach(field => {
let value = getFieldValue(field)
field.value = value
})
}
}
function getFieldValue(field) {
const fieldValue = sampleDataList.value[currentSampleIndex.value][field.fieldIndex]?.value
if (fieldValue) {
return fieldValue
} else {
return ''
}
}
const apiRequest = async url => {
return request({
url: url,
method: 'GET'
})
}
//读取字段里的API选项
const loadFieldApiData = async fieldGroups => {
for (const fields of fieldGroups) {
for (const field of fields.fields) {
const api = field.api
const type = field.type
if (type === 'select' && api && api !== '') {
//读取API选项
try {
const res = await apiRequest(api)
const data = res
field.options = data
field.rangeKey = 'displayName'
} catch (e) {}
}
}
}
}
/*
* 选择器组件确认事件
* event 事件默认参数
* field 当前字段
* 处理逻辑:
* field.value = 选中的值
* field.valueText = 选中的文本
* */
const dicPickerConfirm = (event, field) => {
const checked = event.value[0]
const displayName = checked.displayName
const value = checked.dictValue
const extField1 = checked.extField1
field.value = value
field.valueText = value
const relatedFieldNo = '8'
//查找并处理关联字段
for (const fieldGroup of fieldGroup.value) {
for (const field of fieldGroup.fields) {
if (field.paramNo === relatedFieldNo) {
field.value = extField1
break
}
}
}
//触发计算公式
calcAnalysisValue(fieldGroup.value)
}
// 天平监听
const listenDeviceData = () => {
//监听websocket返回来的数据
//设备数据
uni.$on('deviceData', res => {
switch (res.deviceType) {
case 'balance':
if (currentAuncel.value.id === res.deviceId) {
currentAuncel.value.weightData = res.weightData
currentAuncel.value.weightUnit = res.weightUnit
currentAuncel.value.weightStable = res.weightStable
currentAuncel.value.isConnected = true
if (Number(res.weightData) === 0) {
weightDataIsToZero.value = true
}
}
break
default:
break
}
})
//设备状态
uni.$on('deviceStatus', res => {
if (currentAuncel.value.id === res.deviceId) {
if (res.connected === 0) {
currentAuncel.value.weightStable = 0
currentAuncel.value.weightData = '天平断开'
currentAuncel.value.weightUnit = ''
}
}
currentAuncel.value.isConnected = res.connected
})
//控制设备状态
uni.$on('controlDevice', res => {
if (currentAuncel.value.id === res.deviceId) {
currentAuncel.value.id = ''
currentAuncel.value.name = ''
currentAuncel.value.code = ''
currentAuncel.value.weightStable = 0
currentAuncel.value.weightData = '请选天平'
currentAuncel.value.weightUnit = ''
}
})
//连接断开
uni.$on('connClose', res => {
//重置
currentAuncel.value.weightData = ''
currentAuncel.value.weightUnit = ''
currentAuncel.value.weightStable = 0
currentAuncel.value.controlUserName = ''
currentAuncel.value.isConnected = false
})
}
const closeDeviceListener = () => {
uni.$off('deviceData')
uni.$off('deviceStatus')
uni.$off('controlDevice')
uni.$off('connClose')
}
const loadDevice = () => {
//注册websocket
let regData = {
msgId: nx.$helper.uuid(),
cmd: 'register',
clientType: 'caaClient',
data: {
userId: userInfo.value.id,
tenantId: getTenantId(),
userRealName: userInfo.value.realname
}
}
nx.$measure.setRegData(JSON.stringify(regData))
nx.$measure.open()
}
// 键盘监听
const listenNumKeyboard = () => {
//键盘监听
uni.$on('keyboardOK', res => {
if (res === null) {
clearFieldVal()
return
}
//自动补全小数位数
const decimalPosition = selectedField.value.decimalPosition || 0
let val = res.val
//判断val小数位如果小于设定位数则补全
if (decimalPosition > 0) {
if (val === '') val = '0'
if (typeof val === 'number') val += ''
let dotLen = 0
if (val.indexOf('.') > 0) dotLen = val.length - val.indexOf('.') - 1
else val += '.'
while (dotLen < decimalPosition) {
dotLen++
val += '0'
}
}
selectedField.value.value = val
const dicKey = selectedField.value.dicKey
if (dicKey && dicKey === 'bh_down') {
autoFillBhUp(val)
}
calcAnalysisValue(fieldGroup.value)
//自动跳转下一个字段
setTimeout(() => {
autoNextField()
}, 60)
})
}
const closeNumKeyBoardListener = () => {
uni.$off('keyboardOK')
}
const closeDeviceLink = () => {
const deviceId = currentAuncel.value.id
releaseDeviceControl(deviceId)
}
//释放设备控制
const releaseDeviceControl = deviceId => {
if (!deviceId || deviceId === '') return
let controlDevice = {
msgId: deviceId,
cmd: 'controlDevice',
clientType: 'caaClient',
data: {
deviceId: deviceId,
isControl: false,
controlRealName: userInfo.value.nickname
}
}
//发送控制数据
nx.$measure.send(JSON.stringify(controlDevice))
}
// 天平选择
const selectAuncel = () => {
showAuncelSelector.value = true
}
// 天平选择回调
const auncelDoSelect = res => {
const data = res.data
if (data) {
currentAuncel.value.id = data.deviceId
currentAuncel.value.name = data.deviceName
currentAuncel.value.code = data.deviceCode
}
}
// 生命周期
const { lockOrientation } = useScreenOrientation()
onLoad(param => {
lockOrientation('landscape')
if (param.currentTaskId) {
taskId.value = param.currentTaskId
// getAssayTaskSampleList(taskId.value)
getSampleAnalysisByTaskId()
}
loadFieldApiData(fieldGroup.value)
listenNumKeyboard()
})
onShow(() => {
loadDevice()
listenDeviceData()
})
onHide(() => {
closeDeviceListener()
})
onUnload(() => {
closeDeviceLink()
closeDeviceListener()
closeNumKeyBoardListener()
})
onBackPress(() => {
customBack()
return true
})
</script>
<style lang="scss" scoped>
/* 原始样式保留不变 */
.navbar-right {
font-size: 16px;
color: #fff;
display: flex;
}
.content-title-name {
font-size: 18px;
font-weight: 300;
padding: 4px 0;
display: flex;
justify-content: center;
}
.current-sample-code {
font-weight: bold;
margin-right: 20px;
}
.form-item-my {
display: flex;
align-items: center;
margin-bottom: 10px;
height: 35px;
font-size: 13px;
border-bottom: 1px solid #dcdcdc;
}
.selected-field {
background-color: #bada55;
}
.disabled-field {
background-color: #eee;
}
.label-my {
flex: 5;
}
.label-high-light {
color: #000;
font-weight: 600;
font-size: 1.2em;
}
.field-high-light {
color: #000;
font-weight: 600;
font-size: 1.2em;
}
.label-my sub {
font-size: 0.6em;
vertical-align: sub;
}
.content-my {
flex: 2;
text-align: right;
padding-right: 4px;
.select-my {
color: #303133;
font-size: 15px;
padding: 6px 8px;
border: 1px solid #dcdcdc;
border-radius: 5px;
}
}
.content-my-text-value {
color: rgb(48, 49, 51);
}
.content-my-text-placeholder {
color: #c0c4cc;
}
.content-left-scroll {
height: 68vh;
}
.content-right-scroll {
height: 68vh;
}
.u-tab-item {
display: flex;
align-items: center;
font-size: 16px;
color: #444;
padding: 4px 8px;
box-sizing: border-box;
border-bottom: 2px dotted #444;
}
.u-tab-item-active {
color: #0055a2;
font-weight: 600;
}
.btn-operation {
width: 80%;
font-size: 18px;
margin-top: 10px;
}
.auncel {
width: 90%;
height: 55vh;
background-image: url(/static/images/auncel.png);
background-repeat: no-repeat;
background-size: 100% 100%;
background-position: center;
display: flex;
flex-direction: column;
}
.auncel-title {
flex: 3;
display: flex;
justify-content: center;
align-items: center;
font-size: 32px;
}
.auncel-weight {
flex: 2.5;
display: flex;
align-items: center;
padding-bottom: 20px;
box-sizing: border-box;
position: relative;
}
.weight {
font-size: 42px;
width: 100%;
padding: 0 60px;
}
.weight-data {
color: #4cd964;
font-family: zzjc-lcd;
}
.weight-data-yellow {
color: #ffff00;
font-family: zzjc-lcd;
}
.weight-data-warning {
color: #ff3333;
font-family: zzjc-lcd;
}
.weight-unit {
position: absolute;
right: 30px;
color: #ffffff;
font-size: 32px;
}
@media (max-width: 700px) {
.auncel {
height: 45vh;
}
.auncel-title {
font-size: 20px;
}
.auncel-weight {
padding-bottom: 15px;
}
.weight {
font-size: 26px;
}
.weight-unit {
font-size: 20px;
}
}
.field-name {
font-size: 26px;
padding: 8px;
}
.my-collapse {
display: flex;
align-items: center;
justify-content: space-between;
padding: 5px 10px 5px 0;
.title {
font-weight: bold;
}
}
.content {
overflow: hidden;
padding-right: 10px;
transition: height 100ms;
}
@media (max-width: 700px) {
.content-left-scroll {
height: 45vh;
}
.content-right-scroll {
height: 55vh;
}
.field-name {
font-size: 16px;
}
}
</style>