599 lines
17 KiB
Vue
599 lines
17 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">
|
||
<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>
|
||
</view>
|
||
</scroll-view>
|
||
<u-button type="primary" @click="saveHeadData">保存</u-button>
|
||
</u-col>
|
||
<u-col span="1"> </u-col>
|
||
</u-row>
|
||
</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'
|
||
|
||
// 标题
|
||
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 formFields = ref([])
|
||
// 样品列表
|
||
const sampleList = ref([])
|
||
|
||
// 表单引用
|
||
const uForm = ref(null)
|
||
|
||
const staticFormSchema = [
|
||
{ 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: '请输入' }
|
||
]
|
||
let dynamicFormSchema = []
|
||
|
||
// 页面加载
|
||
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()
|
||
})
|
||
}
|
||
|
||
// 点击字段(打开选择器)
|
||
function handleFieldClick(field) {
|
||
field.showPicker = true
|
||
}
|
||
|
||
// 判断是否需要同步到明细字段(加粗显示)
|
||
function checkFeadToDetailField(headToDetailField) {
|
||
if (headToDetailField && headToDetailField.trim() != '') {
|
||
return true
|
||
} else {
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 获取样品列表
|
||
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
|
||
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)
|
||
}
|
||
}
|
||
|
||
// 组装字段(处理默认值、初始化 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 填入字段)
|
||
function bindFormValue() {
|
||
//formValue
|
||
for (const field of formFields.value) {
|
||
const fieldKey = field.fieldKey
|
||
if (fieldKey) {
|
||
const value = formValue.value[fieldKey]
|
||
if (value) {
|
||
field.value = value
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 获取表单字段和值(老数据)
|
||
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 = []
|
||
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)
|
||
}
|
||
}
|
||
const data = {
|
||
taskNo: currentTaskNo.value,
|
||
conf: conf
|
||
}
|
||
nx.$api.assayTask.saveHeadValueToDetail(data).then(res => {
|
||
if (onComplete) onComplete()
|
||
})
|
||
}
|
||
|
||
// 实际保存逻辑
|
||
function handleSave() {
|
||
//组装数据
|
||
const formValueObj = {}
|
||
for (const field of formFields.value) {
|
||
const prop = field.prop
|
||
if (prop) {
|
||
formValueObj[prop] = field.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
|
||
})
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
// 检查字段修改前和修改后字段值是否相等
|
||
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
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// 保存抬头字段(带确认弹窗)
|
||
function saveHeadData() {
|
||
if (checkPropertyEquality()) {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '您修改的字段将影响样品分析结果,系统将重新计算',
|
||
showCancel: false,
|
||
success: res => {
|
||
if (res.confirm) {
|
||
handleSave()
|
||
}
|
||
}
|
||
})
|
||
} 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)
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 选择器组件确认事件
|
||
* event: 事件默认参数
|
||
* field: 当前字段
|
||
* 处理逻辑:
|
||
* field.value = 选中的值
|
||
* field.valueText = 选中的文本
|
||
* todo: 关联字段在动态字段的change事件处理
|
||
* */
|
||
function pickerConfirm(event, field) {
|
||
if (field.type == 'date') {
|
||
field.value = nx.$dayjs(event.value).format(field.format)
|
||
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
|
||
}
|
||
|
||
// 检查是否需要加载硫值(原逻辑保留)
|
||
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>
|
||
.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>
|