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

622 lines
17 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">{{ userinfo.name || '犇犇' }}</text>
</view>
<view class="form-item form-item-row">
<text class="form-label">公司名称:</text>
<text class="form-value">{{ userinfo.corp_name || '合塑' }}</text>
</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
ref="borrowTimePicker"
type="date"
v-model="formData.use_start_date"
placeholder="请选择时间"
:border="false"
:clear-icon="false"
@change="onBorrowTimeChange"
>
<view class="custom-datetime-input">
<text class="custom-datetime-text" :class="{ 'placeholder': !formData.use_start_date }">
{{ formData.use_start_date || '请选择时间' }}
</text>
<uni-icons type="calendar" size="16" color="#999"></uni-icons>
</view>
</uni-datetime-picker>
</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
ref="returnTimePicker"
type="date"
v-model="formData.use_end_date"
placeholder="请选择时间"
:border="false"
:clear-icon="false"
@change="onReturnTimeChange"
>
<view class="custom-datetime-input">
<text class="custom-datetime-text" :class="{ 'placeholder': !formData.use_end_date }">
{{ formData.use_end_date || '请选择时间' }}
</text>
<uni-icons type="calendar" size="16" color="#999"></uni-icons>
</view>
</uni-datetime-picker>
</view>
</view>
</view>
<!-- ✅ 物品(可添加多条) -->
<view class="goods-card">
<view class="goods-head">
<text class="form-label required">物品</text>
<button class="goods-add-btn" size="mini" @click="addItem">+ 添加</button>
</view>
<view class="goods-module" v-for="(it, idx) in formData.items" :key="idx">
<view class="goods-module-top">
<text class="goods-module-title">物品 {{ idx + 1 }}</text>
<uni-icons
v-if="formData.items.length > 1"
type="trash"
size="18"
color="#ff4d4f"
@click="removeItem(idx)"
></uni-icons>
</view>
<view class="goods-module-body">
<view class="goods-name">
<picker mode="selector" :range="itemNameOptions" range-key="item_name" @change="onItemNameChange($event, idx)">
<view class="category-content">
<text class="category-text">{{it.item_name_snap || '请选择物品'}}</text>
</view>
</picker>
</view>
<view class="goods-qty">
<uni-number-box v-model="it.request_qty" :min="1" :max="999"></uni-number-box>
</view>
</view>
</view>
</view>
<view class="form-card">
<!-- 借用事由 -->
<view class="form-item">
<text class="form-label required form-label1">借用事由</text>
<uni-easyinput
v-model="formData.borrow_reason"
type="textarea"
:autoHeight="true"
placeholder="请填写借用事由"
:maxlength="500"
></uni-easyinput>
</view>
</view>
<!-- 备注卡片 -->
<view class="form-card " style="margin-bottom: 8rpx;">
<view class="form-item">
<text class="form-label">备注</text>
<uni-easyinput
v-model="formData.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">
<!--
说明:
1) 回显用 :value="mediaValue",每项至少包含 { url }。
2) 关闭组件自带自动上传(auto-upload=false),我们用自己的 uploadfn 上传到后端。
3) 提交给后台时只传 file_path(formData.attachment),展示用 file_path_pull(mediaValue.url)。
-->
<uni-file-picker
ref="mediaPicker"
:limit="maxMedia"
fileMediatype="image"
:auto-upload="false"
:value="mediaValue"
@select="selectMedia"
@delete="delfn"
>
<view class="upload-icon-wrapper">
<image src="/static/image/upload.png" mode="" class="addimg"></image>
</view>
</uni-file-picker>
</view>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-container">
<button class="submit-btn" @click="handleSubmit">提交申请</button>
</view>
</view>
</view>
</template>
<script>
import { request,showToast } from '@/api/apiT.js'
export default {
data() {
return {
id:'',
userinfo: {},
maxMedia:20,
// uni-file-picker 用于回显的文件列表(展示用)
// 结构:[{ url: 'https://...', name: 'xxx.jpg' }]
mediaValue: [],
formData: {
use_start_date: '',
use_end_date: '',
items: [{ item_name_snap: '',item_id:'',request_qty:1}],
borrow_reason: '',
remark: '',
attachment: []
},
itemNameOptions: [],
}
},
onLoad(options) {
this.id = options.id
this.getinfo()
this.getItemNameOptions()
// 编辑页:有 id 则回显详情
if (this.id) {
this.getdetils()
}
},
methods: {
leftClick(){
uni.navigateBack({})
},
// 获取详情并回显
getdetils() {
request({
url:'mobile/inv/requisition/show',
method:'get',
data:{id:this.id}
}).then(res => {
const d = (res.data && res.data.data) ? res.data.data : {}
// 1) 基础字段
this.formData.id = d.id
this.formData.use_start_date = d.use_start_date || ''
this.formData.use_end_date = d.use_end_date || ''
this.formData.borrow_reason = d.borrow_reason || ''
this.formData.remark = d.remark || ''
// 2) 物品列表:后端字段是 item,这里统一成 items
const items = Array.isArray(d.item) ? d.item : []
this.formData.items = items.length
? items.map(it => ({
// 保留 id 等字段,方便后端识别是更新还是新增
id: it.id,
requisition_id: it.requisition_id,
item_id: it.item_id,
item_name_snap: it.item_name_snap,
request_qty: Number(it.request_qty || 1),
approved_qty: it.approved_qty,
returned_qty: it.returned_qty,
remark: it.remark || ''
}))
: [{ item_name_snap: '', item_id: '', request_qty: 1 }]
// 3) 附件:
// - formData.attachment:提交给后台(file_path)
// - mediaValue:回显展示(file_path_pull)
const atts = Array.isArray(d.attachment) ? d.attachment : []
this.formData.attachment = atts.map(a => ({
id: a.id,
original_name: a.file_name,
file_path: a.file_path
}))
this.mediaValue = atts.map(a => ({
url: a.file_path_pull || a.file_path,
name: a.file_name
}))
})
},
getinfo(){
request({
url:'common/user/info',
method:'get',
}).then(res => {
this.userinfo=res.data.data.user
})
},
//物品下拉
getItemNameOptions() {
request({
url:'mobile/inv/item/preset',
method:'get',
}).then(res => {
this.itemNameOptions = res.data.data
this.itemNameOptions.forEach(item => {
item.item_name = item.item_name + '(' + item.company_name + ')'
})
console.log(this.itemNameOptions)
})
},
// 选择物品(注意:需要知道是第几行物品)
onItemNameChange(e, rowIndex) {
const optIndex = e.detail.value // picker 返回选项下标
const selectItem = this.itemNameOptions[optIndex]
if (!selectItem) return
if (!Array.isArray(this.formData.items)) this.formData.items = []
if (!this.formData.items[rowIndex]) return
this.formData.items[rowIndex].item_name_snap = selectItem.item_name
this.formData.items[rowIndex].item_id = selectItem.id
},
onBorrowTimeChange(e) {
this.formData.use_start_date = e
},
onReturnTimeChange(e) {
this.formData.use_end_date = e
},
onFileSelect(e) {
console.log('文件选择:', e)
},
addItem() {
// 追加一条物品
this.formData.items = Array.isArray(this.formData.items) ? this.formData.items : []
this.formData.items.push({
item_name_snap: '',
item_id: '',
request_qty: 1
})
},
removeItem(index) {
// 删除指定物品(至少保留 1 条)
if (!Array.isArray(this.formData.items)) return
if (this.formData.items.length <= 1) return
this.formData.items.splice(index, 1)
},
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) => {
const res = JSON.parse(uploadFileRes.data || '{}')
if(res && res.data && res.data.file_name){
resolve(res.data)
}else{
reject('上传失败')
}
},
fail: (err) => reject(err)
});
})
},
selectMedia(e){
// 选择图片后:先占位写入 attachment,确保“删除”时 index 一致
this.formData.attachment = Array.isArray(this.formData.attachment) ? this.formData.attachment : []
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
const startIndex = this.formData.attachment.length
paths.forEach(p => {
this.formData.attachment.push({
__local_path: p,
original_name: '',
file_path: ''
})
})
Promise.all(
paths.map((p, i) =>
this.uploadfn(p)
.then(data => ({ i, ok: true, data }))
.catch(err => ({ i, ok: false, err }))
)
).then(results => {
// 先写入成功结果
results.forEach(r => {
if (!r.ok) return
const idx = startIndex + r.i
const row = this.formData.attachment[idx]
if (!row) return
row.original_name = r.data.original_name
// 后端需要的路径字段(注意:接口返回里叫 file_name,但实际是存储路径)
row.file_path = r.data.file_name
delete row.__local_path
})
// 再从后往前移除失败项(同时把 picker 里也删掉)
const failed = results.filter(r => !r.ok).map(r => r.i).sort((a,b) => b - a)
if (failed.length) {
failed.forEach(i => {
const idx = startIndex + i
// 删除占位
this.formData.attachment.splice(idx, 1)
// 同步删除 picker 中对应图片
this.$nextTick(() => {
if (this.$refs.mediaPicker && this.$refs.mediaPicker.clearFiles) {
this.$refs.mediaPicker.clearFiles(idx)
}
})
})
uni.showToast({
title:'部分图片上传失败,已自动移除,请重试',
icon:'none'
})
}
})
},
delfn(e){
this.formData.attachment.splice(e.index, 1)
// 仅删除“回显列表”里对应的旧附件(新选的图片不在 mediaValue 中)
if (Array.isArray(this.mediaValue) && e.index < this.mediaValue.length) {
this.mediaValue.splice(e.index, 1)
}
},
handleSubmit() {
// 表单验证
if (!this.formData.use_start_date) {
uni.showToast({ title: '请选择借用时间', icon: 'none' })
return
}
if (!this.formData.use_end_date) {
uni.showToast({ title: '请选择计划归还时间', icon: 'none' })
return
}
// 物品列表校验(至少 1 条,且每条需选择名称 + 数量)
if (!Array.isArray(this.formData.items) || this.formData.items.length === 0) {
uni.showToast({ title: '请添加物品', icon: 'none' })
return
}
for (let i = 0; i < this.formData.items.length; i++) {
const it = this.formData.items[i] || {}
if (!it.item_name_snap) {
uni.showToast({ title: `请选择第${i + 1}个物品名称`, icon: 'none' })
return
}
const request_qty = Number(it.request_qty)
if (!request_qty || request_qty <= 0) {
uni.showToast({ title: `请填写第${i + 1}个物品数量`, icon: 'none' })
return
}
}
if (!this.formData.borrow_reason) {
uni.showToast({ title: '请填写借用事由', icon: 'none' })
return
}
// 附件:如有占位未上传完成,不允许提交
if (Array.isArray(this.formData.attachment)) {
const uploading = this.formData.attachment.some(a => !a || !a.file_path)
if (uploading) {
uni.showToast({ title: '附件上传中,请稍后再提交', icon: 'none' })
return
}
}
// 提交表单
console.log('提交表单:', this.formData)
request({
url:'mobile/inv/requisition/update',
method:'post',
data:this.formData
}).then(res => {
uni.showToast({
title: res.data.message,
icon: 'none'
});
if(res.data.code==200){
setTimeout(() => {
uni.reLaunch({
url: '/pages/index/index',// 替换为你要跳转的页面路径
});
}, 1000);
}
})
// 这里可以调用API提交数据
}
}
}
</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;
}
.media-upload-wrapper{
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.addimg{
width: 50px;
height:50px;
}
.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: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
height: 35px;
line-height: 35px;
}
/* 物品(可添加多条) */
.goods-card {
background: #fff;
border-radius: 6px;
padding: 12px 17px;
margin-bottom: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
}
.goods-head {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin-bottom: 10px;
}
.goods-add-btn {
background: #3074F7;
color: #fff;
border-radius: 14px;
font-size: 24rpx;
margin-left: auto;
margin-right: inherit;
}
.goods-module {
background: #F7F8FA;
border-radius: 8px;
padding: 10px;
margin-bottom: 10px;
}
.goods-module-top {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.goods-module-title {
font-size: 26rpx;
color: #333;
}
.goods-module-body {
display: flex;
align-items: center;
}
.goods-name {
flex: 1;
min-width: 0;
}
.goods-qty {
width: 220rpx;
display: flex;
justify-content: flex-end;
margin-left: 16rpx;
}
::v-deep .goods-qty .uni-numbox {
scale: 0.95;
transform-origin: right center;
}
.form-item {
margin-bottom: 32rpx;
&:last-child { margin-bottom: 0; }
}
.form-item-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.form-label1{
margin-bottom: 16rpx;
}
.form-item-row .form-label {
margin-bottom: 0;
margin-right: 24rpx;
}
.form-input-container {
flex: 1;
min-width: 0;
}
.form-label {
font-size: 28rpx;
color: #333;
display: block;
flex-shrink: 0;
min-width: 160rpx;
&.required::before {
content: '*';
color: #ff0000;
margin-right: 4rpx;
}
}
.form-value {
font-size: 28rpx;
color: #666;
flex: 1;
}
.category-text{
font-size: 28rpx;
color: #333;
}
.custom-datetime-input {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.custom-datetime-text {
font-size: 28rpx;
color: #333;
&.placeholder {
color: #999;
}
}
.submit-container {
margin-top: 40rpx;
padding-bottom: 40rpx;
}
.submit-btn {
background: #3074F7;
color: #fff;
font-size: 32rpx;
border-radius: 12rpx;
height: 88rpx;
line-height: 88rpx;
}
::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>