feat:待审数据

This commit is contained in:
houjunxiang
2025-11-11 20:51:08 +08:00
parent fb41fa9a03
commit 970a8b8eae
11 changed files with 416 additions and 651 deletions

View File

@@ -6,111 +6,104 @@
<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">
<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>
<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>
<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 type="primary" @click="saveHeadData">保存</u-button>
<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 request from '@/nx/request'
import { cloneDeep } from 'lodash'
import { calcAnalysisValue } from '@/nx/helper/calcAnalysisValue'
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('编辑指派单')
// 表单模型
const taskInstance = ref({})
// tab标题的滚动条位置
const scrollTop = ref(0)
// 当前任务样品
const currentTaskNo = ref('')
const currentTaskId = ref('')
// 字典选择器显示
const showDicPicker = ref(false)
// 当前日期(用于日期选择器)
const curDate = ref(Number(new Date()))
// 字段值对象
const formValue = ref({})
// 后端返回的字段结构
const sourceFormFields = ref([])
const formData = ref({})
// 处理后的字段结构
const formFields = ref([])
// 样品列表
const sampleList = ref([])
// 表单引用
const uForm = ref(null)
const staticFormSchema = [
{ label: '指派单号', fieldKey: 'businessAssayTaskId', hidden: true },
{ type: 'title', value: '分析原始记录单' },
{ label: '检测方法', type: 'Input', fieldKey: 'configAssayMethodName', disabled: true },
{ label: '分析人', type: 'Input', fieldKey: 'assayOperator', disabled: true },
{ label: '检测时间', type: 'date', fieldKey: 'assayTime' },
{ label: '小数值', type: 'decimal', fieldKey: 'num', decimalPosition: 4, placeholder: '请输入' }
{ label: '检测时间', type: 'date', fieldKey: 'assayTime' }
]
let dynamicFormSchema = []
@@ -119,35 +112,97 @@ onLoad(param => {
if (param.currentTaskId) {
currentTaskId.value = param.currentTaskId
}
// loadHeadFieldsAndValueByTaskNo()
// getSampleList(currentTaskNo.value)
loadTaskDetail()
})
function loadTaskDetail() {
nx.$api.assayTask.getSampleAnalysisByTaskId(currentTaskId.value).then(async res => {
taskInstance.value = res
formValue.value = {
configAssayMethodName: res.configAssayMethodName,
assayOperator: res.assayOperator,
assayTime: res.assayTime,
...JSON.parse(res.formValue)
}
title.value = '样品分析-任务指派单:' + res.businessAssayTasNo
const data = await nx.$api.assayTask.getDynamicBaseFormSchema({ dataCollectionId: res.dataCollectionId })
dynamicFormSchema = data.map(item => ({
label: item.fieldName,
fieldKey: item.fieldKey,
type: item.fieldType,
value: '',
placeholder: '请输入'
}))
formFields.value = [...staticFormSchema, ...dynamicFormSchema]
bindFormValue()
})
}
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
@@ -162,18 +217,6 @@ function checkFeadToDetailField(headToDetailField) {
}
}
// 获取样品列表
function getSampleList(taskNo) {
nx.$api.assayTask
.getAssayTaskDetailListByTaskNo({ taskNo: taskNo })
.then(res => {
sampleList.value = res.result
})
.catch(err => {
console.error(err)
})
}
// 处理小数位数补0或去除多余位数
function checkDecimal(e, field) {
if (e == '') return
@@ -197,34 +240,12 @@ function checkDecimal(e, field) {
}
}
// 组装字段(处理默认值、初始化 picker 状态
function assembleFields() {
const formFieldsArr = []
for (const field of sourceFormFields.value) {
//日期类型的默认值
if (field.type == 'date') {
const value = field.value
field.showPicker = false
if (value == 'curDate') {
field.value = nx.$dayjs.format('yyyy-MM-dd')
}
}
if (field.type == 'select') {
field.showPicker = false
}
formFieldsArr.push(field)
}
//先序列化再转json避免json里定义的方法丢失
formFields.value = JSON.parse(JSON.stringify(formFieldsArr, nx.$helper.replacer), nx.$helper.reviver)
}
// 绑定数据(将 formValue 填入字段)
// 绑定数据(将 formData 填入字段
function bindFormValue() {
//formValue
for (const field of formFields.value) {
const fieldKey = field.fieldKey
if (fieldKey) {
const value = formValue.value[fieldKey]
const value = formData.value[fieldKey]
if (value) {
field.value = value
}
@@ -232,122 +253,76 @@ function bindFormValue() {
}
}
// 获取表单字段和值(老数据)
function loadHeadFieldsAndValueByTaskNo() {
nx.$api.assayTask
.queryHeadValueByTaskNo({ taskNo: currentTaskNo.value })
.then(res => {
const result = res.result
const formConf = result.formConf
sourceFormFields.value = eval(formConf)
formValue.value = JSON.parse(result.formValue)
//加载和处理表单字段
assembleFields()
//绑定数据
bindFormValue()
//读取字段里的动态选项
loadFieldApiData()
console.log('formFields', formFields.value)
})
.catch(err => {
//如果没有查到数据,按配置读取新的表单字段
getHeadFields()
})
}
// 获取表单字段(新数据)
function getHeadFields() {
nx.$api.assayTask.queryHeadFieldsByTaskNo({ taskNo: currentTaskNoVal }).then(res => {
sourceFormFields.value = analysisFormAndFields(res)
//加载和处理表单字段
assembleFields()
//读取字段里的动态选项
loadFieldApiData()
})
}
// 解析表单结构
function analysisFormAndFields(formRet) {
const fieldsArray = []
for (const form of formRet) {
let content = cloneDeep(form.content)
content = eval('(' + content + ')')
const column = content.column
for (const field of column) {
fieldsArray.push(field)
}
}
return fieldsArray
}
// 将抬头字段值同步保存到明细字段
function saveHeadValueToDetail(onComplete) {
//循环抬头字段,提取需哟保存到明细的字段
const conf = []
const realFormData = computed(() => {
const formData = {}
for (const field of formFields.value) {
const prop = field.prop
const headToDetailField = field.headToDetailField
if (prop && headToDetailField && headToDetailField.trim() != '') {
const value = field.value
const r = {
prop: field.prop,
value: value,
headToDetailField: headToDetailField
}
conf.push(r)
if (field.fieldKey) {
formData[field.fieldKey] = field.value
}
}
const data = {
taskNo: currentTaskNo.value,
conf: conf
}
nx.$api.assayTask.saveHeadValueToDetail(data).then(res => {
if (onComplete) onComplete()
})
}
return formData
})
// 实际保存逻辑
function handleSave() {
//组装数据
const formValueObj = {}
for (const field of formFields.value) {
const prop = field.prop
if (prop) {
formValueObj[prop] = field.value
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))
}
}
const value = {
taskNo: currentTaskNo.value,
formValue: JSON.stringify(formValueObj),
formConf: JSON.stringify(sourceFormFields.value, replacer)
}
nx.$api.assayTask.saveHeadValue(value).then(async res => {
await saveHeadValueToDetail(async () => {
if (checkPropertyEquality()) {
await processIds(sampleList.value, 100)
} else {
uni.redirectTo({
url: '/pages/analysis/sample/sample-work-detail?currentTaskNo=' + currentTaskNo.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() {
for (const field of formFields.value) {
const prop = field.prop
if (prop && field['headToDetailField'] && field['headToDetailField'].trim() !== '') {
const flag = formValue.value[prop] !== field.value ? true : false
if (flag) return true
if (configQCSampleMethodInfos.value.length === 0) return false
for (const config of configQCSampleMethodInfos.value) {
const target = config.target
if (!target) continue
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: '提示',
@@ -355,130 +330,12 @@ function saveHeadData() {
showCancel: false,
success: res => {
if (res.confirm) {
handleSave()
handleSave(true)
}
}
})
} else {
handleSave()
}
}
// 通过样品id查询明细
async function getSampleDataById(taskDetailId) {
let fieldGroup = []
const { result, additionalProperties } = await nx.$api.assayTask.queryFieldsByTaskDetail({
taskDetailId,
isSearchSRange: '0'
})
fieldGroup = result
const conAssayTaskId = additionalProperties.conAssayTaskId
const busSubCSampleId = additionalProperties.busSubCSampleId
const detail = additionalProperties.taskDetail
//处理硫值、硫量:未保存过的数据,读取接口返回的硫值、硫量
// loadSValue(detail);
//按公式计算值,并检查原数据与计算后的数据是否一致
try {
calcAnalysisValue(fieldGroup)
} catch (error) {
console.log(error)
}
let valueList = []
let cupNum = 0
for (const g of fieldGroup) {
for (const f of g.fields) {
if (f.dicKey == 'bh' || f.dicKey == 'bh_up') cupNum = f.value
valueList.push({
id: f.detailId,
type: f.pOrE,
value: f.value,
name: f.name,
dataType: f.dataType
})
}
}
let params = {
busSubCSampleId,
conAssayTaskId,
// measureTime : this.curSample.measureTime,
elementParamValueList: valueList,
busAssayTaskDetailId: taskDetailId
}
if (typeof cupNum != 'undefined' && cupNum != null && cupNum != '' && cupNum != 0 && cupNum != '0') {
//提交杯号,保存到后台
params.cupNum = cupNum
}
return params
}
// 提交样品
async function submitData(data) {
try {
await nx.$api.assayTask.saveDetailValue(data)
} catch (error) {
throw error
}
}
// 批量处理样品(带间隔)
async function processIds(list, interval) {
let index = 0
const intervalId = setInterval(async () => {
if (index < list.length) {
const item = list[index]
index++
const params = await getSampleDataById(item['id'])
await submitData(params)
} else {
clearInterval(intervalId) // 所有任务完成后清除定时器
uni.redirectTo({
url: '/pages/analysis/sample/sample-work-detail?currentTaskNo=' + currentTaskNo.value
})
}
}, interval)
}
// 通用 API 请求
async function apiRequest(url) {
return request({
url: url,
method: 'GET'
})
}
// 读取字段里的API选项
async function loadFieldApiData() {
const formFieldsVal = formFields.value
let changeFlag = false
for (const field of formFieldsVal) {
const dicUrl = field.dicUrl
const type = field.type
if (dicUrl && dicUrl != '') {
//读取API选项
try {
const res = await apiRequest(dicUrl)
const data = res
const confLabel = field.props.label
const confValue = field.props.value
const emptyItem = { name: '', displayName: '' }
emptyItem[confLabel] = ''
emptyItem[confValue] = ''
//设置valueText、displayName
for (const item of data) {
if (item[confValue] == field.value) {
changeFlag = true
field.valueText = item[confLabel]
}
}
// data.unshift(emptyItem) //添加空数据
field.options = data
} catch (e) {}
}
}
if (changeFlag) {
// 重新序列化以触发响应式更新(保留函数)
formFields.value = JSON.parse(JSON.stringify(formFieldsVal, nx.$helper.replacer), nx.$helper.reviver)
handleSave(false)
}
}
@@ -493,7 +350,7 @@ async function loadFieldApiData() {
* */
function pickerConfirm(event, field) {
if (field.type == 'date') {
field.value = nx.$dayjs(event.value).format(field.format)
field.value = nx.$dayjs(event.value).valueOf()
field.showPicker = false
return
}
@@ -509,55 +366,6 @@ function pickerConfirm(event, field) {
}
field.showPicker = false
}
// 检查是否需要加载硫值(原逻辑保留)
function 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
}
// 读取硫值、硫量(原逻辑保留,未启用)
// function 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
// }
// }
// 根据 key 获取字段(依赖 fieldGroup但当前无 fieldGroup保留原逻辑结构
function getFieldByKey(key) {
// 注意:原代码中 this.fieldGroup 未定义,此处无法实现,保留函数结构
// const group = this.fieldGroup
// 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
return null
}
// ========== 工具函数(保留原位置) ==========
</script>
<style lang="scss" scoped>