Files
zgty-mas-m/pages/analysis/sample/sample-work-edit-task.vue
2025-10-10 18:16:14 +08:00

749 lines
19 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 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">
<u-form :model="taskInstance" ref="uForm" label-width="210">
<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' && field.display" class="content-title">
{{ field.textValue }}
</view>
<!--普通输入框-->
<u-input
v-if="
field.type == 'input' &&
(field.decimalCount == null || field.decimalCount == '' || Number(field.decimalCount) < 0)
"
v-model="field.value"
clearable
:placeholder="field.placeholder"
:disabled="field.fillingWay != '1'"
/>
<!--数字,限制小数位数-->
<u-input
v-if="field.type == 'input' && field.decimalCount != null && Number(field.decimalCount) >= 0"
type="number"
clearable
@blur="event => checkDecimal(event, field)"
v-model="field.value"
:placeholder="field.placeholder"
:disabled="field.fillingWay != '1'"
/>
<!--select-->
<view v-if="field.type == 'select'" class="x-bc select-my" @click="handleFieldClick(field)">
<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="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">{{ field.value }}</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="date"
@cancel="field.showPicker = false"
@confirm="event => pickerConfirm(event, field)"
></u-datetime-picker>
</view>
</view>
</u-form>
</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'
// ========== data ==========
// 标题
const title = ref('编辑指派单')
// 表单模型
const taskInstance = ref({})
// tab标题的滚动条位置
const scrollTop = ref(0)
// 当前任务样品
const currentTaskNo = 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)
// 页面加载
onLoad(param => {
if (param.currentTaskNo) {
currentTaskNo.value = param.currentTaskNo
}
title.value = '样品分析-任务指派单:' + currentTaskNo.value
loadHeadFieldsAndValueByTaskNo()
getSampleList(currentTaskNo.value)
})
// ========== methods ==========
// 点击字段(打开选择器)
function handleFieldClick(field) {
if (field.type == 'date') {
if (field.fillingWay == '1') {
field.showPicker = true
}
return
}
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)
})
}
// 返回上一页(原注释保留,未启用)
// function customBack() {
// uni.redirectTo({
// url: "/pages/sample/sample-work-detail"
// });
// }
// 右上角导航点击(空实现)
function navRightClick() {}
// 处理小数位数补0或去除多余位数
function checkDecimal(e, field) {
if (e == '') return
const decimalCount = field.decimalCount
if (decimalCount == null || decimalCount == '' || isNaN(decimalCount)) return
const count = Number(field.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, replacer), reviver)
}
// 绑定数据(将 formValue 填入字段)
function bindFormValue() {
//formValue
for (const field of formFields.value) {
const prop = field.prop
if (prop) {
const value = formValue.value[prop]
if (value) {
field.value = value
if (field.type == 'select') {
field.valueText = 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, replacer), 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
}
// ========== 工具函数(保留原位置) ==========
// 自定义 replacer将函数转换为字符串。json序列化和反序列化时避免函数丢失
function replacer(key, value) {
if (typeof value === 'function') {
return value.toString()
}
return value
}
// 自定义 reviver将字符串转换回函数.json序列化和反序列化时避免函数丢失
const functionKeys = ['change', 'dicFormatter']
function reviver(key, value) {
if (functionKeys.includes(key)) {
// 将字符串转换为函数
return new Function('return ' + value)()
}
return value
}
</script>
<style lang="scss" scoped>
.navbar-right {
font-size: 16px;
color: #fff;
margin-right: 20px;
}
.content-title {
width: 100%;
font-size: 20px;
font-weight: 300;
}
.content-title-name {
padding: 10px;
text-align: center;
}
.content-main-height {
height: calc(100vh - 125px);
}
.content-main-left {
height: calc(100vh - 205px);
background-color: #f6f6f6;
}
.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;
}
}
.u-tab-item {
height: 80px;
background: #f6f6f6;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: #444;
font-weight: 400;
line-height: 1;
border-width: 2px;
border-bottom: dotted;
}
.u-tab-item-active {
position: relative;
color: #0055a2;
font-size: 18px;
font-weight: 600;
background: #fff;
}
.u-tab-item-active::before {
content: '';
position: absolute;
height: 16px;
left: 0;
top: 20px;
}
.content-main-left-operation {
height: 80px;
padding-top: 15px;
}
.content-main-right {
height: calc(100vh - 205px);
}
.content-main-right-operation {
height: 80px;
padding-top: 15px;
}
.auncel-container {
display: flex;
justify-content: center;
align-items: center;
}
.auncel {
width: 400px;
height: 400px;
background-image: url(/static/images/auncel.png);
background-repeat: no-repeat;
background-size: 100%;
}
.auncel-title {
width: 100%;
height: 210px;
}
.auncel-weight {
padding: 20px;
}
.weight {
width: 100%;
min-width: 200px;
height: 100px;
padding: 0 15px;
display: flex;
flex-direction: row;
//background-color: #2c405a;
}
.weight-data {
flex-grow: 1;
height: 100%;
color: #4cd964;
text-align: right;
line-height: 100px;
letter-spacing: 2px;
font-size: 75px;
font-family: zzjc-lcd;
}
.weight-data-yellow {
flex-grow: 1;
height: 100%;
color: #ffff00;
text-align: right;
line-height: 100px;
letter-spacing: 2px;
font-size: 75px;
font-family: zzjc-lcd;
}
.weight-data-warning {
flex-grow: 1;
height: 100%;
color: #ff3333;
text-align: right;
line-height: 100px;
font-size: 75px;
font-family: zzjc-lcd;
}
.weight-unit {
color: #ffffff;
font-size: 65px;
line-height: 100px;
padding-left: 10px;
}
</style>