feat: implement cross-table search and debounced dynamic search for borrow and return records

This commit is contained in:
DXC
2026-03-20 09:58:42 +08:00
parent 74089c7d7d
commit 990399a408
3 changed files with 104 additions and 12 deletions

View File

@ -5,7 +5,8 @@ from app.models.transaction import TransBorrow
from app.models.inbound.buy import StockBuy from app.models.inbound.buy import StockBuy
from app.models.inbound.semi import StockSemi from app.models.inbound.semi import StockSemi
from app.models.inbound.product import StockProduct from app.models.inbound.product import StockProduct
from sqlalchemy import desc, func, nullslast, asc from app.models.base import MaterialBase
from sqlalchemy import desc, func, nullslast, asc, or_
class TransService: class TransService:
@ -189,15 +190,36 @@ class TransService:
@staticmethod @staticmethod
def get_records(page=1, limit=10, status='all', keyword=None): def get_records(page=1, limit=10, status='all', keyword=None):
q = TransBorrow.query q = TransBorrow.query
# 如果有关键词,需要联表搜索物料名称和规格型号
if keyword:
# 子查询:关联 material_base 表获取物料名称和规格型号
material_join = db.session.query(
TransBorrow.id
).join(
MaterialBase,
TransBorrow.sku == MaterialBase.sku
).filter(or_(
MaterialBase.name.ilike(f'%{keyword}%'),
MaterialBase.spec_model.ilike(f'%{keyword}%')
)).subquery()
# 主搜索条件借用人、SKU、单号 + 物料名称、规格型号
keyword_conditions = or_(
TransBorrow.borrower_name.ilike(f'%{keyword}%'),
TransBorrow.sku.ilike(f'%{keyword}%'),
TransBorrow.borrow_no.ilike(f'%{keyword}%'),
TransBorrow.id.in_(material_join) # 匹配物料名称/规格型号的记录
)
q = q.filter(keyword_conditions)
if status == 'borrowed': if status == 'borrowed':
q = q.filter(TransBorrow.is_returned == False) q = q.filter(TransBorrow.is_returned == False)
elif status == 'returned': elif status == 'returned':
q = q.filter(TransBorrow.is_returned == True) q = q.filter(TransBorrow.is_returned == True)
if keyword: # 使用 distinct 防止跨表查询产生重复记录
q = q.filter(TransBorrow.borrower_name.ilike(f'%{keyword}%') | q = q.distinct()
TransBorrow.sku.ilike(f'%{keyword}%') |
TransBorrow.borrow_no.ilike(f'%{keyword}%'))
q = q.order_by(nullslast(asc(TransBorrow.expected_return_time))) q = q.order_by(nullslast(asc(TransBorrow.expected_return_time)))
pagination = q.paginate(page=page, per_page=limit, error_out=False) pagination = q.paginate(page=page, per_page=limit, error_out=False)

View File

@ -4,10 +4,11 @@
<el-input <el-input
v-model="listQuery.keyword" v-model="listQuery.keyword"
placeholder="单号/姓名/SKU/名称/规格" placeholder="单号/姓名/SKU/名称/规格"
style="width: 200px;" style="width: 250px;"
class="filter-item" class="filter-item"
clearable clearable
@keyup.enter="fetchData" @input="debouncedSearch"
@clear="handleClearSearch"
/> />
<el-date-picker <el-date-picker
v-model="listQuery.dateRange" v-model="listQuery.dateRange"
@ -118,13 +119,36 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, reactive } from 'vue' import { ref, onMounted, reactive, onBeforeUnmount } from 'vue'
import { getOutboundList } from '@/api/outbound' import { getOutboundList } from '@/api/outbound'
import { Picture } from '@element-plus/icons-vue' import { Picture } from '@element-plus/icons-vue'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
const userStore = useUserStore() const userStore = useUserStore()
// 防抖定时器
let debounceTimer: ReturnType<typeof setTimeout> | null = null
// 防抖搜索函数
const debouncedSearch = () => {
if (debounceTimer) {
clearTimeout(debounceTimer)
}
debounceTimer = setTimeout(() => {
listQuery.page = 1
fetchData()
}, 500)
}
// 清空搜索时立即触发查询
const handleClearSearch = () => {
if (debounceTimer) {
clearTimeout(debounceTimer)
}
listQuery.page = 1
fetchData()
}
// 列与权限Code的映射关系数据库中的code // 列与权限Code的映射关系数据库中的code
const permissionMap: Record<string, string> = { const permissionMap: Record<string, string> = {
outbound_no: 'outbound_list:outbound_no', outbound_no: 'outbound_list:outbound_no',
@ -213,6 +237,14 @@ const getTagType = (type: string) => {
onMounted(() => { onMounted(() => {
fetchData() fetchData()
}) })
// 组件销毁前清理定时器,防止内存泄漏
onBeforeUnmount(() => {
if (debounceTimer) {
clearTimeout(debounceTimer)
debounceTimer = null
}
})
</script> </script>
<style scoped> <style scoped>

View File

@ -7,7 +7,14 @@
<el-radio-button label="returned">已归还</el-radio-button> <el-radio-button label="returned">已归还</el-radio-button>
</el-radio-group> </el-radio-group>
<el-input v-model="keyword" placeholder="搜索借用人/SKU" style="width: 200px" @keyup.enter="fetchData" /> <el-input
v-model="keyword"
placeholder="单号/借用人/SKU/名称/规格"
style="width: 250px"
clearable
@input="debouncedSearch"
@clear="handleClearSearch"
/>
<el-button type="primary" @click="fetchData">查询</el-button> <el-button type="primary" @click="fetchData">查询</el-button>
</div> </div>
@ -104,15 +111,38 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted, onBeforeUnmount } from 'vue'
import request from '@/utils/request' import request from '@/utils/request'
import dayjs from 'dayjs' // 建议使用 dayjs 处理日期,如果没有安装,可以用原生 Date import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn' // 导入中文包 import 'dayjs/locale/zh-cn'
dayjs.locale('zh-cn') dayjs.locale('zh-cn')
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
const userStore = useUserStore() const userStore = useUserStore()
// 防抖定时器
let debounceTimer: ReturnType<typeof setTimeout> | null = null
// 防抖搜索函数
const debouncedSearch = () => {
if (debounceTimer) {
clearTimeout(debounceTimer)
}
debounceTimer = setTimeout(() => {
page.value = 1
fetchData()
}, 500)
}
// 清空搜索时立即触发查询
const handleClearSearch = () => {
if (debounceTimer) {
clearTimeout(debounceTimer)
}
page.value = 1
fetchData()
}
// 列与权限Code的映射关系数据库中的code // 列与权限Code的映射关系数据库中的code
const permissionMap: Record<string, string> = { const permissionMap: Record<string, string> = {
borrow_no: 'op_records:borrow_no', borrow_no: 'op_records:borrow_no',
@ -203,6 +233,14 @@ const formatExpectedTime = (timeStr: string) => {
} }
onMounted(fetchData) onMounted(fetchData)
// 组件销毁前清理定时器,防止内存泄漏
onBeforeUnmount(() => {
if (debounceTimer) {
clearTimeout(debounceTimer)
debounceTimer = null
}
})
</script> </script>
<style> <style>