Files
sh-app/scanpages/hj/material_sampling.vue
T
2026-07-06 18:23:35 +08:00

703 lines
16 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 class="detail-page">
<!-- 焊口主标识 -->
<view class="joint-hero">
<text class="joint-hero__code">{{ info.WeldJointCode || '--' }}</text>
<view class="joint-hero__meta">
<view class="joint-hero__pill">
<text class="pill-label">管线编码</text>
<text class="pill-value">{{ info.PipelineCode || '--' }}</text>
</view>
<view class="joint-hero__pill">
<text class="pill-label">项目名称</text>
<text class="pill-value">{{ info.ProjectName || info.ProjectId || '--' }}</text>
</view>
</view>
</view>
<!-- 下料填报 -->
<view class="section">
<view class="section__header">
<view class="section__bar"></view>
<text class="section__title">下料填报</text>
</view>
<view class="card">
<!-- 下料长度 -->
<view class="card__row" @click="showPaintCodePicker = true">
<text class="card__label required">下料长度</text>
<view class="card__value-wrap">
<input class="card__input" v-model="layingOffForm.CuttingLength" type="digit"
placeholder="请输入下料长度" />
</view>
</view>
<!-- 坡口形式 -->
<view class="card__row">
<text class="card__label required">坡口形式</text>
<view class="card__value-wrap">
<input class="card__input" v-model="layingOffForm.GrooveForm" type="text"
placeholder="请输入坡口形式" />
</view>
</view>
<!-- 坡口角度 -->
<view class="card__row required">
<text class="card__label">坡口角度</text>
<view class="card__value-wrap">
<input class="card__input" v-model="layingOffForm.GrooveAngle" type="digit"
placeholder="请输入坡口角度" />
</view>
</view>
</view>
</view>
<!-- 检查信息 -->
<view class="section">
<view class="section__header">
<view class="section__bar"></view>
<text class="section__title">检查信息</text>
</view>
<view class="card">
<!-- 检查结果 -->
<view class="card__row">
<text class="card__label">检查结果</text>
<view class="card__value-wrap card__value-wrap--clickable">
<nbd-check-btn v-model="layingOffForm.CheckResult" passKey="合格" failKey="不合格" passText="合格"
failText="不合格" />
</view>
</view>
<view class="fail-section"
:class="{ 'is-visible': failVisible, 'is-enter': failAnimating, 'is-leave': failLeaving }">
<!-- 不合格原因 -->
<view class="card__row card__row--stack">
<text class="card__label">不合格原因</text>
<view class="card__value-wrap">
<textarea class="remark-input" v-model="layingOffForm.UnqualifiedReason" placeholder="请输入"
:maxlength="200" auto-height />
</view>
</view>
<!-- 整改要求 -->
<view class="card__row card__row--stack">
<text class="card__label">整改要求</text>
<view class="card__value-wrap">
<textarea class="remark-input" v-model="layingOffForm.RectifyRequirement" placeholder="请输入"
:maxlength="200" auto-height />
</view>
</view>
</view>
<!-- 备注 -->
<view class="card__row card__row--stack">
<text class="card__label">备注</text>
<view class="card__value-wrap">
<textarea class="remark-input" v-model="layingOffForm.Remark" placeholder="请输入备注信息(选填)"
:maxlength="200" auto-height />
</view>
</view>
<!-- 照片 -->
<view class="card__row card__row--stack">
<text class="card__label">照片</text>
<view class="card__value-wrap">
<nbd-photo-upload ref="photoRef" v-model:attachUrl="files" typeName="AntiRust" />
</view>
</view>
</view>
</view>
<block v-for="(item, index) in info.preWeldMaterialItems" :key="item.MaterialCode || index">
<!-- 材料信息 -->
<view class="section">
<view class="section__header">
<view class="section__bar"></view>
<text class="section__title">{{ item.MaterialName || item.MaterialCode || '--' }}</text>
</view>
<view class="card">
<view class="card__row">
<text class="card__label">炉号</text>
<text class="card__value">
{{ item.HeatNo || '--' }}
</text>
</view>
<view class="card__row">
<text class="card__label">批号</text>
<text class="card__value">{{ item.BatchNo || '--' }}</text>
</view>
<view class="card__row">
<text class="card__label">规格</text>
<text class="card__value">{{ item.MaterialSpec || '--' }}</text>
</view>
<view class="card__row">
<text class="card__label">单位</text>
<text class="card__value">{{ item.MaterialUnit || '--' }}</text>
</view>
<view class="card__row card__row--stack">
<text class="card__label">材料描述</text>
<text class="card__value">{{ item.MaterialDef || '--' }}</text>
</view>
</view>
<view class="nav-row" @click="handleMutualCheck(item.MaterialCode)">
<view class="nav-row__left">
<view class="nav-row__dot"></view>
<text class="nav-row__label">互检上一工序</text>
</view>
<text class="nav-row__arrow"></text>
</view>
<!-- 防腐记录 -->
<nbd-anti-record :custom-style="{ margin: '0rpx'}" :params="{ materialCode: item.MaterialCode, projectId: currentProject.ProjectId }" />
</view>
</block>
<!-- 备注 -->
<view class="section">
<view class="section__header">
<view class="section__bar"></view>
<text class="section__title">备注</text>
</view>
<view class="remark-card">
<textarea class="remark-input" v-model="remark" placeholder="请输入备注信息(选填)" :maxlength="200"
auto-height />
<text class="remark-count">{{ remark.length }}/200</text>
</view>
</view>
<!-- 现场照片 -->
<view class="section">
<view class="section__header">
<view class="section__bar"></view>
<text class="section__title">照片</text>
</view>
<view class="img-box">
<nbd-photo-upload ref="photoRef" v-model:attachUrl="formAttachUrl" typeName="MaterialSampling" />
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-bar">
<view class="btn-primary-gradient" @click="handleSubmit">
<text class="btn-primary-gradient__text">提交校验结果</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, watch, nextTick } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/store'
import { reqPreWeldJointByWeldJointId, reqSaveCuttingCheck } from '@/api/hj'
const userStore = useUserStore()
const { userInfo, currentProject } = storeToRefs(userStore)
const info = ref({})
const remark = ref('')
const formAttachUrl = ref('')
const id = ref('')
const layingOffForm = ref({
CuttingLength: '',
GrooveForm: '',
GrooveAngle: '',
CheckResult: '合格',
UnqualifiedReason: '',
RectifyRequirement: '',
Remark: ''
})
const check = ref({
CodeBatch: true,
Quantity: true
})
// 照片组件引用
const photoRef = ref(null)
const failVisible = ref(layingOffForm.value.CheckResult === '不合格')
const failAnimating = ref(false)
const failLeaving = ref(false)
watch(() => layingOffForm.value.CheckResult, async (val) => {
if (val === '不合格') {
failVisible.value = true
await nextTick()
failAnimating.value = true
failLeaving.value = false
} else {
failLeaving.value = true
failAnimating.value = false
setTimeout(() => {
failVisible.value = false
failLeaving.value = false
}, 250)
}
})
const handleSubmit = () => {
uni.showModal({
title: '确认提交',
content: '确定提交校验结果吗?',
success: async (res) => {
if (res.confirm) {
if (photoRef.value?.hasPending()) {
uni.showLoading({ title: '上传照片中...', mask: true })
await photoRef.value.uploadAll()
uni.hideLoading()
}
const CuttingAttachUrl1 = formAttachUrl.value || info.value.CuttingAttachUrl1 || ''
const now = new Date()
const pad = (n) => String(n).padStart(2, '0')
const checkTime = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
const params = {
ProjectId: currentProject.value?.ProjectId,
WeldJointId: info.value?.WeldJointId,
IsMaterialCodeBatchNoAccurate: check.value.CodeBatch,
IsMaterialQuantityAccurate: check.value.Quantity,
CheckPerson: userInfo.value?.PersonId,
CheckTime: checkTime,
Remark: remark.value,
CuttingAttachUrl1
}
console.log('提交参数:', params)
await reqSaveCuttingCheck(params)
uni.showToast({ title: '提交成功', icon: 'none' })
setTimeout(() => { uni.navigateBack() }, 1500)
}
}
})
}
onLoad((opt) => {
if (opt.id) {
id.value = opt.id
}
})
onShow(() => {
if (id.value) {
getInfo(id.value)
}
})
const getInfo = async (id) => {
try {
uni.showLoading({ title: '加载中...', mask: true })
const res = await reqPreWeldJointByWeldJointId(id)
if (res.code === 1) {
info.value = res.data
formAttachUrl.value = res.data?.CuttingAttachUrl1 || ''
}
} catch (error) {
console.error('获取焊口详情失败:', error)
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
uni.hideLoading()
}
}
const handleMutualCheck = (id) => {
if (!id) {
uni.showToast({ title: '缺少材料信息', icon: 'none' })
return
}
uni.navigateTo({ url: `/scanpages/hj/do_anti_rust?id=${id}` })
}
const gaugeWidth = (val, min, max) => {
if (!val && val !== 0) return '0%'
const num = parseFloat(val)
if (isNaN(num)) return '0%'
const pct = Math.min(Math.max((num - min) / (max - min) * 100, 0), 100)
return pct + '%'
}
</script>
<style lang="scss" scoped>
// 继承全局变量(定义在 uni.scss)
$primary: $app-primary;
$primary-light: $app-primary-light;
$primary-mid: $app-primary-mid;
$primary-dark: $app-primary-dark;
$text: $app-text;
$text-secondary: $app-text-secondary;
$border: $app-border;
$radius: $app-radius;
// 页面专属
$bg: #f0f2f5;
$card-bg: #ffffff;
$shadow-sm: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
$shadow-md: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
.detail-page {
background: $bg;
}
// ===== 焊口主标识 =====
.joint-hero {
position: relative;
margin: 24rpx;
padding: 36rpx 32rpx 28rpx;
background: $card-bg;
border-radius: $radius;
box-shadow: $shadow-md;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4rpx;
background: linear-gradient(90deg, $primary, #6ba3ff, $primary);
background-size: 200% 100%;
animation: shimmer 3s ease-in-out infinite;
}
@keyframes shimmer {
0%,
100% {
background-position: 0% 0;
}
50% {
background-position: 100% 0;
}
}
&__code {
display: block;
font-size: 52rpx;
font-weight: 800;
color: $text;
line-height: 1.2;
word-break: break-all;
font-family: monospace;
letter-spacing: 2rpx;
margin-bottom: 28rpx;
position: relative;
padding-left: 24rpx;
&::before {
content: '';
position: absolute;
left: 0;
top: 6rpx;
bottom: 6rpx;
width: 4rpx;
background: $primary;
border-radius: 2rpx;
}
}
&__meta {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
&__pill {
display: inline-flex;
width: 100%;
align-items: center;
gap: 8rpx;
background: $primary-light;
padding: 10rpx 18rpx;
border-radius: 10rpx;
border: 1rpx solid $primary-mid;
.pill-label {
font-size: 22rpx;
color: $text-secondary;
width: 60rpx;
}
.pill-value {
font-size: 24rpx;
font-weight: 600;
color: $primary-dark;
font-family: monospace;
}
}
}
// ===== 坡口参数仪表 =====
.spec-grid {
background: $card-bg;
border-radius: $radius;
padding: 8rpx 0;
box-shadow: $shadow-md;
.spec-item {
padding: 24rpx 28rpx;
border-bottom: 1rpx solid $border;
&:last-child {
border-bottom: none;
}
&__header {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 14rpx;
}
&__label {
font-size: 24rpx;
color: $text-secondary;
font-weight: 500;
}
&__value {
font-size: 40rpx;
font-weight: 700;
color: $text;
line-height: 1;
font-variant-numeric: tabular-nums;
}
&__unit {
font-size: 24rpx;
font-weight: 400;
color: $text-secondary;
margin-left: 6rpx;
}
&__bar-track {
height: 8rpx;
background: #f0f2f5;
border-radius: 4rpx;
overflow: hidden;
position: relative;
}
&__bar-fill {
height: 100%;
border-radius: 4rpx;
background: linear-gradient(90deg, $primary, #6ba3ff);
transition: width 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
position: relative;
&::after {
content: '';
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 12rpx;
border-radius: 4rpx;
background: rgba(255, 255, 255, 0.35);
}
&.fill--amber {
background: linear-gradient(90deg, #f59e0b, #fbbf24);
}
&.fill--teal {
background: linear-gradient(90deg, #14b8a6, #2dd4bf);
}
}
}
}
// ===== 校验卡片 =====
.check-card {
background: $card-bg;
border-radius: $radius;
overflow: hidden;
box-shadow: $shadow-md;
}
.check-item {
display: flex;
align-items: center;
padding: 22rpx 28rpx;
border-bottom: 1rpx solid $border;
&:last-child {
border-bottom: none;
}
&__left {
flex: 1;
min-width: 0;
}
&__label {
font-size: 26rpx;
font-weight: 500;
color: $text;
display: block;
margin-bottom: 4rpx;
}
&__desc {
font-size: 22rpx;
color: $text-secondary;
display: block;
}
&__action {
flex-shrink: 0;
margin-left: 20rpx;
}
}
// ===== 提交栏(悬浮底部) =====
.submit-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 20rpx 24rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
background: linear-gradient(180deg, rgba(240, 242, 245, 0) 0%, $bg 20%);
}
// ===== 备注输入框 =====
.remark-card {
background: $card-bg;
border-radius: $radius;
padding: 24rpx 28rpx;
box-shadow: $shadow-md;
position: relative;
.remark-input {
width: 100%;
min-height: 120rpx;
font-size: 26rpx;
color: $text;
line-height: 1.6;
padding: 0;
margin: 0;
background: transparent;
&::placeholder {
color: #b0bec5;
}
}
.remark-count {
position: absolute;
bottom: 16rpx;
right: 24rpx;
font-size: 22rpx;
color: $text-secondary;
}
}
// ================================================================
// 材料质检区块 —— 三合一 section
// ================================================================
// section 容器:微背景 + 内边距聚合三个子元素
.section {
background: #fafbfc;
border-radius: 20rpx;
padding: 24rpx 0 0;
margin-bottom: 28rpx;
box-shadow: $shadow-md;
}
// ---- 材料详情卡片 ----
// 底部圆角清零,与下方 nav-row 无缝衔接
.card {
border-radius: $radius $radius 0 0;
box-shadow: none;
}
// 编码类字段(炉号 / 批号 / 规格 / 单位)→ monospace
.card__value {
font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
font-size: 28rpx;
font-weight: 500;
color: $text;
letter-spacing: 0.3rpx;
}
// 材料描述 → 回退系统字体
.card__row--stack .card__value {
font-family: inherit;
font-size: 26rpx;
font-weight: 400;
letter-spacing: normal;
color: $text-secondary;
}
// ---- 互检导航按钮 ----
.nav-row {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0;
padding: 24rpx 28rpx;
background: $card-bg;
border-radius: 0;
border-top: 1rpx solid $border;
box-shadow: none;
transition: background 0.2s ease;
&:active {
background: #f5f7fa;
}
&__left {
display: flex;
align-items: center;
gap: 14rpx;
}
&__dot {
width: 10rpx;
height: 10rpx;
border-radius: 50%;
background: $primary;
flex-shrink: 0;
}
&__label {
font-size: 28rpx;
font-weight: 600;
color: $text;
letter-spacing: 1rpx;
}
&__arrow {
font-size: 36rpx;
color: $text-secondary;
font-weight: 300;
line-height: 1;
margin-right: -4rpx;
}
}
// ---- 防腐记录 ----
// 覆盖组件内部边距(已由父 .section 聚合)
:deep(.anti-record__section) {
margin: 0;
}
// 防腐记录卡片底部圆角收口,与 section 容器对齐
:deep(.anti-record__card) {
border-radius: 0 0 $radius $radius;
border-top: 1rpx solid $border;
}
// 防腐记录 header 缩进对齐,隐藏蓝色竖条(父 section header 已承载标题层级)
:deep(.anti-record__header) {
padding: 20rpx 28rpx 12rpx;
margin-bottom: 0;
}
:deep(.anti-record__bar) {
display: none;
}
</style>