You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
crmeb/app/pages/supply_chain/material_receipt/index.vue

897 lines
21 KiB

<template>
<z-paging ref="paging" v-model="receiptRecords" @query="queryList" :layout-only="activeTab === 'form'">
<template #top>
<!-- 顶部标签我要领用 / 领用记录 -->
<view class="tabs">
<view
class="tab"
:class="activeTab === 'form' ? 'active' : ''"
@click="activeTab = 'form'"
>
我要领用
</view>
<view
class="tab"
:class="activeTab === 'list' ? 'active' : ''"
@click="switchToList"
>
领用记录
</view>
</view>
</template>
<!-- 表单区域 -->
<view class="tab-content" v-show="activeTab === 'form'">
<view class="form-card">
<view class="form-item">
<text class="label">物资选择</text>
<view class="multi-select">
<view class="selected-items" v-if="selectedMaterials.length > 0">
<view class="selected-item" v-for="(item, index) in selectedMaterials" :key="item.cargoId">
<text>{{ item.cargoName }} ({{ item.cargoSpec }})</text>
<text class="remove-btn" @click="removeMaterial(index)">×</text>
</view>
</view>
<view class="select-btn" @click="showMaterialSelector">
{{ selectedMaterials.length > 0 ? '已选择 ' + selectedMaterials.length + ' 项' : '请选择物资' }}
</view>
</view>
</view>
<view class="form-item">
<text class="label">领用用途</text>
<textarea
class="textarea"
v-model="receiptForm.purpose"
placeholder="请输入领用用途"
maxlength="200"
:auto-height="true"
/>
<text class="count">{{ receiptForm.purpose.length }}/200</text>
</view>
<!-- <view class="form-item">
<text class="label">领用人</text>
<input
class="input"
type="text"
v-model="receiptForm.applicant"
placeholder="请输入领用人姓名"
/>
</view> -->
</view>
<view class="submit-section">
<view class="submit-btn" @click="submitReceipt"></view>
</view>
</view>
<!-- 物资选择弹窗 -->
<uni-popup ref="materialPopup" type="bottom" :animation="false">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">选择物资</text>
<text class="popup-close" @click="closeMaterialSelector">×</text>
</view>
<view class="popup-body">
<view class="search-box">
<input type="text" v-model="searchKeyword" placeholder="搜索物资名称或规格" class="search-input" />
</view>
<view class="material-list">
<view
class="material-item"
v-for="material in filteredMaterials"
:key="material.cargoId"
@click="toggleMaterial(material)"
>
<view class="material-info">
<text class="material-name">{{ material.cargoName }}</text>
<text class="material-spec">{{ material.cargoSpec }}</text>
</view>
<view class="checkbox" :class="{ checked: isMaterialSelected(material.cargoId) }">
<text v-if="isMaterialSelected(material.cargoId)"></text>
</view>
</view>
</view>
</view>
<view class="popup-footer">
<view class="footer-btn cancel" @click="closeMaterialSelector"></view>
<view class="footer-btn confirm" @click="confirmMaterialSelection"></view>
</view>
</view>
</uni-popup>
<!-- 列表区域 -->
<view class="tab-content" v-show="activeTab === 'list'">
<view
class="record-card"
v-for="item in receiptRecords"
:key="item.id"
>
<view class="card-header">
<view class="left">
<text class="type">领用单</text>
<text
class="status"
:class="getStatusClass(item)"
>
{{ getStatusText(item) }}
</text>
</view>
<view class="header-right">
<text class="time">
{{ item.billDate || '' }}
</text>
<text class="bill-number">
{{ item.billNumber || '' }}
</text>
</view>
</view>
<view class="card-body">
<view class="row">
<text class="label">领用用途</text>
<text class="value">
{{ item.purpose || '—' }}
</text>
</view>
<view class="row">
<text class="label">领用人</text>
<text class="value">
{{ item.applicant || '—' }}
</text>
</view>
<view class="row" v-if="item.ckBillCargos && item.ckBillCargos.length > 0">
<text class="label">领用物资</text>
<text class="value">
<text v-for="(cargo, index) in item.ckBillCargos" :key="cargo.id">
{{ cargo.cargoName }} ({{ cargo.cargoSpec }}){{ index < item.ckBillCargos.length - 1 ? '、' : '' }}
</text>
</text>
</view>
</view>
<view class="card-footer">
<view class="action-buttons">
<view class="action-btn cancel-btn" v-if="item.cancelStatus === '0'" @click="cancelBill(item.id)"></view>
<view class="action-btn voucher-btn" @click="viewVoucher(item)"></view>
</view>
</view>
</view>
</view>
<!-- 凭证弹窗 -->
<uni-popup ref="voucherPopup" type="center" :animation="false">
<view class="voucher-popup">
<view class="voucher-header">
<text class="voucher-title">领用凭证</text>
<text class="voucher-close" @click="closeVoucherPopup">×</text>
</view>
<view class="voucher-body">
<view class="voucher-info">
<text class="voucher-label">单据编号</text>
<text class="voucher-value">{{ currentVoucher ? currentVoucher.billNumber : '' }}</text>
</view>
<view class="qrcode-container">
<canvas canvas-id="qrcode" :style="{width: `${qrcodeSize}px`, height: `${qrcodeSize}px`}" style="opacity: 0;" />
</view>
</view>
<view class="voucher-footer">
<view class="footer-btn confirm" @click="closeVoucherPopup"></view>
</view>
</view>
</uni-popup>
</z-paging>
</template>
<script>
import uQRCode from '@/js_sdk/Sansnn-uQRCode/uqrcode.js';
import { listCkstock, quickOutBill, stockPageList, cancelBill } from '@/api/property.js';
export default {
data() {
return {
activeTab: 'form',
receiptForm: {
purpose: '',
applicant: ''
},
statusClassMap: {
'待审批': 'status-pending',
'已审批': 'status-approved',
'已拒绝': 'status-rejected',
'已取消': 'status-canceled',
'__default__': 'status-pending'
},
materials: [],
selectedMaterials: [],
searchKeyword: '',
tempSelectedMaterials: [],
receiptRecords: [],
currentVoucher: null,
qrcodeSize: 200
};
},
onLoad() {
if (this.activeTab === 'list') {
this.refreshList();
}
},
onShow() {
// 页面显示时获取物资列表
this.getMaterialsList();
},
computed: {
filteredMaterials() {
if (!this.searchKeyword) {
return this.materials;
}
return this.materials.filter(item =>
item.cargoName.includes(this.searchKeyword) ||
item.cargoSpec.includes(this.searchKeyword)
);
}
},
methods: {
switchToList() {
this.activeTab = 'list';
if (!this.receiptRecords.length) {
this.$refs.paging.reload();
}
},
// 获取物资列表
getMaterialsList() {
listCkstock().then(res => {
if (res.code === 200) {
this.materials = res.data;
}
});
},
// 显示物资选择器
showMaterialSelector() {
// 重置临时选择列表
this.tempSelectedMaterials = [...this.selectedMaterials];
this.searchKeyword = '';
// 打开弹窗
this.$refs.materialPopup.open();
},
// 关闭物资选择器
closeMaterialSelector() {
this.$refs.materialPopup.close();
},
// 切换物资选择状态
toggleMaterial(material) {
const index = this.tempSelectedMaterials.findIndex(item => item.cargoId === material.cargoId);
if (index > -1) {
this.tempSelectedMaterials.splice(index, 1);
} else {
// 添加数量字段
this.tempSelectedMaterials.push({ ...material, quantity: 1 });
}
},
// 检查物资是否已选择
isMaterialSelected(cargoId) {
return this.tempSelectedMaterials.some(item => item.cargoId === cargoId);
},
// 确认物资选择
confirmMaterialSelection() {
this.selectedMaterials = [...this.tempSelectedMaterials];
this.closeMaterialSelector();
},
// 移除已选物资
removeMaterial(index) {
this.selectedMaterials.splice(index, 1);
},
submitReceipt() {
if (this.selectedMaterials.length === 0 || !this.receiptForm.purpose || !this.receiptForm.applicant) {
uni.showToast({
title: '请填写完整信息',
icon: 'none'
});
return;
}
// 准备提交数据
const items = this.selectedMaterials.map(item => ({
cargoId: item.cargoId,
quantity: item.quantity
}));
const submitData = {
purpose: this.receiptForm.purpose,
items: items
};
// 调用接口提交领用申请
quickOutBill(submitData).then(res => {
if (res.code === 200) {
uni.showToast({
title: '领用申请提交成功',
icon: 'success'
});
// 重置表单
setTimeout(() => {
this.receiptForm = {
purpose: '',
applicant: ''
};
this.selectedMaterials = [];
},
1500);
// 如果当前在列表页,刷新列表
if (this.activeTab === 'list') {
this.$refs.paging.reload();
}
} else {
uni.showToast({
title: res.message || '提交失败',
icon: 'none'
});
}
});
},
// 获取状态文本
getStatusText(item) {
if (item.cancelStatus === '1') {
return '已取消';
}
switch (item.auditStatus) {
case '0':
return '待审批';
case '1':
return '已审批';
case '2':
return '已拒绝';
default:
return '待审批';
}
},
// 获取状态样式
getStatusClass(item) {
const statusText = this.getStatusText(item);
return this.statusClassMap[statusText] || this.statusClassMap['__default__'];
},
// 取消单据
cancelBill(id) {
uni.showModal({
title: '取消单据',
content: '确定要取消此单据吗?',
confirmText: '确定',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
cancelBill(id).then(response => {
if (response.code === 200) {
uni.showToast({
title: '单据取消成功',
icon: 'success'
});
// 刷新列表
this.$refs.paging.reload();
} else {
uni.showToast({
title: response.message || '取消失败',
icon: 'none'
});
}
});
}
}
});
},
// 查看凭证
viewVoucher(item) {
this.currentVoucher = item;
// 打开弹窗
this.$refs.voucherPopup.open();
// 生成二维码
this.generateQRCode(item.billNumber);
},
// 关闭凭证弹窗
closeVoucherPopup() {
this.$refs.voucherPopup.close();
},
// 生成二维码
generateQRCode(billNumber) {
uQRCode.make({
canvasId: 'qrcode',
text: billNumber,
size: this.qrcodeSize,
margin: 10,
success: res => {
},
complete: () => {
},
fail: res => {
}
});
},
// 获取领用记录列表
queryList(pageNo, pageSize) {
stockPageList({ page: pageNo, limit: pageSize }).then(res => {
if (res.code === 200) {
const list = res.data.list;
// 将请求结果通过complete传给z-paging处理
this.$refs.paging.complete(list);
} else {
this.$refs.paging.complete([]);
}
});
},
refreshList() {
if (this.$refs.paging) {
this.$refs.paging.reload();
}
}
}
};
</script>
<style lang="scss">
.tabs {
padding: 0 30rpx;
display: flex;
margin-bottom: 15rpx;
background-color: #fff;
border-radius: 10rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
.tab {
flex: 1;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
color: #666;
position: relative;
&.active {
color: #409EFF;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background-color: #409EFF;
border-radius: 2rpx;
}
}
}
}
.tab-content {
padding: 0 30rpx;
.form-card {
background-color: #fff;
border-radius: 10rpx;
padding: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.08);
.form-item {
margin-bottom: 30rpx;
.label {
display: block;
font-size: 26rpx;
color: #333;
margin-bottom: 16rpx;
font-weight: 600;
}
.input {
width: 100%;
height: 72rpx;
border-radius: 10rpx;
border: 1rpx solid #e5e5e5;
padding: 0 20rpx;
font-size: 24rpx;
box-sizing: border-box;
}
.textarea {
width: 100%;
min-height: 160rpx;
border-radius: 10rpx;
border: 1rpx solid #e5e5e5;
padding: 20rpx;
font-size: 24rpx;
box-sizing: border-box;
line-height: 1.6;
}
.count {
display: block;
margin-top: 10rpx;
text-align: right;
font-size: 20rpx;
color: #999;
}
.multi-select {
.selected-items {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
margin-bottom: 12rpx;
.selected-item {
display: flex;
align-items: center;
background-color: #f0f9ff;
border: 1rpx solid #e6f7ff;
border-radius: 16rpx;
padding: 8rpx 16rpx;
font-size: 22rpx;
color: #409EFF;
.remove-btn {
margin-left: 8rpx;
font-size: 24rpx;
font-weight: bold;
cursor: pointer;
}
}
}
.select-btn {
width: 100%;
height: 72rpx;
border-radius: 10rpx;
border: 1rpx solid #e5e5e5;
padding: 0 20rpx;
display: flex;
align-items: center;
font-size: 24rpx;
color: #999;
box-sizing: border-box;
cursor: pointer;
}
}
}
}
.submit-section {
margin-top: 40rpx;
margin-bottom: 30rpx;
.submit-btn {
height: 88rpx;
background-color: #409EFF;
color: #fff;
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
font-weight: 600;
box-shadow: 0 4rpx 12rpx rgba(64, 158, 255, 0.35);
}
}
.record-card {
background-color: #fff;
border-radius: 10rpx;
padding: 24rpx 26rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.06);
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16rpx;
.left {
display: flex;
align-items: center;
.type {
font-size: 26rpx;
font-weight: 600;
color: #333;
margin-right: 16rpx;
}
.status {
padding: 6rpx 18rpx;
border-radius: 20rpx;
font-size: 20rpx;
}
.status-pending {
background-color: #E6F7FF;
color: #409EFF;
}
.status-approved {
background-color: #F6FFED;
color: #52C41A;
}
.status-rejected {
background-color: #FFF1F0;
color: #FF4D4F;
}
.status-canceled {
background-color: #F5F5F5;
color: #999;
}
}
.header-right {
display: flex;
align-items: center;
flex-direction: column;
align-items: flex-end;
.time {
font-size: 22rpx;
color: #999;
margin-bottom: 8rpx;
}
.bill-number {
font-size: 20rpx;
color: #666;
}
}
}
.card-body {
.row {
display: flex;
margin-bottom: 10rpx;
.label {
width: 140rpx;
font-size: 24rpx;
color: #666;
}
.value {
flex: 1;
font-size: 24rpx;
color: #333;
word-break: break-word;
}
}
}
.card-footer {
margin-top: 16rpx;
display: flex;
align-items: center;
justify-content: flex-end;
.action-buttons {
display: flex;
gap: 16rpx;
.action-btn {
padding: 8rpx 24rpx;
border-radius: 16rpx;
font-size: 22rpx;
cursor: pointer;
}
.cancel-btn {
background-color: #F5F5F5;
color: #666;
}
.voucher-btn {
background-color: #409EFF;
color: #fff;
}
}
}
}
}
/* 弹窗样式 */
.popup-content {
background-color: #fff;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
padding-bottom: 30rpx;
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.popup-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.popup-close {
font-size: 36rpx;
color: #999;
cursor: pointer;
}
}
.popup-body {
max-height: 60vh;
overflow-y: auto;
.search-box {
padding: 20rpx 30rpx;
.search-input {
width: 100%;
height: 64rpx;
border-radius: 32rpx;
border: 1rpx solid #e5e5e5;
padding: 0 24rpx;
font-size: 24rpx;
box-sizing: border-box;
background-color: #f9f9f9;
}
}
.material-list {
.material-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
cursor: pointer;
.material-info {
flex: 1;
.material-name {
display: block;
font-size: 26rpx;
color: #333;
margin-bottom: 8rpx;
}
.material-spec {
font-size: 22rpx;
color: #999;
}
}
.checkbox {
width: 36rpx;
height: 36rpx;
border: 2rpx solid #e5e5e5;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
color: #fff;
&.checked {
background-color: #409EFF;
border-color: #409EFF;
}
}
}
}
}
.popup-footer {
display: flex;
padding: 20rpx 30rpx 0;
gap: 20rpx;
.footer-btn {
flex: 1;
height: 72rpx;
border-radius: 36rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
font-weight: 600;
cursor: pointer;
}
.cancel {
background-color: #f5f5f5;
color: #666;
}
.confirm {
background-color: #409EFF;
color: #fff;
}
}
}
/* 凭证弹窗样式 */
.voucher-popup {
width: 80%;
background-color: #fff;
border-radius: 20rpx;
overflow: hidden;
.voucher-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.voucher-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.voucher-close {
font-size: 36rpx;
color: #999;
cursor: pointer;
}
}
.voucher-body {
padding: 40rpx 30rpx;
text-align: center;
.voucher-info {
margin-bottom: 30rpx;
.voucher-label {
display: block;
font-size: 24rpx;
color: #666;
margin-bottom: 12rpx;
}
.voucher-value {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
}
.qrcode-container {
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto;
}
}
.voucher-footer {
padding: 0 30rpx 30rpx;
.footer-btn {
width: 100%;
height: 72rpx;
border-radius: 36rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
font-weight: 600;
cursor: pointer;
}
.confirm {
background-color: #409EFF;
color: #fff;
}
}
}
</style>