feat(approval): 添加耗材核销功能及相关页面和接口

- 新增耗材核销页面及路由配置
- 添加核销码验证接口和功能
- 修改审批相关类型定义和接口
- 优化审批处理页面样式和交互
This commit is contained in:
dzq 2025-06-14 11:41:02 +08:00
parent 9ff0984fe1
commit fcee668ebe
10 changed files with 219 additions and 86 deletions

View File

@ -1,5 +1,5 @@
import { request } from '@/http/axios'
import { SubmitApprovalRequestData, SubmitApprovalResponseData, SearchApiReturnApprovalQuery, ApiResponsePageData, ReturnApprovalEntity, HandleApprovalRequestData } from './type'
import { SubmitApprovalRequestData, SubmitApprovalResponseData, SearchApiReturnApprovalQuery, ApiResponsePageData, ReturnApprovalEntity, HandleApprovalRequestData, SearchReturnApprovalAssetQuery, ReturnApprovalAssetDTO } from './type'
import { ShopOrderGoodsEntity } from '../shop/type'
export const getApprovalListApi = (params: SearchApiReturnApprovalQuery) => {
@ -10,6 +10,27 @@ export const getApprovalListApi = (params: SearchApiReturnApprovalQuery) => {
})
}
export const getApprovalAssetListApi = (params: SearchReturnApprovalAssetQuery) => {
return request<ApiResponsePageData<ReturnApprovalAssetDTO>>({
url: 'approval/list/asset',
method: 'get',
params
})
}
export const checkApprovalCodeApi = (params: {
corpid: string,
approvalType: number,
code: string
}) => {
return request<ApiResponseMsgData<string>>(
{
url: 'approval/checkCode',
method: 'post',
params
}
)
}
export const submitApprovalApi = (data: SubmitApprovalRequestData) => {

View File

@ -55,9 +55,17 @@ export interface ReturnApprovalEntity {
externalGoodsId?: number
/** 外部归属类型的审批ID */
externalApprovalId?: number
/** 审批码 */
code?: string
/** 审批码校验状态(0未核销 1已核销) */
codeCheck?: number
/** 企业微信id */
corpid?: string
/** 申请人企业UserID */
applyUserid?: string
/** 申请人姓名 */
applyUserName?: string
/** 审批人企业UserID */
auditUserid?: string
/** 申请数量 */
applyQuantity?: number
@ -79,6 +87,8 @@ export interface ReturnApprovalEntity {
returnRemark: string
/** 审核说明 */
auditRemark: string
/** 审批人姓名 */
auditName: string
/** 审批状态(1待审核 2已通过 3已驳回) */
status: number
/** 审批时间 */
@ -89,20 +99,50 @@ export interface ReturnApprovalEntity {
goodsName: string
/** 封面图URL */
coverImg: string
/** 手机号 */
/** 手机号 */
mobile: string
/** 用户id */
/** 企业微信用户ID或汇邦云用户ID */
userid: string
/** 用户姓名 */
name: string
/** 是否内部用户0否 1汇邦云用户 2企业微信用户 */
isInternal: number
/** 审核人姓名 */
auditName: string
/** 支付方式 */
paymentMethod?: string
}
export interface ApprovalGoodsEntity {
approvalGoodsId: number;
approvalId: number;
goodsName: string;
goodsId: number;
externalGoodsId?: number;
corpid?: string;
belongType: number;
price: number;
applyQuantity: number;
coverImg?: string;
}
export interface ReturnApprovalAssetDTO extends ReturnApprovalEntity {
goodsList?: ApprovalGoodsEntity[];
}
export interface SearchReturnApprovalAssetQuery {
pageNum: number;
pageSize: number;
approvalId?: number;
orderId?: number;
goodsId?: number;
status?: number;
startTime?: string;
endTime?: string;
approvalType?: number;
corpid?: string;
code?: string;
codeCheck?: number;
}
export type SubmitApprovalResponseData = ApiResponseMsgData<{
approvalId: number
status: number

View File

@ -6,6 +6,7 @@ const tabbarItemList = computed(() => {
return routes.filter(route => route.meta.layout?.tabbar?.showTabbar
&& route.path !== '/cabinet'
&& route.path!== '/approval/list'
&& route.path!== '/approvalAsset/list'
)
.map(route => ({
title: route.meta.title,

View File

@ -2,9 +2,9 @@
import { ref } from 'vue'
import { showConfirmDialog, showSuccessToast, showFailToast, showToast, UploaderFileListItem, Popup, Picker } from 'vant'
import axios from "axios"
import { getApprovalOrderGoodsApi, handleApprovalApi } from '@/common/apis/approval'
import { handleApprovalApi } from '@/common/apis/approval'
import { openCabinetApi } from '@/common/apis/shop'
import type { HandleApprovalRequestData } from '@/common/apis/approval/type'
import type { ApprovalGoodsEntity, HandleApprovalRequestData } from '@/common/apis/approval/type'
import { useRoute, useRouter } from 'vue-router'
import { useApprovalStore } from '@/pinia/stores/approval'
import { useWxStore } from '@/pinia/stores/wx';
@ -32,7 +32,7 @@ const formData = ref<HandleApprovalRequestData>({
auditUserid: wxStore.userid
})
const orderGoods = ref<ShopOrderGoodsEntity[]>([]);
const orderGoods = ref<ApprovalGoodsEntity[]>([]);
const submitting = ref(false)
const fileList = ref<UploaderFileListItem[]>([])
const uploading = ref(false)
@ -156,9 +156,7 @@ onMounted(() => {
return;
}
getApprovalOrderGoodsApi(approvalStore.currentApproval.approvalId).then(({ data }) => {
orderGoods.value = data;
})
orderGoods.value = approvalStore.currentApproval.goodsList || [];
if (approvalStore.currentApproval.status !== 1) {
//
@ -268,8 +266,7 @@ const handleReject = () => {
<div class="content-wrapper">
<van-cell-group>
<van-cell title="申请人" :value="approvalStore.currentApproval?.name" />
<van-cell title="手机号" :value="approvalStore.currentApproval?.mobile" />
<van-cell title="申请人" :value="approvalStore.currentApproval?.applyUserName" />
<van-cell title="当前状态" :value="statusMap[approvalStore.currentApproval?.status || 1]" />
<!-- <van-cell title="领用说明" :value="approvalStore.currentApproval?.applyRemark" /> -->
</van-cell-group>
@ -290,7 +287,7 @@ const handleReject = () => {
¥{{ item.price.toFixed(2) }}
</span>
<span class="quantity">
×{{ item.quantity }}
×{{ item.applyQuantity }}
</span>
</div>
</div>
@ -300,10 +297,10 @@ const handleReject = () => {
<van-cell-group>
<!-- 原有表单字段保持不变 -->
<van-button type="primary" size="small"
@click="handleOpenCabinet" :disabled="isButtonDisabled" style="margin-bottom: 12px">
<!-- <van-button type="primary" size="small" @click="handleOpenCabinet" :disabled="isButtonDisabled"
style="margin-bottom: 12px">
打开柜子
</van-button>
</van-button> -->
<van-field v-model="formData.auditRemark" label="审核说明" type="textarea" rows="2" autosize
class="audit-remark-field" />
</van-cell-group>
@ -315,11 +312,13 @@ const handleReject = () => {
</div>
</van-popup>
<div class="submit-bar" v-if="approvalStore.currentApproval?.status !== 2">
<van-button type="danger" block :loading="submitting" loading-text="提交中..." @click="handleReject">
<div class="btn-group" v-if="approvalStore.currentApproval?.status !== 2">
<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" block :loading="submitting" loading-text="提交中..." @click="handleComfirm">
<van-button type="primary" :loading="submitting" loading-text="提交中..." @click="handleComfirm"
style="flex: 1; height: 44px; border-radius: 8px; font-size: 16px;">
同意
</van-button>
</div>
@ -401,15 +400,13 @@ const handleReject = () => {
}
}
.submit-bar {
position: sticky;
bottom: 20px;
.btn-group {
display: flex;
width: 100%;
gap: 12px;
margin: 8px 0;
background-color: #ffffff;
padding: 16px;
background: #fff;
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.1);
border-radius: 8px;
margin: 0 16px;
z-index: 1;
}
.content-wrapper {

View File

@ -2,12 +2,6 @@
<div class="approval-list-container">
<!-- 搜索表单 -->
<van-form @submit="handleSearch">
<van-field v-model="searchParams.approvalId" label="审批单号" type="number" placeholder="请输入审批单号" />
<van-field v-model="searchParams.orderId" label="订单编号" type="number" placeholder="请输入订单编号" />
<van-field v-model="searchParams.goodsId" label="商品ID" type="number" placeholder="请输入商品ID" />
<van-field name="status" label="审批状态" readonly clickable v-model="statusText" placeholder="请选择状态"
@click="showStatusPicker = true" />
<van-popup v-model:show="showStatusPicker" position="bottom">
@ -31,18 +25,26 @@
/>
</van-popup> -->
<div style="margin: 16px;">
<van-button block type="primary" native-type="submit">搜索</van-button>
<van-button block plain type="primary" style="margin-top: 10px;" @click="handleReset">重置</van-button>
<div class="btn-group">
<van-button type="primary" native-type="submit" style="flex: 1; height: 44px; border-radius: 8px; font-size: 16px;">搜索</van-button>
<van-button type="default" @click="showVerificationDialog = true" style="flex: 1; height: 44px; border-radius: 8px; font-size: 16px;">输入核销码</van-button>
</div>
</van-form>
<van-dialog v-model:show="showVerificationDialog" title="输入核销码" @confirm="handleVerification">
<van-field v-model="verificationCode" placeholder="请输入核销码" />
</van-dialog>
<!-- 审批列表 -->
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
<van-cell v-for="item in list" :key="item.approvalId" :title="`审批单号:${item.approvalId}`" clickable
@click="handleCellClick(item.approvalId)">
<template #label>
<div>商品名称{{ item.goodsName }}</div>
<div v-for="goods in item.goodsList" :key="goods.approvalGoodsId">
商品名称{{ goods.goodsName }}
</div>
<div>核销码{{ item.code }}</div>
<div>申请时间{{ item.createTime }}</div>
<van-tag :type="statusTagType(item.status)">
{{ statusMap[item.status] }}
@ -55,8 +57,9 @@
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { getApprovalListApi } from '@/common/apis/approval'
import type { SearchApiReturnApprovalQuery, ReturnApprovalEntity } from '@/common/apis/approval/type'
import { showToast } from 'vant'
import { checkApprovalCodeApi, getApprovalAssetListApi } from '@/common/apis/approval'
import type { SearchReturnApprovalAssetQuery, ReturnApprovalAssetDTO } from '@/common/apis/approval/type'
import type { PickerConfirmEventParams } from 'vant/es';
import { useApprovalStore } from '@/pinia/stores/approval';
import { useWxStore } from '@/pinia/stores/wx';
@ -65,9 +68,11 @@ const router = useRouter();
const wxStore = useWxStore();
//
const searchParams = reactive<SearchApiReturnApprovalQuery>({
const searchParams = reactive<SearchReturnApprovalAssetQuery>({
corpid: wxStore.corpid,
approvalType: 1,
code: '',
codeCheck: 1,
pageNum: 1,
pageSize: 10,
})
@ -93,8 +98,12 @@ const statusText = ref('')
const showDatePicker = ref(false)
const dateRangeText = ref('')
//
const showVerificationDialog = ref(false)
const verificationCode = ref('')
//
const list = ref<ReturnApprovalEntity[]>([])
const list = ref<ReturnApprovalAssetDTO[]>([])
const loading = ref(false)
const finished = ref(false)
@ -107,6 +116,30 @@ const handleCellClick = (approvalId: number) => {
router.push(`/approval/handleApply/${approvalId}`);
}
//
const handleVerification = async () => {
if (!verificationCode.value) {
showToast('请输入核销码')
return
}
try {
const params = {
corpid: wxStore.corpid,
approvalType: 1,
code: verificationCode.value
}
await checkApprovalCodeApi(params)
showToast('核销成功')
showVerificationDialog.value = false
verificationCode.value = ''
//
handleSearch()
} catch (error) {
console.error('核销失败', error)
showToast('核销失败,请重试')
}
}
//
const statusTagType = (status: number) => {
switch (status) {
@ -163,7 +196,7 @@ const handleReset = () => {
const onLoad = async () => {
try {
searchParams.corpid = wxStore.corpid;
const { data } = await getApprovalListApi(searchParams)
const { data } = await getApprovalAssetListApi(searchParams)
list.value.push(...data.rows)
loading.value = false
@ -194,4 +227,14 @@ const onLoad = async () => {
color: #666;
font-size: 12px;
}
.btn-group {
display: flex;
width: 100%;
gap: 12px;
margin: 8px 0;
background-color: #ffffff;
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
padding: 16px;
}
</style>

View File

@ -116,6 +116,11 @@ const filteredGoods = computed(() => {
return goodsList.value.filter(goods =>
goods.goodsName.toLowerCase().includes(searchQuery.value.toLowerCase())
);
/* return goodsList.value.filter(goods => {
const matchesSearch = !searchQuery.value || goods.goodsName.toLowerCase().includes(searchQuery.value.toLowerCase());
const remainingStock = (goods.stock || 0) - (goods.totalStock || 0);
return matchesSearch && remainingStock > 0;
}); */
});
//

View File

@ -108,6 +108,12 @@ wxStore.refreshBalance();
<span>审批中心</span>
</div>
</van-col>
<van-col span="8">
<div v-if="wxStore.isCabinetAdmin" class="custom-btn" @click="router.push('/approvalAsset/list')">
<van-icon name="comment-o" size="20px" />
<span>耗材核销</span>
</div>
</van-col>
</van-row>
</div>
</template>

View File

@ -1,8 +1,8 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { ReturnApprovalEntity } from '@/common/apis/approval/type'
import type { ReturnApprovalAssetDTO } from '@/common/apis/approval/type'
export interface ApprovalDetail extends ReturnApprovalEntity {
export interface ApprovalDetail extends ReturnApprovalAssetDTO {
goodsName: string
coverImg: string
}

View File

@ -116,6 +116,25 @@ export const routes: RouteRecordRaw[] = [
}
}
},
{
path: '/approvalAsset/list',
component: () => import('@/pages/approvalAsset/list.vue'),
name: "ApprovalAsset",
meta: {
title: '耗材核销',
keepAlive: false,
layout: {
navBar: {
showNavBar: false,
showLeftArrow: false
},
tabbar: {
showTabbar: true,
icon: "home-o"
}
}
}
},
/* {
path: '/manage/goods',
component: () => import('@/pages/manage/goods/goodsList.vue'),

View File

@ -16,6 +16,7 @@ declare module 'vue' {
VanCellGroup: typeof import('vant/es')['CellGroup']
VanCol: typeof import('vant/es')['Col']
VanConfigProvider: typeof import('vant/es')['ConfigProvider']
VanDialog: typeof import('vant/es')['Dialog']
VanDivider: typeof import('vant/es')['Divider']
VanEmpty: typeof import('vant/es')['Empty']
VanField: typeof import('vant/es')['Field']