1. 新增钢卷详情、扫码页面与设备巡检组件 2. 调整首页跳转逻辑至扫码页面,更新tabBar配置 3. 隐藏旧的分条、其他操作模块,替换钢卷详情弹窗为页面跳转 4. 复用巡检组件到扫码页面与设备巡检页
1172 lines
30 KiB
Vue
1172 lines
30 KiB
Vue
<template>
|
|
<view class="page-container">
|
|
<!-- 顶部钢卷信息 -->
|
|
<view class="coil-header">
|
|
<view class="header-top">
|
|
<view class="header-left">
|
|
<text class="coil-label">钢卷号</text>
|
|
<text class="coil-no">{{ coilInfo.currentCoilNo || '-' }}</text>
|
|
</view>
|
|
<view class="header-right">
|
|
<text class="coil-status" :class="'status-' + (coilInfo.status || 0)">
|
|
{{ coilInfo.status === 1 ? '已出库' : '在库' }}
|
|
</text>
|
|
</view>
|
|
</view>
|
|
<text class="coil-enter-no">入场号: {{ coilInfo.enterCoilNo || '-' }}</text>
|
|
</view>
|
|
|
|
<!-- Tab 切换 -->
|
|
<view class="tabs-bar">
|
|
<view
|
|
v-for="tab in tabs"
|
|
:key="tab.value"
|
|
class="tab-item"
|
|
:class="{ 'tab-active': currentTab === tab.value }"
|
|
@click="currentTab = tab.value"
|
|
>
|
|
<text class="tab-text">{{ tab.label }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Tab 内容 -->
|
|
<swiper
|
|
:current="tabIndex"
|
|
class="tab-content"
|
|
@change="onSwiperChange"
|
|
:style="{ height: contentHeight + 'px' }"
|
|
>
|
|
<!-- Tab 1: 详情查看 -->
|
|
<swiper-item>
|
|
<scroll-view scroll-y class="tab-scroll" @scroll="onScroll">
|
|
<view class="info-section">
|
|
<view class="section-title">基础信息</view>
|
|
<view class="info-grid">
|
|
<view class="info-item">
|
|
<text class="label">入场钢卷号</text>
|
|
<text class="value">{{ coilInfo.enterCoilNo || '-' }}</text>
|
|
</view>
|
|
<view class="info-item">
|
|
<text class="label">厂家卷号</text>
|
|
<text class="value">{{ coilInfo.supplierCoilNo || '-' }}</text>
|
|
</view>
|
|
<view class="info-item">
|
|
<text class="label">物料类型</text>
|
|
<text class="value">{{ coilInfo.materialType || '-' }}</text>
|
|
</view>
|
|
<view class="info-item">
|
|
<text class="label">品名</text>
|
|
<text class="value">{{ coilInfo.itemName || '-' }}</text>
|
|
</view>
|
|
<view class="info-item">
|
|
<text class="label">规格</text>
|
|
<text class="value">{{ coilInfo.specification || coilInfo.itemSpecification || '-' }}</text>
|
|
</view>
|
|
<view class="info-item">
|
|
<text class="label">材质</text>
|
|
<text class="value">{{ coilInfo.material || coilInfo.itemMaterial || '-' }}</text>
|
|
</view>
|
|
<view class="info-item">
|
|
<text class="label">厂家</text>
|
|
<text class="value">{{ coilInfo.manufacturer || coilInfo.itemManufacturer || '-' }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="info-section">
|
|
<view class="section-title">重量信息</view>
|
|
<view class="info-grid">
|
|
<view class="info-item">
|
|
<text class="label">毛重 (吨)</text>
|
|
<text class="value">{{ coilInfo.grossWeight || '-' }}</text>
|
|
</view>
|
|
<view class="info-item">
|
|
<text class="label">净重 (吨)</text>
|
|
<text class="value">{{ coilInfo.netWeight || '-' }}</text>
|
|
</view>
|
|
<view class="info-item">
|
|
<text class="label">长度 (米)</text>
|
|
<text class="value">{{ coilInfo.length || '-' }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="info-section">
|
|
<view class="section-title">库区信息</view>
|
|
<view class="info-grid">
|
|
<view class="info-item">
|
|
<text class="label">逻辑库区</text>
|
|
<text class="value">{{ coilInfo.warehouseName || '-' }}</text>
|
|
</view>
|
|
<view class="info-item">
|
|
<text class="label">真实库区</text>
|
|
<text class="value">{{ coilInfo.actualWarehouseName || '-' }}</text>
|
|
</view>
|
|
<view class="info-item">
|
|
<text class="label">班组</text>
|
|
<text class="value">{{ coilInfo.team || '-' }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="info-section">
|
|
<view class="section-title">操作信息</view>
|
|
<view class="info-list">
|
|
<view class="list-item">
|
|
<text class="label">创建人</text>
|
|
<text class="value">{{ coilInfo.createBy || '-' }}</text>
|
|
</view>
|
|
<view class="list-item">
|
|
<text class="label">创建时间</text>
|
|
<text class="value">{{ coilInfo.createTime || '-' }}</text>
|
|
</view>
|
|
<view class="list-item">
|
|
<text class="label">更新人</text>
|
|
<text class="value">{{ coilInfo.updateBy || '-' }}</text>
|
|
</view>
|
|
<view class="list-item">
|
|
<text class="label">更新时间</text>
|
|
<text class="value">{{ coilInfo.updateTime || '-' }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
</swiper-item>
|
|
|
|
<!-- Tab 2: 详情编辑 -->
|
|
<swiper-item>
|
|
<scroll-view scroll-y class="tab-scroll" @scroll="onScroll">
|
|
<view class="form-section">
|
|
<view class="form-item">
|
|
<text class="form-label">入场钢卷号</text>
|
|
<input v-model="editForm.enterCoilNo" placeholder="请输入" class="form-input" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">当前钢卷号</text>
|
|
<input v-model="editForm.currentCoilNo" placeholder="请输入" class="form-input" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">厂家卷号</text>
|
|
<input v-model="editForm.supplierCoilNo" placeholder="请输入" class="form-input" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">品名</text>
|
|
<input v-model="editForm.itemName" placeholder="请输入" class="form-input" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">规格</text>
|
|
<input v-model="editForm.itemSpecification" placeholder="请输入" class="form-input" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">材质</text>
|
|
<input v-model="editForm.itemMaterial" placeholder="请输入" class="form-input" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">厂家</text>
|
|
<input v-model="editForm.itemManufacturer" placeholder="请输入" class="form-input" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">毛重 (吨)</text>
|
|
<input v-model="editForm.grossWeight" type="digit" placeholder="请输入" class="form-input" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">净重 (吨)</text>
|
|
<input v-model="editForm.netWeight" type="digit" placeholder="请输入" class="form-input" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">长度 (米)</text>
|
|
<input v-model="editForm.length" type="digit" placeholder="请输入" class="form-input" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">班组</text>
|
|
<input v-model="editForm.team" placeholder="请输入" class="form-input" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">备注</text>
|
|
<input v-model="editForm.remark" placeholder="请输入" class="form-input" />
|
|
</view>
|
|
</view>
|
|
|
|
<view class="submit-bar">
|
|
<button class="btn btn-submit" @click="submitEdit" :loading="submitting">
|
|
{{ submitting ? '提交中...' : '保存修改' }}
|
|
</button>
|
|
</view>
|
|
</scroll-view>
|
|
</swiper-item>
|
|
|
|
<!-- Tab 3: 异常维护 -->
|
|
<swiper-item>
|
|
<scroll-view scroll-y class="tab-scroll" @scroll="onScroll">
|
|
<view v-if="defectList.length === 0" class="empty-state">
|
|
<text>暂无异常记录</text>
|
|
</view>
|
|
|
|
<view v-for="(item, index) in defectList" :key="item.abnormalId || index" class="defect-card">
|
|
<view class="defect-card-top">
|
|
<view class="defect-card-top-left">
|
|
<text class="defect-code-badge">{{ item.defectCode || '-' }}</text>
|
|
<text class="defect-degree-badge">{{ item.degree || '-' }}</text>
|
|
<text class="defect-main-badge" v-if="item.mainMark == 1">主缺陷</text>
|
|
</view>
|
|
<view class="defect-actions">
|
|
<text class="action-btn edit-btn" @click="editDefect(item)">编辑</text>
|
|
<text class="action-btn del-btn" @click="deleteDefect(item)">删除</text>
|
|
</view>
|
|
</view>
|
|
<view class="defect-desc">{{ item.remark || '暂无描述' }}</view>
|
|
<view class="defect-meta">
|
|
<text class="meta-item" v-if="item.plateSurface">板面: {{ item.plateSurface }}</text>
|
|
<text class="meta-item" v-if="item.position">断面: {{ item.position }}</text>
|
|
<text class="meta-item" v-if="item.startPosition || item.endPosition">
|
|
区间: {{ item.startPosition || '?' }}~{{ item.endPosition || '?' }}
|
|
<text class="meta-unit" v-if="item.length"> (长 {{ item.length }})</text>
|
|
</text>
|
|
</view>
|
|
<view class="defect-images" v-if="item.attachmentFiles">
|
|
<image v-for="(url, i) in item.attachmentFiles.split(',')" :key="i" :src="url" class="defect-img" mode="aspectFill" @click="previewImage(url)"></image>
|
|
</view>
|
|
<view class="defect-time">{{ item.createTime || '' }}</view>
|
|
</view>
|
|
|
|
<view class="add-defect-btn" @click="openDefectForm">
|
|
<text class="add-defect-text">+ 新增异常</text>
|
|
</view>
|
|
</scroll-view>
|
|
</swiper-item>
|
|
</swiper>
|
|
|
|
<!-- 缺陷表单弹窗 -->
|
|
<uni-popup ref="defectPopup" type="bottom" :mask-click="false">
|
|
<view class="popup-wrap">
|
|
<view class="popup-header">
|
|
<text class="popup-title">{{ defectFormMode === 'add' ? '新增异常' : '编辑异常' }}</text>
|
|
<text class="popup-close" @click="closeDefectForm">✕</text>
|
|
</view>
|
|
<scroll-view scroll-y class="popup-body">
|
|
<view class="form-item">
|
|
<text class="form-label">缺陷描述 <text class="required">*</text></text>
|
|
<textarea v-model="defectFormData.remark" placeholder="请输入缺陷描述" class="form-textarea" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">上下板面</text>
|
|
<uni-data-checkbox multiple v-model="defectFormData.plateSurface" :localdata="plateSurfaceOptions" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">断面位置</text>
|
|
<uni-data-checkbox multiple v-model="defectFormData.position" :localdata="positionOptions" />
|
|
<view class="form-tip">异常位置为内圈算起</view>
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">异常区间 <text class="required">*</text></text>
|
|
<view class="range-row">
|
|
<input v-model="defectFormData.startPosition" type="number" placeholder="开始位置" class="form-input range-input" />
|
|
<text class="range-separator">~</text>
|
|
<input v-model="defectFormData.endPosition" type="number" placeholder="结束位置" class="form-input range-input" />
|
|
</view>
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">缺陷代码 <text class="required">*</text></text>
|
|
<uni-data-checkbox v-model="defectFormData.defectCode" :localdata="defectCodeOptions" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">程度 <text class="required">*</text></text>
|
|
<uni-data-checkbox v-model="defectFormData.degree" :localdata="degreeOptions" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-label">主缺陷</text>
|
|
<uni-data-checkbox multiple v-model="defectMainMarkBind" :localdata="[{text: '是否为主缺陷', value: '1'}]" />
|
|
</view>
|
|
<view class="form-item" v-if="defectFormData.mainMark === 1">
|
|
<text class="form-label">缺陷图片</text>
|
|
<view class="upload-area">
|
|
<view v-for="(url, i) in uploadedImages" :key="i" class="upload-preview">
|
|
<image :src="url" mode="aspectFill" class="preview-img" @click="previewImage(url)"></image>
|
|
<text class="remove-img" @click="removeImage(i)">✕</text>
|
|
</view>
|
|
<view class="upload-btn" @click="chooseImage">
|
|
<text class="upload-icon">+</text>
|
|
<text class="upload-text">拍照/相册</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
<view class="popup-footer">
|
|
<button class="btn btn-cancel" @click="closeDefectForm">取消</button>
|
|
<button class="btn btn-submit" @click="submitDefectForm" :loading="defectSubmitting">{{ defectSubmitting ? '提交中...' : '保存' }}</button>
|
|
</view>
|
|
</view>
|
|
</uni-popup>
|
|
|
|
<!-- 加载状态 -->
|
|
<view class="loading-mask" v-if="pageLoading">
|
|
<uni-load-more status="loading" />
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import { getMaterialCoil, updateMaterialCoil } from '@/api/wms/coil'
|
|
import { listCoilAbnormal, addCoilAbnormal, updateCoilAbnormal, delCoilAbnormal } from '@/api/wms/coilAbnormal'
|
|
import upload from '@/utils/upload'
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
coilId: null,
|
|
coilInfo: {},
|
|
pageLoading: false,
|
|
|
|
// Tab
|
|
currentTab: 'view',
|
|
tabs: [
|
|
{ label: '详情查看', value: 'view' },
|
|
{ label: '详情编辑', value: 'edit' },
|
|
{ label: '异常维护', value: 'defect' }
|
|
],
|
|
contentHeight: 600,
|
|
|
|
// 编辑表单
|
|
editForm: {},
|
|
submitting: false,
|
|
|
|
// 缺陷列表
|
|
defectList: [],
|
|
defectCodeOptions: [],
|
|
positionOptions: [],
|
|
degreeOptions: [],
|
|
plateSurfaceOptions: [
|
|
{ text: '上板面', value: '上' },
|
|
{ text: '下板面', value: '下' }
|
|
],
|
|
|
|
// 缺陷表单
|
|
defectFormMode: 'add',
|
|
defectFormData: {
|
|
remark: '',
|
|
plateSurface: [],
|
|
position: [],
|
|
startPosition: undefined,
|
|
endPosition: undefined,
|
|
defectCode: '',
|
|
degree: '',
|
|
mainMark: 0,
|
|
attachmentFiles: ''
|
|
},
|
|
defectEditingId: null,
|
|
uploadedImages: [],
|
|
defectSubmitting: false
|
|
}
|
|
},
|
|
computed: {
|
|
tabIndex() {
|
|
return this.tabs.findIndex(t => t.value === this.currentTab)
|
|
},
|
|
defectStats() {
|
|
const stats = {}
|
|
this.defectCodeOptions.forEach(dco => {
|
|
stats[dco.value] = { label: dco.label, count: 0 }
|
|
})
|
|
this.defectList.forEach(d => {
|
|
const key = d.defectCode || ''
|
|
if (stats[key]) stats[key].count++
|
|
})
|
|
return Object.values(stats)
|
|
},
|
|
defectMainMarkBind: {
|
|
get() {
|
|
return this.defectFormData.mainMark === 1 ? ['1'] : []
|
|
},
|
|
set(val) {
|
|
this.defectFormData.mainMark = val.length > 0 ? 1 : 0
|
|
}
|
|
}
|
|
},
|
|
onLoad(options) {
|
|
this.coilId = options.coilId
|
|
if (options.tab) {
|
|
this.currentTab = options.tab
|
|
}
|
|
if (this.coilId) {
|
|
this.loadCoilDetail()
|
|
this.loadDefectList()
|
|
this.loadDicts()
|
|
}
|
|
},
|
|
onReady() {
|
|
this.$nextTick(() => {
|
|
this.updateContentHeight()
|
|
})
|
|
},
|
|
methods: {
|
|
// ========== 数据加载 ==========
|
|
async loadCoilDetail() {
|
|
this.pageLoading = true
|
|
try {
|
|
const res = await getMaterialCoil(this.coilId)
|
|
this.coilInfo = res.data || {}
|
|
this.editForm = {
|
|
enterCoilNo: this.coilInfo.enterCoilNo || '',
|
|
currentCoilNo: this.coilInfo.currentCoilNo || '',
|
|
supplierCoilNo: this.coilInfo.supplierCoilNo || '',
|
|
itemName: this.coilInfo.itemName || '',
|
|
itemSpecification: this.coilInfo.itemSpecification || this.coilInfo.specification || '',
|
|
itemMaterial: this.coilInfo.itemMaterial || this.coilInfo.material || '',
|
|
itemManufacturer: this.coilInfo.itemManufacturer || this.coilInfo.manufacturer || '',
|
|
grossWeight: this.coilInfo.grossWeight,
|
|
netWeight: this.coilInfo.netWeight,
|
|
length: this.coilInfo.length,
|
|
team: this.coilInfo.team || '',
|
|
remark: this.coilInfo.remark || ''
|
|
}
|
|
} catch (err) {
|
|
console.error('加载钢卷详情失败:', err)
|
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
|
} finally {
|
|
this.pageLoading = false
|
|
}
|
|
},
|
|
|
|
loadDicts() {
|
|
this.getDicts('coil_abnormal_code').then(res => {
|
|
this.defectCodeOptions = (res.data || []).map(item => ({
|
|
text: item.dictLabel,
|
|
value: item.dictValue
|
|
}))
|
|
})
|
|
this.getDicts('coil_abnormal_position').then(res => {
|
|
this.positionOptions = (res.data || []).map(item => ({
|
|
text: item.dictLabel,
|
|
value: item.dictValue
|
|
}))
|
|
})
|
|
this.getDicts('coil_abnormal_degree').then(res => {
|
|
this.degreeOptions = (res.data || []).map(item => ({
|
|
text: item.dictLabel,
|
|
value: item.dictValue
|
|
}))
|
|
})
|
|
},
|
|
|
|
async loadDefectList() {
|
|
try {
|
|
const res = await listCoilAbnormal({ coilId: this.coilId, pageNum: 1, pageSize: 999 })
|
|
this.defectList = res.rows || []
|
|
} catch (err) {
|
|
console.error('加载缺陷列表失败:', err)
|
|
}
|
|
},
|
|
|
|
// ========== Tab 切换 ==========
|
|
onSwiperChange(e) {
|
|
const idx = e.detail.current
|
|
this.currentTab = this.tabs[idx].value
|
|
this.updateContentHeight()
|
|
},
|
|
|
|
onScroll() {
|
|
// placeholder
|
|
},
|
|
|
|
updateContentHeight() {
|
|
try {
|
|
const sys = uni.getSystemInfoSync()
|
|
// windowHeight 已排除默认导航栏高度
|
|
// 减去 coil-header(~80px) + tabs-bar(~56px) 的估算高度
|
|
this.contentHeight = Math.max(sys.windowHeight - 160, 300)
|
|
} catch (e) {
|
|
this.contentHeight = 500
|
|
}
|
|
},
|
|
|
|
// ========== 编辑提交 ==========
|
|
async submitEdit() {
|
|
this.submitting = true
|
|
try {
|
|
const data = {
|
|
coilId: this.coilId,
|
|
...this.editForm
|
|
}
|
|
await updateMaterialCoil(data)
|
|
uni.showToast({ title: '保存成功', icon: 'success' })
|
|
this.loadCoilDetail()
|
|
} catch (err) {
|
|
console.error('保存失败:', err)
|
|
uni.showToast({ title: '保存失败', icon: 'none' })
|
|
} finally {
|
|
this.submitting = false
|
|
}
|
|
},
|
|
|
|
// ========== 缺陷操作 ==========
|
|
openDefectForm() {
|
|
this.defectFormMode = 'add'
|
|
this.defectEditingId = null
|
|
this.defectFormData = {
|
|
remark: '',
|
|
plateSurface: [],
|
|
position: [],
|
|
startPosition: undefined,
|
|
endPosition: undefined,
|
|
defectCode: '',
|
|
degree: '',
|
|
mainMark: 0,
|
|
attachmentFiles: ''
|
|
}
|
|
this.uploadedImages = []
|
|
this.$refs.defectPopup.open()
|
|
},
|
|
|
|
editDefect(item) {
|
|
this.defectFormMode = 'edit'
|
|
this.defectEditingId = item.abnormalId
|
|
this.defectFormData = {
|
|
remark: item.remark || '',
|
|
plateSurface: item.plateSurface ? item.plateSurface.split(',') : [],
|
|
position: item.position ? item.position.split(',') : [],
|
|
startPosition: item.startPosition,
|
|
endPosition: item.endPosition,
|
|
defectCode: item.defectCode || '',
|
|
degree: item.degree || '',
|
|
mainMark: item.mainMark !== undefined ? item.mainMark : 0,
|
|
attachmentFiles: item.attachmentFiles || ''
|
|
}
|
|
this.uploadedImages = item.attachmentFiles ? item.attachmentFiles.split(',').filter(u => u.trim()) : []
|
|
this.$refs.defectPopup.open()
|
|
},
|
|
|
|
closeDefectForm() {
|
|
this.$refs.defectPopup.close()
|
|
},
|
|
|
|
async deleteDefect(item) {
|
|
uni.showModal({
|
|
title: '提示',
|
|
content: '确定删除此异常记录吗?',
|
|
success: async (res) => {
|
|
if (res.confirm) {
|
|
try {
|
|
await delCoilAbnormal(item.abnormalId)
|
|
uni.showToast({ title: '删除成功', icon: 'success' })
|
|
this.loadDefectList()
|
|
} catch (err) {
|
|
uni.showToast({ title: '删除失败', icon: 'none' })
|
|
}
|
|
}
|
|
}
|
|
})
|
|
},
|
|
|
|
async submitDefectForm() {
|
|
if (!this.defectFormData.remark) {
|
|
uni.showToast({ title: '请输入缺陷描述', icon: 'none' })
|
|
return
|
|
}
|
|
if (!this.defectFormData.defectCode) {
|
|
uni.showToast({ title: '请选择缺陷代码', icon: 'none' })
|
|
return
|
|
}
|
|
if (!this.defectFormData.degree) {
|
|
uni.showToast({ title: '请选择程度', icon: 'none' })
|
|
return
|
|
}
|
|
if (this.defectFormData.startPosition === undefined || this.defectFormData.startPosition === '') {
|
|
uni.showToast({ title: '请输入开始位置', icon: 'none' })
|
|
return
|
|
}
|
|
if (this.defectFormData.endPosition === undefined || this.defectFormData.endPosition === '') {
|
|
uni.showToast({ title: '请输入结束位置', icon: 'none' })
|
|
return
|
|
}
|
|
if (parseInt(this.defectFormData.startPosition) > parseInt(this.defectFormData.endPosition)) {
|
|
uni.showToast({ title: '开始位置必须小于结束位置', icon: 'none' })
|
|
return
|
|
}
|
|
|
|
this.defectSubmitting = true
|
|
try {
|
|
const formData = {
|
|
coilId: this.coilId,
|
|
remark: this.defectFormData.remark,
|
|
plateSurface: this.defectFormData.plateSurface.join(','),
|
|
position: this.defectFormData.position.join(','),
|
|
startPosition: this.defectFormData.startPosition,
|
|
endPosition: this.defectFormData.endPosition,
|
|
length: this.defectFormData.endPosition - this.defectFormData.startPosition,
|
|
defectCode: this.defectFormData.defectCode,
|
|
degree: this.defectFormData.degree,
|
|
mainMark: this.defectFormData.mainMark,
|
|
attachmentFiles: this.uploadedImages.join(',')
|
|
}
|
|
|
|
if (this.defectFormMode === 'add') {
|
|
await addCoilAbnormal(formData)
|
|
} else {
|
|
formData.abnormalId = this.defectEditingId
|
|
await updateCoilAbnormal(formData)
|
|
}
|
|
|
|
uni.showToast({ title: '保存成功', icon: 'success' })
|
|
this.$refs.defectPopup.close()
|
|
this.loadDefectList()
|
|
} catch (err) {
|
|
console.error('提交缺陷失败:', err)
|
|
uni.showToast({ title: '提交失败', icon: 'none' })
|
|
} finally {
|
|
this.defectSubmitting = false
|
|
}
|
|
},
|
|
|
|
// ========== 图片上传 ==========
|
|
chooseImage() {
|
|
uni.chooseImage({
|
|
count: 9 - this.uploadedImages.length,
|
|
sourceType: ['camera', 'album'],
|
|
success: (res) => {
|
|
uni.showLoading({ title: '上传中...' })
|
|
const filePaths = res.tempFilePaths
|
|
let uploaded = 0
|
|
filePaths.forEach((filePath) => {
|
|
upload({
|
|
url: '/system/oss/upload',
|
|
filePath: filePath,
|
|
name: 'file'
|
|
}).then(result => {
|
|
const url = result.url || result.data?.url || result.data || ''
|
|
if (url) {
|
|
this.uploadedImages.push(url)
|
|
}
|
|
uploaded++
|
|
if (uploaded >= filePaths.length) {
|
|
uni.hideLoading()
|
|
uni.showToast({ title: '上传完成', icon: 'success' })
|
|
}
|
|
}).catch(err => {
|
|
uploaded++
|
|
if (uploaded >= filePaths.length) {
|
|
uni.hideLoading()
|
|
}
|
|
console.error('上传失败:', err)
|
|
})
|
|
})
|
|
}
|
|
})
|
|
},
|
|
|
|
removeImage(index) {
|
|
this.uploadedImages.splice(index, 1)
|
|
},
|
|
|
|
previewImage(url) {
|
|
if (!url) return
|
|
const urls = this.uploadedImages.length > 0 ? this.uploadedImages : [url]
|
|
uni.previewImage({ current: url, urls })
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.page-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
background: #f5f7fa;
|
|
}
|
|
|
|
// ========== 顶部钢卷信息 ==========
|
|
.coil-header {
|
|
background: linear-gradient(135deg, #1a73e8 0%, #4285f4 100%);
|
|
padding: 30rpx;
|
|
|
|
.header-top {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 12rpx;
|
|
|
|
.header-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12rpx;
|
|
|
|
.coil-label {
|
|
font-size: 26rpx;
|
|
color: rgba(255,255,255,0.8);
|
|
}
|
|
|
|
.coil-no {
|
|
font-size: 34rpx;
|
|
color: #fff;
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
|
|
.coil-status {
|
|
font-size: 22rpx;
|
|
padding: 4rpx 18rpx;
|
|
border-radius: 20rpx;
|
|
|
|
&.status-0 { background: rgba(255,255,255,0.25); color: #fff; }
|
|
&.status-1 { background: rgba(255,255,255,0.25); color: #fff; }
|
|
}
|
|
}
|
|
|
|
.coil-enter-no {
|
|
font-size: 24rpx;
|
|
color: rgba(255,255,255,0.7);
|
|
}
|
|
}
|
|
|
|
// ========== Tab 栏 ==========
|
|
.tabs-bar {
|
|
display: flex;
|
|
background: #fff;
|
|
border-bottom: 2rpx solid #f0f0f0;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
|
|
.tab-item {
|
|
flex: 1;
|
|
text-align: center;
|
|
padding: 24rpx 0;
|
|
position: relative;
|
|
|
|
.tab-text {
|
|
font-size: 28rpx;
|
|
color: #666;
|
|
}
|
|
|
|
&.tab-active {
|
|
.tab-text {
|
|
color: #1a73e8;
|
|
font-weight: 600;
|
|
}
|
|
&::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 20%;
|
|
right: 20%;
|
|
height: 4rpx;
|
|
background: #1a73e8;
|
|
border-radius: 2rpx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ========== Tab 内容 ==========
|
|
.tab-content {
|
|
flex: 1;
|
|
}
|
|
|
|
.tab-scroll {
|
|
padding: 20rpx;
|
|
height: 100%;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
// ========== 详情查看 ==========
|
|
.info-section {
|
|
background: #fff;
|
|
border-radius: 16rpx;
|
|
margin-bottom: 20rpx;
|
|
overflow: hidden;
|
|
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
|
|
|
|
.section-title {
|
|
font-size: 28rpx;
|
|
font-weight: 600;
|
|
color: #333;
|
|
padding: 24rpx 30rpx;
|
|
border-bottom: 1rpx solid #f0f0f0;
|
|
}
|
|
|
|
.info-grid {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
padding: 16rpx;
|
|
|
|
.info-item {
|
|
width: 50%;
|
|
padding: 12rpx 14rpx;
|
|
box-sizing: border-box;
|
|
|
|
.label {
|
|
display: block;
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
margin-bottom: 6rpx;
|
|
}
|
|
|
|
.value {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
}
|
|
|
|
.info-list {
|
|
padding: 0 30rpx;
|
|
|
|
.list-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 20rpx 0;
|
|
border-bottom: 1rpx solid #f0f0f0;
|
|
|
|
&:last-child { border-bottom: none; }
|
|
|
|
.label { font-size: 26rpx; color: #666; }
|
|
.value { font-size: 26rpx; color: #333; }
|
|
}
|
|
}
|
|
}
|
|
|
|
// ========== 详情编辑 ==========
|
|
.form-section {
|
|
background: #fff;
|
|
border-radius: 16rpx;
|
|
padding: 24rpx;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.form-item {
|
|
margin-bottom: 24rpx;
|
|
|
|
.form-label {
|
|
display: block;
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
margin-bottom: 10rpx;
|
|
font-weight: 500;
|
|
|
|
.required { color: #ff4d4f; }
|
|
}
|
|
|
|
.form-input {
|
|
width: 100%;
|
|
height: 80rpx;
|
|
padding: 0 24rpx;
|
|
background: #f8f9fa;
|
|
border: 2rpx solid #e8e8e8;
|
|
border-radius: 12rpx;
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
box-sizing: border-box;
|
|
|
|
&:focus {
|
|
background: #fff;
|
|
border-color: #007aff;
|
|
}
|
|
}
|
|
|
|
.form-textarea {
|
|
width: 100%;
|
|
min-height: 140rpx;
|
|
padding: 20rpx 24rpx;
|
|
background: #f8f9fa;
|
|
border: 2rpx solid #e8e8e8;
|
|
border-radius: 12rpx;
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
box-sizing: border-box;
|
|
}
|
|
}
|
|
|
|
.submit-bar {
|
|
padding: 20rpx 0 40rpx;
|
|
|
|
.btn-submit {
|
|
width: 100%;
|
|
height: 88rpx;
|
|
background: linear-gradient(135deg, #007aff 0%, #0051d5 100%);
|
|
color: #fff;
|
|
border-radius: 12rpx;
|
|
font-size: 30rpx;
|
|
font-weight: 600;
|
|
border: none;
|
|
|
|
&::after { border: none; }
|
|
&:active { opacity: 0.9; }
|
|
}
|
|
}
|
|
|
|
// ========== 缺陷列表(复用缺陷页样式) ==========
|
|
.stats-card {
|
|
display: flex;
|
|
background: #fff;
|
|
border-radius: 16rpx;
|
|
padding: 20rpx;
|
|
margin-bottom: 20rpx;
|
|
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
|
|
|
|
.stat-item {
|
|
flex: 1;
|
|
text-align: center;
|
|
|
|
.stat-num { font-size: 36rpx; font-weight: 700; display: block; }
|
|
.stat-label { font-size: 22rpx; color: #999; margin-top: 4rpx; display: block; }
|
|
}
|
|
}
|
|
|
|
.defect-card {
|
|
background: #fff;
|
|
border-radius: 16rpx;
|
|
padding: 24rpx;
|
|
margin-bottom: 16rpx;
|
|
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
|
|
|
|
.defect-card-top {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 12rpx;
|
|
|
|
.defect-card-top-left {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 6rpx;
|
|
}
|
|
|
|
.defect-actions {
|
|
display: flex;
|
|
gap: 16rpx;
|
|
|
|
.action-btn {
|
|
font-size: 24rpx;
|
|
padding: 8rpx 20rpx;
|
|
border-radius: 8rpx;
|
|
font-weight: 500;
|
|
}
|
|
.edit-btn { background: #e6f7ff; color: #1890ff; }
|
|
.del-btn { background: #fff2f0; color: #ff4d4f; }
|
|
}
|
|
}
|
|
|
|
.defect-desc {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
line-height: 1.5;
|
|
margin-bottom: 12rpx;
|
|
}
|
|
|
|
.defect-meta {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 12rpx;
|
|
margin-bottom: 12rpx;
|
|
|
|
.meta-item {
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
background: #f8f9fa;
|
|
padding: 4rpx 14rpx;
|
|
border-radius: 6rpx;
|
|
}
|
|
}
|
|
|
|
.defect-images {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 12rpx;
|
|
margin-bottom: 12rpx;
|
|
|
|
.defect-img {
|
|
width: 140rpx;
|
|
height: 140rpx;
|
|
border-radius: 8rpx;
|
|
background: #f0f0f0;
|
|
}
|
|
}
|
|
|
|
.defect-time { font-size: 22rpx; color: #bbb; }
|
|
}
|
|
|
|
.defect-code-badge {
|
|
display: inline-block;
|
|
padding: 4rpx 14rpx;
|
|
background: #e6f7ff;
|
|
border-radius: 8rpx;
|
|
font-size: 22rpx;
|
|
color: #1890ff;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.defect-degree-badge {
|
|
display: inline-block;
|
|
padding: 4rpx 14rpx;
|
|
background: #f6ffed;
|
|
border-radius: 8rpx;
|
|
font-size: 22rpx;
|
|
color: #52c41a;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.defect-main-badge {
|
|
display: inline-block;
|
|
padding: 4rpx 14rpx;
|
|
background: #fff7e6;
|
|
border-radius: 8rpx;
|
|
font-size: 22rpx;
|
|
color: #fa8c16;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 80rpx 0;
|
|
color: #999;
|
|
font-size: 28rpx;
|
|
}
|
|
|
|
.add-defect-btn {
|
|
background: #fff;
|
|
border: 2rpx dashed #ccc;
|
|
border-radius: 16rpx;
|
|
padding: 30rpx;
|
|
text-align: center;
|
|
margin-bottom: 30rpx;
|
|
|
|
.add-defect-text {
|
|
font-size: 28rpx;
|
|
color: #007aff;
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
|
|
// ========== 弹窗 ==========
|
|
.popup-wrap {
|
|
background: #fff;
|
|
border-radius: 24rpx 24rpx 0 0;
|
|
max-height: 92vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.popup-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 30rpx 30rpx 20rpx;
|
|
border-bottom: 1rpx solid #f0f0f0;
|
|
|
|
.popup-title { font-size: 32rpx; font-weight: 600; color: #333; }
|
|
.popup-close { font-size: 36rpx; color: #999; padding: 10rpx; }
|
|
}
|
|
|
|
.popup-body {
|
|
padding: 20rpx 30rpx;
|
|
max-height: 60vh;
|
|
}
|
|
|
|
.popup-footer {
|
|
display: flex;
|
|
gap: 20rpx;
|
|
padding: 20rpx 30rpx;
|
|
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
|
border-top: 1rpx solid #f0f0f0;
|
|
|
|
.btn {
|
|
flex: 1;
|
|
height: 88rpx;
|
|
line-height: 88rpx;
|
|
border-radius: 12rpx;
|
|
font-size: 30rpx;
|
|
font-weight: 500;
|
|
text-align: center;
|
|
border: none;
|
|
|
|
&::after { border: none; }
|
|
}
|
|
|
|
.btn-cancel { background: #f5f5f5; color: #666; }
|
|
.btn-submit {
|
|
background: linear-gradient(135deg, #007aff 0%, #0051d5 100%);
|
|
color: #fff;
|
|
}
|
|
}
|
|
|
|
// ========== 上传 ==========
|
|
.upload-area {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 16rpx;
|
|
|
|
.upload-preview {
|
|
position: relative;
|
|
width: 160rpx;
|
|
height: 160rpx;
|
|
|
|
.preview-img {
|
|
width: 100%;
|
|
height: 100%;
|
|
border-radius: 8rpx;
|
|
background: #f0f0f0;
|
|
}
|
|
|
|
.remove-img {
|
|
position: absolute;
|
|
top: -12rpx;
|
|
right: -12rpx;
|
|
width: 36rpx;
|
|
height: 36rpx;
|
|
background: rgba(0,0,0,0.6);
|
|
color: #fff;
|
|
border-radius: 50%;
|
|
text-align: center;
|
|
line-height: 36rpx;
|
|
font-size: 20rpx;
|
|
}
|
|
}
|
|
|
|
.upload-btn {
|
|
width: 160rpx;
|
|
height: 160rpx;
|
|
background: #f8f9fa;
|
|
border: 2rpx dashed #ccc;
|
|
border-radius: 8rpx;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
.upload-icon { font-size: 48rpx; color: #999; }
|
|
.upload-text { font-size: 22rpx; color: #999; margin-top: 8rpx; }
|
|
}
|
|
}
|
|
|
|
.form-tip {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
margin-top: 8rpx;
|
|
}
|
|
|
|
.range-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16rpx;
|
|
|
|
.range-input { flex: 1; }
|
|
.range-separator { font-size: 28rpx; color: #999; }
|
|
}
|
|
|
|
// ========== 加载 ==========
|
|
.loading-mask {
|
|
position: fixed;
|
|
top: 0; left: 0; right: 0; bottom: 0;
|
|
background: rgba(255,255,255,0.8);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 999;
|
|
}
|
|
|
|
// ========== uni-data-checkbox 适配 ==========
|
|
.uni-data-checklist { width: 100%; }
|
|
</style> |