Files
klp-mono/apps/hand-factory/pages/defect/coil-defect.vue
Joshi a8ca4353e7 feat: 新增钢卷缺陷维护功能模块
1. 新增缺陷维护页面路由与入口
2. 实现钢卷缺陷的增删改查及图片上传功能
3. 添加缺陷级别统计与分页展示
4. 集成钢卷查询与缺陷管理API
2026-06-05 14:23:49 +08:00

1061 lines
25 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>
<!-- 查询悬浮按钮 -->
<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 class="stats-card" v-if="defectList.length > 0">
<view class="stat-item" v-for="stat in defectStats" :key="stat.label">
<text class="stat-num" :style="{ color: stat.color }">{{ stat.count }}</text>
<text class="stat-label">{{ stat.label }}</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">
<text class="defect-level" :class="'level-' + (item.abnormalLevel || 'C')">{{ item.abnormalLevel || 'C' }}</text>
<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.abnormalDesc || '暂无描述' }}</view>
<!-- 展示图片 -->
<view class="defect-images" v-if="item.imgUrl">
<image v-for="(url, i) in item.imgUrl.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.abnormalDesc" placeholder="请输入异常描述" class="form-textarea" />
</view>
<!-- 异常级别 -->
<view class="form-item">
<text class="form-label">异常级别 <text class="required">*</text></text>
<view class="level-picker">
<view
v-for="level in levelOptions"
:key="level.value"
class="level-option"
:class="{ 'level-active': defectForm.abnormalLevel === level.value }"
:style="{ borderColor: level.value === defectForm.abnormalLevel ? level.color : '#e8e8e8', color: level.value === defectForm.abnormalLevel ? level.color : '#666' }"
@click="defectForm.abnormalLevel = level.value"
>
<text>{{ level.label }}</text>
</view>
</view>
</view>
<!-- 图片上传 -->
<view class="form-item">
<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>
<!-- 备注 -->
<view class="form-item">
<text class="form-label-optional">备注</text>
<input v-model="defectForm.remark" placeholder="选填备注" class="form-input" />
</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: ''
},
// 分页
pager: {
pageNum: 1,
pageSize: 20
},
total: 0,
coilList: [],
// 选中的钢卷
selectedCoil: {},
// 缺陷列表
defectList: [],
// 缺陷级别选项
levelOptions: [
{ value: 'A', label: 'A级(严重)', color: '#ff4d4f' },
{ value: 'B', label: 'B级(较重)', color: '#fa8c16' },
{ value: 'C', label: 'C级(一般)', color: '#fadb14' },
{ value: 'D', label: 'D级(轻微)', color: '#52c41a' }
],
// 缺陷表单
defectForm: {
coilId: '',
coilNo: '',
abnormalDesc: '',
abnormalLevel: 'C',
imgUrl: '',
remark: ''
},
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.levelOptions.forEach(lo => {
stats[lo.value] = { label: lo.label, color: lo.color, count: 0 }
})
this.defectList.forEach(d => {
const key = d.abnormalLevel || 'C'
if (stats[key]) stats[key].count++
})
return Object.values(stats)
}
},
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 || '',
abnormalDesc: '',
abnormalLevel: 'C',
imgUrl: '',
remark: ''
}
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 || '',
abnormalDesc: item.abnormalDesc || '',
abnormalLevel: item.abnormalLevel || 'C',
imgUrl: item.imgUrl || '',
remark: item.remark || ''
}
this.uploadedImages = item.imgUrl ? item.imgUrl.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
})
},
// ========== 提交缺陷表单 ==========
async submitDefectForm() {
if (!this.defectForm.abnormalDesc) {
uni.showToast({ title: '请输入异常描述', icon: 'none' })
return
}
this.submitting = true
try {
const formData = {
coilId: this.defectForm.coilId,
abnormalDesc: this.defectForm.abnormalDesc,
abnormalLevel: this.defectForm.abnormalLevel,
imgUrl: this.uploadedImages.join(','),
remark: this.defectForm.remark
}
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;
}
}
}
// ========== 级别选择器 ==========
.level-picker {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
.level-option {
padding: 14rpx 28rpx;
border: 2rpx solid #e8e8e8;
border-radius: 12rpx;
font-size: 26rpx;
color: #666;
background: #f8f9fa;
&.level-active {
background: #fff;
font-weight: 600;
}
}
}
// ========== 悬浮按钮 ==========
.float-btn {
position: fixed;
right: 30rpx;
bottom: 60rpx;
z-index: 9999;
width: 120rpx;
height: 120rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 28rpx;
font-weight: 500;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.15);
&: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%); }
.back-small-btn {
right: 180rpx;
width: 90rpx;
height: 90rpx;
font-size: 36rpx;
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: 16rpx;
.action-btn {
font-size: 24rpx;
padding: 4rpx 16rpx;
border-radius: 8rpx;
}
.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: 80vh;
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>