Files
im-uniapp/pages/workbench/express/express.vue

670 lines
15 KiB
Vue
Raw Normal View History

2025-07-24 15:45:18 +08:00
<template>
<view class="express-page">
<!-- 顶部搜索与新增 -->
<view class="search-bar">
<view class="search-container">
<view class="search-input">
<u-search v-model="searchKeyword" placeholder="搜索物流编号" @confirm="onSearch" @keyup.enter="onSearch"
class="custom-search-input" :show-action="false" />
</view>
<view class="add-button" @click="handleAdd">
<u-icon name="plus" :color="$im-primary" size="18"></u-icon>
</view>
</view>
</view>
<!-- 列表区 -->
<scroll-view scroll-y class="express-list" @scrolltolower="loadMore">
<view v-for="item in expressList" :key="item.expressId" class="express-card">
<!-- 第一行物流公司单号状态 -->
<view class="card-header new-header">
<text class="express-company">{{ item.expressType || '-' }}</text>
<text class="express-code">{{ item.expressCode || '-' }}</text>
<text class="status" :class="'status-' + item.status">{{ statusText(item.status) }}</text>
</view>
<!-- 第二行发货方向对方->负责人及联系方式 -->
<view class="row row-between row-arrow">
<view class="row-party">
<text class="party-label">{{ item.supplyName || '-' }}</text>
<text class="party-phone">{{ item.supplyPhone || '-' }}</text>
</view>
<text class="arrow"></text>
<view class="row-party">
<text class="party-label">{{ item.ownerName || '-' }}</text>
<text class="party-phone">{{ item.ownerPhone || '-' }}</text>
</view>
</view>
<!-- 第三行计划到货时间和剩余天数 -->
<view class="row row-between">
<text class="plan-date">计划到货{{ item.planDate || '-' }}</text>
<ExpressRemainTime :planDate="item.planDate" :status="item.status" />
</view>
<!-- 第四行物流状态编辑器 -->
<view class="row">
<ExpressStatusEditor :lastStatus.sync="item.lastStatus" :lastUpdateTime.sync="item.lastUpdateTime"
:expressType="item.expressType" @save="val => handleStatusSave(item, val)" />
</view>
<!-- 第五行备注 -->
<view class="row remark-row">
<text class="remark-label">备注</text>
<text class="remark-content">{{ item.remark || '-' }}</text>
</view>
<!-- 第六行操作按钮 -->
<view class="card-actions new-actions">
<!-- 未确认确认修改 -->
<template v-if="item.status === 0">
<view class="action-btn action-confirm" @click="handleUpdateStatus(item, 1)">确认</view>
<view class="action-btn action-edit" @click="handleUpdate(item)">修改</view>
</template>
<!-- 进行中完成同步异常 -->
<template v-else-if="item.status === 1">
<view class="action-btn action-finish" @click="handleUpdateStatus(item, 2)">完成</view>
<view class="action-btn action-sync" @click="handleRefresh(item)">同步</view>
<view class="action-btn action-exception" @click="handleUpdateStatus(item, 3)">异常</view>
</template>
<!-- 已完成无操作按钮仅显示删除 -->
<template v-else-if="item.status === 2">
<!-- -->
</template>
<!-- 异常同步 -->
<template v-else-if="item.status === 3">
<view class="action-btn action-sync" @click="handleRefresh(item)">同步</view>
</template>
<!-- 编辑和删除按钮始终显示 -->
<view class="action-btn action-edit" @click="handleUpdate(item)">编辑</view>
<view class="action-btn action-delete" @click="handleDelete(item)">删除</view>
</view>
</view>
<view v-if="loadingMore" class="loading-more">加载中...</view>
<view v-if="noMore" class="no-more">没有更多了</view>
</scroll-view>
<!-- 新增/编辑弹窗 -->
<uni-popup ref="editPopup" type="bottom">
<view class="popup-content popup-bottom">
<view class="form-row">
<text>物流编号</text>
<uni-easyinput v-model="form.expressCode" placeholder="请输入物流编号" />
</view>
<view class="form-row">
<text>对方姓名</text>
<uni-easyinput v-model="form.supplyName" placeholder="请输入对方姓名" />
</view>
<view class="form-row">
<text>对方手机</text>
<uni-easyinput v-model="form.supplyPhone" placeholder="请输入对方手机" />
</view>
<view class="form-row">
<text>负责人</text>
<oa-user-select v-model="form.ownerId" />
</view>
<view class="form-row">
<text>负责人手机</text>
<uni-easyinput v-model="form.ownerPhone" placeholder="请输入负责人手机" />
</view>
<view class="form-row">
<text>计划到货</text>
<uni-easyinput v-model="form.planDate" placeholder="请输入计划到货时间" />
</view>
<view class="form-row">
<text>物流公司</text>
<oa-dict-select v-model="form.expressType" :dictType="'oa_express_type'" placeholder="请选择物流公司" />
</view>
<view class="form-row">
<text>备注</text>
<uni-easyinput v-model="form.remark" placeholder="请输入备注" />
</view>
<view class="popup-actions">
<uni-button type="primary" @click="submitForm">保存</uni-button>
<uni-button @click="cancel">取消</uni-button>
</view>
</view>
</uni-popup>
<!-- 异常登记弹窗 -->
<uni-popup ref="exceptionPopup" type="bottom">
<view class="popup-content popup-bottom">
<view class="form-row">
<text>问题描述</text>
<uni-easyinput v-model="exceptionForm.description" placeholder="请输入问题描述" />
</view>
<view class="form-row">
<text>汇报时间</text>
<uni-easyinput v-model="exceptionForm.reportTime" placeholder="请选择汇报时间" />
</view>
<view class="form-row">
<text>备注</text>
<uni-easyinput v-model="exceptionForm.remark" placeholder="请输入备注" />
</view>
<view class="popup-actions">
<uni-button type="primary" @click="submitException"> </uni-button>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import { addExpress, delExpress, getExpress, listExpress, updateExpress } from '@/api/oa/express';
import ExpressStatusEditor from './components/ExpressStatusEditor.vue';
import ExpressRemainTime from './components/ExpressRemainTime.vue';
import { addExpressQuestion } from '@/api/oa/expressQuestion';
export default {
components: {
ExpressStatusEditor,
ExpressRemainTime,
},
data() {
return {
expressList: [],
pageNum: 1,
pageSize: 10,
total: 0,
loadingMore: false,
noMore: false,
form: {
ownerUser: {},
},
expressDetail: {},
loading: false,
exceptionForm: {
description: '',
reportTime: '',
remark: ''
},
currentExceptionItem: null,
};
},
onLoad() {
this.getList();
},
methods: {
handleStatusSave(row, val) {
row.lastStatus = val.lastStatus;
row.lastUpdateTime = val.lastUpdateTime;
updateExpress(row).then(() => {
uni.showToast({ title: '保存成功', icon: 'success' });
this.getList();
});
},
getList(isLoadMore = false) {
if (this.loadingMore) return;
this.loadingMore = true;
listExpress({ pageNum: this.pageNum, pageSize: this.pageSize }).then(res => {
const rows = res.rows || [];
if (isLoadMore) {
this.expressList = this.expressList.concat(rows);
} else {
this.expressList = rows;
}
this.total = res.total || 0;
this.noMore = rows.length < this.pageSize;
}).finally(() => {
this.loadingMore = false;
});
},
loadMore() {
if (this.noMore || this.loadingMore) return;
this.pageNum++;
this.getList(true);
},
handleAdd() {
this.form = { ownerUser: {} };
this.$refs.editPopup.open();
},
handleUpdate(item) {
getExpress(item.expressId).then(res => {
const data = res.data || {};
this.form = {
...data,
ownerUser: data.ownerUser || {},
};
this.$refs.editPopup.open();
});
},
handleDelete(item) {
uni.showModal({
title: '提示',
content: '确定要删除该物流单吗?',
success: (res) => {
if (res.confirm) {
delExpress(item.expressId).then(() => {
uni.showToast({ title: '删除成功', icon: 'success' });
this.pageNum = 1;
this.getList();
});
}
}
});
},
submitForm() {
// 提交时将负责人信息拆分到 ownerName/ownerPhone 字段
const submitData = { ...this.form };
if (submitData.expressId) {
updateExpress(submitData).then(() => {
uni.showToast({ title: '修改成功', icon: 'success' });
this.$refs.editPopup.close();
this.pageNum = 1;
this.getList();
});
} else {
addExpress(submitData).then(() => {
uni.showToast({ title: '新增成功', icon: 'success' });
this.$refs.editPopup.close();
this.pageNum = 1;
this.getList();
});
}
},
cancel() {
this.$refs.editPopup.close();
},
handleDetail(item) {
getExpress(item.expressId).then(res => {
this.expressDetail = res.data || {};
this.$refs.detailPopup.open();
});
},
closeDetailPopup() {
this.$refs.detailPopup.close();
},
handleUpdateStatus(item, status) {
if (status === 3) {
this.currentExceptionItem = item;
this.$refs.exceptionPopup.open();
this.exceptionForm = {
description: '',
reportTime: this.formatDate(new Date()),
remark: ''
};
return;
}
item.status = status;
updateExpress(item).then(() => {
uni.showToast({ title: '状态更新成功', icon: 'success' });
this.getList();
});
},
submitException() {
const item = this.currentExceptionItem;
addExpressQuestion({
expressId: item.expressId,
description: this.exceptionForm.description,
reportTime: this.exceptionForm.reportTime,
remark: this.exceptionForm.remark
}).then(() => {
item.status = 3;
return updateExpress(item);
}).then(() => {
uni.showToast({ title: '异常登记成功', icon: 'success' });
this.getList();
}).finally(() => {
this.$refs.exceptionPopup.close();
this.exceptionForm = {
description: '',
reportTime: '',
remark: ''
};
this.currentExceptionItem = null;
});
},
formatDate(date) {
const pad = n => n < 10 ? '0' + n : n;
return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()) + ' '
+ pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds());
},
handleRefresh(item) {
// 实现同步逻辑
uni.showToast({ title: '同步成功', icon: 'success' });
this.getList();
},
statusText(status) {
switch (status) {
case 0: return '未确认';
case 1: return '进行中';
case 2: return '已完成';
case 3: return '异常';
default: return '-';
}
},
}
};
</script>
<style scoped>
.express-page {
padding: 20rpx;
}
.express-list {
max-height: 100vh;
}
.express-card {
background: #fff;
border-radius: 16rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 8rpx #eee;
padding: 20rpx;
}
.card-header {
display: none;
}
.new-header {
display: flex;
align-items: center;
gap: 20rpx;
font-weight: bold;
margin-bottom: 8rpx;
}
.express-company {
color: #409EFF;
font-size: 30rpx;
font-weight: 500;
}
.express-code {
color: #333;
font-size: 28rpx;
}
.status {
margin-left: auto;
font-size: 26rpx;
font-weight: 500;
}
.status-0 {
color: #faad14;
}
.status-1 {
color: #1890ff;
}
.status-2 {
color: #52c41a;
}
.status-3 {
color: #f5222d;
}
.row {
display: flex;
align-items: center;
margin: 16rpx 0;
border-bottom: 1rpx solid #f0f0f0;
padding-bottom: 8rpx;
}
.express-card .row:last-of-type {
border-bottom: none;
}
.row-between {
justify-content: space-between;
align-items: center;
}
.row-arrow {
gap: 16rpx;
}
.row-party {
display: flex;
flex-direction: column;
gap: 2rpx;
}
.party-label {
color: #333;
font-size: 26rpx;
font-weight: 500;
}
.party-phone {
color: #888;
font-size: 24rpx;
}
.arrow {
color: #bbb;
font-size: 32rpx;
margin: 0 8rpx;
}
.plan-date {
color: #666;
font-size: 24rpx;
}
.remark-row {
background: #f7f7f7;
border-radius: 8rpx;
padding: 8rpx 12rpx;
margin-top: 6rpx;
border-bottom: 1rpx solid #f0f0f0;
margin-bottom: 16rpx;
padding-bottom: 8rpx;
}
.remark-label {
color: #999;
font-size: 24rpx;
}
.remark-content {
color: #333;
font-size: 24rpx;
}
.card-actions {
display: none;
}
.new-actions {
display: flex;
justify-content: flex-end;
gap: 16rpx;
margin-top: 12rpx;
}
.action-btn {
padding: 8rpx 28rpx;
border-radius: 8rpx;
font-size: 26rpx;
font-weight: 500;
cursor: pointer;
margin-left: 0;
transition: background 0.2s;
}
.action-confirm {
background: #f6ffed;
color: #52c41a;
border: 1rpx solid #b7eb8f;
}
.action-confirm:active {
background: #d9f7be;
}
.action-finish {
background: #e6f7ff;
color: #1890ff;
border: 1rpx solid #91d5ff;
}
.action-finish:active {
background: #bae7ff;
}
.action-sync {
background: #fffbe6;
color: #faad14;
border: 1rpx solid #ffe58f;
}
.action-sync:active {
background: #fff1b8;
}
.action-exception {
background: #fff7e6;
color: #fa541c;
border: 1rpx solid #ffd591;
}
.action-exception:active {
background: #ffe7ba;
}
.action-edit {
background: #f6ffed;
color: #389e0d;
border: 1rpx solid #b7eb8f;
}
.action-edit:active {
background: #d9f7be;
}
.action-detail {
background: #f0f5ff;
color: #2f54eb;
border: 1rpx solid #adc6ff;
}
.action-detail:active {
background: #d6e4ff;
}
/* 删除按钮:红色文字,边框和背景更警示 */
.action-delete {
background: #fff1f0;
color: #f5222d;
border: 1rpx solid #ffa39e;
}
.action-delete:active {
background: #ffccc7;
}
.loading-more,
.no-more {
text-align: center;
color: #888;
padding: 20rpx 0;
}
.popup-content {
padding: 20rpx;
}
.popup-bottom {
width: 100vw;
background: #fff;
border-top-left-radius: 24rpx;
border-top-right-radius: 24rpx;
box-sizing: border-box;
z-index: 9999;
position: relative;
}
.form-row {
display: flex;
align-items: center;
margin-bottom: 16rpx;
}
.form-row text {
width: 120rpx;
}
.popup-actions {
display: flex;
justify-content: flex-end;
gap: 20rpx;
margin-top: 20rpx;
}
.detail-row {
display: flex;
margin-bottom: 10rpx;
}
/* 新增按钮样式 */
.add-btn-wrapper {
position: fixed;
left: 0;
right: 0;
bottom: 60rpx;
display: flex;
justify-content: center;
z-index: 100;
}
.add-btn {
width: 100rpx;
height: 100rpx;
background: #409EFF;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 16rpx #b3d8ff;
cursor: pointer;
transition: background 0.2s;
}
.add-btn:active {
background: #66b1ff;
}
.add-btn .iconfont {
color: #fff;
font-size: 60rpx;
font-weight: bold;
line-height: 1;
}
.search-bar {
padding: 20rpx;
position: sticky;
top: 0;
z-index: 100;
position: relative;
}
.search-container {
display: flex;
align-items: center;
gap: 20rpx;
}
.task-type-button-container {
position: relative;
}
.search-input {
flex: 1;
}
.task-type-button,
.add-button {
width: 60rpx;
height: 60rpx;
background-color: transparent;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
</style>