优化ui,修复固定资产借用问题
This commit is contained in:
parent
b0e010f80e
commit
38a551da22
@ -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>
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//————————————————————————————————————————————————业务接口函数(易耗品相关)——————————————————————————————————————————
|
||||
//易耗品领用
|
||||
|
||||
|
||||
@ -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] {
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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>
|
||||
|
||||
672
src/pages/consumables/temp-request-edit.vue
Normal file
672
src/pages/consumables/temp-request-edit.vue
Normal 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>
|
||||
@ -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
@ -237,7 +237,7 @@ function previewImage(imageBase64: string) {
|
||||
|
||||
.meta-value {
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.detail {
|
||||
|
||||
547
src/pages/fixed-assets/lent-management.vue
Normal file
547
src/pages/fixed-assets/lent-management.vue
Normal 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>
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user