feat(approval): 添加资产审批相关功能及界面优化
- 新增资产审批接口和类型定义 - 在审批页面添加审批数量控制和商品格口信息展示 - 优化审批表单验证逻辑和界面布局
This commit is contained in:
parent
fcee668ebe
commit
c433debb84
|
@ -1,5 +1,5 @@
|
|||
import { request } from '@/http/axios'
|
||||
import { SubmitApprovalRequestData, SubmitApprovalResponseData, SearchApiReturnApprovalQuery, ApiResponsePageData, ReturnApprovalEntity, HandleApprovalRequestData, SearchReturnApprovalAssetQuery, ReturnApprovalAssetDTO } from './type'
|
||||
import { SubmitApprovalRequestData, SubmitApprovalResponseData, SearchApiReturnApprovalQuery, ApiResponsePageData, ReturnApprovalEntity, HandleApprovalRequestData, SearchReturnApprovalAssetQuery, ReturnApprovalAssetDTO, HandleApprovalAssetRequestData, ApprovalGoodsCellEntity } from './type'
|
||||
import { ShopOrderGoodsEntity } from '../shop/type'
|
||||
|
||||
export const getApprovalListApi = (params: SearchApiReturnApprovalQuery) => {
|
||||
|
@ -49,6 +49,15 @@ export const handleApprovalApi = (data: HandleApprovalRequestData) => {
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
export const handleApprovalAssetApi = (data: HandleApprovalAssetRequestData) => {
|
||||
return request<ApiResponseMsgData<string>>({
|
||||
url: 'approval/handle/asset',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export const getApprovalOrderGoodsApi = (approvalId: number) => {
|
||||
return request<ApiResponseMsgData<ShopOrderGoodsEntity[]>>({
|
||||
url: 'approval/getApprovalOrderGoods',
|
||||
|
@ -56,3 +65,11 @@ export const getApprovalOrderGoodsApi = (approvalId: number) => {
|
|||
params: { approvalId }
|
||||
})
|
||||
}
|
||||
|
||||
export const getApprovalGoodsCellApi = (approvalId: number) => {
|
||||
return request<ApiResponseMsgData<ApprovalGoodsCellEntity[]>>({
|
||||
url: 'approval/getApprovalGoodsCell',
|
||||
method: 'get',
|
||||
params: { approvalId }
|
||||
})
|
||||
}
|
||||
|
|
|
@ -20,6 +20,11 @@ export interface HandleApprovalRequestData {
|
|||
auditUserid: string
|
||||
}
|
||||
|
||||
export interface HandleApprovalAssetRequestData extends HandleApprovalRequestData {
|
||||
/** 审批商品ID */
|
||||
approvalGoodsList: ApprovalGoodsEntity[]
|
||||
}
|
||||
|
||||
export interface SearchApiReturnApprovalQuery {
|
||||
pageNum: number
|
||||
pageSize: number
|
||||
|
@ -121,6 +126,7 @@ export interface ApprovalGoodsEntity {
|
|||
belongType: number;
|
||||
price: number;
|
||||
applyQuantity: number;
|
||||
approvalQuantity?: number;
|
||||
coverImg?: string;
|
||||
}
|
||||
|
||||
|
@ -143,6 +149,29 @@ export interface SearchReturnApprovalAssetQuery {
|
|||
codeCheck?: number;
|
||||
}
|
||||
|
||||
export interface ApprovalGoodsCellEntity {
|
||||
/** 主键ID */
|
||||
approvalGoodsCellId: number;
|
||||
/** 审批ID */
|
||||
approvalId: number;
|
||||
/** 申请领用商品ID */
|
||||
approvalGoodsId: number;
|
||||
/** 商店ID */
|
||||
shopId: number;
|
||||
/** 柜机ID */
|
||||
cabinetId: number;
|
||||
/** 格口ID */
|
||||
cellId: number;
|
||||
/** 分配数量 */
|
||||
allocateQuantity: number;
|
||||
/** 商店名称 */
|
||||
shopName: string;
|
||||
/** 柜机名称 */
|
||||
cabinetName: string;
|
||||
/** 格口号 */
|
||||
cellNo: number;
|
||||
}
|
||||
|
||||
export type SubmitApprovalResponseData = ApiResponseMsgData<{
|
||||
approvalId: number
|
||||
status: number
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { showConfirmDialog, showSuccessToast, showFailToast, showToast, UploaderFileListItem, Popup, Picker } from 'vant'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { showConfirmDialog, showSuccessToast, showFailToast, showToast, UploaderFileListItem, Popup, Picker, Field, Stepper } from 'vant'
|
||||
import axios from "axios"
|
||||
import { handleApprovalApi } from '@/common/apis/approval'
|
||||
import { getApprovalGoodsCellApi, handleApprovalApi, handleApprovalAssetApi } from '@/common/apis/approval'
|
||||
import { openCabinetApi } from '@/common/apis/shop'
|
||||
import type { ApprovalGoodsEntity, HandleApprovalRequestData } from '@/common/apis/approval/type'
|
||||
import type { ApprovalGoodsCellEntity, ApprovalGoodsEntity, HandleApprovalAssetRequestData, HandleApprovalRequestData } from '@/common/apis/approval/type'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useApprovalStore } from '@/pinia/stores/approval'
|
||||
import { useWxStore } from '@/pinia/stores/wx';
|
||||
|
@ -21,7 +21,7 @@ const { openid, balance, corpidLogin, userid: qyUserid, name: qyName } = storeTo
|
|||
const ab98UserStore = useAb98UserStore();
|
||||
const { tel, userid: ab98Userid, name } = storeToRefs(ab98UserStore);
|
||||
|
||||
const formData = ref<HandleApprovalRequestData>({
|
||||
const formData = ref<HandleApprovalAssetRequestData>({
|
||||
approvalId: approvalStore.currentApproval?.approvalId || 0,
|
||||
status: 2,
|
||||
returnAmount: approvalStore.currentApproval?.goodsPrice || 0,
|
||||
|
@ -29,7 +29,8 @@ const formData = ref<HandleApprovalRequestData>({
|
|||
auditRemark: '',
|
||||
userid: wxStore.userid,
|
||||
corpid: wxStore.corpid,
|
||||
auditUserid: wxStore.userid
|
||||
auditUserid: wxStore.userid,
|
||||
approvalGoodsList: []
|
||||
})
|
||||
|
||||
const orderGoods = ref<ApprovalGoodsEntity[]>([]);
|
||||
|
@ -61,72 +62,23 @@ const validateForm = () => {
|
|||
showConfirmDialog({ title: '提示', message: '驳回时必须填写审核说明' })
|
||||
return false
|
||||
}
|
||||
|
||||
// 验证审批数量
|
||||
if (approvalStore.currentApproval?.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 handleFileUpload = async (items: UploaderFileListItem | UploaderFileListItem[]) => {
|
||||
const files = Array.isArray(items) ? items : [items]
|
||||
uploading.value = true
|
||||
try {
|
||||
const uploadPromises = files.map(async (item) => {
|
||||
item.status = 'uploading'
|
||||
item.message = '上传中...'
|
||||
const file = item.file as File
|
||||
let compressedFile = file;
|
||||
try {
|
||||
compressedFile = await new Promise<File>((resolve, reject) => {
|
||||
new Compressor(file, {
|
||||
quality: 0.8,
|
||||
maxWidth: 1280,
|
||||
maxHeight: 1280,
|
||||
success(result) {
|
||||
resolve(new File([result], file.name, {
|
||||
type: 'image/jpeg',
|
||||
lastModified: Date.now()
|
||||
}))
|
||||
},
|
||||
error(err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('压缩失败:', error)
|
||||
}
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('file', compressedFile)
|
||||
|
||||
const { data } = await axios.post<{
|
||||
code: number
|
||||
data: { url: string }
|
||||
message?: string
|
||||
}>(VITE_APP_BASE_API + '/file/upload', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
|
||||
if (data.code !== 0) {
|
||||
throw new Error(data.message || '文件上传失败')
|
||||
}
|
||||
return { url: data.data.url }
|
||||
})
|
||||
|
||||
const urls = await Promise.all(uploadPromises)
|
||||
files.forEach((item, index) => {
|
||||
item.status = 'done'
|
||||
item.message = '上传成功'
|
||||
item.url = urls[index].url
|
||||
})
|
||||
formData.value.auditImages = fileList.value.map(item => item.url).join(',')
|
||||
} catch (error) {
|
||||
showConfirmDialog({
|
||||
title: '上传失败',
|
||||
message: error instanceof Error ? error.message : '未知错误'
|
||||
})
|
||||
} finally {
|
||||
uploading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onStatusConfirm = ({ selectedOptions }: { selectedOptions: { value: number }[] }) => {
|
||||
submitting.value = true
|
||||
|
@ -150,13 +102,29 @@ const previewImage = (url: string) => {
|
|||
currentPreviewImage.value = url
|
||||
showPreview.value = true
|
||||
}
|
||||
onMounted(() => {
|
||||
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 : ''
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
if (!approvalStore.currentApproval) {
|
||||
router.push('/approval/list');
|
||||
router.push('/approvalAsset/list');
|
||||
return;
|
||||
}
|
||||
|
||||
orderGoods.value = approvalStore.currentApproval.goodsList || [];
|
||||
// 初始化订单商品数据,添加approvalQuantity字段
|
||||
orderGoods.value = (approvalStore.currentApproval.goodsList || []).map(item => ({
|
||||
...item,
|
||||
approvalQuantity: item.approvalQuantity || item.applyQuantity
|
||||
}));
|
||||
|
||||
if (approvalStore.currentApproval.status !== 1) {
|
||||
// 填充历史审批数据
|
||||
|
@ -167,15 +135,19 @@ onMounted(() => {
|
|||
returnAmount: approvalStore.currentApproval.returnAmount,
|
||||
auditRemark: approvalStore.currentApproval.auditRemark,
|
||||
auditImages: approvalStore.currentApproval.auditImages
|
||||
}
|
||||
};
|
||||
|
||||
// 处理文件回显
|
||||
if (approvalStore.currentApproval.auditImages) {
|
||||
fileList.value = approvalStore.currentApproval.auditImages.split(',').map(url => ({
|
||||
url,
|
||||
status: 'done',
|
||||
message: '已上传'
|
||||
}))
|
||||
// 获取已审批商品格口信息
|
||||
try {
|
||||
const result = await getApprovalGoodsCellApi(approvalStore.currentApproval.approvalId);
|
||||
if (result.code === 0) {
|
||||
approvalGoodsCells.value = result.data;
|
||||
} else {
|
||||
showFailToast(result.msg || '获取审批商品格口信息失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取审批商品格口信息失败:', error);
|
||||
showFailToast('获取审批商品格口信息失败');
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -216,12 +188,28 @@ const handleOpenCabinet = async () => {
|
|||
const handleSubmit = 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 handleApprovalApi(formData.value)
|
||||
|
||||
const { code, msg } = await handleApprovalAssetApi(formData.value)
|
||||
|
||||
if (code === 0) {
|
||||
showSuccessToast('操作成功')
|
||||
|
@ -287,9 +275,47 @@ const handleReject = () => {
|
|||
¥{{ item.price.toFixed(2) }}
|
||||
</span>
|
||||
<span class="quantity">
|
||||
×{{ item.applyQuantity }}
|
||||
申请数量:{{ item.applyQuantity }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- 审批数量输入框 -->
|
||||
<div v-if="approvalStore.currentApproval?.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-else class="approval-quantity-view">
|
||||
<span class="label">审批数量:</span>
|
||||
<span class="value">{{ item.approvalQuantity }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
</template>
|
||||
</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="product-name van-ellipsis">
|
||||
{{ item.shopName }}
|
||||
</div>
|
||||
<div class="product-name van-ellipsis">
|
||||
{{ item.cabinetName }} - 格口{{ item.cellNo }}
|
||||
</div>
|
||||
<div class="product-name van-ellipsis">
|
||||
分配数量:{{ item.allocateQuantity }}
|
||||
</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
</template>
|
||||
|
@ -312,7 +338,7 @@ const handleReject = () => {
|
|||
</div>
|
||||
</van-popup>
|
||||
|
||||
<div class="btn-group" v-if="approvalStore.currentApproval?.status !== 2">
|
||||
<div class="btn-group" v-if="approvalStore.currentApproval?.status === 1">
|
||||
<van-button type="danger" :loading="submitting" loading-text="提交中..." @click="handleReject"
|
||||
style="flex: 1; height: 44px; border-radius: 8px; font-size: 16px;">
|
||||
拒绝
|
||||
|
@ -353,9 +379,25 @@ const handleReject = () => {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: 60px;
|
||||
height: auto;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
.price-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
@ -370,6 +412,7 @@ const handleReject = () => {
|
|||
|
||||
.quantity {
|
||||
color: #666;
|
||||
margin-right: 20px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
|
|
@ -114,6 +114,8 @@ wxStore.refreshBalance();
|
|||
<span>耗材核销</span>
|
||||
</div>
|
||||
</van-col>
|
||||
<van-col span="8"></van-col>
|
||||
<van-col span="8"></van-col>
|
||||
</van-row>
|
||||
</div>
|
||||
</template>
|
||||
|
|
Loading…
Reference in New Issue