Files
shj-app/scanpages/hj/weld_joint_search.vue
T

386 lines
7.8 KiB
Vue
Raw Normal View History

2026-08-18 18:05:16 +08:00
<template>
<view class="search-page">
<!-- 顶部:输入框内扫码(类似淘宝搜索) -->
<view class="search-bar">
<view class="search-bar__input-wrap">
<u-icon
name="scan"
:size="28"
color="#2979ff"
@click="handleScan"
/>
<input
class="search-bar__input"
v-model="keyword"
type="text"
confirm-type="search"
:placeholder="placeholder"
:focus="autoFocus"
@input="handleSearchInput"
@confirm="handleSearchConfirm"
/>
<u-icon
v-if="keyword"
name="close-circle-fill"
:size="28"
color="#ccc"
@click="handleClear"
/>
</view>
</view>
<!-- 结果列表 -->
<scroll-view
class="result-scroll"
scroll-y
@scrolltolower="loadMore"
>
<view class="result-list">
<view
v-for="(item, index) in listData"
:key="item.WeldJointId || item.Id || index"
class="result-item"
@click="handleSelect(item)"
>
<view class="result-item__main">
<text class="result-item__code">{{ item.WeldJointCode || '--' }}</text>
<text
v-if="item.PipelineCode"
class="result-item__sub"
>管线 {{ item.PipelineCode }}</text>
</view>
<u-icon
name="arrow-right"
:size="24"
color="#ccc"
/>
</view>
<u-empty
v-if="!loading && searched && listData.length === 0"
mode="search"
text="未找到焊口"
/>
<view
v-if="!searched && !loading"
class="result-hint"
>
<text>扫码或输入焊口/管线编号搜索</text>
</view>
<view
v-if="loading && listData.length > 0"
class="loading-more"
>
<u-loading
mode="circle"
size="28"
/>
<text>加载中...</text>
</view>
<view
v-if="!loading && !hasMore && listData.length > 0"
class="no-more"
>
<text>— 没有更多了 —</text>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
/**
* 焊口搜索中间页:扫码进入业务页,或关键字搜索后点选进入
* 入参:target(业务页路径)、title(导航标题)
*/
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { storeToRefs } from 'pinia'
import { useUserStore, useMenuStore } from '@/store'
import { getUrlParam } from '@/utils/request.js'
import { reqPipeJointWeldedJoints } from '@/api/hj.js'
const userStore = useUserStore()
const menuStore = useMenuStore()
const { currentProject } = storeToRefs(userStore)
/** 业务目标页,如 /scanpages/hj/material_sampling */
const targetPath = ref('')
const placeholder = ref('搜索焊口')
const autoFocus = ref(true)
const keyword = ref('')
const listData = ref([])
const loading = ref(false)
const searched = ref(false)
const hasMore = ref(true)
const currentPage = ref(1)
const pageSize = 20
let searchTimer = null
/**
* 从接口响应取出列表
* @param {any} data
*/
const pickList = (data) => {
if (!data) return []
if (Array.isArray(data)) return data
if (Array.isArray(data.getDataList)) return data.getDataList
if (Array.isArray(data.list)) return data.list
if (Array.isArray(data.records)) return data.records
if (Array.isArray(data.Items)) return data.Items
return []
}
const clearList = () => {
listData.value = []
hasMore.value = false
searched.value = false
}
/**
* 拉取焊口列表
* @param {boolean} isRefresh
*/
const loadList = async (isRefresh = true) => {
const kw = keyword.value.trim()
const projectId = currentProject.value?.ProjectId
if (!projectId) {
uni.showToast({ title: '请先选择项目', icon: 'none' })
return
}
if (!kw) {
clearList()
return
}
if (loading.value) return
if (!isRefresh && !hasMore.value) return
loading.value = true
if (isRefresh) {
currentPage.value = 1
hasMore.value = true
listData.value = []
}
try {
const res = await reqPipeJointWeldedJoints({
projectId,
keyword: kw,
pageIndex: currentPage.value,
pageSize,
})
const data = res?.data ?? {}
const pageData = pickList(data)
const totalPages = Number(data.pageCount) || 0
const totalCount = Number(data.totalCount) || 0
if (totalPages > 0) {
hasMore.value = currentPage.value < totalPages
} else if (totalCount > 0) {
hasMore.value = listData.value.length + pageData.length < totalCount
} else {
hasMore.value = pageData.length >= pageSize
}
listData.value = isRefresh ? pageData : listData.value.concat(pageData)
searched.value = true
} catch (e) {
console.error('焊口搜索失败:', e)
if (isRefresh) listData.value = []
searched.value = true
} finally {
loading.value = false
}
}
const handleSearchInput = () => {
if (searchTimer) clearTimeout(searchTimer)
searchTimer = setTimeout(() => {
const kw = keyword.value.trim()
if (!kw) {
clearList()
return
}
loadList(true)
}, 400)
}
const handleSearchConfirm = () => {
if (searchTimer) clearTimeout(searchTimer)
loadList(true)
}
const handleClear = () => {
keyword.value = ''
clearList()
}
const loadMore = () => {
if (!loading.value && hasMore.value && keyword.value.trim()) {
currentPage.value += 1
loadList(false)
}
}
/**
* 进入业务页
* @param {string} id WeldJointId
*/
const goTarget = (id) => {
if (!id) {
uni.showToast({ title: '缺少焊口信息', icon: 'none' })
return
}
if (!targetPath.value) {
uni.showToast({ title: '缺少目标页面', icon: 'none' })
return
}
if (!menuStore.canAccessPath(targetPath.value)) {
uni.showToast({ title: '当前岗位无此功能权限', icon: 'none' })
return
}
uni.navigateTo({
url: `${targetPath.value}?id=${encodeURIComponent(id)}`
})
}
/**
* 点选搜索结果
* @param {object} item
*/
const handleSelect = (item) => {
const id = item.WeldJointId || item.Id || item.id
goTarget(id)
}
/**
* 扫码:与首页菜单扫码一致,解析 id 后进入业务页
*/
const handleScan = () => {
uni.scanCode({
scanType: ['datamatrix', 'barCode', 'qrCode', 'wxCode', 'pdf417'],
success: (res) => {
const result = res.result || ''
let id = getUrlParam(result, 'id')
if (!id) {
id = String(result).trim()
}
if (!id) {
uni.showToast({ title: '未识别到焊口', icon: 'none' })
return
}
goTarget(id)
},
fail: (err) => {
if (err?.errMsg && String(err.errMsg).includes('cancel')) return
uni.showToast({ title: '扫码失败,请重试', icon: 'none' })
}
})
}
onLoad((opt) => {
targetPath.value = decodeURIComponent(opt.target || '')
const title = opt.title ? decodeURIComponent(opt.title) : '选择焊口'
uni.setNavigationBarTitle({ title })
if (!targetPath.value) {
uni.showToast({ title: '入口参数错误', icon: 'none' })
}
})
</script>
<style lang="scss" scoped>
.search-page {
min-height: 100vh;
background: #f5f6f8;
display: flex;
flex-direction: column;
}
.search-bar {
display: flex;
align-items: center;
padding: 16rpx 24rpx;
background: #fff;
position: sticky;
top: 0;
z-index: 10;
&__input-wrap {
flex: 1;
height: 72rpx;
display: flex;
align-items: center;
gap: 12rpx;
padding: 0 20rpx;
background: #f5f6f8;
border-radius: 36rpx;
}
&__input {
flex: 1;
font-size: 28rpx;
color: #333;
height: 72rpx;
}
}
.result-scroll {
flex: 1;
height: 0;
}
.result-list {
padding: 16rpx 24rpx 40rpx;
}
.result-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx 24rpx;
margin-bottom: 16rpx;
background: #fff;
border-radius: 16rpx;
&__main {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 8rpx;
}
&__code {
font-size: 30rpx;
font-weight: 600;
color: #1a2332;
font-family: monospace;
}
&__sub {
font-size: 24rpx;
color: #999;
}
}
.result-hint {
padding: 80rpx 0;
text-align: center;
font-size: 26rpx;
color: #bbb;
}
.loading-more,
.no-more {
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
padding: 24rpx;
font-size: 24rpx;
color: #999;
}
</style>