shop-web/src/pages/approval/handleApply.vue

551 lines
18 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.

<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { showConfirmDialog, showDialog, UploaderFileListItem, Popup, Picker, Field, Stepper } from 'vant'
import axios from "axios"
import { allocateApprovalGoods, getApprovalGoodsCellApi, handleApprovalApi, handleApprovalAssetApi, openCabinetApi } from '@/common/apis/approval'
import type { ApprovalGoodsCellEntity, ApprovalGoodsEntity, HandleApprovalAssetRequestData, HandleApprovalRequestData, ReturnApprovalDetailDTO } from '@/common/apis/approval/type'
import { useRoute, useRouter } from 'vue-router'
import { useApprovalStore } from '@/pinia/stores/approval'
import { useWxStore } from '@/pinia/stores/wx';
import { getApprovalDetailAssetApi } from '@/common/apis/approval';
import Compressor from 'compressorjs';
import { useAb98UserStore } from '@/pinia/stores/ab98-user';
import { ShopOrderGoodsEntity } from '@/common/apis/shop/type';
const { VITE_APP_BASE_API } = import.meta.env;
const router = useRouter()
const route = useRoute()
const approvalStore = useApprovalStore();
const wxStore = useWxStore();
const { openid, balance, corpidLogin, userid: qyUserid, name: qyName } = storeToRefs(wxStore);
const ab98UserStore = useAb98UserStore();
const { tel, userid: ab98Userid, name } = storeToRefs(ab98UserStore);
const detailData = ref<ReturnApprovalDetailDTO>();
const formData = ref<HandleApprovalAssetRequestData>({
approvalId: approvalStore.currentApproval?.approvalId || 0,
status: 2,
returnAmount: approvalStore.currentApproval?.goodsPrice || 0,
auditImages: '',
auditRemark: '',
userid: wxStore.userid,
corpid: wxStore.corpid,
auditUserid: wxStore.userid,
approvalGoodsList: []
})
const orderGoods = ref<ApprovalGoodsEntity[]>([]);
const submitting = ref(false)
const fileList = ref<UploaderFileListItem[]>([])
const uploading = ref(false)
const isButtonDisabled = ref(false)
const showStatusPicker = ref(false)
const showPreview = ref(false)
const currentPreviewImage = ref('')
const statusMap: { [key: number]: string } = {
1: '待审核',
2: '已通过',
3: '已驳回'
}
const statusOptions = [
{ text: '待审核', value: 1 },
{ text: '通过', value: 2 },
{ text: '驳回', value: 3 }
]
const validateForm = () => {
if (!formData.value.approvalId || isNaN(formData.value.approvalId)) {
showConfirmDialog({ title: '错误', message: '审批单ID参数错误' })
return false
}
if (formData.value.status === 3 && !formData.value.auditRemark) {
showConfirmDialog({ title: '提示', message: '驳回时必须填写审核说明' })
return false
}
// 验证审批数量
if (detailData.value?.status === 1) {
for (const item of orderGoods.value) {
if (item.approvalQuantity != null && item.approvalQuantity < 0) {
showConfirmDialog({ title: '提示', message: `商品${item.goodsName}的审批数量不能为负数` })
return false
}
if (item.approvalQuantity != null && item.approvalQuantity > item.applyQuantity) {
showConfirmDialog({ title: '提示', message: `商品${item.goodsName}的审批数量不能超过申请数量` })
return false
}
}
}
return true
}
const onStatusConfirm = ({ selectedOptions }: { selectedOptions: { value: number }[] }) => {
submitting.value = true
try {
if (!selectedOptions?.[0]?.value) {
throw new Error('请选择有效的审批状态')
}
formData.value.status = selectedOptions[0].value
showStatusPicker.value = false
} catch (error) {
showConfirmDialog({
title: '操作失败',
message: error instanceof Error ? error.message : '状态选择异常'
})
} finally {
submitting.value = false
}
}
const previewImage = (url: string) => {
currentPreviewImage.value = url
showPreview.value = true
}
const approvalGoodsCells = ref<ApprovalGoodsCellEntity[]>([]);
const approvalGoodsCellsWithNames = computed(() => {
return approvalGoodsCells.value.map(cell => {
const goods = orderGoods.value.find(g => g.approvalGoodsId === cell.approvalGoodsId);
return {
...cell,
goodsName: goods ? goods.goodsName : ''
};
});
});
const fetchApprovalDetail = async () => {
const approvalId = approvalStore.currentApproval?.approvalId
if (!approvalId) {
router.push('/approvalAsset/list');
return;
}
try {
const result = await getApprovalDetailAssetApi(approvalId);
if (result.code === 0) {
detailData.value = result.data;
orderGoods.value = (result.data.goodsList || []).map(item => ({
...item,
approvalQuantity: result.data.status === 1 ? item.applyQuantity : item.approvalQuantity
}));
if (result.data.status !== 1) {
formData.value = {
...formData.value,
corpid: wxStore.corpid,
status: result.data.status,
returnAmount: result.data.returnAmount,
auditRemark: result.data.auditRemark,
auditImages: result.data.auditImages
};
approvalGoodsCells.value = result.data.approvalGoodsCellList || [];
}
} else {
showDialog({ title: '错误', message: result.msg || '获取审批详情失败' });
}
} catch (error) {
console.error('获取审批详情失败:', error);
showDialog({ title: '错误', message: '获取审批详情失败' });
}
}
onMounted(async () => {
await fetchApprovalDetail();
})
const handleOpenCell = async (approvalGoodsCellId: number) => {
if (!approvalGoodsCellId) {
showDialog({ title: '错误', message: '缺少格口信息' });
return;
}
isButtonDisabled.value = true;
try {
const isInternal = corpidLogin.value ? 2 : ab98Userid.value ? 1 : 0;
const data = {
userid: qyUserid.value,
isInternal: isInternal,
name: isInternal === 2 ? qyName.value : name.value,
mobile: tel.value,
operationType: 2
};
const result = await openCabinetApi(approvalGoodsCellId, data);
if (result.code !== 0) {
showDialog({ title: '错误', message: result.msg || '开启格口失败' });
return;
}
showDialog({ message: '格口已开启' });
} catch (error) {
showDialog({ title: '错误', message: '请求失败' });
} finally {
setTimeout(() => {
isButtonDisabled.value = false;
}, 5000);
}
};
const handleSubmit = async () => {
if (!validateForm()) return
submitting.value = true
try {
formData.value.userid = wxStore.userid;
formData.value.corpid = wxStore.corpid;
formData.value.auditUserid = wxStore.userid;
const { code, msg } = await handleApprovalAssetApi(formData.value)
if (code === 0) {
showDialog({ message: '操作成功' })
await showConfirmDialog({
title: "操作成功",
message: `审批处理已完成`
})
router.push('/approvalAsset/list')
} else {
console.error('操作失败code:', code, 'msg:', msg)
showConfirmDialog({
title: '操作失败',
message: msg || '操作失败'
})
}
} catch (error) {
console.error('提交失败:', error)
showConfirmDialog({
title: '提交失败',
message: error instanceof Error ? error.message : '网络请求异常'
})
} finally {
submitting.value = false
}
}
const handleComfirm = () => {
formData.value.status = 2;
handleSubmit();
}
const handleAllocateQuantity = async () => {
if (!validateForm()) return
// 构建approvalGoodsList
formData.value.approvalGoodsList = orderGoods.value.map(item => ({
approvalGoodsId: item.approvalGoodsId || 0,
approvalId: formData.value.approvalId,
goodsId: item.goodsId,
goodsName: item.goodsName,
externalGoodsId: item.externalGoodsId,
corpid: item.corpid,
belongType: item.belongType,
price: item.price,
applyQuantity: item.applyQuantity,
approvalQuantity: item.approvalQuantity,
coverImg: item.coverImg
}))
submitting.value = true
try {
formData.value.userid = wxStore.userid;
formData.value.corpid = wxStore.corpid;
formData.value.auditUserid = wxStore.userid;
const { code, msg } = await allocateApprovalGoods(formData.value)
if (code === 0) {
showDialog({ message: '数量确定成功' })
await fetchApprovalDetail();
} else {
console.error('操作失败code:', code, 'msg:', msg)
showConfirmDialog({
title: '操作失败',
message: msg || '数量确定失败'
})
}
} catch (error) {
console.error('提交失败:', error)
showConfirmDialog({
title: '提交失败',
message: error instanceof Error ? error.message : '网络请求异常'
})
} finally {
submitting.value = false
}
}
const handleReject = () => {
formData.value.status = 3;
handleSubmit();
}
</script>
<template>
<div class="approval-container">
<van-nav-bar title="审批处理" left-text="返回" left-arrow fixed @click-left="() => router.go(-1)" />
<div class="content-wrapper">
<van-cell-group>
<van-cell title="申请人" :value="detailData?.applyUserName" />
<van-cell title="当前状态" :value="statusMap[detailData?.status || 1]" />
</van-cell-group>
<van-cell-group class="product-list">
<template v-for="item in orderGoods" :key="item.goodsId">
<van-cell class="product-item">
<template #icon>
<van-image :src="item.coverImg" width="60" height="60" class="product-image" />
</template>
<div class="product-info">
<div class="product-name van-ellipsis">
{{ item.goodsName }}
</div>
<div class="price-row">
<span class="product-price">
¥{{ item.price.toFixed(2) }}
</span>
<span class="quantity">
申请数量:{{ item.applyQuantity }}
</span>
</div>
<!-- 审批数量输入框 -->
<div v-if="detailData?.status === 1" class="approval-quantity">
<span class="label">审批数量:</span>
<van-stepper v-model.number="item.approvalQuantity" :min="0" :max="item.applyQuantity"
integer style="width: 180px;" />
</div>
<!-- 查看审批数量 -->
<div v-if="detailData?.status === 2 || detailData?.status === 4"
class="approval-quantity-view">
<span class="label">审批数量:</span>
<span class="value">{{ item.approvalQuantity }}</span>
</div>
</div>
</van-cell>
</template>
<div class="confirm-quantity-container">
<van-button class="confirm-quantity-btn" v-if="detailData?.status === 1" type="primary"
@click="handleAllocateQuantity" :loading="submitting" plain hairline size="small">
确定审批数量
</van-button>
</div>
</van-cell-group>
<van-cell-group class="approval-goods-list" v-if="approvalGoodsCells.length > 0">
<van-cell title="审批通过商品" class="section-title" />
<template v-for="item in approvalGoodsCellsWithNames" :key="item.approvalGoodsCellId">
<van-cell class="product-item">
<div class="product-info">
<div class="product-name van-ellipsis">
{{ item.goodsName }}
</div>
<div class="van-ellipsis">
{{ item.shopName }}
</div>
<div class="van-ellipsis">
{{ item.cabinetName }} - 格口{{ item.cellNo }}
</div>
<div class="van-ellipsis">
分配数量:{{ item.allocateQuantity }}
</div>
<div v-if="detailData?.status === 4" class="open-cell-btn">
<van-button type="primary" size="small"
@click="handleOpenCell(item.approvalGoodsCellId)" :disabled="isButtonDisabled">
打开格口
</van-button>
</div>
</div>
</van-cell>
</template>
</van-cell-group>
<van-cell-group>
<!-- 原有表单字段保持不变 -->
<!-- <van-button type="primary" size="small" @click="handleOpenCabinet" :disabled="isButtonDisabled"
style="margin-bottom: 12px">
打开柜子
</van-button> -->
<van-field v-model="formData.auditRemark" label="审核说明" type="textarea" rows="2" autosize
class="audit-remark-field" />
</van-cell-group>
<van-popup v-model:show="showPreview" position="center" :style="{ width: '90%', height: '80%' }" round>
<div class="preview-wrapper">
<van-icon name="cross" class="close-icon" @click="showPreview = false" />
<van-image :src="currentPreviewImage" fit="contain" class="preview-image" />
</div>
</van-popup>
<div class="btn-group" v-if="detailData?.status === 1 || detailData?.status === 4">
<van-button type="danger" :loading="submitting" loading-text="提交中..." @click="handleReject"
style="flex: 1; height: 44px; border-radius: 8px; font-size: 16px;">
拒绝
</van-button>
<van-button type="primary" :disabled="detailData?.status === 1" :loading="submitting"
loading-text="提交中..." @click="handleComfirm"
style="flex: 1; height: 44px; border-radius: 8px; font-size: 16px;">
同意
</van-button>
</div>
</div>
</div>
</template>
<style scoped>
.approval-container {
padding: 12px 16px;
}
.content-wrapper {
padding-top: 46px;
padding-bottom: 100px;
}
.product-list {
margin-bottom: 20px;
}
.product-item {
align-items: flex-start;
}
.product-image {
margin-right: 12px;
border-radius: 4px;
}
.product-info {
display: flex;
flex-direction: column;
justify-content: space-between;
height: auto;
padding: 5px 0;
.product-name {
font-size: 14px;
color: #333;
font-weight: 500;
}
}
.approval-quantity,
.approval-quantity-view {
margin-top: 8px;
display: flex;
align-items: center;
}
.approval-quantity-view .label {
color: #666;
margin-right: 8px;
}
.approval-quantity-view .value {
/* font-weight: bold; */
}
.approval-quantity-view {
justify-content: flex-end;
}
.price-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.product-price {
color: #e95d5d;
font-weight: bold;
font-size: 14px;
}
.quantity {
color: #666;
/* margin-right: 20px;
font-size: 13px; */
}
.upload-section {
margin: 20px 0;
}
.preview-wrapper {
position: relative;
height: 100%;
padding: 20px;
.close-icon {
position: absolute;
top: 10px;
right: 10px;
z-index: 1;
font-size: 24px;
color: #fff;
background: rgba(0, 0, 0, 0.3);
border-radius: 50%;
padding: 4px;
}
.preview-image {
width: 100%;
height: 100%;
}
}
.btn-group {
display: flex;
width: 100%;
gap: 12px;
margin: 8px 0;
background-color: #ffffff;
padding: 16px;
}
.confirm-quantity-container {
display: flex;
justify-content: flex-end;
margin-top: 8px;
}
.confirm-quantity-btn {
margin-bottom: 8px;
margin-right: 8px;
height: 36px;
width: 140px;
border-radius: 8px;
font-size: 16px;
padding: 0 20px;
}
.content-wrapper {
padding-bottom: 100px;
}
.audit-remark-field::v-deep .van-field__control {
background-color: #ecf3fc;
padding: 8px;
border-radius: 4px;
}
.clickable-status-field:not(:disabled)::v-deep .van-field__control {
cursor: pointer;
transition: all 0.3s ease;
background-color: #f7f8fa;
}
.clickable-status-field:not(:disabled):hover::v-deep .van-field__control {
background-color: #f2f3f5;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
.clickable-status-field:not(:disabled):active::v-deep .van-field__control {
background-color: #ebedf0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
</style>