smart-delivery/src/pages/fixed-assets/lent-management.vue

552 lines
12 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="page">
<!-- 顶部统一卡片 -->
<view class="top-card card">
<!-- 统计信息 -->
<view class="stats-row">
<view class="stat-item">
<text class="stat-value">{{ totalCount }}</text>
<text class="stat-label">借出总数</text>
</view>
<view class="stat-item">
<text class="stat-value highlight">{{ unreturned }}</text>
<text class="stat-label">未归还</text>
</view>
<view class="stat-item">
<text class="stat-value success">{{ returned }}</text>
<text class="stat-label">已归还</text>
</view>
</view>
<!-- 筛选和搜索 -->
<view class="filter-search-row">
<view class="filter-tabs">
<view
class="filter-tab"
:class="{ active: filterStatus === null }"
@tap="changeFilter(null)"
>
全部
</view>
<view
class="filter-tab"
:class="{ active: filterStatus === 0 }"
@tap="changeFilter(0)"
>
未归还
</view>
<view
class="filter-tab"
:class="{ active: filterStatus === 1 }"
@tap="changeFilter(1)"
>
已归还
</view>
</view>
<view class="search-input-wrap">
<text class="search-icon">🔍</text>
<input
class="search-input"
v-model="searchKeyword"
placeholder="手机号"
placeholder-class="search-placeholder"
type="number"
maxlength="11"
/>
<text v-if="searchKeyword" class="clear-icon" @tap="clearSearch"></text>
</view>
</view>
</view>
<!-- 列表 -->
<scroll-view
class="list-container"
scroll-y
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
>
<view v-if="loading && list.length === 0" class="loading-wrap">
<text class="loading-text">加载中...</text>
</view>
<view v-else-if="list.length === 0" class="empty-wrap">
<text class="empty-icon">📦</text>
<text class="empty-text">暂无借出记录</text>
</view>
<view v-else class="list">
<view
v-for="item in filteredList"
:key="item.borrowId"
class="list-item card"
>
<!-- 顶部资产名称 + 状态标签 -->
<view class="item-header">
<view class="asset-name-wrap">
<text class="asset-name">{{ item.assetName }}</text>
</view>
<view class="status-badge" :class="item.isReturn === 1 ? 'returned' : 'unreturned'">
{{ item.isReturn === 1 ? '已归还' : '未归还' }}
</view>
</view>
<!-- 信息行 -->
<view class="item-info">
<view class="info-row">
<text class="info-label">资产SN</text>
<text class="info-value">{{ item.assetId || '-' }}</text>
</view>
<view class="info-row">
<text class="info-label">借用人</text>
<text class="info-value">{{ item.borrowerName || '-' }}</text>
</view>
<view class="info-row">
<text class="info-label">联系方式</text>
<text class="info-value">{{ item.borrowerMobile || '-' }}</text>
</view>
<view class="info-row">
<text class="info-label">借用日期</text>
<text class="info-value">{{ item.borrowDate || '-' }}</text>
</view>
<view class="info-row">
<text class="info-label">应还日期</text>
<text class="info-value">{{ item.returnDate || '-' }}</text>
</view>
<view v-if="item.actualReturnDate" class="info-row">
<text class="info-label">实际归还</text>
<text class="info-value success-text">{{ item.actualReturnDate }}</text>
</view>
<view v-if="item.note" class="info-row">
<text class="info-label">备注</text>
<text class="info-value">{{ item.note }}</text>
</view>
</view>
<!-- 操作按钮 -->
<view v-if="item.isReturn === 0" class="item-actions">
<button class="action-btn return-btn" @tap="handleReturn(item)">
确认归还
</button>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
// @ts-ignore
import { fetchBorrowList, returnAsset } from '@/pages/api/fixedAssets.js'
interface BorrowRecord {
borrowId: number
assetId: number
assetName: string
borrowDate: string
returnDate: string
actualReturnDate: string | null
borrowerId: string
borrowerName: string | null
borrowerMobile: string | null
borrowDepartmentId: string | null
borrowDepartmentName: string | null
location: string | null
lenderId: string
lenderName: string | null
lenderMobile: string | null
approverId: string | null
approverName: string | null
approverMobile: string | null
registrantId: string
registrantName: string | null
registrantMobile: string | null
registrantDate: string
isReturn: number // 0-未归还 1-已归还
note: string
deleteFlag: number
}
const list = ref<BorrowRecord[]>([])
const loading = ref(false)
const refreshing = ref(false)
const filterStatus = ref<number | null>(null)
const searchKeyword = ref('')
// 统计数据
const totalCount = computed(() => list.value.length)
const unreturned = computed(() => list.value.filter(item => item.isReturn === 0).length)
const returned = computed(() => list.value.filter(item => item.isReturn === 1).length)
// 筛选后的列表
const filteredList = computed(() => {
let result = list.value
// 按状态筛选
if (filterStatus.value !== null) {
result = result.filter(item => item.isReturn === filterStatus.value)
}
// 按手机号搜索
if (searchKeyword.value.trim()) {
const keyword = searchKeyword.value.trim()
result = result.filter(item =>
item.borrowerMobile && item.borrowerMobile.includes(keyword)
)
}
return result
})
onMounted(() => {
loadData()
})
onShow(() => {
loadData()
})
// 加载数据
async function loadData() {
loading.value = true
try {
const result = await fetchBorrowList()
if (result.code === 0 && Array.isArray(result.data)) {
list.value = result.data
} else {
uni.showToast({
title: result.msg || '加载失败',
icon: 'none'
})
}
} catch (error) {
console.error('加载借出记录失败:', error)
uni.showToast({
title: '加载失败',
icon: 'none'
})
} finally {
loading.value = false
refreshing.value = false
}
}
// 下拉刷新
function onRefresh() {
refreshing.value = true
loadData()
}
// 切换筛选
function changeFilter(status: number | null) {
filterStatus.value = status
}
// 清除搜索
function clearSearch() {
searchKeyword.value = ''
}
// 归还资产
function handleReturn(item: BorrowRecord) {
uni.showModal({
title: '确认归还',
content: `确认归还资产:${item.assetName}`,
success: async (res) => {
if (res.confirm) {
await doReturnAsset(item)
}
}
})
}
// 执行归还操作
async function doReturnAsset(item: BorrowRecord) {
uni.showLoading({ title: '处理中...' })
try {
const result = await returnAsset(item.assetId)
uni.hideLoading()
if (result.code === 0) {
uni.showToast({
title: '归还成功',
icon: 'success'
})
// 刷新列表
loadData()
} else {
uni.showToast({
title: result.msg || '归还失败',
icon: 'none'
})
}
} catch (error) {
uni.hideLoading()
console.error('归还资产失败:', error)
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
}
</script>
<style scoped>
.page {
min-height: 100vh;
background: #f5f7fb;
padding-bottom: 32rpx;
}
.card {
background: #fff;
border-radius: 20rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
}
/* 顶部统一卡片 */
.top-card {
margin: 24rpx 24rpx 16rpx;
padding: 24rpx;
}
/* 统计信息行 */
.stats-row {
display: flex;
align-items: center;
justify-content: space-around;
margin-bottom: 24rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 4rpx;
}
.stat-value {
font-size: 36rpx;
font-weight: 600;
color: #333;
}
.stat-value.highlight {
color: #ff6b6b;
}
.stat-value.success {
color: #51cf66;
}
.stat-label {
font-size: 22rpx;
color: #999;
}
/* 筛选和搜索行 */
.filter-search-row {
display: flex;
align-items: center;
gap: 16rpx;
}
.filter-tabs {
display: flex;
gap: 8rpx;
flex: 1;
}
.filter-tab {
flex: 1;
text-align: center;
padding: 12rpx 8rpx;
border-radius: 8rpx;
font-size: 24rpx;
color: #666;
background: #f8f9fa;
transition: all 0.3s;
}
.filter-tab.active {
background: #007aff;
color: #fff;
font-weight: 500;
}
/* 搜索框 */
.search-input-wrap {
display: flex;
align-items: center;
background: #f8f9fa;
border-radius: 20rpx;
padding: 10rpx 16rpx;
gap: 8rpx;
width: 200rpx;
flex-shrink: 0;
}
.search-icon {
font-size: 28rpx;
opacity: 0.6;
}
.search-input {
flex: 1;
font-size: 24rpx;
color: #333;
background: transparent;
border: none;
}
.search-placeholder {
color: #999;
}
.clear-icon {
font-size: 28rpx;
color: #999;
padding: 0 4rpx;
}
/* 列表容器 */
.list-container {
height: calc(100vh - 280rpx);
}
.loading-wrap,
.empty-wrap {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
}
.loading-text {
color: #999;
font-size: 28rpx;
}
.empty-icon {
font-size: 120rpx;
margin-bottom: 24rpx;
}
.empty-text {
color: #999;
font-size: 28rpx;
}
/* 列表项 */
.list {
padding: 0 32rpx;
}
.list-item {
margin-bottom: 24rpx;
padding: 32rpx;
}
.item-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 20rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.asset-name-wrap {
flex: 1;
margin-right: 16rpx;
}
.asset-name {
font-size: 32rpx;
font-weight: 600;
color: #222;
line-height: 1.4;
}
.status-badge {
padding: 8rpx 20rpx;
border-radius: 30rpx;
font-size: 24rpx;
font-weight: 500;
white-space: nowrap;
}
.status-badge.unreturned {
background: linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%);
color: #d32f2f;
}
.status-badge.returned {
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
color: #2e7d32;
}
/* 信息行 */
.item-info {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.info-row {
display: flex;
align-items: flex-start;
font-size: 26rpx;
}
.info-label {
color: #999;
min-width: 160rpx;
flex-shrink: 0;
}
.info-value {
color: #333;
flex: 1;
word-break: break-all;
}
.success-text {
color: #51cf66;
font-weight: 500;
}
/* 操作按钮 */
.item-actions {
margin-top: 24rpx;
padding-top: 24rpx;
border-top: 2rpx solid #f0f0f0;
display: flex;
justify-content: flex-end;
}
.action-btn {
padding: 0rpx 40rpx;
border-radius: 40rpx;
font-size: 26rpx;
border: none;
}
.return-btn {
background: #007aff;
color: #fff;
box-shadow: 0 4rpx 12rpx rgba(0, 122, 255, 0.25);
}
.return-btn:active {
opacity: 0.8;
transform: scale(0.98);
}
</style>