优化ui,修复固定资产借用问题

This commit is contained in:
feie9456 2025-10-07 13:20:17 +08:00
parent b0e010f80e
commit 38a551da22
17 changed files with 2465 additions and 516 deletions

View File

@ -13,6 +13,15 @@
<!--preload-links-->
<!--app-context-->
</head>
<style>
body::-webkit-scrollbar {
width: 4px;
}
body::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 10px;
}
</style>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/main.ts"></script>

View File

@ -137,6 +137,12 @@
"navigationBarTitleText": "申请详情"
}
},
{
"path": "pages/consumables/temp-request-edit",
"style": {
"navigationBarTitleText": "编辑申请"
}
},
{
"path": "pages/consumables/temp-approve",
"style": {
@ -160,6 +166,13 @@
"style": {
"navigationBarTitleText": "扫码查看信息"
}
},
{
"path": "pages/fixed-assets/lent-management",
"style": {
"navigationBarTitleText": "出借资产管理",
"enablePullDownRefresh": false
}
}
],
"globalStyle": {

View File

@ -1,6 +1,6 @@
import { formatTime, formatDate, formatDateTime, getHeaders } from './index';
const BASE_URL = 'http://localhost:3000' // 替换为你的实际后端地址
const BASE_URL = 'http://localhost:8090' // 替换为你的实际后端地址
// 请求封装
const request = (url: string, options: any = {}) => {
const token = uni.getStorageSync('token')

View File

@ -178,6 +178,8 @@ export function borrowAssetById(addDTO) {
//作用:前端 “个人借用记录页” 加载数据时调用(如显示当前用户借了哪些资产)。
export function borrowInfo(borrowerId) {
return new Promise((resolve, reject) => {
console.log(1);
uni.request({
url: `${BASE_URL}/api/asset/borrow/list`,// 接口路径:个人借用记录列表
method: 'POST',
@ -222,6 +224,30 @@ export function returnAsset(id) {
});
}
//查询所有借出资产列表
//作用:前端"出借资产管理"页面加载数据时调用(管理员查看所有借出的资产,包括已归还和未归还)。
export function fetchBorrowList() {
return new Promise((resolve, reject) => {
uni.request({
url: `${BASE_URL}/api/asset/borrow/list`, // 接口路径:借出资产列表接口
method: 'POST', // 请求方法POST
header: getHeaders(),
data: {}, // 请求体:空(获取所有借出记录,不过滤条件)
success: (res) => {
if (res.statusCode === 200) {
resolve(res.data); // 返回借出资产列表数据
} else {
reject(res);
}
},
fail: (err) => {
reject(err);
}
});
});
}
//————————————————————————————————————————————————业务接口函数(易耗品相关)——————————————————————————————————————————
//易耗品领用

View File

@ -60,7 +60,7 @@
</view>
<view class="info-row">
<text class="label">需要时间</text>
<text class="value">{{ detail.neededAt || '-' }}</text>
<text class="value">{{ detail.neededAt ? new Date(detail.neededAt).toLocaleDateString() : '-' }}</text>
</view>
<view class="info-row" v-if="detail.approverId">
<text class="label">审批人ID</text>
@ -459,47 +459,51 @@ onMounted(() => {
}
.status-card {
background: #3a5ddd;
background: #fff;
border-radius: 20rpx;
padding: 32rpx;
margin-bottom: 24rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
gap: 16rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
}
.status-badge {
padding: 12rpx 32rpx;
border-radius: 24rpx;
font-size: 32rpx;
font-weight: 600;
background: rgba(255, 255, 255, 0.9);
padding: 8rpx 24rpx;
border-radius: 8rpx;
font-size: 28rpx;
font-weight: 500;
}
.status-pending {
background: #fff3e0;
color: #f57c00;
}
.status-approved {
background: #e8f5e9;
color: #2e7d32;
}
.status-rejected {
background: #ffebee;
color: #c62828;
}
.status-cancelled {
background: #f5f5f5;
color: #757575;
}
.status-completed {
background: #e3f2fd;
color: #1976d2;
}
.status-time {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.9);
color: #999;
}
.section {
@ -527,16 +531,17 @@ onMounted(() => {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: #3a5ddd;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
border: 4rpx solid #f0f0f0;
}
.avatar-text {
font-size: 40rpx;
font-weight: 600;
font-size: 36rpx;
font-weight: 500;
color: #fff;
}
@ -680,25 +685,26 @@ onMounted(() => {
}
.total-card {
background: #f9a825;
background: #fff;
border-radius: 20rpx;
padding: 32rpx;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 8rpx 24rpx rgba(249, 168, 37, 0.2);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
border-left: 6rpx solid #ff9800;
}
.total-label {
font-size: 32rpx;
font-weight: 600;
color: #fff;
font-weight: 500;
color: #666;
}
.total-value {
font-size: 40rpx;
font-weight: 700;
color: #fff;
color: #ff6b00;
}
.footer-actions {
@ -726,12 +732,13 @@ onMounted(() => {
}
.btn-reject {
background: #ef5350;
color: #fff;
background: #fff;
color: #f44336;
border: 2rpx solid #f44336;
}
.btn-approve {
background: #3a5ddd;
background: #007aff;
color: #fff;
}
@ -837,12 +844,12 @@ onMounted(() => {
}
.btn-confirm {
background: #3a5ddd;
background: #007aff;
color: #fff;
}
.btn-confirm.btn-danger {
background: #ef5350;
background: #f44336;
}
.btn-confirm[disabled] {

View File

@ -177,7 +177,7 @@ async function loadList(reset = false) {
// COMPLETED
if (!showCompleted.value) {
newData = newData.filter((item: ConsumableRequest) => item.status !== 'COMPLETED')
newData = newData.filter((item: ConsumableRequest) => item.status !== 'COMPLETED' && item.status !== 'REJECTED')
}
list.value = reset ? newData : [...list.value, ...newData]

View File

@ -247,47 +247,51 @@ onMounted(() => {
}
.status-card {
background: #3a5ddd;
background: #fff;
border-radius: 20rpx;
padding: 32rpx;
margin-bottom: 24rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
gap: 16rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
}
.status-badge {
padding: 12rpx 32rpx;
border-radius: 24rpx;
font-size: 32rpx;
font-weight: 600;
background: rgba(255, 255, 255, 0.9);
padding: 8rpx 24rpx;
border-radius: 8rpx;
font-size: 28rpx;
font-weight: 500;
}
.status-pending {
background: #fff3e0;
color: #f57c00;
}
.status-approved {
background: #e8f5e9;
color: #2e7d32;
}
.status-rejected {
background: #ffebee;
color: #c62828;
}
.status-cancelled {
background: #f5f5f5;
color: #757575;
}
.status-completed {
background: #e3f2fd;
color: #1976d2;
}
.status-time {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.9);
color: #999;
}
.section {
@ -400,25 +404,26 @@ onMounted(() => {
}
.total-card {
background: #f9a825;
background: #fff;
border-radius: 20rpx;
padding: 32rpx;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 8rpx 24rpx rgba(249, 168, 37, 0.2);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
border-left: 6rpx solid #ff9800;
}
.total-label {
font-size: 32rpx;
font-weight: 600;
color: #fff;
font-weight: 500;
color: #666;
}
.total-value {
font-size: 40rpx;
font-weight: 700;
color: #fff;
color: #ff6b00;
}
.footer-actions {
@ -445,12 +450,13 @@ onMounted(() => {
}
.btn-danger {
background: #ef5350;
color: #fff;
background: #fff;
color: #f44336;
border: 2rpx solid #f44336;
}
.btn-primary {
background: #3a5ddd;
background: #007aff;
color: #fff;
}
</style>

View File

@ -0,0 +1,672 @@
<template>
<view class="page">
<scroll-view scroll-y class="content" v-if="!loading">
<!-- 基本信息 -->
<view class="section">
<view class="section-title">
<text>基本信息</text>
</view>
<view class="form-card">
<view class="form-item">
<text class="form-label required">申请人姓名</text>
<input class="form-input" v-model="formData.applicantName" placeholder="请输入申请人姓名" />
</view>
<view class="form-item">
<text class="form-label required">联系电话</text>
<input class="form-input" v-model="formData.applicantPhone" type="number" placeholder="请输入联系电话" />
</view>
<view class="form-item">
<text class="form-label">部门</text>
<input class="form-input" v-model="formData.department" placeholder="请输入部门" />
</view>
<view class="form-item">
<text class="form-label">申请原因</text>
<textarea class="form-textarea" v-model="formData.reason" placeholder="请输入申请原因" maxlength="200" />
</view>
<view class="form-item">
<text class="form-label">需要时间</text>
<picker mode="date" :value="formData.neededAt" @change="onDateChange">
<view class="form-picker">
<text>{{ formData.neededAt || '请选择需要时间' }}</text>
</view>
</picker>
</view>
</view>
</view>
<!-- 物料明细 -->
<view class="section">
<view class="section-title">
<text>物料明细</text>
<button class="btn-add" @tap="addItem">+ 添加物料</button>
</view>
<view v-if="formData.items.length === 0" class="empty-items">
<text>暂无物料请点击上方按钮添加</text>
</view>
<view v-for="(item, index) in formData.items" :key="index" class="item-card">
<view class="item-header">
<text class="item-number">#{{ index + 1 }}</text>
<button class="btn-delete" @tap="deleteItem(index)">删除</button>
</view>
<view class="form-item">
<text class="form-label required">物料名称</text>
<view class="form-row">
<input class="form-input flex-1" v-model="item.name" placeholder="请输入物料名称" />
<button class="btn-catalog" @tap="selectFromCatalog(index)">从目录选择</button>
</view>
</view>
<view class="form-item">
<text class="form-label">规格</text>
<input class="form-input" v-model="item.spec" placeholder="请输入规格" />
</view>
<view class="form-row-group">
<view class="form-item flex-1">
<text class="form-label required">数量</text>
<input class="form-input" v-model.number="item.quantity" type="digit" placeholder="数量" />
</view>
<view class="form-item flex-1">
<text class="form-label">单位</text>
<input class="form-input" v-model="item.unit" placeholder="单位" />
</view>
</view>
<view class="form-item">
<text class="form-label">预估单价</text>
<input class="form-input" v-model.number="item.estimatedUnitCost" type="digit" placeholder="请输入预估单价" />
</view>
<view class="form-item">
<text class="form-label">备注</text>
<textarea class="form-textarea" v-model="item.remark" placeholder="请输入备注" maxlength="100" />
</view>
</view>
</view>
</scroll-view>
<view v-if="loading" class="loading-container">
<text>加载中...</text>
</view>
<!-- 底部操作按钮 -->
<view class="footer-actions" v-if="!loading">
<button class="btn-secondary" @tap="goBack">取消</button>
<button class="btn-primary" @tap="submitForm" :disabled="submitting">
{{ submitting ? '保存中...' : '保存修改' }}
</button>
</view>
<!-- 目录选择弹窗 -->
<view v-if="showCatalogModal" class="modal-mask" @tap="closeCatalog">
<view class="modal-content" @tap.stop>
<view class="modal-header">
<text class="modal-title">选择物料</text>
<text class="modal-close" @tap="closeCatalog"></text>
</view>
<view class="modal-search">
<input class="search-input" v-model="catalogSearch" placeholder="搜索物料名称" />
</view>
<scroll-view scroll-y class="modal-list">
<view v-for="catalog in filteredCatalog" :key="catalog.id"
class="catalog-item"
@tap="selectCatalog(catalog)">
<view class="catalog-name">{{ catalog.name }}</view>
<view class="catalog-info">
<text v-if="catalog.spec">{{ catalog.spec }}</text>
<text v-if="catalog.unit">{{ catalog.unit }}</text>
</view>
</view>
<view v-if="filteredCatalog.length === 0" class="empty">
<text>暂无数据</text>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import consumableTempAPI, { type RequestItem, type CatalogItem } from '@/pages/api/consumable-temp'
//
const formData = ref({
applicantName: '',
applicantPhone: '',
department: '',
reason: '',
neededAt: '',
items: [] as RequestItem[]
})
const loading = ref(true)
const submitting = ref(false)
const requestId = ref('')
const currentItemIndex = ref(-1)
const showCatalogModal = ref(false)
const catalogList = ref<CatalogItem[]>([])
const catalogSearch = ref('')
//
const filteredCatalog = computed(() => {
if (!catalogSearch.value) return catalogList.value
const keyword = catalogSearch.value.toLowerCase()
return catalogList.value.filter(item =>
item.name.toLowerCase().includes(keyword) ||
item.spec?.toLowerCase().includes(keyword)
)
})
//
async function loadDetail() {
if (!requestId.value) return
loading.value = true
try {
const res = await consumableTempAPI.getRequestDetail(requestId.value)
if (res.statusCode === 200 && (res.data as any)?.data) {
const detail = (res.data as any).data
//
formData.value.applicantName = detail.applicantName || ''
formData.value.applicantPhone = detail.applicantPhone || ''
formData.value.department = detail.department || ''
formData.value.reason = detail.reason || ''
formData.value.neededAt = detail.neededAt ? detail.neededAt.split('T')[0] : ''
//
formData.value.items = detail.items?.map((item: any) => ({
name: item.name || '',
spec: item.spec || '',
quantity: item.quantity || 1,
unit: item.unit || '',
estimatedUnitCost: item.estimatedUnitCost || 0,
remark: item.remark || '',
catalogId: item.catalogId
})) || []
} else {
uni.showToast({ title: '加载失败', icon: 'none' })
setTimeout(() => uni.navigateBack(), 1500)
}
} catch (err) {
console.error('加载详情失败:', err)
uni.showToast({ title: '加载失败', icon: 'none' })
setTimeout(() => uni.navigateBack(), 1500)
} finally {
loading.value = false
}
}
//
async function loadCatalog() {
try {
const res = await consumableTempAPI.getCatalogList({
isActive: true,
pageSize: 99
})
if (res.statusCode === 200 && (res.data as any)?.data) {
catalogList.value = (res.data as any).data
}
} catch (err) {
console.error('加载目录失败:', err)
}
}
//
function onDateChange(e: any) {
formData.value.neededAt = e.detail.value
}
//
function addItem() {
formData.value.items.push({
name: '',
spec: '',
quantity: 1,
unit: '',
estimatedUnitCost: 0,
remark: ''
})
}
//
function deleteItem(index: number) {
uni.showModal({
title: '确认删除',
content: '确定要删除这个物料吗?',
success: (res) => {
if (res.confirm) {
formData.value.items.splice(index, 1)
}
}
})
}
//
function selectFromCatalog(index: number) {
currentItemIndex.value = index
showCatalogModal.value = true
}
//
function selectCatalog(catalog: CatalogItem) {
if (currentItemIndex.value >= 0) {
const item = formData.value.items[currentItemIndex.value]
item.name = catalog.name
item.spec = catalog.spec || ''
item.unit = catalog.unit || ''
item.catalogId = catalog.id
}
closeCatalog()
}
//
function closeCatalog() {
showCatalogModal.value = false
catalogSearch.value = ''
currentItemIndex.value = -1
}
//
function validateForm() {
if (!formData.value.applicantPhone) {
uni.showToast({ title: '请输入联系电话', icon: 'none' })
return false
}
if (formData.value.applicantPhone.length < 3) {
uni.showToast({ title: '联系电话格式不正确', icon: 'none' })
return false
}
if (formData.value.items.length === 0) {
uni.showToast({ title: '请至少添加一项物料', icon: 'none' })
return false
}
for (let i = 0; i < formData.value.items.length; i++) {
const item = formData.value.items[i]
if (!item.name) {
uni.showToast({ title: `${i + 1}项物料名称不能为空`, icon: 'none' })
return false
}
if (!item.quantity || item.quantity <= 0) {
uni.showToast({ title: `${i + 1}项物料数量必须大于0`, icon: 'none' })
return false
}
}
return true
}
//
async function submitForm() {
if (!validateForm()) return
if (submitting.value) return
submitting.value = true
try {
const res = await consumableTempAPI.updateRequest(requestId.value, formData.value)
if (res.statusCode === 200) {
uni.showToast({
title: '保存成功',
icon: 'success',
success: () => {
setTimeout(() => {
//
uni.$emit('tempRequest:changed')
uni.navigateBack()
}, 1500)
}
})
} else {
uni.showToast({ title: '保存失败', icon: 'none' })
}
} catch (err) {
console.error('保存失败:', err)
uni.showToast({ title: '保存失败', icon: 'none' })
} finally {
submitting.value = false
}
}
//
function goBack() {
uni.showModal({
title: '提示',
content: '确定要放弃当前修改吗?',
success: (res) => {
if (res.confirm) {
uni.navigateBack()
}
}
})
}
onMounted(() => {
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1] as any
const options = currentPage.options || {}
if (options.id) {
requestId.value = options.id
loadDetail()
loadCatalog()
} else {
uni.showToast({ title: '参数错误', icon: 'none' })
setTimeout(() => uni.navigateBack(), 1500)
}
})
</script>
<style scoped>
.page {
min-height: 100vh;
background: #f5f7fb;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
.content {
flex: 1;
padding: 24rpx 32rpx 140rpx;
box-sizing: border-box;
}
.loading-container {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #999;
}
.section {
margin-bottom: 32rpx;
}
.section-title {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 12rpx 16rpx;
font-size: 32rpx;
font-weight: 600;
color: #111;
}
.btn-add {
padding: 8rpx 20rpx;
margin: 0;
height: 56rpx;
line-height: 40rpx;
background: #007aff;
color: #fff;
border-radius: 8rpx;
font-size: 24rpx;
}
.form-card,
.item-card {
background: #fff;
border-radius: 20rpx;
padding: 24rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
}
.item-card {
margin-bottom: 24rpx;
position: relative;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 16rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.item-number {
font-size: 28rpx;
font-weight: 600;
color: #007aff;
}
.btn-delete {
padding: 8rpx 16rpx;
height: 48rpx;
line-height: 32rpx;
background: #fff;
color: #f44336;
border: 2rpx solid #f44336;
border-radius: 8rpx;
font-size: 24rpx;
}
.form-item {
margin-bottom: 24rpx;
}
.form-item:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 12rpx;
}
.form-label.required::before {
content: '*';
color: #ff4d4f;
margin-right: 4rpx;
}
.form-input,
.form-picker {
width: 100%;
height: 80rpx;
padding: 0 24rpx;
background: #f5f7fb;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
}
.form-textarea {
width: 100%;
min-height: 120rpx;
padding: 16rpx 24rpx;
background: #f5f7fb;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
}
.form-picker {
display: flex;
align-items: center;
}
.form-row {
display: flex;
gap: 12rpx;
align-items: center;
}
.form-row .flex-1 {
flex: 1;
}
.btn-catalog {
padding: 0 20rpx;
height: 80rpx;
line-height: 80rpx;
background: #e3f2fd;
color: #1976d2;
border-radius: 12rpx;
font-size: 24rpx;
white-space: nowrap;
}
.form-row-group {
display: flex;
gap: 16rpx;
}
.form-row-group .form-item {
flex: 1;
}
.empty-items {
padding: 80rpx 0;
text-align: center;
font-size: 28rpx;
color: #999;
background: #fff;
border-radius: 20rpx;
}
.footer-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 24rpx 32rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
background: #fff;
box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.06);
display: flex;
gap: 16rpx;
}
.btn-secondary,
.btn-primary {
flex: 1;
height: 88rpx;
line-height: 88rpx;
border-radius: 16rpx;
font-size: 32rpx;
font-weight: 600;
}
.btn-secondary {
background: #f5f5f5;
color: #666;
}
.btn-primary {
background: #007aff;
color: #fff;
}
.btn-primary[disabled] {
opacity: 0.6;
}
/* 弹窗样式 */
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: flex-end;
z-index: 1000;
}
.modal-content {
width: 100%;
max-height: 80vh;
background: #fff;
border-radius: 32rpx 32rpx 0 0;
display: flex;
flex-direction: column;
}
.modal-header {
padding: 32rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1rpx solid #f0f0f0;
}
.modal-title {
font-size: 32rpx;
font-weight: 600;
color: #111;
}
.modal-close {
font-size: 40rpx;
color: #999;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.modal-search {
padding: 24rpx 32rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.search-input {
width: 100%;
height: 72rpx;
line-height: 72rpx;
padding: 0 24rpx;
background: #f5f7fb;
border-radius: 12rpx;
font-size: 28rpx;
}
.modal-list {
flex: 1;
padding: 12rpx 0;
}
.catalog-item {
padding: 24rpx 32rpx;
border-bottom: 1rpx solid #f5f5f5;
}
.catalog-name {
font-size: 30rpx;
color: #333;
margin-bottom: 8rpx;
}
.catalog-info {
font-size: 24rpx;
color: #999;
display: flex;
gap: 16rpx;
}
.empty {
padding: 80rpx 0;
text-align: center;
font-size: 28rpx;
color: #999;
}
</style>

View File

@ -241,7 +241,7 @@ onUnload(() => {
}
.btn-outline {
padding: 12rpx 24rpx;
padding: 0rpx 24rpx;
background: #fff;
color: #4b7aff;
border: 2rpx solid #4b7aff;

File diff suppressed because it is too large Load Diff

View File

@ -237,7 +237,7 @@ function previewImage(imageBase64: string) {
.meta-value {
color: #333;
font-size: 28rpx;
font-size: 24rpx;
}
.detail {

View File

@ -0,0 +1,547 @@
<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">借用人</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>

View File

@ -1,133 +1,394 @@
<template>
<view class="page">
<view class="borrow-list">
<view v-for="item in borrowList.filter(item => !item.isReturn)" :key="item.borrowId || item.id"
class="borrow-item card">
<view class="item-header">
<text class="asset-name">{{ item.assetName }}</text>
<!-- <button class="btn btn-primary outline" @tap="button_returnAsset(item)">归还</button> -->
</view>
<view class="detail">借用日期{{ item.borrowDate }}</view>
<view class="detail">预计归还日期{{ item.returnDate }}</view>
<view class="detail">借用人{{ item.borrowerName }}{{ item.borrowerMobile }}</view>
<view class="detail">出借人{{ item.lenderName }}{{ item.lenderMobile }}</view>
<view class="detail">备注{{ item.note || '无' }}</view>
</view>
</view>
<view class="empty" v-if="borrowList.length === 0">暂无更多数据</view>
<view class="page">
<!-- 顶部统计卡片 -->
<view class="stats-card card">
<view class="stat-item">
<text class="stat-value">{{ totalCount }}</text>
<text class="stat-label">借用总数</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-value highlight">{{ borrowingCount }}</text>
<text class="stat-label">借用中</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-value success">{{ returnedCount }}</text>
<text class="stat-label">已归还</text>
</view>
</view>
<!-- 筛选栏 -->
<view class="filter-bar card">
<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>
<!-- 列表 -->
<scroll-view
class="list-container"
scroll-y
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
>
<view v-if="loading && myBorrowList.length === 0" class="loading-wrap">
<text class="loading-text">加载中...</text>
</view>
<view v-else-if="myBorrowList.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' : 'borrowing'">
{{ item.isReturn === 1 ? '已归还' : '借用中' }}
</view>
</view>
<!-- 信息行 -->
<view class="item-info">
<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 class="info-row">
<text class="info-label">出借人</text>
<text class="info-value">{{ item.lenderName || '-' }}</text>
</view>
<view v-if="item.lenderMobile" class="info-row">
<text class="info-label">联系方式</text>
<text class="info-value">{{ item.lenderMobile }}</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>
</view>
</scroll-view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ref, computed } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
// @ts-ignore JS
import { borrowInfo, returnAsset } from '@/pages/api/fixedAssets.js'
// @ts-ignore
import { fetchBorrowList } from '@/pages/api/fixedAssets.js'
const borrowList = ref<any[]>([])
const userInfo = uni.getStorageSync('staff') || {}
onLoad(() => { loadBorrowList() })
onShow(() => { loadBorrowList() })
function loadBorrowList() {
if (!userInfo?.id) {
borrowList.value = []
return
}
borrowInfo(userInfo.id)
.then((res: any) => { borrowList.value = res?.data || [] })
.catch((err: any) => {
console.error(err)
uni.showToast({ title: '加载失败', icon: 'none' })
})
interface BorrowRecord {
borrowId: number
assetId: number
assetName: string
borrowDate: string
returnDate: string
actualReturnDate: string | null
borrowerId: string
borrowerName: string | null
borrowerMobile: string | null
lenderId: string
lenderName: string | null
lenderMobile: string | null
registrantDate: string
isReturn: number // 0- 1-
note: string
}
function button_returnAsset(item: any) {
uni.showModal({
content: '确认归还?',
success: async (r) => {
if (r.confirm) {
try {
await returnAsset(item.assetId)
uni.showToast({ title: '归还成功', icon: 'success' })
loadBorrowList()
} catch (e) {
uni.showToast({ title: '归还失败', icon: 'none' })
}
}
}
const myBorrowList = ref<BorrowRecord[]>([])
const loading = ref(false)
const refreshing = ref(false)
const filterStatus = ref<number | null>(null)
const userMobile = uni.getStorageSync('userName') || ''
//
const totalCount = computed(() => myBorrowList.value.length)
const borrowingCount = computed(() => myBorrowList.value.filter(item => item.isReturn === 0).length)
const returnedCount = computed(() => myBorrowList.value.filter(item => item.isReturn === 1).length)
//
const filteredList = computed(() => {
if (filterStatus.value === null) {
return myBorrowList.value
}
return myBorrowList.value.filter(item => item.isReturn === filterStatus.value)
})
onLoad(() => {
loadData()
})
onShow(() => {
loadData()
})
//
async function loadData() {
loading.value = true
try {
const result = await fetchBorrowList()
if (result.code === 0 && Array.isArray(result.data)) {
//
myBorrowList.value = result.data.filter((item: BorrowRecord) =>
item.borrowerMobile === userMobile
)
} 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
}
</script>
<style scoped>
.page {
min-height: 100vh;
background: #f5f7fb;
padding: 24rpx;
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);
background: #fff;
border-radius: 20rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
}
.borrow-list {
display: flex;
flex-direction: column;
gap: 20rpx;
/* 统计卡片 */
.stats-card {
margin: 24rpx 24rpx 16rpx;
padding: 24rpx;
display: flex;
align-items: center;
justify-content: space-around;
}
.borrow-item {
padding: 20rpx;
.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: #ff9800;
}
.stat-value.success {
color: #51cf66;
}
.stat-label {
font-size: 22rpx;
color: #999;
}
.stat-divider {
width: 2rpx;
height: 50rpx;
background: #e8e8e8;
}
/* 筛选栏 */
.filter-bar {
margin: 0 24rpx 16rpx;
padding: 12rpx;
}
.filter-tabs {
display: flex;
gap: 8rpx;
}
.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;
}
/* 列表容器 */
.list-container {
height: calc(100vh - 260rpx);
}
.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 24rpx;
}
.list-item {
margin-bottom: 20rpx;
padding: 24rpx;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 16rpx;
padding-bottom: 16rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.asset-name-wrap {
flex: 1;
margin-right: 16rpx;
}
.asset-name {
font-size: 30rpx;
color: #222;
font-weight: 600;
flex: 1;
font-size: 30rpx;
font-weight: 600;
color: #222;
line-height: 1.4;
}
.detail {
font-size: 24rpx;
color: #666;
margin-top: 6rpx;
.status-badge {
padding: 6rpx 16rpx;
border-radius: 20rpx;
font-size: 22rpx;
font-weight: 500;
white-space: nowrap;
}
.btn {
padding: 0 20rpx;
background: #e9efff;
color: #3a5ddd;
border-radius: 10rpx;
font-size: 24rpx;
.status-badge.borrowing {
background: #fff3e0;
color: #ff9800;
}
.btn-primary {
background: #3a5ddd;
color: #fff;
.status-badge.returned {
background: #e8f5e9;
color: #4caf50;
}
.btn-primary.outline {
background: #fff;
color: #3a5ddd;
border: 2rpx solid #3a5ddd;
/* 信息行 */
.item-info {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.empty {
text-align: center;
color: #9a9aa0;
margin-top: 40rpx;
.info-row {
display: flex;
align-items: flex-start;
font-size: 24rpx;
}
.info-label {
color: #999;
min-width: 140rpx;
flex-shrink: 0;
}
.info-value {
color: #333;
flex: 1;
word-break: break-all;
}
.success-text {
color: #51cf66;
font-weight: 500;
}
</style>

View File

@ -83,6 +83,8 @@ import config, { AssetRole } from '@/config'
import * as staffRole from '@/constant/staffRole.js'
// @ts-ignore JS
import util from '@/utils/util.js'
import { onShow } from '@dcloudio/uni-app'
import { fetchPendingCount } from '@/utils/messageManager'
//
const assetOpen = ref(true)
@ -90,15 +92,15 @@ const assetOpen = ref(true)
const assetItems = computed(() => {
const role = assetsRole.value || 0
return [
{ key: 'fixed', text: '固定资产', icon: '/static/icons/fixed-assets.png' },
{ key: 'mine', text: '我的借用', icon: '/static/icons/my-loans.png' },
{ key: 'scan', text: '扫码查看信息', icon: '/static/icons/scan-to-view-info.png' },
{ key: 'inventory', text: '资产盘点', icon: '/static/icons/asset-inventory.png' },
{ key: 'plan', text: '易耗品领用计划', icon: '/static/icons/consumables-plan.png' },
{ key: 'consumable', text: '易耗品盘点', icon: '/static/icons/consumables-inventory.png' },
{ key: 'lent', text: '出借资产管理', icon: '/static/icons/lent-assets-management.png', show: !!(role & AssetRole.TEAM_LEAD) },
{ key: 'request', text: '临时易耗品申请', icon: '/static/icons/consumables-temp-request.png', show: !!(role & AssetRole.TEAM_LEAD) },
{ key: 'approve', text: '临时易耗品申请审批', icon: '/static/icons/consumables-temp-approve.png', show: !!(role & AssetRole.CONSUMABLES_MANAGER) },
{ key: 'fixed', text: '固定资产', icon: '/static/icons/fixed-assets.png', url: '/pages/fixed-assets/index' },
{ key: 'mine', text: '我的借用', icon: '/static/icons/my-loans.png', url: '/pages/fixed-assets/my-borrows' },
{ key: 'scan', text: '扫码查看信息', icon: '/static/icons/scan-to-view-info.png', url: '/pages/fixed-assets/scan' },
{ key: 'inventory', text: '资产盘点', icon: '/static/icons/asset-inventory.png', url: '/pages/fixed-assets/inventory-plan' },
{ key: 'plan', text: '易耗品领用计划', icon: '/static/icons/consumables-plan.png', url: '/pages/consumables/plan' },
{ key: 'consumable', text: '易耗品盘点', icon: '/static/icons/consumables-inventory.png', url: '/pages/consumables/inventory-plan' },
{ key: 'lent', text: '出借资产管理', icon: '/static/icons/lent-assets-management.png', url: '/pages/fixed-assets/lent-management', show: !!(role & AssetRole.DEVICE_MANAGER) },
{ key: 'request', text: '临时易耗品申请', icon: '/static/icons/consumables-temp-request.png', url: '/pages/consumables/temp-request', show: !!(role & AssetRole.TEAM_LEAD) },
{ key: 'approve', text: '临时易耗品申请审批', icon: '/static/icons/consumables-temp-approve.png', url: '/pages/consumables/temp-approve', show: !!(role & AssetRole.CONSUMABLES_MANAGER) },
].filter(item => item.show !== false)
})
@ -146,6 +148,10 @@ onMounted(() => {
syncLoginState()
})
onShow(() => {
syncLoginState()
})
//
const isLoggedIn = ref(false)
const staff = ref<any | null>(null)
@ -158,7 +164,7 @@ const isRider = ref(false)
function syncLoginState() {
staff.value = uni.getStorageSync('staff') || null
assetsRole.value = uni.getStorageSync('assetsRole') || null //
isLoggedIn.value = !!staff.value
isLoggedIn.value = (!!staff.value) || typeof assetsRole.value === 'number'
//
// @ts-ignore
isWeixin.value = process?.env?.VUE_APP_PLATFORM === 'mp-weixin'
@ -182,6 +188,8 @@ function goLogin() {
function logout() {
uni.setStorageSync('staff', null)
uni.setStorageSync('token', null)
uni.setStorageSync('assetsRole', null)
fetchPendingCount()
isLoggedIn.value = false
staff.value = null
isRider.value = false
@ -203,44 +211,12 @@ function toggleAsset() {
function handleAsset(key: string) {
const item = assetItems.value.find((i: any) => i.key === key)
if (!item) return
if (key === 'fixed') {
uni.navigateTo({ url: '/pages/fixed-assets/index' })
return
}
if (key === 'mine') {
uni.navigateTo({ url: '/pages/fixed-assets/my-borrows' })
return
}
if (key === 'inventory') {
uni.navigateTo({ url: '/pages/fixed-assets/inventory-plan' })
return
}
if (key === 'plan') {
uni.navigateTo({ url: '/pages/consumables/plan' })
return
}
if (key === 'consumable') {
uni.navigateTo({ url: '/pages/consumables/inventory-plan' })
return
}
if (key === 'scan') {
uni.navigateTo({ url: '/pages/fixed-assets/scan' })
return
}
if (key === 'lent') {
if (item.url) {
uni.navigateTo({ url: item.url })
} else {
//
uni.showToast({ title: `打开:${item.text}`, icon: 'none' })
return
}
if (key === 'request') {
uni.navigateTo({ url: '/pages/consumables/temp-request' })
return
}
if (key === 'approve') {
uni.navigateTo({ url: '/pages/consumables/temp-approve' })
return
}
//
uni.showToast({ title: `打开:${item.text}`, icon: 'none' })
}
function previewCharge() {
uni.previewImage({
@ -251,7 +227,6 @@ function previewCharge() {
<style scoped>
.page {
min-height: 100vh;
background: #f5f7fb;
}

View File

@ -8,11 +8,13 @@
<view class="card form">
<view class="field">
<text class="label">手机号</text>
<input class="input" v-model="loginRequest.username" placeholder="请输入用户名或电话" :focus="usernameFocus" />
<input class="input" v-model="loginRequest.username"
placeholder="请输入用户名或电话" :focus="usernameFocus" />
</view>
<view class="field">
<text class="label">密码</text>
<input class="input" v-model="loginRequest.password" placeholder="请输入密码" password />
<input class="input" v-model="loginRequest.password" placeholder="请输入密码"
password />
</view>
<view class="actions">
<button class="btn primary" @tap="onLogin">登录</button>
@ -26,6 +28,7 @@
<script setup lang="ts">
import { reactive, ref, onMounted } from 'vue'
import { login } from '../api/user'
import { fetchPendingCount } from '@/utils/messageManager'
const usernameFocus = ref(true)
const loginRequest = reactive({
@ -82,6 +85,7 @@ async function onLogin() {
uni.setStorageSync('assetsRole', staff.assetsRole) //
// tabBar
setTimeout(() => {
fetchPendingCount() //
uni.switchTab({ url: '/pages/index/index' })
}, 300)

View File

@ -9,7 +9,7 @@
</view>
<view class="info" v-else>
<text class="name">{{ staff?.user_name || staff?.name || '已登录' }}</text>
<text class="sub">类型{{ userTypeText }}</text>
<!-- <text class="sub">类型{{ userTypeText }}</text> -->
<!-- <text class="sub">角色{{ staff?.role_name || '-' }}</text> -->
<text class="sub">角色{{ [
assetRole & AssetRole.CONSUMABLES_MANAGER ? '易耗品发放负责人' : undefined,
@ -175,6 +175,7 @@ import queryService from '@/service/queryService.js'
import getService from '@/service/getService.js'
// @ts-ignore JS
import config, { AssetRole } from '@/config'
import { fetchPendingCount } from '@/utils/messageManager'
type Task = Record<string, any>
@ -220,6 +221,8 @@ function goLogin() {
function logout() {
uni.setStorageSync('staff', null)
uni.setStorageSync('token', null)
uni.setStorageSync('assetsRole', null)
fetchPendingCount()
staff.value = null
isLoggedIn.value = false
taskList.value = []
@ -371,6 +374,7 @@ onShow(async () => {
}
.info {
flex: 1;
display: flex;
flex-direction: column;
}
@ -378,6 +382,7 @@ onShow(async () => {
.name {
font-size: 32rpx;
color: #333;
margin-bottom: 12rpx;
}
.sub {
@ -390,7 +395,7 @@ onShow(async () => {
}
.btn {
padding: 12rpx 20rpx;
padding: 0rpx 20rpx;
background: #3a5ddd;
color: #fff;
border-radius: 10rpx;

View File

@ -3,6 +3,7 @@
* TabBar
*/
import { AssetRole } from '@/config'
import consumableTempAPI from '@/pages/api/consumable-temp'
// 消息数量缓存
@ -14,11 +15,11 @@ let pollingTimer: number | null = null
*/
export async function fetchPendingCount(): Promise<number> {
try {
const res = await consumableTempAPI.getRequestList({
const res = (uni.getStorageSync('assetsRole') & AssetRole.CONSUMABLES_MANAGER) ? await consumableTempAPI.getRequestList({
page: 1,
pageSize: 100, // 获取足够多的数据来计算总数
status: 'PENDING'
})
}) : { statusCode: 200, data: { data: [] } }
if (res.statusCode === 200 && (res.data as any)?.data) {
const data = (res.data as any).data