Files
klp-mono/apps/hand-factory/pages/coil-detail/index.vue
砂糖 a24e369ebb feat: 重构扫码与巡检模块,优化页面路由
1. 新增钢卷详情、扫码页面与设备巡检组件
2. 调整首页跳转逻辑至扫码页面,更新tabBar配置
3. 隐藏旧的分条、其他操作模块,替换钢卷详情弹窗为页面跳转
4. 复用巡检组件到扫码页面与设备巡检页
2026-06-06 14:57:23 +08:00

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>