408 lines
12 KiB
Vue
408 lines
12 KiB
Vue
<template>
|
||
<view>
|
||
<navbar-back titleWidth="800" :title="title"></navbar-back>
|
||
<u-row>
|
||
<u-col span="1"></u-col>
|
||
<u-col span="10">
|
||
<scroll-view scroll-y scroll-with-animation :scroll-top="scrollTop">
|
||
<view class="form-item-my" v-for="(field, index) in formFields" :key="'field_' + index">
|
||
<template v-if="!field.hidden">
|
||
<view
|
||
class="label-my"
|
||
:style="{ fontWeight: checkFeadToDetailField(field.headToDetailField) ? 'bold' : 'normal' }"
|
||
>{{ field.label }}</view
|
||
>
|
||
<view class="content-my">
|
||
<view v-if="field.type == 'title'" class="content-title">
|
||
{{ field.value }}
|
||
</view>
|
||
<!--普通输入框-->
|
||
<u-input
|
||
v-if="field.type == 'Input'"
|
||
v-model="field.value"
|
||
clearable
|
||
:placeholder="field.placeholder"
|
||
:disabled="field.disabled || field.fillingWay == 'calculate'"
|
||
/>
|
||
<!--数字,限制小数位数-->
|
||
<u-input
|
||
v-if="field.type == 'decimal'"
|
||
type="number"
|
||
clearable
|
||
@blur="event => checkDecimal(event, field)"
|
||
v-model="field.value"
|
||
:placeholder="field.placeholder"
|
||
:disabled="field.disabled || field.fillingWay == 'calculate'"
|
||
/>
|
||
<!--select-->
|
||
<view v-if="field.type == 'select'" class="x-bc select-my" @click="handleFieldClick(field)">
|
||
<text v-if="field.value">{{ field.value }}</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="displayName"
|
||
@cancel="field.showPicker = false"
|
||
@confirm="event => pickerConfirm(event, field)"
|
||
/>
|
||
<!--日期-->
|
||
<view v-if="field.type == 'date'" class="x-bc select-my" @click="handleFieldClick(field)">
|
||
<text v-if="field.value">{{ nx.$dayjs(field.value).format('YYYY-MM-DD HH:mm:ss') }}</text>
|
||
<text v-else>请选择</text>
|
||
<u-icon name="calendar-fill" size="20"></u-icon>
|
||
</view>
|
||
|
||
<u-datetime-picker
|
||
v-if="field.type == 'date'"
|
||
:show="field.showPicker"
|
||
v-model="curDate"
|
||
mode="datetime"
|
||
@cancel="field.showPicker = false"
|
||
@confirm="event => pickerConfirm(event, field)"
|
||
></u-datetime-picker>
|
||
</view>
|
||
</template>
|
||
</view>
|
||
</scroll-view>
|
||
<u-button style="width: 50%" type="primary" @click="saveHeadData">保存</u-button>
|
||
</u-col>
|
||
<u-col span="1"> </u-col>
|
||
</u-row>
|
||
<up-loading-page :loading="pageLoading"></up-loading-page>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue'
|
||
import nx from '@/nx'
|
||
import { onLoad } from '@dcloudio/uni-app'
|
||
import { calcRowAnalysisValue } from '@/nx/helper/calcAnalysisValue'
|
||
import tools from '@/nx/utils/tools'
|
||
|
||
const pageLoading = ref(false)
|
||
// 标题
|
||
const title = ref('编辑指派单')
|
||
// tab标题的滚动条位置
|
||
const scrollTop = ref(0)
|
||
// 当前任务样品
|
||
const currentTaskId = ref('')
|
||
// 字典选择器显示
|
||
// 当前日期(用于日期选择器)
|
||
const curDate = ref(Number(new Date()))
|
||
// 字段值对象
|
||
const formData = ref({})
|
||
|
||
// 处理后的字段结构
|
||
const formFields = ref([])
|
||
|
||
const staticFormSchema = [
|
||
{ label: '指派单号', fieldKey: 'businessAssayTaskId', hidden: true },
|
||
{ type: 'title', value: '分析原始记录单' },
|
||
{ label: '检测方法', type: 'Input', fieldKey: 'configAssayMethodName' },
|
||
{ label: '分析人', type: 'Input', fieldKey: 'assayOperator', disabled: true },
|
||
{ label: '检测时间', type: 'date', fieldKey: 'assayTime' }
|
||
]
|
||
let dynamicFormSchema = []
|
||
|
||
// 页面加载
|
||
onLoad(param => {
|
||
if (param.currentTaskId) {
|
||
currentTaskId.value = param.currentTaskId
|
||
}
|
||
loadTaskDetail()
|
||
})
|
||
|
||
let tabs = ref([])
|
||
const dynamicFormData = ref({})
|
||
async function loadTaskDetail() {
|
||
pageLoading.value = true
|
||
const {
|
||
assayTaskAnalysisDataList,
|
||
businessAssayTasNo,
|
||
configAssayMethodName,
|
||
assayOperator,
|
||
assayTime,
|
||
formValue,
|
||
dataCollectionId,
|
||
dataCollectionKey
|
||
} = await nx.$api.assayTask.batchSampleAndQcAnalysisByTaskId(currentTaskId.value)
|
||
formData.value = {
|
||
businessAssayTaskId: currentTaskId.value,
|
||
configAssayMethodName: configAssayMethodName,
|
||
assayOperator: assayOperator,
|
||
assayTime: assayTime
|
||
}
|
||
if (formValue) {
|
||
formData.value = { ...formData.value, ...JSON.parse(formValue) }
|
||
dynamicFormData.value = JSON.parse(formValue)
|
||
}
|
||
title.value = '样品分析-任务指派单:' + businessAssayTasNo
|
||
// 查询动态字段
|
||
const dataCollectionParams = {}
|
||
if (dataCollectionId) {
|
||
dataCollectionParams.dataCollectionId = dataCollectionId
|
||
} else {
|
||
dataCollectionParams.dataCollectionKey = dataCollectionKey
|
||
}
|
||
const data = await nx.$api.assayTask.getDynamicBaseFormSchema(dataCollectionParams)
|
||
dynamicFormSchema = data.map(item => ({
|
||
label: item.fieldName,
|
||
fieldKey: item.fieldKey,
|
||
type: item.fieldType,
|
||
placeholder: '请输入'
|
||
}))
|
||
formFields.value = [...staticFormSchema, ...dynamicFormSchema]
|
||
bindFormValue()
|
||
|
||
// 处理样品分类数据
|
||
const tabGroups = assayTaskAnalysisDataList
|
||
tabs.value = tabGroups.map(group => {
|
||
// 必须深拷贝 datas!防止多个表格共享引用
|
||
const tableData = JSON.parse(JSON.stringify(group.datas || []))
|
||
const columns = group.columns || []
|
||
return {
|
||
name: group.analysisType,
|
||
label: group.analysisName,
|
||
columns,
|
||
tableData,
|
||
configQCSampleMethodInfo: group.configQCSampleMethod?.configInfomation
|
||
? JSON.parse(group.configQCSampleMethod['configInfomation'])['set']
|
||
: []
|
||
}
|
||
})
|
||
pageLoading.value = false
|
||
}
|
||
// 空白样或者标样影响计算配置字段
|
||
const configQCSampleMethodInfos = computed(() => {
|
||
return tabs.value.filter(t => t.name === 'by' || t.name === 'kby').flatMap(tab => tab.configQCSampleMethodInfo)
|
||
})
|
||
// 表格数据更新后重新计算
|
||
async function updateTableDataByConfigFields() {
|
||
let needCalcTabs = []
|
||
needCalcTabs = tabs.value.filter(t => t.name !== 'by' && t.name !== 'kby')
|
||
const formData = realFormData.value
|
||
if (needCalcTabs.length === 0) return
|
||
for (const key in formData) {
|
||
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 = formData[key]
|
||
calcRowAnalysisValue(row, columnObj, tab.columns)
|
||
})
|
||
}
|
||
}
|
||
}
|
||
// 点击字段(打开选择器)
|
||
function handleFieldClick(field) {
|
||
field.showPicker = true
|
||
}
|
||
|
||
// 判断是否需要同步到明细字段(加粗显示)
|
||
function checkFeadToDetailField(headToDetailField) {
|
||
if (headToDetailField && headToDetailField.trim() != '') {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 处理小数位数:补0或去除多余位数
|
||
function checkDecimal(e, field) {
|
||
if (e == '') return
|
||
const decimalCount = field.decimalPosition
|
||
const count = Number(decimalCount)
|
||
const value = field.value
|
||
const pos = value.indexOf('.') + 1
|
||
let length = value.length - pos
|
||
if (pos == 0) {
|
||
length = 0
|
||
}
|
||
while (length < count) {
|
||
if (field.value.indexOf('.') < 0) field.value += '.'
|
||
field.value = field.value + '0'
|
||
length++
|
||
}
|
||
if (count === 0) {
|
||
field.value = parseInt(field.value) + ''
|
||
} else if (length > count) {
|
||
field.value = field.value.substring(0, pos + count)
|
||
}
|
||
}
|
||
|
||
// 绑定数据(将 formData 填入字段)
|
||
function bindFormValue() {
|
||
for (const field of formFields.value) {
|
||
const fieldKey = field.fieldKey
|
||
if (fieldKey) {
|
||
const value = formData.value[fieldKey]
|
||
if (value) {
|
||
field.value = value
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
const realFormData = computed(() => {
|
||
const formData = {}
|
||
for (const field of formFields.value) {
|
||
if (field.fieldKey) {
|
||
formData[field.fieldKey] = field.value
|
||
}
|
||
}
|
||
return formData
|
||
})
|
||
// 实际保存逻辑
|
||
async function handleSave(change) {
|
||
let params = {}
|
||
if (change) {
|
||
// 计算样品数据
|
||
updateTableDataByConfigFields()
|
||
params = {
|
||
...realFormData.value,
|
||
formValue: JSON.stringify(getDynamicFormSchemaFormData(realFormData.value)),
|
||
assayTaskAnalysisDataList: tabs.value.map(item => ({
|
||
datas: item.tableData,
|
||
analysisType: item.name
|
||
}))
|
||
}
|
||
} else {
|
||
params = {
|
||
...realFormData.value,
|
||
formValue: JSON.stringify(getDynamicFormSchemaFormData(realFormData.value))
|
||
}
|
||
}
|
||
await nx.$api.assayTask.saveBatchSmpleAndQcAnalysis(params)
|
||
uni.redirectTo({
|
||
url: '/pages/analysis/sample/sample-work-detail?currentTaskId=' + currentTaskId.value
|
||
})
|
||
}
|
||
|
||
function getDynamicFormSchemaFormData(obj) {
|
||
const includeKeys = new Set(dynamicFormSchema.filter(item => item.fieldKey).map(item => item.fieldKey))
|
||
return Object.fromEntries(Object.entries(obj).filter(([key]) => includeKeys.has(key)))
|
||
}
|
||
// 检查动态字段值在空白样或者标样的(影响计算配置字段)中是否变化
|
||
function checkPropertyEquality() {
|
||
let allColumns = tabs.value.flatMap(tab => tab.columns)
|
||
for (const column of allColumns) {
|
||
const formula = column.formula
|
||
if (!formula) continue
|
||
const target = formula.split(':')[1]
|
||
const originalValue = dynamicFormData.value[target]
|
||
const currentValue = realFormData.value[target]
|
||
console.log('checkPropertyEquality', originalValue, currentValue)
|
||
if (!looseEqual(originalValue, currentValue)) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
function looseEqual(a, b) {
|
||
const aEmpty = tools.isEmpty(a)
|
||
const bEmpty = tools.isEmpty(b)
|
||
|
||
if (aEmpty && bEmpty) {
|
||
return true // 都是空,算相等
|
||
}
|
||
if (aEmpty || bEmpty) {
|
||
return false // 一个空一个非空,不等
|
||
}
|
||
return a === b // 都非空,严格相等
|
||
}
|
||
// 保存抬头字段(带确认弹窗)
|
||
function saveHeadData() {
|
||
if (!realFormData.value['assayTime']) return nx.$helper.showToast('请选择分析时间')
|
||
if (checkPropertyEquality()) {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '您修改的字段将影响样品分析结果,系统将重新计算',
|
||
showCancel: false,
|
||
success: res => {
|
||
if (res.confirm) {
|
||
handleSave(true)
|
||
}
|
||
}
|
||
})
|
||
} else {
|
||
handleSave(false)
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 选择器组件确认事件
|
||
* event: 事件默认参数
|
||
* field: 当前字段
|
||
* 处理逻辑:
|
||
* field.value = 选中的值
|
||
* field.valueText = 选中的文本
|
||
* todo: 关联字段在动态字段的change事件处理
|
||
* */
|
||
function pickerConfirm(event, field) {
|
||
if (field.type == 'date') {
|
||
field.value = nx.$dayjs(event.value).valueOf()
|
||
field.showPicker = false
|
||
return
|
||
}
|
||
const confLabel = field.props.label
|
||
const confValue = field.props.value
|
||
const selected = event.value[0]
|
||
const value = selected[confValue]
|
||
const displayName = selected[confLabel]
|
||
field.value = value
|
||
field.valueText = displayName
|
||
if (typeof field.change == 'function') {
|
||
field.change({ value }, selected, { formFields: formFields.value })
|
||
}
|
||
field.showPicker = false
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.content-title {
|
||
width: 100%;
|
||
font-size: 20px;
|
||
font-weight: 300;
|
||
}
|
||
|
||
.form-item-my {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.label-my {
|
||
width: 170px; /* 标签宽度 */
|
||
padding-right: 10px;
|
||
text-align: right;
|
||
}
|
||
|
||
.label-my sub {
|
||
font-size: 0.6em; /* 调整下标字体大小 */
|
||
vertical-align: sub; /* 调整下标垂直对齐 */
|
||
}
|
||
|
||
.content-my {
|
||
flex: 1;
|
||
padding-left: 7px;
|
||
.select-my {
|
||
color: #303133;
|
||
font-size: 15px;
|
||
padding: 6px 8px;
|
||
border: 1px solid #dcdcdc;
|
||
border-radius: 5px;
|
||
}
|
||
}
|
||
</style>
|