feat: 添加待办事项模块及相关功能

新增待办事项页面,包含钢卷列表展示、筛选功能、钢卷详情页,支持重贴标签操作和查看改判/调拨记录,同时添加底部 tab 栏入口和对应图标资源
This commit is contained in:
王文昊
2026-05-20 11:09:08 +08:00
parent d54955a450
commit 8f96197aa5
10 changed files with 2121 additions and 12 deletions

View File

@@ -0,0 +1,19 @@
import request from '@/utils/request'
// 查询改判记录根据钢卷ID
export function listChangeHistory(coilId) {
return request({
url: '/wms/coilQualityRejudge/list',
method: 'get',
params: { coilId }
})
}
// 查询调拨记录根据钢卷ID
export function listTransferHistory(coilId) {
return request({
url: '/wms/transferOrderItem/list',
method: 'get',
params: { coilId }
})
}

View File

@@ -100,6 +100,19 @@
"style": {
"navigationBarTitleText": "发货"
}
},
{
"path": "pages/todo/index",
"style": {
"navigationBarTitleText": "待办事项",
"navigationStyle": "custom"
}
},
{
"path": "pages/todo/coil-detail",
"style": {
"navigationBarTitleText": "钢卷详情"
}
}
],
"globalStyle": {
@@ -116,24 +129,18 @@
"selectedIconPath": "/static/images/tabbar/home_.png",
"iconPath": "/static/images/tabbar/home.png"
},
{
"text": "待办",
"pagePath": "pages/todo/index",
"selectedIconPath": "/static/images/tabbar/todo_.png",
"iconPath": "/static/images/tabbar/todo.png"
},
{
"text": "扫码",
"pagePath": "pages/easycode/easycode",
"selectedIconPath": "/static/images/tabbar/work_.png",
"iconPath": "/static/images/tabbar/work.png"
},
// {
// "text": "收货",
// "pagePath": "pages/receive/receive",
// "selectedIconPath": "/static/images/tabbar/receive_.png",
// "iconPath": "/static/images/tabbar/receive.png"
// },
// {
// "text": "查找",
// "pagePath": "pages/search/search",
// "selectedIconPath": "/static/images/tabbar/search_.png",
// "iconPath": "/static/images/tabbar/search.png"
// },
{
"text": "报餐",
"pagePath": "pages/meal/meal",

View File

@@ -0,0 +1,387 @@
<template>
<view class="detail-container">
<!-- 头部信息卡片 -->
<view class="header-card">
<view class="header-title">
<text class="title-text">钢卷信息</text>
<text class="coil-no">{{ coilInfo.currentCoilNo || '-' }}</text>
</view>
<view class="header-subtitle">
<text class="subtitle-text">入场钢卷号: {{ coilInfo.enterCoilNo || '-' }}</text>
</view>
</view>
<!-- 基础信息 -->
<view class="info-section">
<view class="section-title">
<text class="title-icon">📋</text>
<text class="title-text">基础信息</text>
</view>
<view class="info-grid">
<view class="info-item">
<text class="label">厂家卷号</text>
<text class="value">{{ coilInfo.factoryCoilNo || '-' }}</text>
</view>
<view class="info-item">
<text class="label">物料类型</text>
<text class="value">{{ coilInfo.itemType || '-' }}</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 || '-' }}</text>
</view>
<view class="info-item">
<text class="label">材质</text>
<text class="value">{{ coilInfo.material || '-' }}</text>
</view>
<view class="info-item">
<text class="label">厂家</text>
<text class="value">{{ coilInfo.manufacturer || '-' }}</text>
</view>
</view>
</view>
<!-- 库存信息 -->
<view class="info-section">
<view class="section-title">
<text class="title-icon">📦</text>
<text class="title-text">库存信息</text>
</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.netWeight || '-' }}</text>
</view>
<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.length || '-' }}</text>
</view>
<view class="info-item">
<text class="label">质量状态</text>
<text class="value" :class="getQualityClass(coilInfo.qualityStatus)">
{{ coilInfo.qualityStatus || '-' }}
</text>
</view>
</view>
</view>
<!-- 加工信息 -->
<view class="info-section">
<view class="section-title">
<text class="title-icon"></text>
<text class="title-text">加工信息</text>
</view>
<view class="info-grid">
<view class="info-item">
<text class="label">表面处理</text>
<text class="value">{{ coilInfo.surfaceTreatment || '-' }}</text>
</view>
<view class="info-item">
<text class="label">镀层质量</text>
<text class="value">{{ coilInfo.coatingWeight || '-' }}</text>
</view>
<view class="info-item">
<text class="label">包装要求</text>
<text class="value">{{ coilInfo.packaging || '-' }}</text>
</view>
<view class="info-item">
<text class="label">切边</text>
<text class="value">{{ coilInfo.cuttingEdge || '-' }}</text>
</view>
<view class="info-item">
<text class="label">班组</text>
<text class="value">{{ coilInfo.team || '-' }}</text>
</view>
<view class="info-item">
<text class="label">备注</text>
<text class="value">{{ coilInfo.remark || '-' }}</text>
</view>
</view>
</view>
<!-- 操作信息 -->
<view class="info-section">
<view class="section-title">
<text class="title-icon">👤</text>
<text class="title-text">操作信息</text>
</view>
<view class="info-list">
<view class="list-item">
<text class="label">创建人</text>
<text class="value">{{ coilInfo.creator || '-' }}</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.updater || '-' }}</text>
</view>
<view class="list-item">
<text class="label">更新时间</text>
<text class="value">{{ coilInfo.updateTime || '-' }}</text>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="footer-bar" v-if="actionId">
<button class="btn btn-primary" @click="handleComplete">
<text class="btn-icon"></text>
<text class="btn-text">完成待办</text>
</button>
</view>
</view>
</template>
<script>
import { getMaterialCoil } from '@/api/wms/coil'
export default {
data() {
return {
coilId: null,
actionId: null,
coilInfo: {},
loading: false
}
},
onLoad(options) {
this.coilId = options.coilId
this.actionId = options.actionId
if (this.coilId) {
this.fetchDetail()
} else {
uni.showToast({
title: '参数错误',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
},
methods: {
async fetchDetail() {
this.loading = true
try {
const res = await getMaterialCoil(this.coilId)
this.coilInfo = res.data || {}
} catch (error) {
console.error('获取详情失败:', error)
uni.showToast({
title: '获取详情失败: ' + (error.message || '未知错误'),
icon: 'none'
})
} finally {
this.loading = false
}
},
getQualityClass(status) {
if (!status) return ''
const statusMap = {
'A': 'quality-a',
'B': 'quality-b',
'C': 'quality-c',
'D': 'quality-d'
}
return statusMap[status] || ''
},
async handleComplete() {
uni.showToast({
title: '功能开发中',
icon: 'none'
})
}
}
}
</script>
<style scoped lang="scss">
.detail-container {
min-height: 100vh;
background: #f5f7fa;
padding-bottom: 140rpx;
.header-card {
background: linear-gradient(135deg, #1a73e8 0%, #4285f4 100%);
padding: 40rpx 30rpx;
.header-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
.title-text {
font-size: 32rpx;
color: rgba(255, 255, 255, 0.8);
}
.coil-no {
font-size: 36rpx;
color: #ffffff;
font-weight: 600;
}
}
.header-subtitle {
.subtitle-text {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
}
}
}
.info-section {
background: #ffffff;
margin: 20rpx;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
.section-title {
display: flex;
align-items: center;
padding: 24rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.title-icon {
font-size: 32rpx;
margin-right: 12rpx;
}
.title-text {
font-size: 30rpx;
font-weight: 600;
color: #333333;
}
}
.info-grid {
display: flex;
flex-wrap: wrap;
padding: 20rpx;
.info-item {
width: 50%;
padding: 16rpx 10rpx;
box-sizing: border-box;
.label {
display: block;
font-size: 24rpx;
color: #999999;
margin-bottom: 8rpx;
}
.value {
font-size: 28rpx;
color: #333333;
font-weight: 500;
&.quality-a {
color: #52c41a;
}
&.quality-b {
color: #1890ff;
}
&.quality-c {
color: #faad14;
}
&.quality-d {
color: #ff4d4f;
}
}
}
}
.info-list {
padding: 0 30rpx;
.list-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.label {
font-size: 28rpx;
color: #666666;
}
.value {
font-size: 28rpx;
color: #333333;
}
}
}
}
.footer-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #ffffff;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.06);
.btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: #1a73e8;
color: #ffffff;
font-size: 32rpx;
font-weight: 600;
border-radius: 12rpx;
border: none;
display: flex;
align-items: center;
justify-content: center;
&::after {
border: none;
}
.btn-icon {
font-size: 32rpx;
margin-right: 12rpx;
}
.btn-text {
font-size: 32rpx;
}
}
}
}
</style>

View File

@@ -0,0 +1,238 @@
<template>
<view class="coil-card">
<!-- 头部信息 -->
<view class="card-header">
<view class="header-item">
<text class="label">入场钢卷号</text>
<text class="value">{{ data.enterCoilNo || '-' }}</text>
</view>
</view>
<!-- 主体信息 -->
<view class="card-body">
<view class="info-row">
<view class="info-item">
<text class="label">当前钢卷号</text>
<text class="value highlight">{{ data.currentCoilNo || '-' }}</text>
</view>
<view class="info-item">
<text class="label">产品类型</text>
<text class="value">{{ data.itemName || '-' }}</text>
</view>
</view>
<view class="info-row">
<view class="info-item">
<text class="label">实际库区</text>
<text class="value">{{ data.actualWarehouseName || '-' }}</text>
</view>
<view class="info-item">
<text class="label">备注</text>
<text class="value">{{ data.remark || '-' }}</text>
</view>
</view>
<view class="info-row">
<view class="info-item">
<text class="label">调拨类型</text>
<text class="value tag" v-if="data.transferType">{{ data.transferType }}</text>
<text class="value" v-else>-</text>
</view>
<view class="info-item">
<text class="label">改判原因</text>
<text class="value">{{ data.changeReason || '-' }}</text>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="card-footer" v-if="showActions">
<view class="action-btn primary" @click.stop="handleRelabel">
<text class="btn-icon">🏷</text>
<text class="btn-text">重贴标签</text>
</view>
<view class="action-btn" @click.stop="handleViewRecord">
<text class="btn-icon">📋</text>
<text class="btn-text">查看记录</text>
</view>
<view class="action-btn" @click.stop="handleViewDetail">
<text class="btn-icon">👁</text>
<text class="btn-text">查看详情</text>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'CoilCard',
props: {
data: {
type: Object,
required: true,
default: () => ({})
},
showActions: {
type: Boolean,
default: true
}
},
methods: {
handleRelabel() {
this.$emit('relabel', this.data)
},
handleViewRecord() {
this.$emit('view-record', this.data)
},
handleViewDetail() {
this.$emit('view-detail', this.data)
}
}
}
</script>
<style scoped lang="scss">
.coil-card {
background: #ffffff;
border-radius: 16rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
overflow: hidden;
.card-header {
background: linear-gradient(135deg, #1a73e8 0%, #4285f4 100%);
padding: 24rpx 30rpx;
.header-item {
display: flex;
align-items: center;
.label {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
margin-right: 16rpx;
}
.value {
font-size: 32rpx;
color: #ffffff;
font-weight: 600;
}
}
}
.card-body {
padding: 24rpx 30rpx;
.info-row {
display: flex;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.info-item {
flex: 1;
display: flex;
flex-direction: column;
.label {
font-size: 24rpx;
color: #999999;
margin-bottom: 8rpx;
}
.value {
font-size: 28rpx;
color: #333333;
word-break: break-all;
word-wrap: break-word;
overflow-wrap: break-word;
line-height: 1.4;
&.highlight {
color: #1a73e8;
font-weight: 500;
}
&.tag {
display: inline-block;
background: #e8f0fe;
color: #1a73e8;
padding: 4rpx 16rpx;
border-radius: 8rpx;
font-size: 24rpx;
white-space: nowrap;
}
}
}
}
}
.card-footer {
display: flex;
padding: 20rpx 30rpx;
border-top: 1rpx solid #f0f0f0;
gap: 16rpx;
.action-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 16rpx 0;
border-radius: 8rpx;
background: #f5f5f5;
transition: all 0.2s;
&:active {
opacity: 0.8;
}
&.primary {
background: #1a73e8;
.btn-text {
color: #ffffff;
}
}
.btn-icon {
font-size: 28rpx;
margin-right: 8rpx;
}
.btn-text {
font-size: 26rpx;
color: #666666;
}
}
}
}
/* H5 浏览器特定样式 */
/* #ifdef H5 */
.coil-card {
cursor: pointer;
&:hover {
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.1);
}
.card-footer {
.action-btn {
cursor: pointer;
&:hover {
opacity: 0.9;
}
&:active {
opacity: 0.8;
}
}
}
}
/* #endif */
</style>

View File

@@ -0,0 +1,317 @@
<template>
<view class="filter-bar">
<view class="filter-header" @click="toggleExpand">
<view class="filter-title">
<text class="title-icon">🔍</text>
<text class="title-text">筛选条件</text>
</view>
<view class="filter-toggle">
<text class="toggle-text">{{ isExpanded ? '收起' : '展开' }}</text>
<text class="toggle-icon" :class="{ 'expanded': isExpanded }"></text>
</view>
</view>
<view class="filter-content" v-show="isExpanded">
<view class="filter-row">
<view class="filter-item">
<text class="item-label">入场卷号</text>
<input
class="item-input"
v-model="form.enterCoilNo"
placeholder="请输入入场钢卷号"
placeholder-class="input-placeholder"
/>
</view>
<view class="filter-item">
<text class="item-label">当前卷号</text>
<input
class="item-input"
v-model="form.currentCoilNo"
placeholder="请输入当前钢卷号"
placeholder-class="input-placeholder"
/>
</view>
</view>
<view class="filter-row">
<view class="filter-item">
<text class="item-label">产品名称</text>
<input
class="item-input"
v-model="form.itemName"
placeholder="请选择物料"
placeholder-class="input-placeholder"
/>
</view>
<view class="filter-item">
<text class="item-label">规格</text>
<input
class="item-input"
v-model="form.itemSpecification"
placeholder="请选择规格"
placeholder-class="input-placeholder"
/>
</view>
</view>
<view class="filter-row">
<view class="filter-item">
<text class="item-label">材质</text>
<input
class="item-input"
v-model="form.itemMaterial"
placeholder="请选择材质"
placeholder-class="input-placeholder"
/>
</view>
<view class="filter-item">
<text class="item-label">厂家</text>
<input
class="item-input"
v-model="form.itemManufacturer"
placeholder="请选择厂家"
placeholder-class="input-placeholder"
/>
</view>
</view>
<view class="filter-actions">
<button class="btn btn-reset" @click="handleReset" :disabled="loading">
<text v-if="!loading">重置</text>
<text v-else>重置中...</text>
</button>
<button class="btn btn-search" @click="handleSearch" :disabled="loading">
<text v-if="!loading">搜索</text>
<text v-else>搜索中...</text>
</button>
</view>
<!-- 筛选结果统计 -->
<view v-if="showResult" class="filter-result">
<text class="result-text">找到 {{ total }} 条记录</text>
<text class="result-close" @click="hideResult"></text>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'FilterBar',
props: {
loading: {
type: Boolean,
default: false
},
total: {
type: Number,
default: 0
}
},
data() {
return {
isExpanded: false,
showResult: false,
form: {
enterCoilNo: '',
currentCoilNo: '',
itemName: '',
itemSpecification: '',
itemMaterial: '',
itemManufacturer: ''
}
}
},
methods: {
toggleExpand() {
this.isExpanded = !this.isExpanded
},
handleReset() {
this.form = {
enterCoilNo: '',
currentCoilNo: '',
itemName: '',
itemSpecification: '',
itemMaterial: '',
itemManufacturer: ''
}
this.showResult = false
this.$emit('reset')
// 显示重置成功提示
uni.showToast({
title: '已重置',
icon: 'success',
duration: 1500
})
},
handleSearch() {
this.showResult = true
this.isExpanded = false // 搜索后自动收起筛选栏
this.$emit('search', { ...this.form })
},
hideResult() {
this.showResult = false
}
}
}
</script>
<style scoped lang="scss">
.filter-bar {
background: #ffffff;
border-radius: 16rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
overflow: hidden;
.filter-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.filter-title {
display: flex;
align-items: center;
.title-icon {
font-size: 32rpx;
margin-right: 12rpx;
}
.title-text {
font-size: 30rpx;
font-weight: 600;
color: #333333;
}
}
.filter-toggle {
display: flex;
align-items: center;
.toggle-text {
font-size: 26rpx;
color: #666666;
margin-right: 8rpx;
}
.toggle-icon {
font-size: 24rpx;
color: #999999;
transition: transform 0.2s;
&.expanded {
transform: rotate(180deg);
}
}
}
}
.filter-content {
padding: 24rpx 30rpx;
.filter-row {
display: flex;
gap: 20rpx;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.filter-item {
flex: 1;
.item-label {
display: block;
font-size: 24rpx;
color: #666666;
margin-bottom: 12rpx;
}
.item-input {
height: 72rpx;
background: #f8f8f8;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #333333;
&::placeholder {
color: #cccccc;
}
}
.input-placeholder {
color: #cccccc;
}
}
}
.filter-actions {
display: flex;
gap: 20rpx;
margin-top: 30rpx;
padding-top: 24rpx;
border-top: 1rpx solid #f0f0f0;
.btn {
flex: 1;
height: 80rpx;
line-height: 80rpx;
text-align: center;
border-radius: 8rpx;
font-size: 28rpx;
border: none;
&::after {
border: none;
}
&.btn-reset {
background: #f5f5f5;
color: #666666;
}
&.btn-search {
background: #1a73e8;
color: #ffffff;
&:disabled {
opacity: 0.6;
}
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.filter-result {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20rpx;
padding: 16rpx 20rpx;
background: #e8f4fd;
border-radius: 8rpx;
.result-text {
font-size: 26rpx;
color: #1a73e8;
font-weight: 500;
}
.result-close {
font-size: 28rpx;
color: #666666;
padding: 4rpx 8rpx;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,446 @@
<template>
<uni-popup ref="popup" type="bottom" :safe-area="true">
<view class="popup-container">
<view class="popup-header">
<text class="popup-title">{{ title }}</text>
<text class="popup-close" @click="close"></text>
</view>
<view class="popup-content">
<!-- Tab切换 -->
<view class="tab-bar">
<view
class="tab-item"
:class="{ active: activeTab === 'change' }"
@click="activeTab = 'change'"
>
<text class="tab-text">改判记录</text>
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'transfer' }"
@click="activeTab = 'transfer'"
>
<text class="tab-text">调拨记录</text>
</view>
</view>
<!-- 改判记录列表 -->
<scroll-view
v-show="activeTab === 'change'"
scroll-y
class="record-list"
:style="{ height: scrollHeight + 'px' }"
@scrolltolower="onLoadMoreChange"
>
<view v-if="changeRecords.length === 0" class="empty-state">
<text class="empty-icon">📋</text>
<text class="empty-text">暂无改判记录</text>
</view>
<view
v-else
v-for="(record, index) in changeRecords"
:key="index"
class="record-item"
>
<view class="record-header">
<text class="record-type">{{ getRejudgeTypeText(record.rejudgeType) }}</text>
<text class="record-time">{{ formatTime(record.createTime) }}</text>
</view>
<view class="record-body">
<view class="record-row">
<text class="label">原值</text>
<text class="value old">{{ record.originalValue }}</text>
</view>
<view class="record-row">
<text class="label">新值</text>
<text class="value new">{{ record.newValue }}</text>
</view>
<view class="record-row">
<text class="label">改判原因</text>
<text class="value">{{ record.rejudgeReason }}</text>
</view>
<view class="record-row">
<text class="label">操作人</text>
<text class="value">{{ record.createBy }}</text>
</view>
<view class="record-row" v-if="record.remark">
<text class="label">备注</text>
<text class="value">{{ record.remark }}</text>
</view>
</view>
</view>
</scroll-view>
<!-- 调拨记录列表 -->
<scroll-view
v-show="activeTab === 'transfer'"
scroll-y
class="record-list"
:style="{ height: scrollHeight + 'px' }"
@scrolltolower="onLoadMoreTransfer"
>
<view v-if="transferRecords.length === 0" class="empty-state">
<text class="empty-icon">📋</text>
<text class="empty-text">暂无调拨记录</text>
</view>
<view
v-else
v-for="(record, index) in transferRecords"
:key="index"
class="record-item"
>
<view class="record-header">
<text class="record-type">{{ getTransferTypeText(record.transferType) }}</text>
<text class="record-time">{{ formatTime(record.createTime) }}</text>
</view>
<view class="record-body">
<view class="record-row">
<text class="label">调拨单号</text>
<text class="value">{{ record.transferOrderNo }}</text>
</view>
<view class="record-row">
<text class="label">原库区</text>
<text class="value">{{ record.fromWarehouseName }}</text>
</view>
<view class="record-row">
<text class="label">目标库区</text>
<text class="value new">{{ record.toWarehouseName }}</text>
</view>
<view class="record-row">
<text class="label">调拨数量</text>
<text class="value">{{ record.transferQuantity }}</text>
</view>
<view class="record-row">
<text class="label">操作人</text>
<text class="value">{{ record.createBy }}</text>
</view>
<view class="record-row" v-if="record.remark">
<text class="label">备注</text>
<text class="value">{{ record.remark }}</text>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</uni-popup>
</template>
<script>
import { listChangeHistory, listTransferHistory } from '@/api/wms/todo'
export default {
name: 'RecordPopup',
props: {
title: {
type: String,
default: '查看记录'
}
},
data() {
return {
activeTab: 'change',
changeRecords: [],
transferRecords: [],
coilId: null,
scrollHeight: 400 // 默认滚动区域高度
}
},
mounted() {
this.calcScrollHeight()
},
methods: {
open(coilId) {
this.coilId = coilId
this.activeTab = 'change'
this.$refs.popup.open()
// 延迟计算高度和加载数据,确保弹窗已渲染
this.$nextTick(() => {
this.calcScrollHeight()
this.fetchRecords()
})
},
close() {
this.$refs.popup.close()
},
async fetchRecords() {
if (!this.coilId) return
uni.showLoading({ title: '加载中...' })
try {
// 获取改判记录
const changeRes = await listChangeHistory(this.coilId)
this.changeRecords = changeRes.rows || []
// 获取调拨记录
const transferRes = await listTransferHistory(this.coilId)
this.transferRecords = transferRes.rows || []
// 显示记录数量提示
const totalCount = this.changeRecords.length + this.transferRecords.length
if (totalCount > 0) {
uni.showToast({
title: `找到 ${totalCount} 条记录`,
icon: 'none',
duration: 2000
})
}
} catch (error) {
console.error('获取记录失败:', error)
uni.showToast({
title: '获取记录失败: ' + (error.message || '未知错误'),
icon: 'none'
})
} finally {
uni.hideLoading()
}
},
// 获取改判类型文本
getRejudgeTypeText(type) {
const typeMap = {
'quality': '质量改判',
'specification': '规格改判',
'material': '材质改判',
'other': '其他改判'
}
return typeMap[type] || '改判记录'
},
// 获取调拨类型文本
getTransferTypeText(type) {
const typeMap = {
'in': '入库调拨',
'out': '出库调拨',
'move': '库内调拨',
'return': '退货调拨'
}
return typeMap[type] || '调拨记录'
},
// 格式化时间
formatTime(time) {
if (!time) return '-'
const date = new Date(time)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hour = String(date.getHours()).padStart(2, '0')
const minute = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hour}:${minute}`
},
// 计算滚动区域高度
calcScrollHeight() {
const systemInfo = uni.getSystemInfoSync()
const windowHeight = systemInfo.windowHeight
const safeAreaBottom = systemInfo.safeAreaInsets?.bottom || 0
const tabBarHeight = 50 // TabBar高度
// 弹窗最大高度为屏幕高度的80%减去底部安全区域和TabBar
const popupMaxHeight = windowHeight * 0.8 - safeAreaBottom - tabBarHeight
// 减去:弹窗头部(约60px) + Tab栏(约50px)
const headerHeight = 60
const tabHeight = 50
this.scrollHeight = popupMaxHeight - headerHeight - tabHeight - 20 // 20px为底部padding
console.log('计算滚动高度:', {
windowHeight,
safeAreaBottom,
tabBarHeight,
popupMaxHeight,
scrollHeight: this.scrollHeight
})
},
// 加载更多改判记录
onLoadMoreChange() {
console.log('加载更多改判记录')
// 如果需要分页加载,可以在这里实现
},
// 加载更多调拨记录
onLoadMoreTransfer() {
console.log('加载更多调拨记录')
// 如果需要分页加载,可以在这里实现
}
}
}
</script>
<style scoped lang="scss">
.popup-container {
background: #ffffff;
border-radius: 24rpx 24rpx 0 0;
max-height: 80vh;
display: flex;
flex-direction: column;
margin-bottom: constant(safe-area-inset-bottom);
margin-bottom: env(safe-area-inset-bottom);
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
.popup-close {
font-size: 36rpx;
color: #999999;
padding: 10rpx;
}
}
.popup-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
.tab-bar {
display: flex;
padding: 20rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.tab-item {
flex: 1;
text-align: center;
padding: 20rpx 0;
position: relative;
.tab-text {
font-size: 30rpx;
color: #666666;
}
&.active {
.tab-text {
color: #1a73e8;
font-weight: 600;
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background: #1a73e8;
border-radius: 2rpx;
}
}
}
}
.record-list {
flex: 1;
padding: 20rpx 30rpx;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
.empty-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
}
.empty-text {
font-size: 28rpx;
color: #999999;
}
}
.record-item {
background: #f8f8f8;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 20rpx;
.record-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16rpx;
padding-bottom: 16rpx;
border-bottom: 1rpx solid #eeeeee;
gap: 20rpx;
.record-type {
font-size: 28rpx;
font-weight: 600;
color: #1a73e8;
flex: 1;
word-break: break-all;
line-height: 1.4;
}
.record-time {
font-size: 24rpx;
color: #999999;
flex-shrink: 0;
white-space: nowrap;
}
}
.record-body {
.record-row {
display: flex;
margin-bottom: 12rpx;
align-items: flex-start;
&:last-child {
margin-bottom: 0;
}
.label {
font-size: 26rpx;
color: #666666;
width: 140rpx;
flex-shrink: 0;
line-height: 1.5;
}
.value {
font-size: 26rpx;
color: #333333;
flex: 1;
word-break: break-all;
word-wrap: break-word;
overflow-wrap: break-word;
line-height: 1.5;
&.old {
color: #999999;
text-decoration: line-through;
}
&.new {
color: #1a73e8;
font-weight: 500;
}
}
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,222 @@
<template>
<uni-popup ref="popup" type="center" :safe-area="true">
<view class="popup-container">
<view class="popup-header">
<text class="popup-title">重贴标签</text>
<text class="popup-close" @click="close"></text>
</view>
<view class="popup-content">
<!-- 钢卷信息展示 -->
<view class="coil-info">
<view class="info-row">
<text class="label">入场钢卷号</text>
<text class="value">{{ coilInfo.enterCoilNo || '-' }}</text>
</view>
<view class="info-row">
<text class="label">当前钢卷号</text>
<text class="value highlight">{{ coilInfo.currentCoilNo || '-' }}</text>
</view>
<view class="info-row">
<text class="label">产品名称</text>
<text class="value">{{ coilInfo.itemName || '-' }}</text>
</view>
</view>
<!-- 操作人信息 -->
<view class="operator-info">
<text class="label">操作人</text>
<text class="value">{{ operatorName }}</text>
</view>
</view>
<view class="popup-footer">
<button class="btn btn-cancel" @click="close">取消</button>
<button class="btn btn-confirm" @click="handleConfirm" :disabled="loading">
{{ loading ? '提交中...' : '确认重贴' }}
</button>
</view>
</view>
</uni-popup>
</template>
<script>
import { relabelCoil, completeTodoAction } from '@/api/wms/todo'
export default {
name: 'RelabelPopup',
data() {
return {
loading: false,
coilInfo: {}
}
},
computed: {
operatorName() {
return this.$store.state.user.nickName || this.$store.state.user.name || '未知'
}
},
methods: {
open(coilInfo) {
this.coilInfo = coilInfo
this.$refs.popup.open()
},
close() {
this.$refs.popup.close()
},
async handleConfirm() {
this.loading = true
try {
// 1. 提交重贴标签操作
await relabelCoil({
coilId: this.coilInfo.coilId,
currentCoilNo: this.coilInfo.currentCoilNo,
operator: this.operatorName,
operateTime: new Date().toISOString()
})
// 2. 完成待办操作
if (this.coilInfo.actionId) {
await completeTodoAction(this.coilInfo.actionId)
}
uni.showToast({
title: '重贴标签成功',
icon: 'success'
})
this.close()
this.$emit('success')
} catch (error) {
console.error('重贴标签失败:', error)
uni.showToast({
title: '操作失败,请重试',
icon: 'none'
})
} finally {
this.loading = false
}
}
}
}
</script>
<style scoped lang="scss">
.popup-container {
background: #ffffff;
border-radius: 16rpx;
width: 80vw;
max-width: 600rpx;
overflow: hidden;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
.popup-close {
font-size: 36rpx;
color: #999999;
padding: 10rpx;
}
}
.popup-content {
padding: 30rpx;
.coil-info {
background: #f8f8f8;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 30rpx;
.info-row {
display: flex;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
.label {
font-size: 26rpx;
color: #666666;
width: 160rpx;
flex-shrink: 0;
}
.value {
font-size: 26rpx;
color: #333333;
flex: 1;
&.highlight {
color: #1a73e8;
font-weight: 500;
}
}
}
}
.operator-info {
display: flex;
align-items: center;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
.label {
font-size: 26rpx;
color: #666666;
}
.value {
font-size: 26rpx;
color: #333333;
font-weight: 500;
}
}
}
.popup-footer {
display: flex;
padding: 20rpx 30rpx 40rpx;
gap: 20rpx;
.btn {
flex: 1;
height: 88rpx;
line-height: 88rpx;
text-align: center;
border-radius: 8rpx;
font-size: 30rpx;
border: none;
&::after {
border: none;
}
&.btn-cancel {
background: #f5f5f5;
color: #666666;
}
&.btn-confirm {
background: #1a73e8;
color: #ffffff;
&:disabled {
opacity: 0.6;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,473 @@
<template>
<view class="todo-container">
<!-- 自定义导航栏 -->
<view class="custom-nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-content">
<text class="nav-title">待办事项</text>
</view>
</view>
<!-- Tab切换 -->
<view class="tab-bar">
<view
v-for="tab in tabs"
:key="tab.key"
class="tab-item"
:class="{ active: activeTab === tab.key }"
@click="handleTabChange(tab.key)"
>
<text class="tab-text">{{ tab.label }}</text>
<text v-if="tab.badge > 0" class="tab-badge">{{ tab.badge }}</text>
</view>
</view>
<!-- 筛选栏 -->
<filter-bar
:loading="loading"
:total="total"
@search="handleSearch"
@reset="handleReset"
/>
<!-- 列表内容 -->
<scroll-view
scroll-y
class="list-container"
:refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
>
<!-- 待贴标签列表 -->
<view v-if="activeTab === 'label'" class="coil-list">
<coil-card
v-for="item in list"
:key="item.actionId"
:data="item"
@relabel="handleRelabel"
@view-record="handleViewRecord"
@view-detail="handleViewDetail"
/>
</view>
<!-- 其他Tab占位 -->
<view v-else class="placeholder-page">
<text class="placeholder-icon">🚧</text>
<text class="placeholder-text">功能开发中</text>
</view>
<!-- 空状态 -->
<view v-if="activeTab === 'label' && list.length === 0 && !loading" class="empty-state">
<text class="empty-icon">📭</text>
<text class="empty-text">暂无待办事项</text>
</view>
<!-- 加载状态 -->
<view v-if="loading && list.length === 0" class="loading-state">
<uni-load-more status="loading" />
</view>
<!-- 加载更多 -->
<view v-if="activeTab === 'label' && list.length > 0" class="load-more-wrapper" @click="onLoadMore">
<uni-load-more
:status="loadMoreStatus"
:content-text="{ contentdown: '点击加载更多', contentrefresh: '加载中...', contentnomore: '没有更多了' }"
/>
</view>
</scroll-view>
<!-- 记录弹窗 -->
<record-popup ref="recordPopup" />
<!-- 重贴标签弹窗 -->
<relabel-popup ref="relabelPopup" @success="handleRelabelSuccess" />
</view>
</template>
<script>
import { listMaterialCoil } from '@/api/wms/coil'
import FilterBar from './components/filter-bar.vue'
import CoilCard from './components/coil-card.vue'
import RecordPopup from './components/record-popup.vue'
import RelabelPopup from './components/relabel-popup.vue'
export default {
components: {
FilterBar,
CoilCard,
RecordPopup,
RelabelPopup
},
data() {
return {
statusBarHeight: 0,
tabs: [
{ key: 'label', label: '待贴标签', badge: 0 },
{ key: 'inspect', label: '检验任务', badge: 0 },
{ key: 'approval', label: '质保书审批', badge: 0 },
{ key: 'other', label: '其他代办', badge: 0 }
],
activeTab: 'label',
list: [],
loading: false,
refreshing: false,
query: {
pageNum: 1,
pageSize: 10,
enterCoilNo: '',
currentCoilNo: '',
itemName: '',
itemSpecification: '',
itemMaterial: '',
itemManufacturer: '',
hasTransferType: true // 待贴标签只显示有调拨类型的钢卷
},
total: 0
}
},
created() {
// 获取状态栏高度
const systemInfo = uni.getSystemInfoSync()
this.statusBarHeight = systemInfo.statusBarHeight || 0
},
computed: {
loadMoreStatus() {
if (this.loading) return 'loading'
if (this.list.length >= this.total) return 'noMore'
return 'more'
}
},
onLoad() {
this.fetchList()
},
methods: {
// 切换Tab
handleTabChange(key) {
this.activeTab = key
if (key === 'label') {
this.fetchList()
}
},
// 获取列表数据
async fetchList(isLoadMore = false) {
if (this.loading) return
this.loading = true
try {
console.log('开始获取列表,查询参数:', this.query)
const res = await listMaterialCoil(this.query)
console.log('获取列表响应:', res)
const rows = res.rows || []
this.total = res.total || 0
console.log('解析后的数据:', { rows, total: this.total })
if (isLoadMore) {
this.list = [...this.list, ...rows]
} else {
this.list = rows
}
// 更新待贴标签数量
const labelTab = this.tabs.find(t => t.key === 'label')
if (labelTab) {
labelTab.badge = this.total
}
} catch (error) {
console.error('获取列表失败:', error)
uni.showToast({
title: '获取数据失败: ' + (error.message || '未知错误'),
icon: 'none'
})
} finally {
this.loading = false
this.refreshing = false
}
},
// 搜索
handleSearch(form) {
this.query = {
...this.query,
...form,
pageNum: 1
}
this.fetchList()
},
// 重置
handleReset() {
this.query = {
pageNum: 1,
pageSize: 10,
enterCoilNo: '',
currentCoilNo: '',
itemName: '',
itemSpecification: '',
itemMaterial: '',
itemManufacturer: '',
hasTransferType: true
}
this.fetchList()
},
// 下拉刷新
onRefresh() {
this.refreshing = true
this.query.pageNum = 1
this.fetchList()
},
// 加载更多
onLoadMore() {
console.log('触发加载更多', {
listLength: this.list.length,
total: this.total,
loading: this.loading,
pageNum: this.query.pageNum
})
if (this.list.length >= this.total || this.loading) {
console.log('加载更多被阻止:已到最后一页或正在加载中')
return
}
this.query.pageNum++
console.log('加载第', this.query.pageNum, '页')
this.fetchList(true)
},
// 重贴标签
handleRelabel(coilInfo) {
this.$refs.relabelPopup.open(coilInfo)
},
// 重贴标签成功回调
handleRelabelSuccess() {
this.fetchList()
},
// 查看记录
handleViewRecord(coilInfo) {
const coilId = coilInfo.coilId || coilInfo.materialCoilId
if (!coilId) {
uni.showToast({
title: '无法获取钢卷ID',
icon: 'none'
})
return
}
this.$refs.recordPopup.open(coilId)
},
// 查看详情
handleViewDetail(coilInfo) {
const coilId = coilInfo.coilId || coilInfo.materialCoilId
if (!coilId) {
uni.showToast({
title: '无法获取钢卷ID',
icon: 'none'
})
return
}
uni.navigateTo({
url: `/pages/todo/coil-detail?coilId=${coilId}`
})
}
}
}
</script>
<style scoped lang="scss">
.todo-container {
min-height: 100vh;
background: #f5f7fa;
display: flex;
flex-direction: column;
.custom-nav-bar {
background: #ffffff;
border-bottom: 1rpx solid #f0f0f0;
.nav-content {
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
.nav-title {
font-size: 34rpx;
font-weight: 600;
color: #333333;
}
}
}
.tab-bar {
display: flex;
background: #ffffff;
padding: 0 10rpx;
border-bottom: 1rpx solid #f0f0f0;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
.tab-item {
flex: 0 0 auto;
min-width: 140rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20rpx 16rpx;
position: relative;
.tab-text {
font-size: 26rpx;
color: #666666;
white-space: nowrap;
line-height: 1.2;
}
.tab-badge {
position: absolute;
top: 8rpx;
right: 8rpx;
min-width: 32rpx;
height: 32rpx;
line-height: 32rpx;
text-align: center;
background: #ff4d4f;
color: #ffffff;
font-size: 20rpx;
border-radius: 16rpx;
padding: 0 8rpx;
}
&.active {
.tab-text {
color: #1a73e8;
font-weight: 600;
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 4rpx;
background: #1a73e8;
border-radius: 2rpx;
}
}
}
}
.list-container {
flex: 1;
padding: 20rpx;
box-sizing: border-box;
.coil-list {
padding-bottom: 40rpx;
}
.load-more-wrapper {
padding: 30rpx 0;
display: flex;
justify-content: center;
align-items: center;
&:active {
opacity: 0.7;
}
}
.placeholder-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 0;
.placeholder-icon {
font-size: 120rpx;
margin-bottom: 30rpx;
}
.placeholder-text {
font-size: 32rpx;
color: #999999;
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 0;
.empty-icon {
font-size: 120rpx;
margin-bottom: 30rpx;
}
.empty-text {
font-size: 32rpx;
color: #999999;
}
}
.loading-state {
padding: 100rpx 0;
}
}
}
/* 浏览器环境适配 */
@media screen and (min-width: 768px) {
.todo-container {
max-width: 750rpx;
margin: 0 auto;
.tab-bar {
.tab-item {
min-width: 160rpx;
.tab-text {
font-size: 28rpx;
}
}
}
}
}
/* H5 浏览器特定样式 */
/* #ifdef H5 */
.todo-container {
.tab-bar {
.tab-item {
cursor: pointer;
&:hover {
background: rgba(26, 115, 232, 0.05);
}
}
}
.coil-card {
cursor: pointer;
&:hover {
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.1);
}
}
}
/* #endif */
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB