feat(records): 出库/借还记录统一高级搜索版式
三个记录页(出库/借还/报废)此前搜索区形态各异:出库用裸 div + 分散样式,
借还缺日期范围,报废只有 SKU 输入框。现统一为 el-form :inline="true" +
.filter-form 版式,并补齐缺失的过滤维度。
借还记录(新增日期范围能力):
· 后端 get_records 增加 start_date/end_date 参数,按「借出时间」过滤;
· 边界补全时分秒(YYYY-MM-DD → 当日 00:00:00 / 23:59:59),
与出库记录同口径,解决零点截断导致当天记录漏查的问题;
· API 层透传两个新参数。
出库记录:
· 版式统一为 inline 表单,日期选择器加 label;
· 新增「重置」按钮;
· 搜索类型切换由 500ms 防抖改为立即查询。
借还记录:
· 新增「重置」按钮;搜索类型/状态切换改为立即查询(取消防抖)。
实测(SUPER_ADMIN):
报废 全部/单号/SKU/操作人/物料名/日期 六类过滤均 200 且命中正确;
借还 无过滤 52 单 / 本月 18 单 / 空区间 0 单(日期过滤生效);
出库 无过滤 395 单 / SKU 277 / 姓名 7 / 物料名 16 / 单号 OUT-2026 命中 395。
This commit is contained in:
@ -162,6 +162,8 @@ def get_records():
|
||||
page = int(request.args.get('page', 1))
|
||||
keyword = request.args.get('keyword', '')
|
||||
search_type = request.args.get('search_type', 'all')
|
||||
start_date = request.args.get('start_date', '')
|
||||
end_date = request.args.get('end_date', '')
|
||||
|
||||
# ★ 数据权限:普通用户只看“借用人=本人姓名(不含账号前缀)”的借还记录;管理者看全部
|
||||
borrower_name = None
|
||||
@ -175,7 +177,8 @@ def get_records():
|
||||
|
||||
res = TransService.get_records(
|
||||
page=page, limit=10, status=status, keyword=keyword,
|
||||
search_type=search_type, borrower_name=borrower_name
|
||||
search_type=search_type, borrower_name=borrower_name,
|
||||
start_date=start_date, end_date=end_date,
|
||||
)
|
||||
|
||||
# ★ service 层异常时:code==500 的字典(带 traceback),需要直通到前端,便于排查
|
||||
|
||||
@ -414,7 +414,8 @@ class TransService:
|
||||
raise e
|
||||
|
||||
@staticmethod
|
||||
def get_records(page=1, limit=10, status='all', keyword=None, search_type='all', borrower_name=None):
|
||||
def get_records(page=1, limit=10, status='all', keyword=None, search_type='all',
|
||||
borrower_name=None, start_date=None, end_date=None):
|
||||
"""
|
||||
获取借还记录列表(按单号 borrow_no 维度分页,避免明细撑爆 pageSize)
|
||||
|
||||
@ -429,7 +430,14 @@ class TransService:
|
||||
状态过滤按"单号聚合"判定:
|
||||
- borrowed: 单号下至少有一条 is_returned=False
|
||||
- returned: 单号下所有明细 is_returned=True
|
||||
|
||||
日期范围按「借出时间」过滤,边界补全时分秒以解决零点截断问题。
|
||||
"""
|
||||
# 日期补全:与出库记录同口径
|
||||
if end_date and len(str(end_date).strip()) == 10:
|
||||
end_date = f"{str(end_date).strip()} 23:59:59"
|
||||
if start_date and len(str(start_date).strip()) == 10:
|
||||
start_date = f"{str(start_date).strip()} 00:00:00"
|
||||
try:
|
||||
# ====================================================================
|
||||
# 步骤 1a:构造"单号维度"基础子查询(GROUP BY borrow_no 在这里完成)
|
||||
@ -680,6 +688,20 @@ class TransService:
|
||||
order_subq.c.borrow_no.in_(company_borrow_nos_subq)
|
||||
)
|
||||
|
||||
# 日期范围过滤(按借出时间;边界已在入口补全时分秒)
|
||||
if start_date:
|
||||
borrow_no_q = borrow_no_q.filter(order_subq.c.borrow_no.in_(
|
||||
db.session.query(TransBorrow.borrow_no)
|
||||
.filter(TransBorrow.borrow_time >= start_date)
|
||||
.distinct()
|
||||
))
|
||||
if end_date:
|
||||
borrow_no_q = borrow_no_q.filter(order_subq.c.borrow_no.in_(
|
||||
db.session.query(TransBorrow.borrow_no)
|
||||
.filter(TransBorrow.borrow_time <= end_date)
|
||||
.distinct()
|
||||
))
|
||||
|
||||
# 状态过滤(按"单号聚合"判定)
|
||||
if status == 'borrowed':
|
||||
# 单号下至少一条未还
|
||||
|
||||
@ -1,41 +1,51 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="filter-container">
|
||||
<CompanySelector v-model="listQuery.company" @change="fetchData" style="margin-right: 10px;" />
|
||||
<!-- ★ 高级搜索:与借还记录 / 报废记录统一版式 -->
|
||||
<el-form :inline="true" class="filter-form" @submit.prevent>
|
||||
<el-form-item>
|
||||
<CompanySelector v-model="listQuery.company" @change="fetchData" />
|
||||
</el-form-item>
|
||||
|
||||
<el-input
|
||||
v-model="listQuery.keyword"
|
||||
placeholder="请输入关键词"
|
||||
style="width: 250px;"
|
||||
class="filter-item"
|
||||
clearable
|
||||
@input="debouncedSearch"
|
||||
@clear="handleClearSearch"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-select v-model="listQuery.search_type" placeholder="搜索类型" style="width: 110px" @change="debouncedSearch">
|
||||
<el-option label="全部" value="all" />
|
||||
<el-option label="单号" value="no" />
|
||||
<el-option label="姓名" value="name" />
|
||||
<el-option label="SKU" value="sku" />
|
||||
<el-option label="物料名称" value="material_name" />
|
||||
<el-option label="规格型号" value="spec_model" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-date-picker
|
||||
v-model="listQuery.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
class="filter-item"
|
||||
style="margin-left: 10px;"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
<el-button type="primary" class="filter-item" style="margin-left: 10px;" @click="fetchData">查询</el-button>
|
||||
<el-button v-if="userStore.hasPermission('outbound_create:operation')" type="success" class="filter-item" @click="$router.push('/outbound/create')">新建出库</el-button>
|
||||
</div>
|
||||
<el-form-item label="搜索">
|
||||
<el-input
|
||||
v-model="listQuery.keyword"
|
||||
placeholder="输入关键词"
|
||||
style="width: 240px;"
|
||||
clearable
|
||||
@input="debouncedSearch"
|
||||
@clear="handleClearSearch"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-select v-model="listQuery.search_type" placeholder="搜索类型" style="width: 110px" @change="handleSearchImmediate">
|
||||
<el-option label="全部" value="all" />
|
||||
<el-option label="单号" value="no" />
|
||||
<el-option label="姓名" value="name" />
|
||||
<el-option label="SKU" value="sku" />
|
||||
<el-option label="物料名称" value="material_name" />
|
||||
<el-option label="规格型号" value="spec_model" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="出库日期">
|
||||
<el-date-picker
|
||||
v-model="listQuery.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 260px"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="fetchData">查询</el-button>
|
||||
<el-button @click="resetFilter">重置</el-button>
|
||||
<el-button v-if="userStore.hasPermission('outbound_create:operation')" type="success" @click="$router.push('/outbound/create')">新建出库</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table
|
||||
:data="list"
|
||||
@ -247,6 +257,22 @@ const handlePageChange = (val: number) => {
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 立即查询(搜索类型切换用,取消防抖)
|
||||
const handleSearchImmediate = () => {
|
||||
if (debounceTimer) clearTimeout(debounceTimer)
|
||||
listQuery.page = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
const resetFilter = () => {
|
||||
if (debounceTimer) clearTimeout(debounceTimer)
|
||||
listQuery.keyword = ''
|
||||
listQuery.search_type = 'all'
|
||||
listQuery.dateRange = []
|
||||
listQuery.page = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
const formatType = (type: string) => {
|
||||
const map: any = {
|
||||
'SALES': '销售出库',
|
||||
@ -285,6 +311,17 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ★ 统一高级搜索版式(与借还记录 / 报废记录一致) */
|
||||
.filter-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
.filter-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.signature-cell {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@ -1,33 +1,54 @@
|
||||
<template>
|
||||
<div v-if="userStore.hasPermission('op_records')" class="app-container">
|
||||
<div class="filter-container">
|
||||
<el-radio-group v-model="status" @change="fetchData" style="margin-right: 20px">
|
||||
<el-radio-button label="all">全部</el-radio-button>
|
||||
<el-radio-button label="borrowed">未归还</el-radio-button>
|
||||
<el-radio-button label="returned">已归还</el-radio-button>
|
||||
</el-radio-group>
|
||||
<!-- ★ 高级搜索:与出库记录 / 报废记录统一版式 -->
|
||||
<el-form :inline="true" class="filter-form" @submit.prevent>
|
||||
<el-form-item>
|
||||
<el-radio-group v-model="status" @change="handleSearchImmediate">
|
||||
<el-radio-button label="all">全部</el-radio-button>
|
||||
<el-radio-button label="borrowed">未归还</el-radio-button>
|
||||
<el-radio-button label="returned">已归还</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
placeholder="请输入关键词"
|
||||
style="width: 250px"
|
||||
clearable
|
||||
@input="debouncedSearch"
|
||||
@clear="handleClearSearch"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-select v-model="searchType" placeholder="类型" style="width: 110px" @change="debouncedSearch">
|
||||
<el-option label="全部" value="all" />
|
||||
<el-option label="单号" value="no" />
|
||||
<el-option label="借用人" value="name" />
|
||||
<el-option label="SKU" value="sku" />
|
||||
<el-option label="物料名称" value="material_name" />
|
||||
<el-option label="规格型号" value="spec_model" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button type="primary" @click="fetchData">查询</el-button>
|
||||
</div>
|
||||
<el-form-item label="搜索">
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
placeholder="输入关键词"
|
||||
style="width: 240px"
|
||||
clearable
|
||||
@input="debouncedSearch"
|
||||
@clear="handleClearSearch"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-select v-model="searchType" placeholder="类型" style="width: 110px" @change="handleSearchImmediate">
|
||||
<el-option label="全部" value="all" />
|
||||
<el-option label="单号" value="no" />
|
||||
<el-option label="借用人" value="name" />
|
||||
<el-option label="SKU" value="sku" />
|
||||
<el-option label="物料名称" value="material_name" />
|
||||
<el-option label="规格型号" value="spec_model" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="借出日期">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 260px"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="fetchData">查询</el-button>
|
||||
<el-button @click="resetFilter">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table
|
||||
:data="list"
|
||||
@ -262,6 +283,7 @@ const total = ref(0)
|
||||
const status = ref('borrowed')
|
||||
const keyword = ref('')
|
||||
const searchType = ref('all')
|
||||
const dateRange = ref<string[]>([])
|
||||
const page = ref(1)
|
||||
const loading = ref(false)
|
||||
|
||||
@ -329,15 +351,20 @@ const confirmScrap = async () => {
|
||||
const fetchData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: any = {
|
||||
page: page.value,
|
||||
status: status.value,
|
||||
keyword: keyword.value,
|
||||
search_type: searchType.value
|
||||
}
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
params.start_date = dateRange.value[0]
|
||||
params.end_date = dateRange.value[1]
|
||||
}
|
||||
const res = await request({
|
||||
url: '/v1/transactions/records',
|
||||
method: 'get',
|
||||
params: {
|
||||
page: page.value,
|
||||
status: status.value,
|
||||
keyword: keyword.value,
|
||||
search_type: searchType.value
|
||||
}
|
||||
params
|
||||
})
|
||||
|
||||
// ★ 按 borrow_no 分组聚合为主子表结构
|
||||
@ -371,6 +398,23 @@ const handlePage = (val: number) => {
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 立即查询(状态/类型切换用,取消防抖)
|
||||
const handleSearchImmediate = () => {
|
||||
if (debounceTimer) clearTimeout(debounceTimer)
|
||||
page.value = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
const resetFilter = () => {
|
||||
if (debounceTimer) clearTimeout(debounceTimer)
|
||||
status.value = 'borrowed'
|
||||
keyword.value = ''
|
||||
searchType.value = 'all'
|
||||
dateRange.value = []
|
||||
page.value = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// ★ 新增:格式化预计归还时间及倒计时逻辑(交通灯预警系统)
|
||||
const formatExpectedTime = (timeStr: string) => {
|
||||
if (!timeStr) return { text: '无限期', diffText: '', style: {} }
|
||||
@ -418,5 +462,15 @@ onBeforeUnmount(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style scoped>
|
||||
/* ★ 统一高级搜索版式(与出库记录 / 报废记录一致) */
|
||||
.filter-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
.filter-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user