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))
|
page = int(request.args.get('page', 1))
|
||||||
keyword = request.args.get('keyword', '')
|
keyword = request.args.get('keyword', '')
|
||||||
search_type = request.args.get('search_type', 'all')
|
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
|
borrower_name = None
|
||||||
@ -175,7 +177,8 @@ def get_records():
|
|||||||
|
|
||||||
res = TransService.get_records(
|
res = TransService.get_records(
|
||||||
page=page, limit=10, status=status, keyword=keyword,
|
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),需要直通到前端,便于排查
|
# ★ service 层异常时:code==500 的字典(带 traceback),需要直通到前端,便于排查
|
||||||
|
|||||||
@ -414,7 +414,8 @@ class TransService:
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
@staticmethod
|
@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)
|
获取借还记录列表(按单号 borrow_no 维度分页,避免明细撑爆 pageSize)
|
||||||
|
|
||||||
@ -429,7 +430,14 @@ class TransService:
|
|||||||
状态过滤按"单号聚合"判定:
|
状态过滤按"单号聚合"判定:
|
||||||
- borrowed: 单号下至少有一条 is_returned=False
|
- borrowed: 单号下至少有一条 is_returned=False
|
||||||
- returned: 单号下所有明细 is_returned=True
|
- 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:
|
try:
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
# 步骤 1a:构造"单号维度"基础子查询(GROUP BY borrow_no 在这里完成)
|
# 步骤 1a:构造"单号维度"基础子查询(GROUP BY borrow_no 在这里完成)
|
||||||
@ -680,6 +688,20 @@ class TransService:
|
|||||||
order_subq.c.borrow_no.in_(company_borrow_nos_subq)
|
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':
|
if status == 'borrowed':
|
||||||
# 单号下至少一条未还
|
# 单号下至少一条未还
|
||||||
|
|||||||
@ -1,41 +1,51 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<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
|
<el-form-item label="搜索">
|
||||||
v-model="listQuery.keyword"
|
<el-input
|
||||||
placeholder="请输入关键词"
|
v-model="listQuery.keyword"
|
||||||
style="width: 250px;"
|
placeholder="输入关键词"
|
||||||
class="filter-item"
|
style="width: 240px;"
|
||||||
clearable
|
clearable
|
||||||
@input="debouncedSearch"
|
@input="debouncedSearch"
|
||||||
@clear="handleClearSearch"
|
@clear="handleClearSearch"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<el-select v-model="listQuery.search_type" placeholder="搜索类型" style="width: 110px" @change="debouncedSearch">
|
<el-select v-model="listQuery.search_type" placeholder="搜索类型" style="width: 110px" @change="handleSearchImmediate">
|
||||||
<el-option label="全部" value="all" />
|
<el-option label="全部" value="all" />
|
||||||
<el-option label="单号" value="no" />
|
<el-option label="单号" value="no" />
|
||||||
<el-option label="姓名" value="name" />
|
<el-option label="姓名" value="name" />
|
||||||
<el-option label="SKU" value="sku" />
|
<el-option label="SKU" value="sku" />
|
||||||
<el-option label="物料名称" value="material_name" />
|
<el-option label="物料名称" value="material_name" />
|
||||||
<el-option label="规格型号" value="spec_model" />
|
<el-option label="规格型号" value="spec_model" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
<el-date-picker
|
</el-form-item>
|
||||||
v-model="listQuery.dateRange"
|
|
||||||
type="daterange"
|
<el-form-item label="出库日期">
|
||||||
range-separator="至"
|
<el-date-picker
|
||||||
start-placeholder="开始日期"
|
v-model="listQuery.dateRange"
|
||||||
end-placeholder="结束日期"
|
type="daterange"
|
||||||
class="filter-item"
|
range-separator="至"
|
||||||
style="margin-left: 10px;"
|
start-placeholder="开始日期"
|
||||||
value-format="YYYY-MM-DD"
|
end-placeholder="结束日期"
|
||||||
/>
|
value-format="YYYY-MM-DD"
|
||||||
<el-button type="primary" class="filter-item" style="margin-left: 10px;" @click="fetchData">查询</el-button>
|
style="width: 260px"
|
||||||
<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>
|
||||||
|
|
||||||
|
<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
|
<el-table
|
||||||
:data="list"
|
:data="list"
|
||||||
@ -247,6 +257,22 @@ const handlePageChange = (val: number) => {
|
|||||||
fetchData()
|
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 formatType = (type: string) => {
|
||||||
const map: any = {
|
const map: any = {
|
||||||
'SALES': '销售出库',
|
'SALES': '销售出库',
|
||||||
@ -285,6 +311,17 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<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 {
|
.signature-cell {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@ -1,33 +1,54 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="userStore.hasPermission('op_records')" class="app-container">
|
<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-form :inline="true" class="filter-form" @submit.prevent>
|
||||||
<el-radio-button label="all">全部</el-radio-button>
|
<el-form-item>
|
||||||
<el-radio-button label="borrowed">未归还</el-radio-button>
|
<el-radio-group v-model="status" @change="handleSearchImmediate">
|
||||||
<el-radio-button label="returned">已归还</el-radio-button>
|
<el-radio-button label="all">全部</el-radio-button>
|
||||||
</el-radio-group>
|
<el-radio-button label="borrowed">未归还</el-radio-button>
|
||||||
|
<el-radio-button label="returned">已归还</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-input
|
<el-form-item label="搜索">
|
||||||
v-model="keyword"
|
<el-input
|
||||||
placeholder="请输入关键词"
|
v-model="keyword"
|
||||||
style="width: 250px"
|
placeholder="输入关键词"
|
||||||
clearable
|
style="width: 240px"
|
||||||
@input="debouncedSearch"
|
clearable
|
||||||
@clear="handleClearSearch"
|
@input="debouncedSearch"
|
||||||
>
|
@clear="handleClearSearch"
|
||||||
<template #prepend>
|
>
|
||||||
<el-select v-model="searchType" placeholder="类型" style="width: 110px" @change="debouncedSearch">
|
<template #prepend>
|
||||||
<el-option label="全部" value="all" />
|
<el-select v-model="searchType" placeholder="类型" style="width: 110px" @change="handleSearchImmediate">
|
||||||
<el-option label="单号" value="no" />
|
<el-option label="全部" value="all" />
|
||||||
<el-option label="借用人" value="name" />
|
<el-option label="单号" value="no" />
|
||||||
<el-option label="SKU" value="sku" />
|
<el-option label="借用人" value="name" />
|
||||||
<el-option label="物料名称" value="material_name" />
|
<el-option label="SKU" value="sku" />
|
||||||
<el-option label="规格型号" value="spec_model" />
|
<el-option label="物料名称" value="material_name" />
|
||||||
</el-select>
|
<el-option label="规格型号" value="spec_model" />
|
||||||
</template>
|
</el-select>
|
||||||
</el-input>
|
</template>
|
||||||
<el-button type="primary" @click="fetchData">查询</el-button>
|
</el-input>
|
||||||
</div>
|
</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
|
<el-table
|
||||||
:data="list"
|
:data="list"
|
||||||
@ -262,6 +283,7 @@ const total = ref(0)
|
|||||||
const status = ref('borrowed')
|
const status = ref('borrowed')
|
||||||
const keyword = ref('')
|
const keyword = ref('')
|
||||||
const searchType = ref('all')
|
const searchType = ref('all')
|
||||||
|
const dateRange = ref<string[]>([])
|
||||||
const page = ref(1)
|
const page = ref(1)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
@ -329,15 +351,20 @@ const confirmScrap = async () => {
|
|||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
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({
|
const res = await request({
|
||||||
url: '/v1/transactions/records',
|
url: '/v1/transactions/records',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params
|
||||||
page: page.value,
|
|
||||||
status: status.value,
|
|
||||||
keyword: keyword.value,
|
|
||||||
search_type: searchType.value
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// ★ 按 borrow_no 分组聚合为主子表结构
|
// ★ 按 borrow_no 分组聚合为主子表结构
|
||||||
@ -371,6 +398,23 @@ const handlePage = (val: number) => {
|
|||||||
fetchData()
|
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) => {
|
const formatExpectedTime = (timeStr: string) => {
|
||||||
if (!timeStr) return { text: '无限期', diffText: '', style: {} }
|
if (!timeStr) return { text: '无限期', diffText: '', style: {} }
|
||||||
@ -418,5 +462,15 @@ onBeforeUnmount(() => {
|
|||||||
})
|
})
|
||||||
</script>
|
</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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user