552 lines
12 KiB
Vue
552 lines
12 KiB
Vue
<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>
|