feat(approval): 添加资产审批相关功能及界面优化
- 新增资产审批接口和类型定义 - 在审批页面添加审批数量控制和商品格口信息展示 - 优化审批表单验证逻辑和界面布局
This commit is contained in:
parent
fcee668ebe
commit
c433debb84
|
@ -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 }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue