<template>
|
<view class="stock-out-page">
|
<!-- 页面标题 -->
|
<PageHeader title="自定义出库" @back="goBack" />
|
|
<!-- 搜索区域 -->
|
<view class="search-section">
|
<view class="search-bar">
|
<view class="search-input">
|
<u-input
|
v-model="searchForm.supplierName"
|
placeholder="请输入供应商名称"
|
border="none"
|
clearable
|
/>
|
</view>
|
<view class="search-button" @click="handleQuery">
|
<u-icon name="search" size="24" color="#999"></u-icon>
|
</view>
|
</view>
|
<view class="date-filter" @click="openDatePickerHandler">
|
<text class="date-text">{{ searchForm.timeStr || '选择日期' }}</text>
|
<up-icon name="calendar" size="18" color="#999"></up-icon>
|
</view>
|
</view>
|
|
<!-- 日期选择器 -->
|
<up-popup :show="showDatePicker" mode="bottom" @close="showDatePicker = false">
|
<up-datetime-picker
|
:show="true"
|
v-model="dateValue"
|
@confirm="onDateConfirm"
|
@cancel="showDatePicker = false"
|
mode="date"
|
/>
|
</up-popup>
|
|
<!-- 数据列表 -->
|
<view class="stock-list" v-if="tableData.length > 0">
|
<view v-for="(item, index) in tableData" :key="item.id" class="stock-item">
|
<view class="item-header">
|
<view class="item-left">
|
<view class="batch-icon">
|
<u-icon name="file-text" size="16" color="#ffffff"></u-icon>
|
</view>
|
<text class="batch-text">{{ item.inboundBatches || '未知批次' }}</text>
|
</view>
|
<view class="item-right">
|
<text class="time-text">{{ item.inboundDate || item.createTime || '-' }}</text>
|
</view>
|
</view>
|
<u-divider></u-divider>
|
|
<view class="item-details">
|
<view class="detail-row">
|
<text class="detail-label">供应商名称</text>
|
<text class="detail-value">{{ item.supplierName || '未知供应商' }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">产品大类</text>
|
<text class="detail-value">{{ item.productCategory || '-' }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">规格型号</text>
|
<text class="detail-value">{{ item.specificationModel || '-' }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">入库数量</text>
|
<text class="detail-value highlight">{{ item.inboundNum || 0 }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">含税单价</text>
|
<text class="detail-value">¥{{ item.taxInclusiveUnitPrice || 0 }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">含税总价</text>
|
<text class="detail-value price">¥{{ item.taxInclusiveTotalPrice || 0 }}</text>
|
</view>
|
</view>
|
|
<view class="item-actions">
|
<u-button type="primary" size="small" @click="openForm(item)">领用</u-button>
|
<u-button type="warning" size="small" plain @click="handleOut">导出</u-button>
|
</view>
|
</view>
|
</view>
|
|
<view v-else class="no-data">
|
<text>暂无数据</text>
|
</view>
|
|
<!-- 加载更多 -->
|
<view class="load-more" v-if="tableData.length > 0">
|
<u-loadmore :status="loadStatus" @loadmore="loadMore" />
|
</view>
|
|
<!-- 出库表单弹窗 -->
|
<u-popup
|
v-model="dialogFormVisible"
|
mode="center"
|
:closeable="true"
|
@close="closeDia"
|
round="10"
|
>
|
<view class="popup-content">
|
<view class="popup-header">
|
<text class="popup-title">新增出库</text>
|
</view>
|
|
<view class="popup-body">
|
<u-form :model="form" :rules="rules" ref="formRef" labelWidth="80">
|
<u-form-item label="出库数量" prop="inboundQuantity" borderBottom>
|
<u-input
|
v-model="form.inboundQuantity"
|
placeholder="请输入出库数量"
|
type="number"
|
border="none"
|
/>
|
</u-form-item>
|
|
<u-form-item label="出库日期" prop="inboundTime" borderBottom>
|
<u-input
|
v-model="form.inboundTime"
|
placeholder="请选择出库日期"
|
border="none"
|
readonly
|
@click="showOutDatePicker = true"
|
>
|
<template #suffix>
|
<u-icon name="calendar" size="18"></u-icon>
|
</template>
|
</u-input>
|
</u-form-item>
|
|
<u-form-item label="出库人" prop="nickName" borderBottom>
|
<u-select
|
v-model="form.nickName"
|
:list="userList"
|
labelName="nickName"
|
valueName="userId"
|
placeholder="请选择出库人"
|
></u-select>
|
</u-form-item>
|
</u-form>
|
</view>
|
|
<view class="popup-footer">
|
<u-button type="primary" @click="submitForm" size="normal">确认</u-button>
|
<u-button @click="closeDia" size="normal" plain>取消</u-button>
|
</view>
|
</view>
|
</u-popup>
|
|
<!-- 出库日期选择器 -->
|
<u-datetime-picker
|
v-model="form.inboundTime"
|
:show="showOutDatePicker"
|
mode="date"
|
@confirm="showOutDatePicker = false"
|
@cancel="showOutDatePicker = false"
|
/>
|
</view>
|
</template>
|
|
<script setup>
|
import { ref, reactive, toRefs, onMounted, getCurrentInstance, nextTick } from 'vue'
|
import dayjs from 'dayjs'
|
import PageHeader from '@/components/PageHeader.vue'
|
import useUserStore from '@/store/modules/user'
|
import { formatDateToYMD } from '@/utils/ruoyi'
|
import { userListNoPageByTenantId } from "@/api/system/user.js";
|
import { getInPageByCustom } from "@/api/inventoryManagement/stockIn.js";
|
import { stockOut } from "@/api/inventoryManagement/stockManage.js";
|
|
const userStore = useUserStore()
|
const { proxy } = getCurrentInstance()
|
|
// 返回上一页
|
const goBack = () => {
|
uni.navigateBack()
|
}
|
|
// 打开日期选择器(简单可靠)
|
const openDatePickerHandler = () => {
|
// 若已有选中日期,用它初始化;否则用今天
|
dateValue.value = searchForm.value.timeStr
|
? dayjs(searchForm.value.timeStr, 'YYYY-MM-DD').valueOf()
|
: Date.now()
|
showDatePicker.value = true
|
}
|
|
// 日期选择确认(与其他页一致:拿时间戳 -> YYYY-MM-DD)
|
const onDateConfirm = (e) => {
|
searchForm.value.timeStr = formatDateToYMD(e.value)
|
showDatePicker.value = false
|
handleQuery()
|
}
|
|
// 响应式数据
|
const tableData = ref([])
|
const userList = ref([])
|
const tableLoading = ref(false)
|
const dialogFormVisible = ref(false)
|
const showDatePicker = ref(false)
|
const showOutDatePicker = ref(false)
|
const loadStatus = ref('loadmore')
|
const dateValue = ref(new Date().getTime())
|
|
const page = reactive({
|
current: 1,
|
size: 10,
|
})
|
|
const total = ref(0)
|
const currentRowId = ref(null)
|
const currentRowNum = ref(0)
|
|
const data = reactive({
|
searchForm: {
|
supplierName: '',
|
timeStr: '',
|
},
|
form: {
|
inboundQuantity: '',
|
inboundTime: '',
|
nickName: '',
|
},
|
rules: {
|
inboundQuantity: [
|
{ required: true, message: '请输入出库数量', trigger: 'blur' },
|
{
|
validator: (rule, value, callback) => {
|
if (value && (value <= 0 || value > currentRowNum.value)) {
|
callback(new Error('请填入有效数字'))
|
} else {
|
callback()
|
}
|
},
|
trigger: 'blur'
|
}
|
],
|
inboundTime: [
|
{ required: true, message: '请选择出库日期', trigger: 'change' }
|
],
|
nickName: [
|
{ required: true, message: '请选择出库人', trigger: 'change' }
|
]
|
}
|
})
|
|
const { searchForm, form, rules } = toRefs(data)
|
|
|
|
// 查询列表
|
const handleQuery = () => {
|
page.current = 1
|
getList()
|
}
|
|
const getList = () => {
|
tableLoading.value = true
|
const params = {
|
...page,
|
supplierName: searchForm.value.supplierName,
|
timeStr: searchForm.value.timeStr
|
}
|
|
getInPageByCustom(params).then(res => {
|
tableLoading.value = false
|
if (page.current === 1) {
|
tableData.value = res.data.records || []
|
} else {
|
tableData.value = [...tableData.value, ...(res.data.records || [])]
|
}
|
total.value = res.data.total || 0
|
|
// 更新加载状态
|
if (tableData.value.length >= total.value) {
|
loadStatus.value = 'nomore'
|
} else {
|
loadStatus.value = 'loadmore'
|
}
|
}).catch(() => {
|
tableLoading.value = false
|
loadStatus.value = 'error'
|
})
|
}
|
|
// 加载更多
|
const loadMore = () => {
|
if (loadStatus.value === 'nomore') return
|
|
loadStatus.value = 'loading'
|
page.current++
|
getList()
|
}
|
|
// 打开出库表单
|
const openForm = async (row) => {
|
dialogFormVisible.value = true
|
currentRowId.value = row.id
|
currentRowNum.value = row.inboundNum || 0
|
|
// 初始化表单数据
|
form.value = {
|
inboundQuantity: '',
|
inboundTime: getCurrentDate(),
|
nickName: '',
|
}
|
|
// 加载用户列表
|
try {
|
const userLists = await userListNoPageByTenantId()
|
userList.value = userLists.data.map(item => ({
|
...item,
|
label: item.nickName,
|
value: item.userId
|
}))
|
} catch (error) {
|
console.error('加载用户列表失败:', error)
|
userList.value = []
|
}
|
}
|
|
// 提交表单
|
const submitForm = () => {
|
proxy.$refs.formRef.validate().then(valid => {
|
if (valid && currentRowId.value) {
|
const outData = {
|
id: currentRowId.value,
|
salesLedgerProductId: 0,
|
quantity: form.value.inboundQuantity,
|
time: form.value.inboundTime,
|
userId: form.value.nickName,
|
type: 3 // 自定义出库类型
|
}
|
|
stockOut(outData).then(res => {
|
uni.$u.toast('提交成功')
|
closeDia()
|
getList()
|
}).catch(err => {
|
uni.$u.toast('出库失败')
|
})
|
}
|
}).catch(err => {
|
console.log('表单验证失败:', err)
|
})
|
}
|
|
// 关闭弹窗
|
const closeDia = () => {
|
dialogFormVisible.value = false
|
proxy.$refs.formRef.resetFields()
|
}
|
|
// 导出
|
const handleOut = () => {
|
uni.showModal({
|
title: '导出',
|
content: '是否确认导出?',
|
success: (res) => {
|
if (res.confirm) {
|
proxy.download("/stockin/exportTwo", {}, '自定义出库台账.xlsx')
|
}
|
}
|
})
|
}
|
|
// 获取当前日期
|
function getCurrentDate() {
|
const today = new Date()
|
const year = today.getFullYear()
|
const month = String(today.getMonth() + 1).padStart(2, '0')
|
const day = String(today.getDate()).padStart(2, '0')
|
return `${year}-${month}-${day}`
|
}
|
|
onMounted(() => {
|
getList()
|
})
|
</script>
|
|
<style scoped lang="scss">
|
.stock-out-page {
|
min-height: 100vh;
|
background: #f5f5f5;
|
padding-bottom: 80px;
|
}
|
|
.search-section {
|
background: #fff;
|
padding: 16px;
|
margin-bottom: 12px;
|
}
|
|
.search-bar {
|
display: flex;
|
align-items: center;
|
gap: 12px;
|
margin-bottom: 12px;
|
}
|
|
.search-input {
|
flex: 1;
|
}
|
|
.search-button {
|
width: 44px;
|
height: 44px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
background: #f5f5f5;
|
border-radius: 8px;
|
}
|
|
.date-filter {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
padding: 12px 16px;
|
background: #f5f5f5;
|
border-radius: 8px;
|
}
|
|
.date-text {
|
font-size: 14px;
|
color: #666;
|
}
|
|
.stock-list {
|
padding: 0 16px;
|
}
|
|
.stock-item {
|
background: #fff;
|
border-radius: 12px;
|
padding: 16px;
|
margin-bottom: 12px;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
}
|
|
.item-header {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 12px;
|
}
|
|
.item-left {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
}
|
|
.batch-icon {
|
width: 32px;
|
height: 32px;
|
background: #2979ff;
|
border-radius: 8px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.batch-text {
|
font-size: 14px;
|
font-weight: 500;
|
color: #333;
|
}
|
|
.time-text {
|
font-size: 12px;
|
color: #999;
|
}
|
|
.item-details {
|
margin: 12px 0;
|
}
|
|
.detail-row {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
padding: 8px 0;
|
}
|
|
.detail-label {
|
font-size: 14px;
|
color: #666;
|
}
|
|
.detail-value {
|
font-size: 14px;
|
color: #333;
|
text-align: right;
|
flex: 1;
|
margin-left: 12px;
|
}
|
|
.detail-value.highlight {
|
color: #2979ff;
|
font-weight: 500;
|
}
|
|
.detail-value.price {
|
color: #ff6b00;
|
font-weight: 500;
|
}
|
|
.item-actions {
|
display: flex;
|
gap: 12px;
|
margin-top: 12px;
|
padding-top: 12px;
|
border-top: 1px solid #f5f5f5;
|
}
|
|
.no-data {
|
text-align: center;
|
padding: 60px 0;
|
color: #999;
|
font-size: 14px;
|
}
|
|
.load-more {
|
padding: 20px 16px;
|
}
|
|
.popup-content {
|
width: 600rpx;
|
background: #ffffff;
|
border-radius: 20rpx;
|
|
.popup-header {
|
padding: 40rpx 30rpx 20rpx;
|
text-align: center;
|
border-bottom: 1rpx solid #f0f0f0;
|
|
.popup-title {
|
font-size: 32rpx;
|
font-weight: bold;
|
color: #333;
|
}
|
}
|
|
.popup-body {
|
padding: 30rpx;
|
}
|
|
.popup-footer {
|
padding: 30rpx;
|
display: flex;
|
gap: 20rpx;
|
border-top: 1rpx solid #f0f0f0;
|
|
.u-button {
|
flex: 1;
|
}
|
}
|
}
|
</style>
|