feat(bid): add close date order management feature
新增结单时间管理功能,包括: 1. 新增结单统计接口与页面,展示待结单、今日/本周结单量和平均处理周期 2. 支持按单号、订单状态搜索查询结单列表 3. 支持批量设置收货日期并批量确认结单 4. 优化现有订单列表的表格列宽与布局
This commit is contained in:
@@ -115,4 +115,10 @@ public class BizDeliveryOrderController extends BaseController {
|
||||
public AjaxResult historyStats() {
|
||||
return success(service.selectHistoryStats());
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('bid:order:closeDate:edit')")
|
||||
@GetMapping("/closeDate/stats")
|
||||
public AjaxResult closeDateStats() {
|
||||
return success(service.selectCloseDateStats());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,4 +28,6 @@ public interface BizDeliveryOrderMapper {
|
||||
Map<String, Object> selectTransitStats();
|
||||
// 历史统计
|
||||
Map<String, Object> selectHistoryStats();
|
||||
// 结单统计
|
||||
Map<String, Object> selectCloseDateStats();
|
||||
}
|
||||
|
||||
@@ -25,4 +25,6 @@ public interface IBizDeliveryOrderService {
|
||||
Map<String, Object> selectTransitStats();
|
||||
// 历史统计
|
||||
Map<String, Object> selectHistoryStats();
|
||||
// 结单统计
|
||||
Map<String, Object> selectCloseDateStats();
|
||||
}
|
||||
|
||||
@@ -150,4 +150,9 @@ public class BizDeliveryOrderServiceImpl implements IBizDeliveryOrderService {
|
||||
public Map<String, Object> selectHistoryStats() {
|
||||
return mapper.selectHistoryStats();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> selectCloseDateStats() {
|
||||
return mapper.selectCloseDateStats();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,16 @@
|
||||
WHERE delivery_status = 'transit'
|
||||
</select>
|
||||
|
||||
<select id="selectCloseDateStats" resultType="java.util.Map">
|
||||
SELECT
|
||||
COUNT(*) AS pendingClose,
|
||||
SUM(CASE WHEN actual_close_date = CURDATE() THEN 1 ELSE 0 END) AS todayClosed,
|
||||
SUM(CASE WHEN YEARWEEK(actual_close_date, 1) = YEARWEEK(CURDATE(), 1) THEN 1 ELSE 0 END) AS weekClosed,
|
||||
ROUND(AVG(DATEDIFF(actual_close_date, delivery_date)), 1) AS avgCycleDays
|
||||
FROM biz_delivery_order
|
||||
WHERE delivery_status = 'history'
|
||||
</select>
|
||||
|
||||
<select id="selectHistoryStats" resultType="java.util.Map">
|
||||
SELECT
|
||||
COUNT(*) AS totalHistory,
|
||||
|
||||
@@ -223,6 +223,17 @@ export const dynamicRoutes = [
|
||||
meta: { title: '历史订单', activeMenu: '/bid/order' }
|
||||
}]
|
||||
},
|
||||
{
|
||||
path: '/bid/order/closeDate',
|
||||
component: Layout,
|
||||
permissions: ['bid:order:closeDate'],
|
||||
children: [{
|
||||
path: '',
|
||||
component: () => import('@/views/bid/order/closeDate'),
|
||||
name: 'CloseDate',
|
||||
meta: { title: '结单时间管理', activeMenu: '/bid/order' }
|
||||
}]
|
||||
},
|
||||
|
||||
{
|
||||
path: '/bid/comparison/detail',
|
||||
|
||||
207
ruoyi-ui/src/views/bid/order/closeDate.vue
Normal file
207
ruoyi-ui/src/views/bid/order/closeDate.vue
Normal file
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<div class="cd-page">
|
||||
<!-- ═══ 统计卡片 ═══ -->
|
||||
<el-row :gutter="12" class="stat-row">
|
||||
<el-col :span="6" v-for="c in statCards" :key="c.key">
|
||||
<div class="stat-card" :style="{ borderTopColor: c.color }">
|
||||
<div class="stat-body"><div class="stat-num">{{ stats[c.key] != null ? stats[c.key] : '-' }}</div><div class="stat-lbl">{{ c.label }}</div></div>
|
||||
<i :class="c.icon" class="stat-icon" :style="{ color: c.color }"></i>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="cd-body">
|
||||
<!-- ═══ 左侧列表 ═══ -->
|
||||
<div class="cd-left">
|
||||
<div class="left-header">
|
||||
<span class="left-title">订单列表</span>
|
||||
<div class="left-tools">
|
||||
<el-input v-model="q.doNo" placeholder="搜索单号" clearable size="small" style="width:130px" @keyup.enter.native="handleSearch" />
|
||||
<el-select v-model="q.deliveryStatus" placeholder="状态" clearable size="small" style="width:100px" @change="getList">
|
||||
<el-option label="待发" value="pending" />
|
||||
<el-option label="在途" value="transit" />
|
||||
<el-option label="历史" value="history" />
|
||||
</el-select>
|
||||
<el-button size="small" icon="el-icon-search" @click="handleSearch">搜索</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table ref="table" v-loading="loading" :data="list" border stripe size="small"
|
||||
@selection-change="onSelectionChange" class="cd-table" style="width:100%"
|
||||
:row-class-name="rowClass">
|
||||
<el-table-column type="selection" width="38" align="center" />
|
||||
<el-table-column label="单号" prop="doNo" width="125" />
|
||||
<el-table-column label="供应商" prop="supplierName" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="交货期" prop="deliveryDate" width="85" align="center" />
|
||||
<el-table-column label="收货日期" width="115" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-date-picker v-model="s.row._editDate" type="date" value-format="yyyy-MM-dd"
|
||||
size="mini" style="width:105px" placeholder="选择日期" :clearable="true"
|
||||
@change="onDateChange(s.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="周期" width="75" align="center">
|
||||
<template slot-scope="s">
|
||||
<span :class="cycleClass(s.row._cycleDays)">{{ s.row._cycleDays != null ? s.row._cycleDays + '天' : '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="差异" width="75" align="center">
|
||||
<template slot-scope="s">
|
||||
<span :class="diffClass(s.row._diffDays)">{{ s.row._diffDays != null ? diffLabel(s.row._diffDays) : '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination v-show="total>0" :total="total" :page.sync="q.pageNum" :limit.sync="q.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
|
||||
<!-- ═══ 右侧操作区 ═══ -->
|
||||
<div class="cd-right">
|
||||
<div class="right-panel">
|
||||
<div class="right-title">批量操作</div>
|
||||
<div class="right-section">
|
||||
<div class="rs-header">已选择 <b>{{ selected.length }}</b> 条</div>
|
||||
<div v-if="selected.length" class="rs-list">
|
||||
<div v-for="r in selected" :key="r.doId" class="rs-item">{{ r.doNo }}</div>
|
||||
</div>
|
||||
<div v-else class="rs-empty">请在左侧勾选订单</div>
|
||||
</div>
|
||||
<div class="right-section">
|
||||
<div class="rs-header">批量设置收货日期</div>
|
||||
<div class="rs-date-row">
|
||||
<el-date-picker v-model="batchDate" type="date" value-format="yyyy-MM-dd" size="small" style="width:140px" placeholder="选择日期" />
|
||||
<el-button size="small" @click="applyBatchDate" :disabled="!selected.length || !batchDate">应用到选中</el-button>
|
||||
</div>
|
||||
<div class="rs-quick">
|
||||
<el-button size="mini" @click="batchDate = todayStr()">今天</el-button>
|
||||
<el-button size="mini" @click="batchDate = yesterdayStr()">昨天</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-section">
|
||||
<div class="rs-header">批量确认结单</div>
|
||||
<el-button type="primary" size="small" style="width:100%" @click="batchConfirm"
|
||||
:disabled="!selected.length || !allHaveDate">确认结单 ({{ selected.length }})</el-button>
|
||||
<div v-if="selected.length && !allHaveDate" class="rs-warn">有订单未设置收货日期</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listDelivery, setCloseDate } from "@/api/bid/delivery"
|
||||
import request from '@/utils/request'
|
||||
|
||||
export default {
|
||||
name: "CloseDate",
|
||||
data() {
|
||||
return {
|
||||
loading: false, list: [], total: 0, stats: {},
|
||||
selected: [],
|
||||
batchDate: null,
|
||||
q: { pageNum: 1, pageSize: 50, doNo: "", deliveryStatus: "" },
|
||||
statCards: [
|
||||
{ key: "pendingClose", label: "已收货未结单", icon: "el-icon-document", color: "#4A6FA5" },
|
||||
{ key: "todayClosed", label: "今日结单", icon: "el-icon-circle-check", color: "#67c23a" },
|
||||
{ key: "weekClosed", label: "本周结单", icon: "el-icon-data-line", color: "#e6a23c" },
|
||||
{ key: "avgCycleDays", label: "平均周期(天)", icon: "el-icon-time", color: "#8e44ad" }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
allHaveDate() { return this.selected.every(r => r._editDate) }
|
||||
},
|
||||
created() { this.getList(); this.getStats() },
|
||||
methods: {
|
||||
getList() {
|
||||
this.loading = true
|
||||
listDelivery(this.q).then(r => {
|
||||
this.list = (r.rows || []).map(d => ({
|
||||
...d,
|
||||
deliveryDate: d.deliveryDate ? d.deliveryDate.substring(0, 10) : '',
|
||||
_editDate: d.actualCloseDate ? d.actualCloseDate.substring(0, 10) : '',
|
||||
_cycleDays: null,
|
||||
_diffDays: null
|
||||
})).map(d => { this.calcRow(d); return d })
|
||||
this.total = r.total || 0; this.loading = false
|
||||
}).catch(() => { this.loading = false })
|
||||
},
|
||||
getStats() {
|
||||
request({ url: '/bid/delivery/closeDate/stats', method: 'get' }).then(r => { this.stats = r.data || {} }).catch(() => {})
|
||||
},
|
||||
handleSearch() { this.q.pageNum = 1; this.getList() },
|
||||
onSelectionChange(rows) { this.selected = rows },
|
||||
rowClass({ row }) { return this.selected.includes(row) ? 'selected-row' : '' },
|
||||
|
||||
onDateChange(row) { this.calcRow(row) },
|
||||
calcRow(row) {
|
||||
if (!row.deliveryDate || !row._editDate) { row._cycleDays = null; row._diffDays = null; return }
|
||||
const cd = new Date(row._editDate)
|
||||
const dd = new Date(row.deliveryDate)
|
||||
row._cycleDays = Math.round((cd - dd) / 86400000)
|
||||
row._diffDays = row._cycleDays
|
||||
},
|
||||
cycleClass(d) { if (d === null) return ''; return d <= 0 ? 'diff-early' : 'diff-late' },
|
||||
diffClass(d) { if (d === null) return ''; return d <= 0 ? 'diff-early' : 'diff-late' },
|
||||
diffLabel(d) { if (d === 0) return '准时'; if (d < 0) return '提前' + Math.abs(d) + '天'; return '延期' + d + '天' },
|
||||
|
||||
todayStr() { const d = new Date(); return d.toISOString().slice(0, 10) },
|
||||
yesterdayStr() { const d = new Date(); d.setDate(d.getDate() - 1); return d.toISOString().slice(0, 10) },
|
||||
applyBatchDate() {
|
||||
if (!this.batchDate || !this.selected.length) return
|
||||
this.selected.forEach(r => { r._editDate = this.batchDate; this.calcRow(r) })
|
||||
this.$modal.msgSuccess("已应用到 " + this.selected.length + " 条")
|
||||
},
|
||||
batchConfirm() {
|
||||
if (!this.selected.length) return
|
||||
if (!this.allHaveDate) { this.$modal.msgError("有订单未设置收货日期"); return }
|
||||
this.$modal.confirm("确认批量结单 " + this.selected.length + " 条?").then(() => {
|
||||
const promises = this.selected.map(r => setCloseDate(r.doId, r._editDate))
|
||||
return Promise.all(promises)
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess("批量结单成功"); this.getList(); this.getStats(); this.selected = []
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cd-page { background: #f5f7fa; padding: 12px; min-height: calc(100vh - 84px); }
|
||||
.stat-row { margin-bottom: 12px !important; }
|
||||
.stat-card {
|
||||
background: #fff; border-radius: 4px; border-top: 3px solid #4A6FA5;
|
||||
padding: 16px 20px; display: flex; align-items: center; justify-content: space-between;
|
||||
}
|
||||
.stat-num { font-size: 26px; font-weight: 700; color: #1a2c4e; line-height: 1.2; }
|
||||
.stat-lbl { font-size: 12px; color: #8c97a8; margin-top: 4px; }
|
||||
.stat-icon { font-size: 28px; opacity: 0.5; }
|
||||
|
||||
.cd-body { display: flex; gap: 12px; align-items: flex-start; }
|
||||
|
||||
/* ═══ 左侧列表 ═══ */
|
||||
.cd-left { flex: 1; background: #fff; border-radius: 4px; padding: 12px; }
|
||||
.left-header { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
|
||||
.left-title { font-size: 14px; font-weight: 700; color: #1a2c4e; white-space: nowrap; }
|
||||
.left-tools { display: flex; align-items: center; gap: 6px; margin-left: auto; }
|
||||
|
||||
/* ═══ 右侧操作区 ═══ */
|
||||
.cd-right { width: 320px; flex-shrink: 0; background: #fff; border-radius: 4px; padding: 16px; }
|
||||
.right-panel { }
|
||||
.right-title { font-size: 14px; font-weight: 700; color: #1a2c4e; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 2px solid #4A6FA5; }
|
||||
.right-section { margin-bottom: 20px; }
|
||||
.rs-header { font-size: 12px; color: #606266; margin-bottom: 8px; }
|
||||
.rs-list { max-height: 150px; overflow-y: auto; border: 1px solid #ebeef5; border-radius: 4px; padding: 4px; }
|
||||
.rs-item { padding: 4px 8px; font-size: 12px; color: #303133; border-bottom: 1px solid #f5f7fa; }
|
||||
.rs-item:last-child { border-bottom: none; }
|
||||
.rs-empty { text-align: center; padding: 20px; color: #c0c4cc; font-size: 12px; }
|
||||
.rs-date-row { display: flex; gap: 6px; margin-bottom: 8px; }
|
||||
.rs-quick { display: flex; gap: 6px; }
|
||||
.rs-warn { font-size: 11px; color: #f56c6c; margin-top: 6px; }
|
||||
|
||||
/* ═══ 选中行高亮 ═══ */
|
||||
::v-deep .selected-row td { background: #ecf5ff !important; }
|
||||
|
||||
/* ═══ 差异颜色 ═══ */
|
||||
.diff-early { color: #67c23a; font-weight: 600; }
|
||||
.diff-late { color: #f56c6c; font-weight: 600; }
|
||||
</style>
|
||||
@@ -32,26 +32,24 @@
|
||||
</div>
|
||||
|
||||
<!-- ═══ 表格 ═══ -->
|
||||
<el-table v-loading="loading" :data="list" border stripe size="small" class="order-table">
|
||||
<el-table-column label="发货单号" prop="doNo" width="160" />
|
||||
<el-table v-loading="loading" :data="list" border stripe size="small" class="order-table" style="width:100%">
|
||||
<el-table-column label="发货单号" prop="doNo" width="150" />
|
||||
<el-table-column label="供应商" prop="supplierName" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="金额" width="120" align="right">
|
||||
<template slot-scope="s"><span class="amount">¥{{ s.row.totalAmount }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="交货期" prop="deliveryDate" width="100" align="center" />
|
||||
<el-table-column label="收货日期" prop="actualCloseDate" width="100" align="center" />
|
||||
<el-table-column label="交货期" prop="deliveryDate" width="95" align="center" />
|
||||
<el-table-column label="收货" prop="actualCloseDate" width="95" align="center" />
|
||||
<el-table-column label="交期差异" width="100" align="center">
|
||||
<template slot-scope="s">
|
||||
<span :class="diffClass(s.row)">{{ diffLabel(s.row) }}</span>
|
||||
</template>
|
||||
<template slot-scope="s"><span :class="diffClass(s.row)">{{ diffLabel(s.row) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="物料" prop="itemCount" width="60" align="center" />
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<el-table-column label="物料" prop="itemCount" width="55" align="center" />
|
||||
<el-table-column label="状态" width="90" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag :type="tagType(s.row)" size="small" effect="dark">{{ tagLabel(s.row) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="170" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-button size="mini" type="text" @click="handleView(s.row)">详情</el-button>
|
||||
<el-button size="mini" type="text" @click="handleReOrder(s.row)">再次下单</el-button>
|
||||
|
||||
@@ -18,28 +18,21 @@
|
||||
</div>
|
||||
|
||||
<!-- ═══ 表格 ═══ -->
|
||||
<el-table v-loading="loading" :data="list" border stripe size="small" class="order-table">
|
||||
<el-table-column label="发货单号" prop="doNo" width="170" />
|
||||
<el-table-column label="供应商" prop="supplierName" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="金额" width="130" align="right">
|
||||
<el-table v-loading="loading" :data="list" border stripe size="small" class="order-table" style="width:100%">
|
||||
<el-table-column label="发货单号" prop="doNo" width="150" />
|
||||
<el-table-column label="供应商" prop="supplierName" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="金额" width="120" align="right">
|
||||
<template slot-scope="s"><span class="amount">¥{{ s.row.totalAmount }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="交货期" prop="deliveryDate" width="100" align="center" />
|
||||
<el-table-column label="延期日期" prop="delayDate" width="100" align="center">
|
||||
<el-table-column label="交货期" prop="deliveryDate" width="95" align="center" />
|
||||
<el-table-column label="延期" prop="delayDate" width="90" align="center">
|
||||
<template slot-scope="s">{{ s.row.delayDate || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="物料" prop="itemCount" width="60" align="center" />
|
||||
<el-table-column label="逾期提示" width="110" align="center">
|
||||
<template slot-scope="s">
|
||||
<span v-html="getUrgentBadge(s.row)" />
|
||||
</template>
|
||||
<el-table-column label="逾期" width="100" align="center">
|
||||
<template slot-scope="s"><span v-html="getUrgentBadge(s.row)" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="90" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag type="warning" size="small" effect="dark">PENDING</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="210" align="center" fixed="right">
|
||||
<el-table-column label="物料" prop="itemCount" width="55" align="center" />
|
||||
<el-table-column label="操作" width="200" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-button size="mini" type="text" @click="handleView(s.row)">详情</el-button>
|
||||
<el-button size="mini" type="text" @click="handleEdit(s.row)" v-if="s.row.deliveryStatus==='pending'">编辑</el-button>
|
||||
|
||||
@@ -46,30 +46,28 @@
|
||||
</div>
|
||||
|
||||
<!-- ═══ 表格 ═══ -->
|
||||
<el-table v-loading="loading" :data="list" border stripe size="small" class="order-table">
|
||||
<el-table-column label="发货单号" prop="doNo" width="165" />
|
||||
<el-table-column label="供应商" prop="supplierName" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="金额" width="130" align="right">
|
||||
<el-table v-loading="loading" :data="list" border stripe size="small" class="order-table" style="width:100%">
|
||||
<el-table-column label="发货单号" prop="doNo" width="150" />
|
||||
<el-table-column label="供应商" prop="supplierName" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="金额" width="120" align="right">
|
||||
<template slot-scope="s"><span class="amount">¥{{ s.row.totalAmount }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="交货期" width="100" align="center">
|
||||
<template slot-scope="s">
|
||||
<span :class="getUrgentClass(s.row)">{{ s.row.deliveryDate }}</span>
|
||||
</template>
|
||||
<el-table-column label="交货期" width="95" align="center">
|
||||
<template slot-scope="s"><span :class="getUrgentClass(s.row)">{{ s.row.deliveryDate }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="延期至" prop="delayDate" width="100" align="center">
|
||||
<el-table-column label="延期至" prop="delayDate" width="90" align="center">
|
||||
<template slot-scope="s">{{ s.row.delayDate || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="物料" prop="itemCount" width="60" align="center" />
|
||||
<el-table-column label="逾期提示" width="110" align="center">
|
||||
<el-table-column label="逾期" width="90" align="center">
|
||||
<template slot-scope="s"><span v-html="getUrgentBadge(s.row)" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="90" align="center">
|
||||
<el-table-column label="物料" prop="itemCount" width="55" align="center" />
|
||||
<el-table-column label="状态" width="85" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag :type="transitTagType(s.row)" size="small" effect="dark">{{ transitStatusLabel(s.row) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="230" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="220" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-button size="mini" type="text" @click="handleView(s.row)">详情</el-button>
|
||||
<el-button size="mini" type="text" style="color:#67C23A" @click="handleComplete(s.row)">收货完成</el-button>
|
||||
|
||||
Reference in New Issue
Block a user