Files
klp-mono/apps/hand-factory/pages/defect/coil-defect.vue
砂糖 c17b60dbe0 feat(mine+defect): 新增钢卷异常管理页面及相关功能
1. 个人中心页面新增钢卷异常入口
2. 实现钢卷异常列表页:支持按状态筛选、缺陷代码/程度统计
3. 新增异常新增/编辑弹窗,完善表单校验和字段展示
4. 优化列表项UI展示,新增主缺陷标识和元信息展示
2026-06-05 14:50:47 +08:00

1283 lines
30 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="page-container">
<!-- ========== 视图1: 钢卷搜索 ========== -->
<view v-show="currentView === 'search'" class="search-view">
<view class="form-card">
<view class="card-title">
<text class="title-dot"></text>
<text class="title-text">查询钢卷</text>
</view>
<view class="form-item">
<text class="form-label">入场钢卷号</text>
<input v-model="searchForm.enterCoilNo" placeholder="请输入入场钢卷号" class="form-input" />
</view>
<view class="form-item">
<text class="form-label">当前钢卷号</text>
<input v-model="searchForm.currentCoilNo" placeholder="请输入当前钢卷号" class="form-input" />
</view>
<view class="form-item">
<text class="form-label-optional">品名</text>
<input v-model="searchForm.itemName" placeholder="请输入品名" class="form-input" />
</view>
<view class="form-item">
<text class="form-label-optional">规格</text>
<input v-model="searchForm.itemSpecification" placeholder="请输入规格" class="form-input" />
</view>
<view class="form-item">
<text class="form-label-optional">材质</text>
<input v-model="searchForm.itemMaterial" placeholder="请输入材质" class="form-input" />
</view>
<view class="form-item">
<text class="form-label-optional">逻辑库区</text>
<klp-warehouse-picker v-model="searchForm.warehouseId" placeholder="请选择逻辑库区" />
</view>
<view class="form-item">
<text class="form-label-optional">钢卷状态</text>
<view class="status-picker">
<view
v-for="s in statusOptions"
:key="s.value"
class="status-option"
:class="{ 'status-active': searchForm.status === s.value }"
@click="searchForm.status = s.value"
>
<text>{{ s.label }}</text>
</view>
</view>
</view>
</view>
<!-- 查询悬浮按钮 -->
<view class="float-btn search-btn" @click="searchCoil">
<text class="btn-text">查询</text>
</view>
</view>
<!-- ========== 视图2: 钢卷列表 ========== -->
<view v-show="currentView === 'list'" class="list-view">
<view class="list-header">
<text class="list-title">钢卷列表</text>
<text class="list-total"> {{ total }} </text>
</view>
<view v-if="coilList.length === 0" class="empty-list">
<text>暂无符合条件的钢卷数据</text>
</view>
<view v-for="item in coilList" :key="item.coilId" class="coil-card" @click="selectCoil(item)">
<view class="coil-card-header">
<text class="coil-no">{{ item.currentCoilNo || '-' }}</text>
<text class="coil-status" :class="'status-' + (item.status || 0)">{{ item.status === 1 ? '已出库' : '在库' }}</text>
</view>
<view class="coil-card-body">
<text class="coil-info">入场号: {{ item.enterCoilNo || '-' }}</text>
<text class="coil-info">品名: {{ item.itemName || '-' }}</text>
<text class="coil-info">规格: {{ item.itemSpecification || '-' }}</text>
<text class="coil-info">净重: {{ item.netWeight || '-' }}</text>
</view>
<view class="coil-card-footer">
<text class="footer-tip">点击查看缺陷记录 </text>
</view>
</view>
<!-- 分页 -->
<view class="pagination" v-if="total > 0">
<view class="pagination-info">
<text> {{ total }} / {{ totalPages }} </text>
<text> {{ pager.pageNum }}/{{ totalPages }} </text>
</view>
<view class="pagination-btns">
<button class="page-btn" @click="prevPage" :disabled="pager.pageNum <= 1">上一页</button>
<button class="page-btn" @click="nextPage" :disabled="pager.pageNum >= totalPages">下一页</button>
</view>
</view>
<!-- 返回搜索按钮 -->
<view class="float-btn back-btn" @click="backToSearch">
<text class="btn-text">重查</text>
</view>
</view>
<!-- ========== 视图3: 缺陷列表选中钢卷后 ========== -->
<view v-show="currentView === 'defectList'" class="defect-view">
<view class="defect-header">
<view class="defect-header-left">
<text class="defect-coil-label">钢卷:</text>
<text class="defect-coil-no">{{ selectedCoil.currentCoilNo || '-' }}</text>
</view>
<view class="defect-header-right">
<text class="defect-count">异常记录: {{ defectList.length }} </text>
</view>
</view>
<view v-if="defectList.length === 0" class="empty-list">
<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="float-btn add-btn" @click="addDefect">
<text class="btn-text">+</text>
</view>
<!-- 返回列表 -->
<view class="float-btn back-small-btn" @click="backToCoilList">
<text class="btn-text"></text>
</view>
</view>
<!-- ========== 缺陷表单弹窗新增/编辑 ========== -->
<uni-popup ref="defectPopup" type="bottom" :mask-click="false">
<view class="popup-wrap">
<view class="popup-header">
<text class="popup-title">{{ formMode === '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>
<input v-model="defectForm.coilNo" class="form-input form-input-disabled" disabled placeholder="自动关联" />
</view>
<!-- 缺陷描述 -->
<view class="form-item">
<text class="form-label">缺陷描述 <text class="required">*</text></text>
<textarea v-model="defectForm.remark" placeholder="请输入缺陷描述" class="form-textarea" />
</view>
<!-- 上下板面 -->
<view class="form-item">
<text class="form-label">上下板面</text>
<uni-data-checkbox multiple v-model="defectForm.plateSurface" :localdata="plateSurfaceOptions" />
</view>
<!-- 断面位置 -->
<view class="form-item">
<text class="form-label">断面位置</text>
<uni-data-checkbox multiple v-model="defectForm.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="defectForm.startPosition" type="number" placeholder="开始位置" class="form-input range-input" @blur="calculateLength" />
<text class="range-separator">~</text>
<input v-model="defectForm.endPosition" type="number" placeholder="结束位置" class="form-input range-input" @blur="calculateLength" />
</view>
</view>
<!-- 缺陷代码 -->
<view class="form-item">
<text class="form-label">缺陷代码 <text class="required">*</text></text>
<uni-data-checkbox v-model="defectForm.defectCode" :localdata="defectCodeOptions" />
</view>
<!-- 程度 -->
<view class="form-item">
<text class="form-label">程度 <text class="required">*</text></text>
<uni-data-checkbox v-model="defectForm.degree" :localdata="degreeOptions" />
</view>
<!-- 主缺陷 -->
<view class="form-item">
<text class="form-label">主缺陷</text>
<uni-data-checkbox multiple v-model="mainMarkBind" :localdata="[{text: '是否为主缺陷', value: '1'}]" />
</view>
<!-- 缺陷图片仅主缺陷时显示 -->
<view class="form-item" v-if="defectForm.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="submitting">{{ submitting ? '提交中...' : '保存' }}</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import { listMaterialCoil } from '@/api/wms/coil'
import { listCoilAbnormal, addCoilAbnormal, updateCoilAbnormal, delCoilAbnormal } from '@/api/wms/coilAbnormal'
import upload from '@/utils/upload'
export default {
data() {
return {
// 视图控制: search | list | defectList
currentView: 'search',
// 钢卷搜索表单
searchForm: {
enterCoilNo: '',
currentCoilNo: '',
itemName: '',
itemSpecification: '',
itemMaterial: '',
warehouseId: '',
status: '',
netWeightStart: '',
netWeightEnd: ''
},
// 状态选项
statusOptions: [
{ label: '全部', value: '' },
{ label: '在库', value: '0' },
{ label: '已出库', value: '1' }
],
// 分页
pager: {
pageNum: 1,
pageSize: 20
},
total: 0,
coilList: [],
// 选中的钢卷
selectedCoil: {},
// 缺陷列表
defectList: [],
// 缺陷代码选项(从字典加载)
defectCodeOptions: [],
// 断面位置选项(从字典加载)
positionOptions: [],
// 程度选项(从字典加载)
degreeOptions: [],
// 上下板面选项
plateSurfaceOptions: [
{ text: '上板面', value: '上' },
{ text: '下板面', value: '下' }
],
// 缺陷表单
defectForm: {
coilId: '',
coilNo: '',
remark: '',
plateSurface: [],
position: [],
startPosition: undefined,
endPosition: undefined,
defectCode: '',
degree: '',
mainMark: 0,
attachmentFiles: ''
},
formMode: 'add', // add | edit
editingId: null,
// 上传的图片URL列表暂存
uploadedImages: [],
submitting: false,
// 搜索状态
searching: false
}
},
computed: {
totalPages() {
if (this.total === 0) return 0
return Math.ceil(this.total / this.pager.pageSize)
},
// 缺陷代码统计
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)
},
/** 主缺陷双向绑定uni-data-checkbox multiple 模式需要数组) */
mainMarkBind: {
get() {
return this.defectForm.mainMark === 1 ? ['1'] : []
},
set(val) {
this.defectForm.mainMark = val.length > 0 ? 1 : 0
}
}
},
created() {
this.loadDicts()
},
methods: {
// ========== 钢卷搜索 ==========
async searchCoil() {
this.searching = true
this.pager.pageNum = 1
uni.showLoading({ title: '查询中...' })
try {
const res = await listMaterialCoil({ ...this.searchForm, ...this.pager })
this.coilList = res.rows || []
this.total = res.total || 0
this.currentView = 'list'
uni.pageScrollTo({ scrollTop: 0, duration: 300 })
} catch (err) {
console.error('查询钢卷失败:', err)
uni.showToast({ title: '查询失败', icon: 'none' })
} finally {
uni.hideLoading()
this.searching = false
}
},
async loadPageData() {
uni.showLoading({ title: '加载中...' })
try {
const res = await listMaterialCoil({ ...this.searchForm, ...this.pager })
this.coilList = res.rows || []
this.total = res.total || 0
uni.pageScrollTo({ scrollTop: 0, duration: 300 })
} catch (err) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
uni.hideLoading()
}
},
prevPage() {
if (this.pager.pageNum <= 1) return
this.pager.pageNum--
this.loadPageData()
},
nextPage() {
if (this.pager.pageNum >= this.totalPages) return
this.pager.pageNum++
this.loadPageData()
},
backToSearch() {
this.currentView = 'search'
uni.pageScrollTo({ scrollTop: 0, duration: 300 })
},
// ========== 选中钢卷 → 加载缺陷 ==========
async selectCoil(coil) {
this.selectedCoil = coil
uni.showLoading({ title: '加载缺陷...' })
try {
const res = await listCoilAbnormal({ coilId: coil.coilId, pageNum: 1, pageSize: 999 })
this.defectList = res.rows || []
this.currentView = 'defectList'
} catch (err) {
console.error('加载缺陷失败:', err)
uni.showToast({ title: '加载缺陷失败', icon: 'none' })
} finally {
uni.hideLoading()
}
},
backToCoilList() {
this.currentView = 'list'
},
// ========== 缺陷 CRUD ==========
addDefect() {
this.formMode = 'add'
this.editingId = null
this.defectForm = {
coilId: this.selectedCoil.coilId || '',
coilNo: this.selectedCoil.currentCoilNo || '',
remark: '',
plateSurface: [],
position: [],
startPosition: undefined,
endPosition: undefined,
defectCode: '',
degree: '',
mainMark: 0,
attachmentFiles: ''
}
this.uploadedImages = []
this.$refs.defectPopup.open()
},
editDefect(item) {
this.formMode = 'edit'
this.editingId = item.abnormalId
this.defectForm = {
coilId: item.coilId || this.selectedCoil.coilId,
coilNo: this.selectedCoil.currentCoilNo || '',
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' })
// 刷新列表
const r = await listCoilAbnormal({ coilId: this.selectedCoil.coilId, pageNum: 1, pageSize: 999 })
this.defectList = r.rows || []
} catch (err) {
uni.showToast({ title: '删除失败', icon: 'none' })
}
}
}
})
},
// ========== 图片上传 ==========
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 => {
// 假设返回数据中有 url 字段
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)
})
})
},
fail: (err) => {
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: urls
})
},
// ========== 提交缺陷表单 ==========
/** 计算缺陷长度 */
calculateLength() {
// 可在 submit 中自动计算
},
/** 加载字典数据 */
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 submitDefectForm() {
if (!this.defectForm.remark) {
uni.showToast({ title: '请输入缺陷描述', icon: 'none' })
return
}
if (!this.defectForm.defectCode) {
uni.showToast({ title: '请选择缺陷代码', icon: 'none' })
return
}
if (!this.defectForm.degree) {
uni.showToast({ title: '请选择程度', icon: 'none' })
return
}
if (this.defectForm.startPosition === undefined || this.defectForm.startPosition === '') {
uni.showToast({ title: '请输入开始位置', icon: 'none' })
return
}
if (this.defectForm.endPosition === undefined || this.defectForm.endPosition === '') {
uni.showToast({ title: '请输入结束位置', icon: 'none' })
return
}
if (parseInt(this.defectForm.startPosition) > parseInt(this.defectForm.endPosition)) {
uni.showToast({ title: '开始位置必须小于结束位置', icon: 'none' })
return
}
this.submitting = true
try {
const formData = {
coilId: this.defectForm.coilId,
remark: this.defectForm.remark,
plateSurface: this.defectForm.plateSurface.join(','),
position: this.defectForm.position.join(','),
startPosition: this.defectForm.startPosition,
endPosition: this.defectForm.endPosition,
length: this.defectForm.endPosition - this.defectForm.startPosition,
defectCode: this.defectForm.defectCode,
degree: this.defectForm.degree,
mainMark: this.defectForm.mainMark,
attachmentFiles: this.uploadedImages.join(',')
}
if (this.formMode === 'add') {
await addCoilAbnormal(formData)
uni.showToast({ title: '新增成功', icon: 'success' })
} else {
formData.abnormalId = this.editingId
await updateCoilAbnormal(formData)
uni.showToast({ title: '更新成功', icon: 'success' })
}
this.$refs.defectPopup.close()
// 刷新缺陷列表
const r = await listCoilAbnormal({ coilId: this.selectedCoil.coilId, pageNum: 1, pageSize: 999 })
this.defectList = r.rows || []
} catch (err) {
console.error('提交缺陷失败:', err)
uni.showToast({ title: '提交失败', icon: 'none' })
} finally {
this.submitting = false
}
}
}
}
</script>
<style scoped lang="scss">
.page-container {
min-height: 100vh;
background: #f5f7fa;
padding-bottom: 140rpx;
}
// ========== 卡片统一样式 ==========
.form-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.card-title {
display: flex;
align-items: center;
margin-bottom: 25rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
.title-dot {
width: 8rpx;
height: 28rpx;
background: #007aff;
border-radius: 4rpx;
margin-right: 12rpx;
}
.title-text {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
}
// ========== 表单 ==========
.form-item {
margin-bottom: 24rpx;
.form-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 12rpx;
font-weight: 500;
.required {
color: #ff4d4f;
}
}
.form-label-optional {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 12rpx;
font-weight: 500;
}
.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-input-disabled {
background: #f5f5f5;
color: #999;
}
}
.form-textarea {
width: 100%;
min-height: 160rpx;
padding: 20rpx 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;
}
}
}
// ========== uni-data-checkbox 适配 ==========
.uni-data-checklist {
width: 100%;
}
// ========== 范围输入行 ==========
.range-row {
display: flex;
align-items: center;
gap: 16rpx;
.range-input {
flex: 1;
}
.range-separator {
font-size: 28rpx;
color: #999;
}
}
// ========== 状态选择器 ==========
.status-picker {
display: flex;
gap: 16rpx;
.status-option {
padding: 14rpx 28rpx;
border: 2rpx solid #e8e8e8;
border-radius: 12rpx;
font-size: 26rpx;
color: #666;
background: #f8f9fa;
&.status-active {
background: #e6f7ff;
border-color: #1890ff;
color: #1890ff;
font-weight: 500;
}
}
}
// ========== 提示文字 ==========
.form-tip {
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
}
// ========== 缺陷代码标识 ==========
.defect-code-badge {
display: inline-block;
padding: 4rpx 16rpx;
background: #e6f7ff;
border-radius: 8rpx;
font-size: 24rpx;
color: #1890ff;
font-weight: 500;
}
.defect-degree-badge {
display: inline-block;
padding: 4rpx 16rpx;
background: #f6ffed;
border-radius: 8rpx;
font-size: 24rpx;
color: #52c41a;
font-weight: 500;
margin-left: 8rpx;
}
.defect-main-badge {
display: inline-block;
padding: 4rpx 16rpx;
background: #fff7e6;
border-radius: 8rpx;
font-size: 22rpx;
color: #fa8c16;
font-weight: 500;
margin-left: 8rpx;
}
.defect-card-top-left {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6rpx;
}
.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;
.meta-unit {
color: #999;
}
}
}
// ========== 悬浮按钮 ==========
.float-btn {
position: fixed;
right: 30rpx;
bottom: 60rpx;
z-index: 9999;
width: 140rpx;
height: 140rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 32rpx;
font-weight: 600;
box-shadow: 0 8rpx 28rpx rgba(0, 0, 0, 0.2);
&:active {
transform: scale(0.95);
opacity: 0.9;
}
.btn-text { color: #fff; }
}
.search-btn { background: linear-gradient(135deg, #007aff 0%, #0051d5 100%); }
.back-btn { background: linear-gradient(135deg, #666 0%, #333 100%); }
.add-btn {
background: linear-gradient(135deg, #52c41a 0%, #389e0d 100%);
font-size: 60rpx;
}
.back-small-btn {
right: 210rpx;
width: 100rpx;
height: 100rpx;
font-size: 48rpx;
background: rgba(0,0,0,0.5);
}
// ========== 钢卷列表 ==========
.list-view {
padding: 20rpx;
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10rpx;
margin-bottom: 20rpx;
.list-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.list-total {
font-size: 26rpx;
color: #999;
}
}
.coil-card {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
&:active {
transform: scale(0.98);
background: #f8f9fa;
}
.coil-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
.coil-no {
font-size: 30rpx;
font-weight: 600;
color: #333;
}
.coil-status {
font-size: 22rpx;
padding: 4rpx 16rpx;
border-radius: 20rpx;
&.status-0 { background: #e6f7ff; color: #1890ff; }
&.status-1 { background: #f6ffed; color: #52c41a; }
}
}
.coil-card-body {
display: flex;
flex-wrap: wrap;
gap: 8rpx 20rpx;
margin-bottom: 12rpx;
.coil-info {
font-size: 24rpx;
color: #666;
}
}
.coil-card-footer {
.footer-tip {
font-size: 24rpx;
color: #007aff;
}
}
}
// ========== 缺陷列表 ==========
.defect-view {
padding: 20rpx;
}
.defect-header {
display: flex;
justify-content: space-between;
align-items: center;
background: linear-gradient(135deg, #1a73e8 0%, #4285f4 100%);
border-radius: 16rpx;
padding: 24rpx 30rpx;
margin-bottom: 20rpx;
.defect-header-left {
display: flex;
align-items: center;
gap: 12rpx;
.defect-coil-label {
font-size: 26rpx;
color: rgba(255,255,255,0.8);
}
.defect-coil-no {
font-size: 32rpx;
color: #fff;
font-weight: 600;
}
}
.defect-header-right {
.defect-count {
font-size: 24rpx;
color: rgba(255,255,255,0.8);
}
}
}
// 统计卡片
.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-level {
font-size: 24rpx;
font-weight: 600;
padding: 4rpx 16rpx;
border-radius: 20rpx;
&.level-A { background: #fff2f0; color: #ff4d4f; }
&.level-B { background: #fff7e6; color: #fa8c16; }
&.level-C { background: #feffe6; color: #d4b106; }
&.level-D { background: #f6ffed; color: #52c41a; }
}
.defect-actions {
display: flex;
gap: 20rpx;
.action-btn {
font-size: 28rpx;
padding: 12rpx 28rpx;
border-radius: 10rpx;
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-images {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
margin-bottom: 12rpx;
.defect-img {
width: 160rpx;
height: 160rpx;
border-radius: 8rpx;
background: #f0f0f0;
}
}
.defect-time {
font-size: 22rpx;
color: #bbb;
}
}
// ========== 图片上传区域 ==========
.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;
}
}
}
// ========== 弹窗 ==========
.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;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
.popup-body {
padding: 24rpx 30rpx;
max-height: 550rpx;
}
.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;
border: none;
text-align: center;
&:active {
transform: scale(0.98);
}
&::after { border: none; }
}
.btn-cancel {
background: #f5f5f5;
color: #666;
}
.btn-submit {
background: linear-gradient(135deg, #007aff 0%, #0051d5 100%);
color: #fff;
box-shadow: 0 4rpx 16rpx rgba(0, 122, 255, 0.3);
}
}
// ========== 空状态 & 分页 ==========
.empty-list {
text-align: center;
padding: 100rpx 0;
color: #999;
font-size: 28rpx;
}
.pagination {
margin-top: 40rpx;
padding: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
.pagination-info {
display: flex;
gap: 30rpx;
font-size: 26rpx;
color: #666;
}
.pagination-btns {
display: flex;
gap: 20rpx;
.page-btn {
width: 160rpx;
height: 70rpx;
line-height: 70rpx;
background: #f0f7ff;
border: 1rpx solid #007aff;
border-radius: 8rpx;
color: #007aff;
font-size: 28rpx;
&:disabled {
background: #f5f5f5;
border-color: #ddd;
color: #999;
}
}
}
}
</style>