Files
art-design-uniapp/pages/borrow/return.vue
T
2026-02-06 14:39:37 +08:00

596 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="home-page">
<uni-nav-bar
leftText=" "
:fixed="true"
leftIcon="back"
:border="false"
color="#fff"
backgroundColor="rgb(79 137 247)"
:statusBar="true"
title="物品归还"
@clickLeft="leftClick"
></uni-nav-bar>
<view class="content-wrapper">
<!-- 借用基本信息 -->
<view class="form-card">
<view class="form-item form-item-row">
<text class="form-label">申请人</text>
<text class="form-value">{{ detail.applicant_name || '-' }}</text>
</view>
<view class="form-item form-item-row">
<text class="form-label">单号</text>
<text class="form-value">{{ detail.order_no || '-' }}</text>
</view>
<view class="form-item form-item-row">
<text class="form-label">借用时间</text>
<text class="form-value">{{ detail.use_start_date || '-' }}</text>
</view>
<view class="form-item form-item-row">
<text class="form-label">计划归还</text>
<text class="form-value">{{ detail.use_end_date || '-' }}</text>
</view>
<view class="form-item">
<text class="form-label">借用事由</text>
<view class="text-block">{{ detail.borrow_reason || '-' }}</view>
</view>
</view>
<!-- 归还物品(可删减 + 改数量) -->
<view class="goods-card">
<view class="goods-head">
<text class="goods-title">归还物品</text>
<text class="goods-sub">可删减物品,数量不超过“可归还”</text>
</view>
<view v-if="returnItems.length === 0" class="empty-tip">
暂无可归还物品
</view>
<view class="goods-module" v-for="(it, idx) in returnItems" :key="it._key">
<view class="goods-module-top">
<view class="goods-module-title">
<text class="name">{{ it.item_name_snap || '-' }}</text>
</view>
<uni-icons
type="trash"
size="18"
color="#ff4d4f"
@click="removeReturnItem(idx)"
></uni-icons>
</view>
<view class="goods-meta">
<view class="meta">
<text class="k">借用</text>
<text class="v">{{ it.approved_qty }}</text>
</view>
<view class="meta">
<text class="k">已还</text>
<text class="v">{{ it.returned_qty }}</text>
</view>
<view class="meta">
<text class="k">可归还</text>
<text class="v">{{ it.can_return }}</text>
</view>
</view>
<view class="goods-module-body">
<text class="qty-label required">本次归还数量</text>
<uni-number-box
v-model="it.return_qty"
:min="1"
:max="it.can_return"
></uni-number-box>
</view>
</view>
</view>
<!-- 实际归还时间 -->
<view class="form-card4">
<view class="form-item form-item-row">
<text class="form-label required">实际归还时间</text>
<view class="form-input-container">
<uni-datetime-picker
type="date"
v-model="formData.return_at"
placeholder="请选择时间"
:border="false"
:clear-icon="false"
>
<view class="custom-datetime-input">
<text class="custom-datetime-text" :class="{ placeholder: !formData.return_at }">
{{ formData.return_at || '请选择时间' }}
</text>
<uni-icons type="calendar" size="16" color="#999"></uni-icons>
</view>
</uni-datetime-picker>
</view>
</view>
</view>
<!-- 备注 -->
<view class="form-card" style="margin-bottom: 8rpx;">
<view class="form-item">
<text class="form-label">归还备注</text>
<uni-easyinput
v-model="formData.apply_remark"
type="textarea"
:autoHeight="true"
placeholder="请填写归还备注"
:maxlength="500"
></uni-easyinput>
</view>
</view>
<!-- 附件(多图) -->
<view class="form-card">
<view class="form-item">
<text class="form-label">附件</text>
<view class="media-upload-wrapper">
<uni-file-picker
:value="mediaValue"
limit="20"
fileMediatype="image"
:disabled="uploading"
:auto-upload="false"
@select="selectMedia"
@delete="delfn"
>
<view class="upload-icon-wrapper">
<image src="/static/image/upload.png" class="addimg"></image>
</view>
</uni-file-picker>
</view>
</view>
</view>
<!-- 提交 -->
<view class="submit-container">
<button class="submit-btn" :disabled="submitting || uploading" @click="handleSubmit">
{{ submitting ? '提交中...' : '提交归还' }}
</button>
</view>
</view>
</view>
</template>
<script>
import { request } from '@/api/apiT.js'
export default {
data() {
return {
id: '',
detail: {},
returnItems: [],
formData: {
return_at: '',
apply_remark: '',
attachment: [] // 仅用于提交后台:[{original_name,file_path}]
},
mediaValue: [], // 仅用于 file-picker 展示:[{url,name,...}]
uploading: false,
submitting: false,
maxMedia: 20,
// 注意:根据你后端实际路由调整(目前项目里只有 apply/list/show)
RETURN_API: 'mobile/inv/requisition/return/store'
}
},
onLoad(options) {
this.id = options.id || ''
if (this.id) {
this.getDetails()
}
},
methods: {
leftClick() {
uni.navigateBack({})
},
// 拉取详情并回显
getDetails() {
request({
url: 'mobile/inv/requisition/show',
method: 'get',
data: { id: this.id }
}).then(res => {
const d = (res && res.data && res.data.data) ? res.data.data : {}
this.detail = d || {}
this.buildReturnItems(d)
})
},
// 把借用明细转换为“可归还”明细
buildReturnItems(d) {
const list = Array.isArray(d && d.item) ? d.item : []
const items = []
list.forEach(row => {
const approved = Number(row.approved_qty || row.request_qty || 0)
const returned = Number(row.returned_qty || 0)
const canReturn = Math.max(approved - returned, 0)
if (canReturn <= 0) return
items.push({
_key: `${row.id || row.item_id || ''}_${Date.now()}_${Math.random()}`,
id: row.id, // requisition_item_id
item_id: row.item_id,
item_name_snap: row.item_name_snap,
approved_qty: approved,
returned_qty: returned,
can_return: canReturn,
return_qty: canReturn // 默认全部归还(可自行改小/删减)
})
})
this.returnItems = items
},
removeReturnItem(idx) {
if (!Array.isArray(this.returnItems)) return
this.returnItems.splice(idx, 1)
},
// 上传单张(复用你项目里的 OSS 上传地址 + Header)
uploadfn(imgUrl) {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: 'https://fdesk.hnxinyouhe.com/upload?scene=itemsimg',
filePath: imgUrl,
name: 'file',
header: {
'Fdesk-Corp-Abbr': uni.getStorageSync('corp_abbr'),
'Fdesk-Auth-Token': 'Bearer ' + uni.getStorageSync('token'),
'Fdesk-Language': uni.getStorageSync('i18locale') ? uni.getStorageSync('i18locale') : 'zh',
'Fdesk-Login-Type': 'feishu',
'Fdesk-System-Code': 'fdesk'
},
success: (uploadFileRes) => {
let json = {}
try {
json = JSON.parse(uploadFileRes.data || '{}')
} catch (e) {}
if (json && json.data && (json.data.file_name || json.data.file_path || json.data.file)) {
resolve(json.data)
} else {
reject('上传失败')
}
},
fail: (err) => reject(err)
})
})
},
selectMedia(e) {
if (this.uploading) return
const allowCount = this.maxMedia - this.formData.attachment.length
const tempFilePaths = e.tempFilePaths || (e.tempFiles || []).map(item => item.path || item.url)
const paths = tempFilePaths.slice(0, allowCount)
if (paths.length === 0) return
this.uploading = true
Promise.all(paths.map(p => this.uploadfn(p)))
.then(list => {
list.forEach((u, i) => {
// 兼容后端返回字段(你上面示例是 file_name/original_name)
const original = u.original_name || u.file_original_name || u.name || ('image_' + Date.now())
const filePath = u.file_name || u.file_path || u.path || ''
this.formData.attachment.push({
original_name: original,
file_path: filePath
})
// file-picker 展示用:本地选择的临时路径更直观
this.mediaValue.push({
name: original,
url: paths[i]
})
})
})
.catch(() => {
uni.showToast({ title: '图片上传失败,请重试', icon: 'none' })
})
.finally(() => {
this.uploading = false
})
},
delfn(e) {
// file-picker 的 index 和本地 mediaValue 一致;我们让 attachment 同步删除
const idx = e.index
if (idx >= 0) {
this.mediaValue.splice(idx, 1)
this.formData.attachment.splice(idx, 1)
}
},
handleSubmit() {
if (this.submitting || this.uploading) return
if (!this.id) {
uni.showToast({ title: '缺少借用申请ID', icon: 'none' })
return
}
if (!this.formData.return_at) {
uni.showToast({ title: '请选择归还时间', icon: 'none' })
return
}
if (!Array.isArray(this.returnItems) || this.returnItems.length === 0) {
uni.showToast({ title: '请选择需要归还的物品', icon: 'none' })
return
}
for (let i = 0; i < this.returnItems.length; i++) {
const it = this.returnItems[i] || {}
const qty = Number(it.return_qty)
if (!qty || qty <= 0) {
uni.showToast({ title: `请填写第${i + 1}个物品的归还数量`, icon: 'none' })
return
}
if (qty > Number(it.can_return || 0)) {
uni.showToast({ title: `第${i + 1}个物品归还数量超过可归还`, icon: 'none' })
return
}
}
const payload = {
requisition_id: this.id,
return_at: this.formData.return_at,
apply_remark: this.formData.apply_remark,
attachment: this.formData.attachment,
items: this.returnItems.map(it => ({
requisition_item_name: it.item_name_snap || '',
requisition_item_id: it.id, // requisition_item_id
return_qty: it.return_qty
}))
}
this.submitting = true
request({
url: this.RETURN_API,
method: 'post',
data: payload
}).then(res => {
uni.showToast({
title: res.data.message,
icon: 'none'
});
if(res.data.code==200){
setTimeout(() => {
uni.navigateTo({
url: '/pages/borrow/borrow_list'
});
}, 1000);
}
}).finally(() => {
this.submitting = false
})
}
}
}
</script>
<style lang="scss">
.home-page {
min-height: 100vh;
background: linear-gradient(180.05deg, #3074F7 0.04%, #F4F6F8 34.07%);
box-sizing: border-box;
padding-bottom: 120rpx;
padding: 0px 28rpx;
}
.content-wrapper {
margin-top: 20rpx;
}
.form-card {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 24rpx;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
}
.form-card4 {
background: #fff;
border-radius: 6px;
padding: 3px 17px;
margin-bottom: 10px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
height: 35px;
line-height: 35px;
}
.form-item {
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
&.form-item-row {
display: flex;
align-items: center;
flex-wrap: nowrap;
}
}
.form-label {
font-size: 28rpx;
color: #333;
display: block;
flex-shrink: 0;
min-width: 160rpx;
&.required::before {
content: '*';
color: #ff0000;
margin-right: 4rpx;
}
}
.form-item-row .form-label {
margin-bottom: 0;
margin-right: 24rpx;
}
.form-input-container {
flex: 1;
min-width: 0;
}
.form-value {
font-size: 28rpx;
color: #666;
flex: 1;
text-align: right;
}
.text-block {
font-size: 28rpx;
color: #666;
line-height: 1.6;
padding-top: 10rpx;
}
.custom-datetime-input {
border-radius: 4px;
box-sizing: border-box;
width: 100%;
text-align: right;
}
.custom-datetime-text {
font-size: 28rpx;
color: #333;
&.placeholder {
color: #999;
}
}
::v-deep .uni-date-editor {
width: 100%;
}
::v-deep .uni-date-editor--x {
border: none !important;
background: transparent !important;
padding: 0 !important;
}
::v-deep .uni-date__x-input {
display: none;
}
::v-deep .icon-calendar {
display: none;
}
.goods-card {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 24rpx;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
}
.goods-head {
display: flex;
flex-direction: column;
gap: 6rpx;
margin-bottom: 12rpx;
}
.goods-title {
font-size: 30rpx;
color: #111;
font-weight: 600;
}
.goods-sub {
font-size: 24rpx;
color: #999;
}
.empty-tip {
padding: 24rpx 0;
text-align: center;
color: #999;
font-size: 26rpx;
}
.goods-module {
border: 1px solid #eef0f3;
border-radius: 14rpx;
padding: 18rpx;
margin-top: 16rpx;
}
.goods-module-top {
display: flex;
align-items: center;
justify-content: space-between;
}
.goods-module-title .name {
font-size: 28rpx;
color: #333;
font-weight: 600;
}
.goods-meta {
display: flex;
justify-content: space-between;
margin-top: 12rpx;
padding: 12rpx;
border-radius: 12rpx;
background: #f7f8fa;
}
.goods-meta .meta {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.goods-meta .k {
font-size: 22rpx;
color: #999;
}
.goods-meta .v {
font-size: 28rpx;
color: #333;
margin-top: 6rpx;
}
.goods-module-body {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 14rpx;
}
.qty-label {
font-size: 26rpx;
color: #333;
&.required::before {
content: '*';
color: #ff0000;
margin-right: 4rpx;
}
}
.media-upload-wrapper {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.addimg {
width: 50px;
height: 50px;
}
.submit-container {
margin-top: 30rpx;
padding-bottom: 40rpx;
display: flex;
justify-content: center;
}
.submit-btn {
width: 70%;
height: 88rpx;
background: #3074F7;
color: #fff;
border-radius: 44rpx;
font-size: 32rpx;
line-height: 88rpx;
border: none;
&::after {
border: none;
}
&[disabled] {
opacity: 0.6;
}
}
::v-deep .uni-easyinput__content {
border: none;
min-height: 120rpx;
}
::v-deep .uni-easyinput__content-textarea {
font-size: 28rpx;
color: #333;
min-height: 120rpx;
padding-left: 0px;
}
::v-deep .uni-file-picker {
width: 100%;
}
</style>