feat(approval): 添加资产审批相关功能及界面优化

- 新增资产审批接口和类型定义
- 在审批页面添加审批数量控制和商品格口信息展示
- 优化审批表单验证逻辑和界面布局
This commit is contained in:
dzq 2025-06-14 17:54:48 +08:00
parent fcee668ebe
commit c433debb84
4 changed files with 176 additions and 85 deletions

View File

@ -1,5 +1,5 @@
import { request } from '@/http/axios' 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' import { ShopOrderGoodsEntity } from '../shop/type'
export const getApprovalListApi = (params: SearchApiReturnApprovalQuery) => { 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) => { export const getApprovalOrderGoodsApi = (approvalId: number) => {
return request<ApiResponseMsgData<ShopOrderGoodsEntity[]>>({ return request<ApiResponseMsgData<ShopOrderGoodsEntity[]>>({
url: 'approval/getApprovalOrderGoods', url: 'approval/getApprovalOrderGoods',
@ -56,3 +65,11 @@ export const getApprovalOrderGoodsApi = (approvalId: number) => {
params: { approvalId } params: { approvalId }
}) })
} }
export const getApprovalGoodsCellApi = (approvalId: number) => {
return request<ApiResponseMsgData<ApprovalGoodsCellEntity[]>>({
url: 'approval/getApprovalGoodsCell',
method: 'get',
params: { approvalId }
})
}

View File

@ -20,6 +20,11 @@ export interface HandleApprovalRequestData {
auditUserid: string auditUserid: string
} }
export interface HandleApprovalAssetRequestData extends HandleApprovalRequestData {
/** 审批商品ID */
approvalGoodsList: ApprovalGoodsEntity[]
}
export interface SearchApiReturnApprovalQuery { export interface SearchApiReturnApprovalQuery {
pageNum: number pageNum: number
pageSize: number pageSize: number
@ -121,6 +126,7 @@ export interface ApprovalGoodsEntity {
belongType: number; belongType: number;
price: number; price: number;
applyQuantity: number; applyQuantity: number;
approvalQuantity?: number;
coverImg?: string; coverImg?: string;
} }
@ -143,6 +149,29 @@ export interface SearchReturnApprovalAssetQuery {
codeCheck?: number; 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<{ export type SubmitApprovalResponseData = ApiResponseMsgData<{
approvalId: number approvalId: number
status: number status: number

View File

@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref, onMounted, computed } from 'vue'
import { showConfirmDialog, showSuccessToast, showFailToast, showToast, UploaderFileListItem, Popup, Picker } from 'vant' import { showConfirmDialog, showSuccessToast, showFailToast, showToast, UploaderFileListItem, Popup, Picker, Field, Stepper } from 'vant'
import axios from "axios" 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 { 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 { useRoute, useRouter } from 'vue-router'
import { useApprovalStore } from '@/pinia/stores/approval' import { useApprovalStore } from '@/pinia/stores/approval'
import { useWxStore } from '@/pinia/stores/wx'; import { useWxStore } from '@/pinia/stores/wx';
@ -21,7 +21,7 @@ const { openid, balance, corpidLogin, userid: qyUserid, name: qyName } = storeTo
const ab98UserStore = useAb98UserStore(); const ab98UserStore = useAb98UserStore();
const { tel, userid: ab98Userid, name } = storeToRefs(ab98UserStore); const { tel, userid: ab98Userid, name } = storeToRefs(ab98UserStore);
const formData = ref<HandleApprovalRequestData>({ const formData = ref<HandleApprovalAssetRequestData>({
approvalId: approvalStore.currentApproval?.approvalId || 0, approvalId: approvalStore.currentApproval?.approvalId || 0,
status: 2, status: 2,
returnAmount: approvalStore.currentApproval?.goodsPrice || 0, returnAmount: approvalStore.currentApproval?.goodsPrice || 0,
@ -29,7 +29,8 @@ const formData = ref<HandleApprovalRequestData>({
auditRemark: '', auditRemark: '',
userid: wxStore.userid, userid: wxStore.userid,
corpid: wxStore.corpid, corpid: wxStore.corpid,
auditUserid: wxStore.userid auditUserid: wxStore.userid,
approvalGoodsList: []
}) })
const orderGoods = ref<ApprovalGoodsEntity[]>([]); const orderGoods = ref<ApprovalGoodsEntity[]>([]);
@ -61,72 +62,23 @@ const validateForm = () => {
showConfirmDialog({ title: '提示', message: '驳回时必须填写审核说明' }) showConfirmDialog({ title: '提示', message: '驳回时必须填写审核说明' })
return false 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 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 }[] }) => { const onStatusConfirm = ({ selectedOptions }: { selectedOptions: { value: number }[] }) => {
submitting.value = true submitting.value = true
@ -150,13 +102,29 @@ const previewImage = (url: string) => {
currentPreviewImage.value = url currentPreviewImage.value = url
showPreview.value = true 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) { if (!approvalStore.currentApproval) {
router.push('/approval/list'); router.push('/approvalAsset/list');
return; 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) { if (approvalStore.currentApproval.status !== 1) {
// //
@ -167,15 +135,19 @@ onMounted(() => {
returnAmount: approvalStore.currentApproval.returnAmount, returnAmount: approvalStore.currentApproval.returnAmount,
auditRemark: approvalStore.currentApproval.auditRemark, auditRemark: approvalStore.currentApproval.auditRemark,
auditImages: approvalStore.currentApproval.auditImages auditImages: approvalStore.currentApproval.auditImages
} };
// //
if (approvalStore.currentApproval.auditImages) { try {
fileList.value = approvalStore.currentApproval.auditImages.split(',').map(url => ({ const result = await getApprovalGoodsCellApi(approvalStore.currentApproval.approvalId);
url, if (result.code === 0) {
status: 'done', approvalGoodsCells.value = result.data;
message: '已上传' } else {
})) showFailToast(result.msg || '获取审批商品格口信息失败');
}
} catch (error) {
console.error('获取审批商品格口信息失败:', error);
showFailToast('获取审批商品格口信息失败');
} }
} }
}) })
@ -216,12 +188,28 @@ const handleOpenCabinet = async () => {
const handleSubmit = async () => { const handleSubmit = async () => {
if (!validateForm()) return 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 submitting.value = true
try { try {
formData.value.userid = wxStore.userid; formData.value.userid = wxStore.userid;
formData.value.corpid = wxStore.corpid; formData.value.corpid = wxStore.corpid;
formData.value.auditUserid = wxStore.userid; formData.value.auditUserid = wxStore.userid;
const { code, msg } = await handleApprovalApi(formData.value)
const { code, msg } = await handleApprovalAssetApi(formData.value)
if (code === 0) { if (code === 0) {
showSuccessToast('操作成功') showSuccessToast('操作成功')
@ -287,9 +275,47 @@ const handleReject = () => {
¥{{ item.price.toFixed(2) }} ¥{{ item.price.toFixed(2) }}
</span> </span>
<span class="quantity"> <span class="quantity">
×{{ item.applyQuantity }} 申请数量{{ item.applyQuantity }}
</span> </span>
</div> </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> </div>
</van-cell> </van-cell>
</template> </template>
@ -312,7 +338,7 @@ const handleReject = () => {
</div> </div>
</van-popup> </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" <van-button type="danger" :loading="submitting" loading-text="提交中..." @click="handleReject"
style="flex: 1; height: 44px; border-radius: 8px; font-size: 16px;"> style="flex: 1; height: 44px; border-radius: 8px; font-size: 16px;">
拒绝 拒绝
@ -353,9 +379,25 @@ const handleReject = () => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; 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 { .price-row {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -370,6 +412,7 @@ const handleReject = () => {
.quantity { .quantity {
color: #666; color: #666;
margin-right: 20px;
font-size: 13px; font-size: 13px;
} }

View File

@ -114,6 +114,8 @@ wxStore.refreshBalance();
<span>耗材核销</span> <span>耗材核销</span>
</div> </div>
</van-col> </van-col>
<van-col span="8"></van-col>
<van-col span="8"></van-col>
</van-row> </van-row>
</div> </div>
</template> </template>