Files
im-uniapp/pages/workbench/express/express.vue
2025-07-24 15:45:18 +08:00

670 lines
15 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="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>