0930-noon

This commit is contained in:
feie9454 2025-09-30 17:02:05 +08:00
parent 998310cf2e
commit 7734c0cd89
8 changed files with 846 additions and 158 deletions

View File

@ -1,4 +1,4 @@
const BASE_URL = 'http://10.3.1.212:8089';//定义后端基础接口地址 const BASE_URL = 'http://110.42.33.196:8089';//定义后端基础接口地址
import config from '../../config.js'; // 引入配置文件 import config from '../../config.js'; // 引入配置文件

View File

@ -1,5 +1,5 @@
const BASE_URL = 'http://10.3.1.212:8089'; const BASE_URL = 'http://110.42.33.196:8089';
const config = require('config.js'); const config = require('config.js');
const formatTime = date => { const formatTime = date => {
date = new Date(date); date = new Date(date);
const hour = date.getHours() const hour = date.getHours()
@ -25,9 +25,9 @@ const formatDate = date => {
const formatDateTime = date => { const formatDateTime = date => {
return `${formatDate(date)} ${formatTime(date)}` return `${formatDate(date)} ${formatTime(date)}`
} }
let staff = uni.getStorageSync('staff'); let staff = uni.getStorageSync('staff');
let staffisnull = (staff == null || staff == undefined || staff == ''); let staffisnull = (staff == null || staff == undefined || staff == '');
export function getHeaders() { export function getHeaders() {
const staff = uni.getStorageSync('staff') || null const staff = uni.getStorageSync('staff') || null
const staffisnull = !staff const staffisnull = !staff
@ -40,19 +40,19 @@ export function getHeaders() {
'datetime': formatDateTime(new Date()), 'datetime': formatDateTime(new Date()),
'createby': staffisnull ? null : staff.id 'createby': staffisnull ? null : staff.id
} }
} }
//分页请求固定资产盘点信息 //分页请求固定资产盘点信息
export function fetchAssetInventory(pageNum, pageSize) { export function fetchAssetInventory(pageNum, pageSize) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
uni.request({ uni.request({
url: `${BASE_URL}/api/asset/inventory/page?pageNum=${pageNum}&pageSize=${pageSize}`, url: `${BASE_URL}/api/asset/inventory/page?pageNum=${pageNum}&pageSize=${pageSize}`,
method: 'POST', method: 'POST',
data: { data: {
query:{}, query:{},
}, },
header:getHeaders(), header:getHeaders(),
success: (res) => { success: (res) => {
if (res.statusCode === 200) { if (res.statusCode === 200) {
@ -66,5 +66,5 @@ export function fetchAssetInventory(pageNum, pageSize) {
} }
}); });
}); });
} }

View File

@ -1,25 +1,95 @@
<template> <template>
<scroll-view scroll-y class="page"> <scroll-view scroll-y class="page">
<view v-for="item in detailPlan" :key="item.Id || item.id" class="item-card"> <!-- 搜索栏 -->
<view class="row"><text class="label">物资名称</text><text class="value">{{ item.goodsName }}</text></view> <view class="search-sticky">
<view class="row"><text class="label">发放数量</text><text class="value highlight">{{ item.actualQuantity <view class="search-bar">
}}</text></view> <text class="search-label">领用小组</text>
<view class="row"><text class="label">领用小组</text><text class="value">{{ item.groupName }}</text></view> <input
<view class="row"><text class="label">备注</text><text class="value">{{ item.notes || '无' }}</text></view> class="search-input"
<view class="row"><text class="label">发放计划</text><text class="value">{{ item.planTitle }}</text></view> v-model.trim="keyword"
type="text"
<view class="action"> confirm-type="search"
<button v-if="item.isDistributed == 0" size="mini" class="primary" @tap="reciept(item)">领用</button> placeholder="输入小组名称筛选"
<view v-else class="done">已领用</view> @confirm="onSearchConfirm"
/>
<button v-if="keyword" size="mini" class="clear-btn" @tap="clearKeyword">清除</button>
</view>
<view class="search-meta">
<text class="meta-chip"> {{ detailPlan.length }} </text>
<text v-if="keyword" class="meta-dot">·</text>
<text v-if="keyword" class="meta-chip accent">筛选出 {{ filteredPlan.length }} </text>
</view> </view>
</view> </view>
<view v-if="detailPlan.length === 0 && !loading" class="empty">暂无更多数据</view> <!-- 加载骨架屏 -->
<view v-if="loading" class="skeleton-list">
<view class="card skeleton" v-for="n in 3" :key="n">
<view class="skeleton-line w-70"></view>
<view class="skeleton-line w-40"></view>
<view class="skeleton-line w-90"></view>
</view>
</view>
<!-- 列表卡片 -->
<view v-else>
<view v-for="item in filteredPlan" :key="item.Id || item.id" class="card">
<!-- 卡片头部名称 + 状态 -->
<view class="card-header">
<text class="goods-name">{{ item.goodsName }}</text>
<view class="status-badge" :class="item.isDistributed == 0 ? 'pending' : 'done'">
<text>{{ item.isDistributed == 0 ? '待领用' : '已领用' }}</text>
</view>
</view>
<!-- 关键指标数量组别 -->
<view class="key-meta">
<view class="meta-block">
<text class="meta-label">发放数量</text>
<text class="meta-value number">{{ item.actualQuantity }}</text>
</view>
<view class="meta-block">
<text class="meta-label">领用小组</text>
<text class="meta-value tag">{{ item.groupName }}</text>
</view>
</view>
<!-- 详细信息 -->
<view class="detail">
<view class="detail-row">
<text class="label">备注</text>
<text class="value ellipsis-2">{{ item.notes || '无' }}</text>
</view>
<view class="detail-row">
<text class="label">发放计划</text>
<text class="value">{{ item.planTitle }}</text>
</view>
<view class="detail-row">
<text class="label">领用时间</text>
<text class="value">{{ item.getTime ?? '-' }}</text>
</view>
</view>
<!-- 操作区 -->
<view class="action">
<button
v-if="item.isDistributed == 0"
size="mini"
class="primary"
@tap="reciept(item)"
>领用</button>
<view v-else class="done-text">已领用</view>
</view>
</view>
<view v-if="!loading && filteredPlan.length === 0 && detailPlan.length > 0" class="empty">未找到匹配的小组</view>
</view>
<view v-if="!loading && detailPlan.length === 0" class="empty">暂无数据</view>
</scroll-view> </scroll-view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
// @ts-ignore JS // @ts-ignore JS
import { getConsumptionDetail, consumptonConfirm } from '@/pages/api/fixedAssets.js' import { getConsumptionDetail, consumptonConfirm } from '@/pages/api/fixedAssets.js'
@ -29,6 +99,13 @@ type Item = Record<string, any>
const detailPlan = ref<Item[]>([]) const detailPlan = ref<Item[]>([])
const loading = ref(false) const loading = ref(false)
const planId = ref<string | number>('') const planId = ref<string | number>('')
const keyword = ref('')
const filteredPlan = computed<Item[]>(() => {
const kw = keyword.value.trim().toLowerCase()
if (!kw) return detailPlan.value
return detailPlan.value.filter((i: any) => String(i?.groupName ?? '').toLowerCase().includes(kw))
})
async function loadDetail(id: string | number) { async function loadDetail(id: string | number) {
loading.value = true loading.value = true
@ -69,6 +146,14 @@ async function reciept(item: any) {
}) })
} }
function clearKeyword() {
keyword.value = ''
}
function onSearchConfirm() {
//
}
onLoad((query) => { onLoad((query) => {
const id = query?.id const id = query?.id
if (!id) { if (!id) {
@ -88,18 +173,155 @@ onLoad((query) => {
box-sizing: border-box; box-sizing: border-box;
} }
.item-card { .search-sticky {
position: sticky;
top: 0;
z-index: 10;
background: #f5f7fb;
padding-bottom: 16rpx;
}
.search-bar {
display: flex;
align-items: center;
gap: 16rpx;
background: #fff; background: #fff;
border-radius: 16rpx; border-radius: 999rpx;
padding: 20rpx; padding: 12rpx 16rpx;
margin-bottom: 16rpx;
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.06); box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.06);
} }
.row { .search-label {
color: #666;
font-size: 26rpx;
margin-left: 8rpx;
}
.search-input {
flex: 1;
height: 64rpx;
line-height: 64rpx;
padding: 0 8rpx;
border: none;
outline: none;
font-size: 28rpx;
background: transparent;
}
.clear-btn {
margin: 0;
border-radius: 24rpx;
background: #eff3ff;
color: #4b7aff;
}
.search-meta {
margin-top: 10rpx;
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 8rpx; gap: 8rpx;
padding: 0 8rpx;
}
.meta-chip {
color: #8a8f99;
font-size: 24rpx;
}
.meta-chip.accent {
color: #4b7aff;
}
.meta-dot {
color: #c0c4cc;
}
.card {
background: #fff;
border-radius: 16rpx;
padding: 20rpx;
margin: 16rpx 0;
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.06);
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12rpx;
}
.goods-name {
font-size: 32rpx;
font-weight: 600;
color: #222;
}
.status-badge {
padding: 4rpx 12rpx;
border-radius: 999rpx;
font-size: 22rpx;
}
.status-badge.pending {
background: #fff4e5;
color: #ff9800;
}
.status-badge.done {
background: #e8f5e9;
color: #2e7d32;
}
.key-meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16rpx;
margin: 8rpx 0 12rpx;
}
.meta-block {
background: #f7f9ff;
border-radius: 12rpx;
padding: 16rpx;
}
.meta-label {
display: block;
color: #8a8f99;
font-size: 22rpx;
margin-bottom: 6rpx;
}
.meta-value {
color: #333;
font-size: 28rpx;
}
.meta-value.number {
color: #e53935;
font-weight: 700;
font-size: 36rpx;
}
.meta-value.tag {
display: inline-flex;
align-items: center;
padding: 2rpx 12rpx;
background: #eff3ff;
color: #4b7aff;
border-radius: 999rpx;
font-size: 26rpx;
}
.detail {
margin-top: 8rpx;
}
.detail-row {
display: flex;
align-items: flex-start;
margin: 8rpx 0;
} }
.label { .label {
@ -111,9 +333,12 @@ onLoad((query) => {
color: #333; color: #333;
} }
.highlight { .ellipsis-2 {
color: #e53935; display: -webkit-box;
font-weight: 600; line-clamp: 2;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
} }
.action { .action {
@ -123,7 +348,7 @@ onLoad((query) => {
align-items: center; align-items: center;
} }
.done { .done-text {
color: #2e7d32; color: #2e7d32;
font-size: 24rpx; font-size: 24rpx;
} }
@ -141,4 +366,40 @@ button.primary {
padding: 0rpx 24rpx; padding: 0rpx 24rpx;
margin: 0; margin: 0;
} }
/* 骨架屏样式 */
.skeleton-list .card {
overflow: hidden;
}
.skeleton {
position: relative;
}
.skeleton::after {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.04) 50%, rgba(0,0,0,0) 100%);
animation: shimmer 1.2s infinite;
}
.skeleton-line {
height: 28rpx;
background: #f0f2f5;
border-radius: 8rpx;
margin: 14rpx 0;
}
.skeleton-line.w-70 { width: 70%; }
.skeleton-line.w-40 { width: 40%; }
.skeleton-line.w-90 { width: 90%; }
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
</style> </style>

View File

@ -1,24 +1,53 @@
<template> <template>
<scroll-view scroll-y class="page"> <scroll-view scroll-y class="page">
<view v-if="inventoryPlanList.length === 0 && !loading" class="empty">暂无更多数据</view> <!-- 加载骨架屏 -->
<view v-if="loading" class="skeleton-list">
<view class="inventory-list"> <view class="card skeleton" v-for="n in 3" :key="n">
<view class="inventory-item" v-for="item in inventoryPlanList" :key="item.inventoryId || item.id" @tap="gotoPlanDetail(item)"> <view class="skeleton-line w-70"></view>
<view class="header"> <view class="skeleton-line w-40"></view>
<text class="title">📋 盘点计划</text> <view class="skeleton-line w-90"></view>
<text class="status-tag" :class="statusClass(item.status)">{{ item.status || '未知' }}</text>
</view>
<view class="content">
<view class="row">开始日期<text class="value">{{ item.checkStartDate || '-' }}</text></view>
<view class="row">结束日期<text class="value">{{ item.checkEndDate || '-' }}</text></view>
<view class="row">创建人<text class="value">{{ item.createByName || '-' }}</text></view>
</view>
</view> </view>
</view> </view>
<view class="load-status"> <!-- 列表内容 -->
<text v-if="loading">加载中...</text> <view v-else>
<text v-else-if="noMore">没有更多数据了</text> <view v-if="inventoryPlanList.length === 0" class="empty">暂无数据</view>
<view class="inventory-list">
<view class="card inventory-item" v-for="item in inventoryPlanList" :key="item.inventoryId || item.id" @tap="gotoPlanDetail(item)">
<!-- 卡片头部标题 + 状态 -->
<view class="card-header">
<text class="title ellipsis-1">📋 盘点计划</text>
<text class="status-badge" :class="statusClass(item.status)">{{ item.status || '未知' }}</text>
</view>
<!-- 关键指标开始/结束日期 -->
<view class="key-meta">
<view class="meta-block">
<text class="meta-label">开始日期</text>
<text class="meta-value">{{ item.checkStartDate || '-' }}</text>
</view>
<view class="meta-block">
<text class="meta-label">结束日期</text>
<text class="meta-value">{{ item.checkEndDate || '-' }}</text>
</view>
</view>
<!-- 详情信息 -->
<view class="detail">
<view class="detail-row">
<text class="label">创建人</text>
<text class="value ellipsis-1">{{ item.createByName || '-' }}</text>
</view>
<view class="detail-row">
<text class="label">计划ID</text>
<text class="value">{{ item.id || item.inventoryId }}</text>
</view>
</view>
</view>
</view>
<view class="load-status" v-if="noMore && inventoryPlanList.length > 0">没有更多数据了</view>
</view> </view>
</scroll-view> </scroll-view>
</template> </template>
@ -88,14 +117,37 @@ onReachBottom(() => {
.page { min-height: 100vh; background: #f5f7fb; padding: 24rpx; box-sizing: border-box; } .page { min-height: 100vh; background: #f5f7fb; padding: 24rpx; box-sizing: border-box; }
.empty { color: #9a9aa0; text-align: center; padding: 24rpx 0; } .empty { color: #9a9aa0; text-align: center; padding: 24rpx 0; }
.inventory-list { display: flex; flex-direction: column; gap: 16rpx; } .inventory-list { display: flex; flex-direction: column; gap: 16rpx; }
.inventory-item { background: #fff; border-radius: 16rpx; padding: 20rpx; box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.06); }
.header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12rpx; } .card { background: #fff; border-radius: 16rpx; padding: 20rpx; box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.06); }
.title { font-size: 30rpx; color: #222; font-weight: 600; } .card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12rpx; }
.status-tag { font-size: 24rpx; padding: 4rpx 10rpx; border-radius: 999rpx; } .title { font-size: 32rpx; color: #222; font-weight: 600; }
.status-tag.running { color: #1e88e5; background: #e3f2fd; }
.status-tag.ended { color: #2e7d32; background: #e8f5e9; } .status-badge { font-size: 24rpx; padding: 4rpx 12rpx; border-radius: 999rpx; }
.status-tag.unknown { color: #757575; background: #eeeeee; } .status-badge.running { color: #1e88e5; background: #e3f2fd; }
.content .row { margin-top: 6rpx; color: #444; } .status-badge.ended { color: #2e7d32; background: #e8f5e9; }
.status-badge.unknown { color: #757575; background: #eeeeee; }
.key-meta { display: grid; grid-template-columns: 1fr 1fr; gap: 16rpx; margin: 8rpx 0 12rpx; }
.meta-block { background: #f7f9ff; border-radius: 12rpx; padding: 16rpx; }
.meta-label { display: block; color: #8a8f99; font-size: 22rpx; margin-bottom: 6rpx; }
.meta-value { color: #333; font-size: 28rpx; }
.detail { margin-top: 8rpx; }
.detail-row { display: flex; align-items: flex-start; margin: 8rpx 0; }
.label { color: #888; min-width: 140rpx; }
.value { color: #333; } .value { color: #333; }
.ellipsis-1 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.load-status { text-align: center; color: #9a9aa0; padding: 20rpx 0; } .load-status { text-align: center; color: #9a9aa0; padding: 20rpx 0; }
/* 骨架屏样式 */
.skeleton-list .card { overflow: hidden; }
.skeleton { position: relative; }
.skeleton::after { content: ''; position: absolute; left: 0; top: 0; width: 100%; height: 100%; background: linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.04) 50%, rgba(0,0,0,0) 100%); animation: shimmer 1.2s infinite; }
.skeleton-line { height: 28rpx; background: #f0f2f5; border-radius: 8rpx; margin: 14rpx 0; }
.skeleton-line.w-70 { width: 70%; }
.skeleton-line.w-40 { width: 40%; }
.skeleton-line.w-90 { width: 90%; }
@keyframes shimmer { 0% { transform: translateX(-100%);} 100% { transform: translateX(100%);} }
</style> </style>

View File

@ -1,51 +1,58 @@
<template> <template>
<scroll-view scroll-y class="page"> <scroll-view scroll-y class="page">
<view v-if="planList.length === 0 && !loading" class="empty">暂无更多数据</view> <!-- 加载骨架屏 -->
<view v-if="loading" class="skeleton-list">
<view v-for="plan in planList" :key="plan.id" class="plan-card"> <view class="card skeleton" v-for="n in 3" :key="n">
<view class="plan-header"> <view class="skeleton-line w-70"></view>
<text class="plan-title">{{ plan.planTitle }}</text> <view class="skeleton-line w-40"></view>
<text class="plan-action" @tap="goToDistribution(plan.id)">发放物资</text> <view class="skeleton-line w-90"></view>
</view>
<view class="plan-info">
<view class="info-item">
<text class="label">医院</text>
<text class="value">{{ plan.hospName }}</text>
</view>
<view class="info-item">
<text class="label">周期类型</text>
<text class="value">{{ plan.periodType }}</text>
</view>
<view class="info-item">
<text class="label">发放数量</text>
<text class="value">{{ plan.quantityPerPeriod }}</text>
</view>
</view>
<view class="plan-time">
<view class="info-item">
<text class="label">上次分配</text>
<text class="value">{{ plan.lastDistributionDate || '-' }}</text>
</view>
<view class="info-item">
<text class="label">下次分配</text>
<text class="value">{{ plan.nextDistributionDate || '-' }}</text>
</view>
</view>
<view class="plan-footer">
<text class="created">创建人{{ plan.createdByName }}{{ plan.createdByMobile }}</text>
<text class="time">创建时间{{ plan.createdTime }}</text>
</view> </view>
</view> </view>
<view class="load-status"> <!-- 列表内容 -->
<text v-if="loading">加载中...</text> <view v-else>
<text v-else-if="noMore">没有更多数据了</text> <view v-if="planList.length === 0" class="empty">暂无数据</view>
<view v-for="plan in planList" :key="plan.id" class="card plan-card">
<!-- 头部标题 + 操作 -->
<view class="card-header">
<text class="plan-title ellipsis-1">{{ plan.planTitle }}</text>
<text class="plan-action" @tap="goToDistribution(plan.id)">发放物资</text>
</view>
<!-- 关键指标数量 + 周期类型 -->
<view class="key-meta">
<view class="meta-block">
<text class="meta-label">发放数量</text>
<text class="meta-value number">{{ plan.quantityPerPeriod }}</text>
</view>
<view class="meta-block">
<text class="meta-label">周期类型</text>
<text class="meta-value tag">{{ plan.periodType }}</text>
</view>
<view class="time-item">
<text class="time-label">上次分配</text>
<text class="time-value">{{ plan.lastDistributionDate || '-' }}</text>
</view>
<view class="time-item">
<text class="time-label">下次分配</text>
<text class="time-value">{{ plan.nextDistributionDate || '-' }}</text>
</view>
</view>
<!-- 底部信息 -->
<view class="plan-footer">
<text class="hosp ellipsis-1">医院{{ plan.hospName }}</text>
<text class="created ellipsis-1">创建人{{ plan.createdByName }}{{ plan.createdByMobile }}</text>
<text class="time ellipsis-1">创建时间{{ plan.createdTime }}</text>
</view>
</view>
<view class="load-status" v-if="noMore && planList.length > 0">没有更多数据了</view>
</view> </view>
</scroll-view> </scroll-view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -95,7 +102,7 @@ function goToDistribution(id: string | number) {
if (!id && id !== 0) { if (!id && id !== 0) {
uni.showToast({ title: '缺少计划ID', icon: 'none' }) uni.showToast({ title: '缺少计划ID', icon: 'none' })
return return
} }
uni.navigateTo({ url: `/pages/consumables/distribution?id=${id}` }) uni.navigateTo({ url: `/pages/consumables/distribution?id=${id}` })
} }
@ -118,42 +125,196 @@ onReachBottom(() => {
background: #f5f7fb; background: #f5f7fb;
box-sizing: border-box; box-sizing: border-box;
} }
.empty { .empty {
color: #9a9aa0; color: #9a9aa0;
text-align: center; text-align: center;
padding: 24rpx 0; padding: 24rpx 0;
} }
.plan-card {
.card {
background: #fff; background: #fff;
border-radius: 16rpx; border-radius: 16rpx;
padding: 20rpx; padding: 20rpx;
margin-bottom: 16rpx; margin-bottom: 16rpx;
box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.06); box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.06);
} }
.plan-header {
.card-header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: 12rpx; margin-bottom: 12rpx;
} }
.plan-title { .plan-title {
font-size: 30rpx; font-size: 32rpx;
color: #222; color: #222;
font-weight: 600; font-weight: 600;
} }
.plan-action { .plan-action {
font-size: 26rpx; font-size: 26rpx;
color: #4b7aff; color: #4b7aff;
padding: 8rpx 16rpx;
border: 2rpx solid #4b7aff;
border-radius: 999rpx;
} }
.plan-info, .plan-time {
.key-meta {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: 1fr 1fr;
gap: 12rpx; gap: 16rpx;
margin-bottom: 8rpx; margin: 8rpx 0 12rpx;
}
.meta-block {
background: #f7f9ff;
border-radius: 12rpx;
padding: 16rpx;
}
.meta-label {
display: block;
color: #8a8f99;
font-size: 22rpx;
margin-bottom: 6rpx;
}
.meta-value {
color: #333;
font-size: 28rpx;
}
.meta-value.number {
color: #e53935;
font-weight: 700;
font-size: 36rpx;
}
.meta-value.tag {
display: inline-flex;
align-items: center;
padding: 2rpx 12rpx;
background: #eff3ff;
color: #4b7aff;
border-radius: 999rpx;
font-size: 26rpx;
}
.detail {
margin: 8rpx 0;
}
.detail-row {
display: flex;
align-items: flex-start;
margin: 8rpx 0;
}
.label {
color: #888;
min-width: 140rpx;
}
.value {
color: #333;
}
.time-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12rpx;
margin-top: 4rpx;
}
.time-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 6rpx;
background: #fafafa;
border-radius: 10rpx;
padding: 12rpx;
}
.time-label {
color: #8a8f99;
font-size: 22rpx;
}
.time-value {
color: #333;
font-size: 24rpx;
}
.plan-footer {
color: #666;
font-size: 24rpx;
display: flex;
flex-direction: column;
gap: 6rpx;
margin-top: 6rpx;
}
.load-status {
text-align: center;
color: #9a9aa0;
padding: 20rpx 0;
}
.ellipsis-1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 骨架屏样式 */
.skeleton-list .card {
overflow: hidden;
}
.skeleton {
position: relative;
}
.skeleton::after {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.04) 50%, rgba(0, 0, 0, 0) 100%);
animation: shimmer 1.2s infinite;
}
.skeleton-line {
height: 28rpx;
background: #f0f2f5;
border-radius: 8rpx;
margin: 14rpx 0;
}
.skeleton-line.w-70 {
width: 70%;
}
.skeleton-line.w-40 {
width: 40%;
}
.skeleton-line.w-90 {
width: 90%;
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
} }
.info-item { color: #444; }
.label { color: #888; }
.value { color: #333; }
.plan-footer { color: #666; font-size: 24rpx; display: flex; flex-direction: column; gap: 6rpx; }
.load-status { text-align: center; color: #9a9aa0; padding: 20rpx 0; }
</style> </style>

View File

@ -62,8 +62,8 @@
<view class="image-viewer"> <view class="image-viewer">
<view class="image-label">设备照片</view> <view class="image-label">设备照片</view>
<view class="image-container" @tap="previewImage"> <view class="image-container" @tap="previewImage">
<image class="image" :src="detailData.assetPic || defaultImage" mode="aspectFit" /> <image class="image" :src="detailData.image || defaultImage" mode="aspectFit" />
<view class="image-hint" v-if="detailData.assetPic">点击预览大图</view> <view class="image-hint" v-if="detailData.image">点击预览大图</view>
</view> </view>
</view> </view>
</view> </view>
@ -99,10 +99,7 @@ async function fetchBySn(sn: string) {
const d = data?.data const d = data?.data
if (Array.isArray(d) && d.length > 0) { if (Array.isArray(d) && d.length > 0) {
detailData.value = d[0] detailData.value = d[0]
} else if (Array.isArray(d?.list) && d.list.length > 0) { detailData.value.image = 'data:image/png;base64,' + detailData.value.image
detailData.value = d.list[0]
} else if (d && typeof d === 'object') {
detailData.value = d
} else { } else {
detailData.value = {} detailData.value = {}
} }
@ -113,8 +110,8 @@ async function fetchBySn(sn: string) {
} }
function previewImage() { function previewImage() {
if (detailData.value.assetPic) { if (detailData.value.image) {
uni.previewImage({ urls: [detailData.value.assetPic], current: detailData.value.assetPic, uni.previewImage({ urls: [detailData.value.image], current: detailData.value.image,
fail: () => uni.showToast({ title: '预览失败,请稍后重试', icon: 'none' }) }) fail: () => uni.showToast({ title: '预览失败,请稍后重试', icon: 'none' }) })
} else { } else {
uni.showToast({ title: '暂无设备照片', icon: 'none' }) uni.showToast({ title: '暂无设备照片', icon: 'none' })

View File

@ -73,7 +73,7 @@
</view> --> </view> -->
<view class="form-item"> <view class="form-item">
<text class="label">借用日期</text> <text class="label">借用日期</text>
<picker mode="date" @change="onBorrowDateChange"> <picker mode="date" :value="borrowForm.borrowDate" @change="onBorrowDateChange">
<view class="picker-value">{{ borrowForm.borrowDate || '请选择借用日期' }}</view> <view class="picker-value">{{ borrowForm.borrowDate || '请选择借用日期' }}</view>
</picker> </picker>
</view> </view>
@ -129,6 +129,13 @@ const userSuggestions = ref<any[]>([])
let searchTimeout: any = null let searchTimeout: any = null
const isSearching = ref(false) const isSearching = ref(false)
function formatDate(d: Date) {
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
}
onLoad(() => { onLoad(() => {
getAssetList() getAssetList()
}) })
@ -199,6 +206,8 @@ function borrow(item: AssetItem) {
showBorrowPopup.value = true showBorrowPopup.value = true
userSuggestions.value = [] userSuggestions.value = []
borrowForm.value.lenderMobile = '' borrowForm.value.lenderMobile = ''
//
borrowForm.value.borrowDate = formatDate(new Date())
} }
function cancelBorrow() { function cancelBorrow() {
@ -356,7 +365,7 @@ async function confirmBorrow() {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
z-index: 1000; z-index: 100;
} }
.modal-content { .modal-content {

View File

@ -1,27 +1,70 @@
<template> <template>
<view class="page"> <view class="page">
<view class="load-status" v-if="!inventoryPlanList.length && !loading">没有更多数据了</view> <!-- 加载骨架屏 -->
<view class="inventory-list"> <view v-if="loading" class="skeleton-list">
<view class="inventory-item card" v-for="item in inventoryPlanList" :key="item.inventoryPlanId"> <view class="card skeleton" v-for="n in 3" :key="n">
<view class="left" @tap="gotoPlanDetail(item)"> <view class="skeleton-line w-70"></view>
<view class="header"><text class="title">📋 盘点计划</text></view> <view class="skeleton-line w-40"></view>
<view class="content"> <view class="skeleton-line w-90"></view>
<view class="row">开始日期<text class="value">{{ item.inventoryStartDate }}</text></view> </view>
<view class="row">结束日期<text class="value">{{ item.inventoryEndDate }}</text></view> </view>
<view class="row">盘点人<text class="value">{{ item.inventoryManName }}</text></view>
<view class="row">备注<text class="value">{{ item.note || '无' }}</text></view> <!-- 列表内容 -->
<view v-else>
<view v-if="!inventoryPlanList.length" class="empty">暂无数据</view>
<view class="inventory-list">
<view class="inventory-item card" v-for="item in inventoryPlanList" :key="item.inventoryPlanId">
<view class="left">
<!-- 头部标题 + 状态徽章 -->
<view class="card-header">
<text class="title ellipsis-1">📋 盘点计划</text>
<text class="status-badge" :class="statusClass(item.status)">{{ item.status || '未知'
}}</text>
</view>
<!-- 关键指标开始/结束日期 -->
<view class="key-meta">
<view class="meta-block">
<text class="meta-label">开始日期</text>
<text class="meta-value">{{ item.inventoryStartDate || '-' }}</text>
</view>
<view class="meta-block">
<text class="meta-label">结束日期</text>
<text class="meta-value">{{ item.inventoryEndDate || '-' }}</text>
</view>
</view>
<!-- 详情盘点人备注 -->
<view class="detail">
<view class="detail-row">
<text class="label">盘点人</text>
<text class="value ellipsis-1">{{ item.inventoryManName || '-' }}</text>
</view>
<view class="detail-row">
<text class="label">备注</text>
<text class="value ellipsis-2">{{ item.note || '无' }}</text>
</view>
</view>
</view>
<!-- 右侧预览图仅当有图片时显示 -->
<view class="preview" v-if="item.image" @click="previewImage(item.image)">
<image class="preview-img" :src="'data:image/png;base64,' + item.image" mode="aspectFill" />
</view>
<view class="action">
<view class="chevron-link" @tap="gotoPlanDetail(item)">
<svg class="chevron-icon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</view>
</view> </view>
<view class="status">计划状态<text class="value">{{ item.status || '无' }}</text></view>
</view>
<view class="preview" @click="previewImage(item.image)">
<image class="preview-img" :src="'data:image/png;base64,' + item.image" mode="aspectFill" />
</view> </view>
</view> </view>
</view>
<view class="load-status"> <view class="load-status" v-if="noMore && inventoryPlanList.length">没有更多数据了</view>
<text v-if="loading">加载中...</text>
<text v-else-if="noMore && inventoryPlanList.length">没有更多数据了</text>
</view> </view>
</view> </view>
</template> </template>
@ -38,6 +81,12 @@ const loading = ref(false)
const noMore = ref(false) const noMore = ref(false)
const inventoryPlanList = ref<any[]>([]) const inventoryPlanList = ref<any[]>([])
function statusClass(status?: string) {
if (status === '已结束') return 'ended'
if (status === '正在进行') return 'running'
return 'unknown'
}
onLoad(() => { fetchInventoryList() }) onLoad(() => { fetchInventoryList() })
onReachBottom(() => { onReachBottom(() => {
@ -113,15 +162,15 @@ function previewImage(imageBase64: string) {
} }
.left { .left {
/* 按内容宽度,不挤占右侧空间 */ /* 左侧内容占据剩余空间 */
flex: 0 0 auto; flex: 1 1 auto;
min-width: 0; min-width: 0;
} }
.preview { .preview {
/* 占据剩余空间 */ /* 固定宽度的预览区域,避免占满 */
flex: 1 1 0; flex: 0 0 220rpx;
min-width: 0; width: 220rpx;
display: flex; display: flex;
/* 使内部图片可以 100% 拉伸 */ /* 使内部图片可以 100% 拉伸 */
} }
@ -131,21 +180,108 @@ function previewImage(imageBase64: string) {
} }
.title { .title {
font-size: 28rpx; font-size: 32rpx;
color: #222; color: #222;
font-weight: 600; font-weight: 600;
} }
.content .row { /* 卡片头与状态徽章 */
font-size: 24rpx; .card-header {
color: #666; display: flex;
margin-top: 6rpx; align-items: center;
justify-content: space-between;
margin-bottom: 10rpx;
} }
.status { .status-badge {
font-size: 24rpx; font-size: 24rpx;
padding: 4rpx 12rpx;
border-radius: 999rpx;
}
.status-badge.running {
color: #1e88e5;
background: #e3f2fd;
}
.status-badge.ended {
color: #2e7d32;
background: #e8f5e9;
}
.status-badge.unknown {
color: #757575;
background: #eeeeee;
}
/* 关键指标与详情 */
.key-meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16rpx;
margin: 8rpx 0 12rpx;
}
.meta-block {
background: #f7f9ff;
border-radius: 12rpx;
padding: 16rpx;
}
.meta-label {
display: block;
color: #8a8f99;
font-size: 22rpx;
margin-bottom: 6rpx;
}
.meta-value {
color: #333; color: #333;
margin-top: 10rpx; font-size: 28rpx;
}
.detail {
margin-top: 8rpx;
}
.detail-row {
display: flex;
align-items: flex-start;
margin: 8rpx 0;
}
.label {
color: #888;
min-width: 140rpx;
}
.value {
color: #333;
}
/* 操作区 */
.action {
display: flex;
align-items: center;
justify-content: center;
}
/* 简约右箭头点击区域 */
.chevron-link {
display: inline-flex;
align-items: center;
justify-content: center;
width: 48rpx;
height: 48rpx;
border-radius: 50%;
background: #f1f3f9;
}
.chevron-icon {
width: 32rpx;
height: 32rpx;
color: #86909c;
} }
.load-status { .load-status {
@ -154,17 +290,89 @@ function previewImage(imageBase64: string) {
margin: 20rpx 0; margin: 20rpx 0;
} }
.empty {
color: #9a9aa0;
text-align: center;
padding: 24rpx 0;
}
.value { .value {
color: #333; color: #333;
} }
.ellipsis-1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ellipsis-2 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
}
/* 右侧图片自适应填满容器,保持裁剪 */ /* 右侧图片自适应填满容器,保持裁剪 */
.preview-img { .preview-img {
width: 100%; width: 100%;
height: 100%; height: auto;
aspect-ratio: 1 / 1;
display: block; display: block;
object-fit: cover; object-fit: cover;
/* H5 有效;小程序端由 mode=aspectFill 生效 */ /* H5 有效;小程序端由 mode=aspectFill 生效 */
border-radius: 12rpx; border-radius: 12rpx;
} }
/* 骨架屏样式 */
.skeleton-list .card {
overflow: hidden;
}
.skeleton {
position: relative;
padding: 20rpx;
margin-bottom: 16rpx;
}
.skeleton::after {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.04) 50%, rgba(0, 0, 0, 0) 100%);
animation: shimmer 1.2s infinite;
}
.skeleton-line {
height: 28rpx;
background: #f0f2f5;
border-radius: 8rpx;
margin: 14rpx 0;
}
.skeleton-line.w-70 {
width: 70%;
}
.skeleton-line.w-40 {
width: 40%;
}
.skeleton-line.w-90 {
width: 90%;
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
</style> </style>