2025-09-30 17:02:05 +08:00

452 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="container">
<!-- 搜索栏 -->
<view class="search-bar card">
<input v-model="searchName" placeholder="请输入资产名" class="search-input" />
<button class="btn btn-primary" @tap="search">搜索</button>
</view>
<!-- 空状态/加载状态 -->
<view class="load-status" v-if="loading">加载中...</view>
<view class="load-status" v-else-if="!assetList.length">没有更多数据了</view>
<!-- 资产列表 -->
<view class="list">
<view class="asset-item card" v-for="item in assetList" :key="item.assetId || item.assetid">
<view class="asset-main" @tap="gotoDetail(item)">
<view class="asset-name">{{ item.assetName }}</view>
<view class="asset-detail">采购日期{{ item.purchaseDate }}</view>
<view class="asset-detail">采购价格{{ item.purchasePrice }}</view>
<view class="asset-detail">采购数量{{ item.purchaseQuantity }}</view>
<view class="asset-detail">设备状态
<text v-if="item.status === 1 || item.status === 0">使用中</text>
<text v-else-if="item.status === 2">报废</text>
<text v-else-if="item.status === 3">闲置</text>
<text v-else-if="item.status === 4">维修中</text>
<text v-else-if="item.status === 5">外借</text>
<text v-else>未知</text>
</view>
</view>
<view class="asset-actions">
<button class="btn btn-primary outline" v-if="item.status === 3"
@tap.stop="borrow(item)">借用</button>
<button class="btn" @tap="gotoShowMaintenanceInfo(item)">维修信息</button>
<button class="btn" @tap="gotoDetail(item)">详细信息</button>
</view>
</view>
</view>
<!-- 分页加载状态 -->
<view class="load-status">
<text v-if="loading && assetList.length">加载中...</text>
<text v-else-if="noMore && assetList.length">没有更多数据了</text>
</view>
<!-- 借用弹窗 -->
<view class="modal" v-if="showBorrowPopup">
<view class="modal-content">
<view class="modal-header">
<text class="modal-title">借用设备信息</text>
<text class="modal-close" @tap="cancelBorrow">×</text>
</view>
<view class="modal-body">
<view class="row"><text class="label">设备名称</text><text class="value">{{ selectedAsset?.assetName
}}</text></view>
<view class="form-item">
<text class="label">借用人电话</text>
<input v-model="borrowForm.lenderMobile" placeholder="请输入借用人电话" type="number"
@input="searchUserByMobile" />
<view class="user-suggestions" v-if="userSuggestions.length">
<view class="suggestion-item" v-for="u in userSuggestions" :key="u.userMobile"
@tap="selectUser(u)">
{{ u.userName }} ({{ u.userMobile }}) (id: {{ u.id }})
</view>
</view>
</view>
<!-- <view class="form-item">
<text class="label">借用人姓名</text>
<input v-model="borrowForm.borrowerName" disabled placeholder="请输入手机号查询" />
</view>
<view class="form-item">
<text class="label">借用人ID</text>
<input v-model="borrowForm.borrowerId" disabled placeholder="请输入手机号查询" />
</view> -->
<view class="form-item">
<text class="label">借用日期</text>
<picker mode="date" :value="borrowForm.borrowDate" @change="onBorrowDateChange">
<view class="picker-value">{{ borrowForm.borrowDate || '请选择借用日期' }}</view>
</picker>
</view>
<view class="form-item">
<text class="label">预计归还日期</text>
<picker mode="date" @change="onReturnDateChange">
<view class="picker-value">{{ borrowForm.returnDate || '请选择归还日期' }}</view>
</picker>
</view>
<view class="form-item">
<text class="label">备注</text>
<textarea v-model="borrowForm.note" placeholder="请输入备注" />
</view>
</view>
<view class="modal-footer">
<button class="btn" @tap="cancelBorrow">取消</button>
<button class="btn btn-primary" @tap="confirmBorrow">确认</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { onLoad, onReachBottom } from '@dcloudio/uni-app'
// @ts-ignore JS模块无类型声明
import { searchFixedAssets, fetchFixedAssets, fetchMaintenanceInfoById, borrowAssetById, returnAsset, searchUserByMobile as apiSearchUserByMobile } from '@/pages/api/fixedAssets.js'
interface AssetItem { [k: string]: any }
const assetList = ref<AssetItem[]>([])
const page = ref(1)
const pageSize = ref(10)
const loading = ref(false)
const noMore = ref(false)
const searchName = ref('')
const showBorrowPopup = ref(false)
const selectedAsset = ref<AssetItem | null>(null)
const userInfo = uni.getStorageSync('staff') || {}
const borrowForm = ref({
lenderMobile: '',
borrowDate: '',
returnDate: '',
location: '',
borrowDepartmentId: '',
approverId: '',
approverMobile: '',
note: ''
})
const userSuggestions = ref<any[]>([])
let searchTimeout: any = null
const isSearching = ref(false)
function formatDate(d: Date) {
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
}
onLoad(() => {
getAssetList()
})
onReachBottom(() => {
if (!loading.value && !noMore.value) {
page.value++
getAssetList()
}
})
async function search() {
loading.value = true
try {
if (!searchName.value) {
assetList.value = []
page.value = 1
noMore.value = false
await getAssetList()
return
}
const data: any = await searchFixedAssets(searchName.value)
assetList.value = data?.data || []
noMore.value = true
} catch (e) {
console.error('获取数据失败', e)
} finally {
loading.value = false
}
}
async function getAssetList() {
loading.value = true
try {
const data: any = await fetchFixedAssets(page.value, pageSize.value)
const list = data?.data?.list || []
if (list.length < pageSize.value) noMore.value = true
assetList.value = assetList.value.concat(list)
} catch (e) {
console.error('获取数据失败', e)
} finally {
loading.value = false
}
}
function gotoDetail(item: AssetItem) {
const sn = item.assetSn || item.assetSN || item.asset_sn
if (sn) {
uni.navigateTo({ url: `/pages/fixed-assets/detail?assetSn=${encodeURIComponent(sn)}` })
} else {
uni.navigateTo({ url: '/pages/fixed-assets/detail', success(res) { res.eventChannel.emit('sendData', { data: item }) } })
}
}
function gotoShowMaintenanceInfo(item: AssetItem) {
const id = item.assetId || item.assetid
if (id)
uni.navigateTo({ url: `/pages/fixed-assets/maintenance-info?assetId=${id}` })
else
uni.navigateTo({
url: '/pages/fixed-assets/maintenance-info',
success(res) { res.eventChannel.emit('sendData', { data: item }) }
})
}
function borrow(item: AssetItem) {
selectedAsset.value = item
showBorrowPopup.value = true
userSuggestions.value = []
borrowForm.value.lenderMobile = ''
// 默认借用日期为今天
borrowForm.value.borrowDate = formatDate(new Date())
}
function cancelBorrow() {
showBorrowPopup.value = false
userSuggestions.value = []
}
function onBorrowDateChange(e: any) { borrowForm.value.borrowDate = e.detail.value }
function onReturnDateChange(e: any) { borrowForm.value.returnDate = e.detail.value }
async function searchUserByMobile() {
const mobile = borrowForm.value.lenderMobile
if (searchTimeout) clearTimeout(searchTimeout)
if (!mobile || mobile.length < 3) { userSuggestions.value = []; return }
isSearching.value = true
searchTimeout = setTimeout(async () => {
try {
const resp: any = await apiSearchUserByMobile(mobile)
userSuggestions.value = resp.data ?? []
} catch (err) {
userSuggestions.value = []
uni.showToast({ title: '搜索失败', icon: 'none' })
} finally {
isSearching.value = false
}
}, 500)
}
function selectUser(user: any) {
borrowForm.value.lenderMobile = user.userMobile
}
async function confirmBorrow() {
const s = selectedAsset.value || {}
const payload = {
assetId: s.assetId || s.assetid,
assetName: s.assetName,
borrowDate: borrowForm.value.borrowDate,
borrowerMobile: borrowForm.value.lenderMobile,
lenderMobile: s.headerMobile,
note: borrowForm.value.note,
registrantDate: new Date().toISOString(),
registrantId: s.registrantId,
registrantMobile: s.registrantMobile,
returnDate: borrowForm.value.returnDate
}
try {
await borrowAssetById(payload)
uni.showModal({ content: '借用成功', showCancel: false })
showBorrowPopup.value = false
// 刷新当前页
assetList.value = []
page.value = 1
noMore.value = false
await getAssetList()
} catch (err) {
uni.showToast({ title: '借用失败', icon: 'none' })
}
}
</script>
<style scoped>
.container {
padding: 24rpx;
}
.card {
background: #fff;
border-radius: 20rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
}
.search-bar {
display: flex;
gap: 12rpx;
padding: 20rpx;
align-items: center;
}
.search-input {
flex: 1;
background: #f5f7fb;
border-radius: 12rpx;
padding: 12rpx 16rpx;
}
.btn {
padding: 0 20rpx;
background: #e9efff;
color: #3a5ddd;
border-radius: 10rpx;
font-size: 24rpx;
}
.btn-primary {
background: #3a5ddd;
color: #fff;
}
.btn-primary.outline {
background: #fff;
color: #3a5ddd;
border: 2rpx solid #3a5ddd;
}
.list {
display: flex;
flex-direction: column;
gap: 20rpx;
margin-top: 20rpx;
}
.asset-item {
padding: 20rpx;
}
.asset-main {
border-bottom: 1rpx solid #f0f1f5;
padding-bottom: 16rpx;
margin-bottom: 12rpx;
}
.asset-name {
font-size: 30rpx;
color: #222;
font-weight: 600;
}
.asset-detail {
font-size: 24rpx;
color: #666;
margin-top: 6rpx;
}
.asset-actions {
display: flex;
gap: 12rpx;
justify-content: flex-end;
}
.load-status {
text-align: center;
color: #9a9aa0;
margin: 20rpx 0;
}
/* 弹窗样式与项目统一 */
.modal {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.45);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
}
.modal-content {
width: 86%;
max-height: 76vh;
background: #fff;
border-radius: 16rpx;
overflow: hidden;
display: flex;
flex-direction: column;
}
.modal-header {
padding: 20rpx 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1rpx solid #eee;
}
.modal-title {
font-size: 30rpx;
color: #222;
font-weight: 600;
}
.modal-close {
font-size: 40rpx;
color: #999;
}
.modal-body {
padding: 16rpx 24rpx;
max-height: 60vh;
}
.modal-footer {
padding: 16rpx 24rpx;
display: flex;
justify-content: flex-end;
gap: 16rpx;
border-top: 1rpx solid #eee;
}
.modal-footer .btn {
flex: 1;
}
.label {
color: #666;
font-size: 24rpx;
}
.value {
color: #333;
font-size: 26rpx;
}
.form-item {
margin: 16rpx 0;
}
.picker-value {
padding: 10rpx 12rpx;
background: #f5f7fb;
border-radius: 8rpx;
color: #333;
}
.user-suggestions {
background: #fff;
border: 1rpx solid #eee;
border-radius: 8rpx;
margin-top: 8rpx;
max-height: 240rpx;
overflow: auto;
}
.suggestion-item {
padding: 10rpx 12rpx;
border-bottom: 1rpx solid #f5f5f5;
}
</style>