Compare commits

...

10 Commits

Author SHA1 Message Date
dzq df72e1317c feat(订单): 扩展订单查询接口以支持企业微信用户
修改订单查询接口,增加对企业微信用户ID的支持
当qyUserId存在时使用新接口查询,否则使用原有接口
更新所有调用getOrders的地方传递corpid和qyUserId参数
2025-06-20 16:19:53 +08:00
dzq d37dba9206 fix: 统一错误消息处理并简化错误显示
修改axios拦截器以优先使用apiData.msg作为错误消息,回退到apiData.message
简化submit.vue中的错误显示,仅展示error.message
2025-06-20 10:08:19 +08:00
dzq a2e30e7bed feat(ProductList): 支持通过URL参数shopId自动选择店铺
添加从URL参数中获取shopId的功能,当shopId存在且在店铺列表中时自动选中对应店铺
2025-06-20 08:41:07 +08:00
dzq 1224419c9e feat(approval): 添加审批详情接口和开柜中状态处理
- 新增审批详情接口getApprovalDetailAssetApi
- 添加开柜中状态(4)到状态映射和类型定义
- 重构审批处理页面,支持分步操作和状态判断
- 添加数量确定功能allocateApprovalGoods
- 优化审批流程,支持开柜中状态下的操作
2025-06-18 15:12:51 +08:00
dzq b113afa71f feat(审批资产列表): 添加标签页切换和搜索功能
- 新增标签页切换功能,区分待处理和已处理审批
- 添加搜索框支持按内容筛选
- 优化列表加载逻辑,避免重复数据
- 将核销码按钮改为悬浮样式
2025-06-17 09:39:59 +08:00
dzq e446ad2b38 feat(产品列表): 为店铺列表项添加封面图片并调整样式
在店铺列表项中添加封面图片显示功能,当没有封面图时使用默认图片。同时调整了列表项的布局样式,将高度改为自适应并移除内边距,使封面图片能够完整显示。
2025-06-16 15:35:59 +08:00
dzq 1914dacfe5 fix(approval): 修复审批列表和详情页的显示问题及错误提示
- 在审批列表中添加申领人信息显示
- 统一使用showDialog替换showToast和showFailToast进行错误提示
- 修复审批数量初始化逻辑,根据状态设置默认值
- 优化审批详情页的样式和布局
- 修正操作成功后的跳转路径
2025-06-16 11:12:42 +08:00
dzq 50715f07b8 feat(approval): 添加打开格口功能并优化审批商品展示
- 将打开储物柜接口从shop模块移动到approval模块
- 重构打开格口逻辑,改为接收approvalGoodsCellId参数
- 在审批商品列表中添加打开格口按钮
- 优化审批商品列表标题显示
2025-06-16 09:18:13 +08:00
dzq c433debb84 feat(approval): 添加资产审批相关功能及界面优化
- 新增资产审批接口和类型定义
- 在审批页面添加审批数量控制和商品格口信息展示
- 优化审批表单验证逻辑和界面布局
2025-06-14 17:54:48 +08:00
dzq fcee668ebe feat(approval): 添加耗材核销功能及相关页面和接口
- 新增耗材核销页面及路由配置
- 添加核销码验证接口和功能
- 修改审批相关类型定义和接口
- 优化审批处理页面样式和交互
2025-06-14 11:41:02 +08:00
18 changed files with 652 additions and 259 deletions

View File

@ -1,6 +1,6 @@
import { request } from '@/http/axios'
import { SubmitApprovalRequestData, SubmitApprovalResponseData, SearchApiReturnApprovalQuery, ApiResponsePageData, ReturnApprovalEntity, HandleApprovalRequestData } from './type'
import { ShopOrderGoodsEntity } from '../shop/type'
import { SubmitApprovalRequestData, SubmitApprovalResponseData, SearchApiReturnApprovalQuery, ApiResponsePageData, ReturnApprovalEntity, HandleApprovalRequestData, SearchReturnApprovalAssetQuery, ReturnApprovalAssetDTO, HandleApprovalAssetRequestData, ApprovalGoodsCellEntity, ReturnApprovalDetailDTO } from './type'
import { OpenCabinetApiData, ShopOrderGoodsEntity } from '../shop/type'
export const getApprovalListApi = (params: SearchApiReturnApprovalQuery) => {
return request<ApiResponsePageData<ReturnApprovalEntity>>({
@ -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) => {
@ -28,6 +49,23 @@ export const handleApprovalApi = (data: HandleApprovalRequestData) => {
})
}
export const handleApprovalAssetApi = (data: HandleApprovalRequestData) => {
return request<ApiResponseMsgData<string>>({
url: 'approval/handle/asset',
method: 'post',
data
})
}
export const allocateApprovalGoods = (data: HandleApprovalAssetRequestData) => {
return request<ApiResponseMsgData<string>>({
url: 'approval/handle/allocateApprovalGoods',
method: 'post',
data
})
}
export const getApprovalOrderGoodsApi = (approvalId: number) => {
return request<ApiResponseMsgData<ShopOrderGoodsEntity[]>>({
url: 'approval/getApprovalOrderGoods',
@ -35,3 +73,32 @@ export const getApprovalOrderGoodsApi = (approvalId: number) => {
params: { approvalId }
})
}
export const getApprovalGoodsCellApi = (approvalId: number) => {
return request<ApiResponseMsgData<ApprovalGoodsCellEntity[]>>(
{
url: 'approval/getApprovalGoodsCell',
method: 'get',
params: { approvalId }
}
)
}
export const getApprovalDetailAssetApi = (approvalId: number) => {
return request<ApiResponseMsgData<ReturnApprovalDetailDTO>>(
{
url: 'approval/detail/asset',
method: 'get',
params: { approval_id: approvalId }
}
)
}
/** 打开储物柜接口 */
export function openCabinetApi(approvalGoodsCellId: number, data: OpenCabinetApiData) {
return request<ApiResponseData<void>>({
url: `approval/openCabinet/${approvalGoodsCellId}`,
method: "post",
data
})
}

View File

@ -20,6 +20,11 @@ export interface HandleApprovalRequestData {
auditUserid: string
}
export interface HandleApprovalAssetRequestData extends HandleApprovalRequestData {
/** 审批商品ID */
approvalGoodsList: ApprovalGoodsEntity[]
}
export interface SearchApiReturnApprovalQuery {
pageNum: number
pageSize: number
@ -55,9 +60,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,7 +92,9 @@ export interface ReturnApprovalEntity {
returnRemark: string
/** 审核说明 */
auditRemark: string
/** 审批状态(1待审核 2已通过 3已驳回) */
/** 审批人姓名 */
auditName: string
/** 审批状态(1待审核 2已通过 3已驳回 4开柜中) */
status: number
/** 审批时间 */
approvalTime?: string
@ -89,20 +104,81 @@ 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;
approvalQuantity?: number;
coverImg?: string;
}
export interface ReturnApprovalAssetDTO extends ReturnApprovalEntity {
goodsList?: ApprovalGoodsEntity[];
}
export interface ReturnApprovalDetailDTO extends ReturnApprovalAssetDTO {
statusStr: string;
approvalGoodsCellList?: ApprovalGoodsCellEntity[];
}
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;
handleStatus?: number;
searchStr?: string;
}
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

View File

@ -50,9 +50,18 @@ export function qyLogin(params: QyLoginRequestParams) {
}
/** 根据openid获取用户订单信息 */
export function getOrdersByOpenIdApi(openid: string) {
export function getOrdersByOpenIdApi(corpid: string, openid: string) {
return request<ApiResponseData<GetOrdersByOpenIdDTO>>({
url: `order/user/${openid}`,
method: "get",
params: { corpid }
})
}
/** 根据openid获取用户订单信息 */
export function getOrdersByQyUserIdApi(qyUserId: number) {
return request<ApiResponseData<GetOrdersByOpenIdDTO>>({
url: `order/user/qy/${qyUserId}`,
method: "get"
})
}

View File

@ -44,7 +44,7 @@ function createInstance() {
return logout()
default:
// 不是正确的 code
return Promise.reject(new Error(apiData.message || "Error"))
return Promise.reject(new Error(apiData.msg || apiData.message || "Error"))
}
},
(error) => {

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

@ -1,16 +1,16 @@
<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, showDialog, UploaderFileListItem, Popup, Picker, Field, Stepper } from 'vant'
import axios from "axios"
import { getApprovalOrderGoodsApi, handleApprovalApi } from '@/common/apis/approval'
import { openCabinetApi } from '@/common/apis/shop'
import type { HandleApprovalRequestData } from '@/common/apis/approval/type'
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'
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()
@ -21,7 +21,9 @@ const { openid, balance, corpidLogin, userid: qyUserid, name: qyName } = storeTo
const ab98UserStore = useAb98UserStore();
const { tel, userid: ab98Userid, name } = storeToRefs(ab98UserStore);
const formData = ref<HandleApprovalRequestData>({
const detailData = ref<ReturnApprovalDetailDTO>();
const formData = ref<HandleApprovalAssetRequestData>({
approvalId: approvalStore.currentApproval?.approvalId || 0,
status: 2,
returnAmount: approvalStore.currentApproval?.goodsPrice || 0,
@ -29,10 +31,11 @@ const formData = ref<HandleApprovalRequestData>({
auditRemark: '',
userid: wxStore.userid,
corpid: wxStore.corpid,
auditUserid: wxStore.userid
auditUserid: wxStore.userid,
approvalGoodsList: []
})
const orderGoods = ref<ShopOrderGoodsEntity[]>([]);
const orderGoods = ref<ApprovalGoodsEntity[]>([]);
const submitting = ref(false)
const fileList = ref<UploaderFileListItem[]>([])
const uploading = ref(false)
@ -61,72 +64,23 @@ const validateForm = () => {
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 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,71 +104,90 @@ const previewImage = (url: string) => {
currentPreviewImage.value = url
showPreview.value = true
}
onMounted(() => {
if (!approvalStore.currentApproval) {
router.push('/approval/list');
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;
}
getApprovalOrderGoodsApi(approvalStore.currentApproval.approvalId).then(({ data }) => {
orderGoods.value = data;
})
if (approvalStore.currentApproval.status !== 1) {
//
formData.value = {
...formData.value,
corpid: wxStore.corpid,
status: approvalStore.currentApproval.status,
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: '已上传'
}))
}
}
})
const handleOpenCabinet = async () => {
if (!approvalStore.currentApproval?.orderId || !approvalStore.currentApproval?.orderGoodsId) {
showFailToast('缺少订单信息')
return
}
isButtonDisabled.value = true
try {
const isInternal = corpidLogin.value ? 2 : ab98Userid.value ? 1 : 0;
const result = await openCabinetApi(
approvalStore.currentApproval.orderId,
approvalStore.currentApproval.orderGoodsId, {
userid: isInternal === 2 ? qyUserid.value : ab98Userid.value,
isInternal: isInternal,
name: isInternal === 2 ? qyName.value : name.value,
mobile: tel.value,
operationType: 2
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 || [];
}
)
if (result.code !== 0) {
showFailToast(result.msg || '开启失败')
return
} else {
showDialog({ title: '错误', message: result.msg || '获取审批详情失败' });
}
showSuccessToast('柜口已开启')
} catch (error) {
showFailToast('请求失败')
} finally {
setTimeout(() => {
isButtonDisabled.value = false
}, 5000)
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
@ -223,15 +196,16 @@ const handleSubmit = async () => {
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('操作成功')
showDialog({ message: '操作成功' })
await showConfirmDialog({
title: "操作成功",
message: `审批处理已完成`
})
router.push('/approval/list')
router.push('/approvalAsset/list')
} else {
console.error('操作失败code:', code, 'msg:', msg)
showConfirmDialog({
@ -255,6 +229,53 @@ const handleComfirm = () => {
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();
@ -267,11 +288,9 @@ const handleReject = () => {
<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="approvalStore.currentApproval?.name" />
<van-cell title="手机号" :value="approvalStore.currentApproval?.mobile" />
<van-cell title="当前状态" :value="statusMap[approvalStore.currentApproval?.status || 1]" />
<!-- <van-cell title="领用说明" :value="approvalStore.currentApproval?.applyRemark" /> -->
<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">
@ -290,9 +309,54 @@ const handleReject = () => {
¥{{ item.price.toFixed(2) }}
</span>
<span class="quantity">
×{{ item.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>
@ -300,10 +364,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 +379,14 @@ 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="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" block :loading="submitting" loading-text="提交中..." @click="handleComfirm">
<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>
@ -333,46 +400,74 @@ const handleReject = () => {
}
.content-wrapper {
padding-top: 46px;
padding-bottom: 100px;
}
padding-top: 46px;
padding-bottom: 100px;
}
.product-list {
margin-bottom: 20px;
}
.product-list {
margin-bottom: 20px;
}
.product-item {
align-items: flex-start;
}
.product-item {
align-items: flex-start;
}
.product-image {
margin-right: 12px;
border-radius: 4px;
}
.product-image {
margin-right: 12px;
border-radius: 4px;
}
.product-info {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 60px;
}
.product-info {
display: flex;
flex-direction: column;
justify-content: space-between;
height: auto;
padding: 5px 0;
.price-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.product-price {
color: #e95d5d;
font-weight: bold;
.product-name {
font-size: 14px;
color: #333;
font-weight: 500;
}
}
.quantity {
color: #666;
font-size: 13px;
}
.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;
@ -401,23 +496,38 @@ 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;
}
.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: #fffbe6;
background-color: #ecf3fc;
padding: 8px;
border-radius: 4px;
}

View File

@ -126,7 +126,8 @@ const handleSubmit = async () => {
const { code, data } = await submitApprovalApi(formData.value)
if (code === 0) {
orderStore.getOrders(wxStore.openid);
orderStore.getOrders(wxStore.corpid, wxStore.openid, wxStore.qyUserId);
try {
await showConfirmDialog({
title: "提交成功",
@ -139,7 +140,7 @@ const handleSubmit = async () => {
console.error('提交失败:', error)
showConfirmDialog({
title: '提交失败',
message: error instanceof Error ? error.name + error.message + error.cause + error.stack : '网络请求异常'
message: error instanceof Error ? error.message : '网络请求异常'
})
} finally {
submitting.value = false

View File

@ -1,48 +1,30 @@
<template>
<div class="approval-list-container">
<!-- 状态切换标签页 -->
<van-tabs v-model:active="activeTab" @change="handleTabChange">
<van-tab title="待处理"></van-tab>
<van-tab title="已处理"></van-tab>
</van-tabs>
<!-- 搜索表单 -->
<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">
<van-picker :columns="statusOptions" @confirm="onStatusConfirm" @cancel="showStatusPicker = false" />
</van-popup>
<!-- <van-field
readonly
clickable
name="date"
:value="dateRangeText"
label="申请时间"
placeholder="选择时间范围"
@click="showDatePicker = true"
/>
<van-popup v-model:show="showDatePicker" position="bottom">
<van-datetime-picker
type="daterange"
@confirm="onDateConfirm"
@cancel="showDatePicker = false"
/>
</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>
<van-search v-model="searchParams.searchStr" placeholder="请输入搜索内容" />
</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-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"
class="approval-list">
<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.applyUserName }}</div>
<div>核销码{{ item.code }}</div>
<div>申请时间{{ item.createTime }}</div>
<van-tag :type="statusTagType(item.status)">
{{ statusMap[item.status] }}
@ -50,13 +32,21 @@
</template>
</van-cell>
</van-list>
<!-- 悬浮核销码按钮 -->
<van-button type="primary" @click="showVerificationDialog = true" class="floating-verification-btn">
<van-icon name="plus" style="font-size: 20px;" />
<!-- <span>核销码</span> -->
</van-button>
</div>
</template>
<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 { ref, reactive, watch, onUnmounted } from 'vue'
import { debounce } from 'lodash-es'
import { showDialog, 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 +55,14 @@ const router = useRouter();
const wxStore = useWxStore();
//
const searchParams = reactive<SearchApiReturnApprovalQuery>({
const activeTab = ref(0);
const searchParams = reactive<SearchReturnApprovalAssetQuery>({
corpid: wxStore.corpid,
handleStatus: activeTab.value,
approvalType: 1,
code: '',
codeCheck: 1,
searchStr: '',
pageNum: 1,
pageSize: 10,
})
@ -84,7 +79,8 @@ const statusOptions = [
const statusMap: { [key: number]: string } = {
1: '待审核',
2: '已通过',
3: '已驳回'
3: '已驳回',
4: '开柜中'
}
const statusText = ref('')
@ -93,8 +89,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 +107,30 @@ const handleCellClick = (approvalId: number) => {
router.push(`/approval/handleApply/${approvalId}`);
}
//
const handleVerification = async () => {
if (!verificationCode.value) {
showDialog({ message: '请输入核销码' })
return
}
try {
const params = {
corpid: wxStore.corpid,
approvalType: 1,
code: verificationCode.value
}
await checkApprovalCodeApi(params)
showDialog({ message: '核销成功' })
showVerificationDialog.value = false
verificationCode.value = ''
//
handleSearch()
} catch (error) {
console.error('核销失败', error)
showDialog({ message: '核销失败' })
}
}
//
const statusTagType = (status: number) => {
switch (status) {
@ -135,6 +159,12 @@ const onDateConfirm = (values: Date[]) => {
showDatePicker.value = false
}
//
const handleTabChange = (tabIndex: number) => {
searchParams.handleStatus = tabIndex;
handleSearch();
};
//
const handleSearch = () => {
list.value = []
@ -142,6 +172,10 @@ const handleSearch = () => {
onLoad()
}
const debouncedHandleSearch = debounce(() => {
handleSearch();
}, 500);
//
const handleReset = () => {
Object.assign(searchParams, {
@ -163,8 +197,11 @@ const handleReset = () => {
const onLoad = async () => {
try {
searchParams.corpid = wxStore.corpid;
const { data } = await getApprovalListApi(searchParams)
list.value.push(...data.rows)
const { data } = await getApprovalAssetListApi(searchParams)
// approvalId
const existingIds = new Set(list.value.map(item => item.approvalId));
const newItems = data.rows.filter(item => !existingIds.has(item.approvalId));
list.value.push(...newItems);
loading.value = false
if (list.value.length >= data.total) {
@ -177,6 +214,15 @@ const onLoad = async () => {
finished.value = true
}
}
//
watch(() => searchParams.searchStr, (newVal) => {
debouncedHandleSearch();
});
onUnmounted(() => {
debouncedHandleSearch.cancel();
});
</script>
<style lang="scss" scoped>
@ -184,6 +230,10 @@ const onLoad = async () => {
padding: 12px;
}
.approval-list {
margin-top: 12px;
}
.van-cell__title {
font-size: 14px;
color: #333;
@ -194,4 +244,28 @@ 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;
}
.floating-verification-btn {
position: fixed;
bottom: 80px;
right: 20px;
border-radius: 50%;
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
padding: 0;
}
</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

@ -6,11 +6,12 @@
<van-row :gutter="[10, 10]" class="shop-row" justify="start">
<van-col v-for="shop in shopList" :key="shop.shopId" span="12" class="shop-col">
<div class="shop-item" @click="handleShopSelect(shop.shopId)">
<div class="shop-info">
<van-icon name="shop-o" size="20" class="shop-icon" />
<div class="shop-name van-ellipsis">{{ shop.shopName }}</div>
</div>
</div>
<van-image :src="shop.coverImg || `${publicPath}product-image.png`" class="shop-cover-img" fit="cover" />
<div class="shop-info">
<van-icon name="shop-o" size="20" class="shop-icon" />
<div class="shop-name van-ellipsis">{{ shop.shopName }}</div>
</div>
</div>
</van-col>
<van-col v-if="shopList.length % 2 === 0" span="12" class="shop-col"></van-col>
</van-row>
@ -341,19 +342,25 @@ onBeforeUnmount(() => {
}
.shop-item {
height: 60px !important;
padding: 12px;
height: auto !important;
padding: 0;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
transition: transform 0.2s;
.shop-cover-img {
width: 100%;
height: 80px;
object-fit: cover;
}
.shop-info {
display: flex;
align-items: center;
justify-content: start;
height: 100%;
padding: 12px;
}
.shop-name {
@ -366,7 +373,6 @@ onBeforeUnmount(() => {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
}
}

View File

@ -108,6 +108,14 @@ 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-col span="8"></van-col>
<van-col span="8"></van-col>
</van-row>
</div>
</template>

View File

@ -50,7 +50,7 @@ function backToHome() {
}
watch(() => orderId.value, async (newVal) => {
await orderStore.getOrders(wxStore.openid)
await orderStore.getOrders(wxStore.corpid, wxStore.openid, wxStore.qyUserId)
currentOrder.value = orderStore.orders.find(o => o.orderId === newVal)
}, { immediate: true })
</script>

View File

@ -8,11 +8,11 @@ const orderStore = useOrderStore()
const wxStore = useWxStoreOutside()
onMounted(() => {
orderStore.getOrders(wxStore.openid)
orderStore.getOrders(wxStore.corpid, wxStore.openid, wxStore.qyUserId)
})
onBeforeRouteUpdate(() => {
orderStore.getOrders(wxStore.openid)
orderStore.getOrders(wxStore.corpid, wxStore.openid, wxStore.qyUserId)
})
const statusMap: Record<number, string> = {

View File

@ -137,10 +137,16 @@ const currentProducts = computed(() => {
//
onMounted(() => {
const urlParams = new URLSearchParams(window.location.search);
const shopIdParam = urlParams.get('shopId') || undefined;
if (showShopList.value) {
getShopListApi(wxStore.corpid).then((res) => {
if (res?.code === 0 && res?.data?.length > 0) {
shopList.value = res.data;
if (shopIdParam && shopList.value.some(shop => shop.shopId.toString() == shopIdParam)) {
handleShopSelect(parseInt(shopIdParam));
}
}
});
} else {
@ -220,6 +226,7 @@ async function handleAb98Bind() {
<van-row :gutter="[10, 10]" class="shop-row" justify="start">
<van-col v-for="shop in shopList" :key="shop.shopId" span="12" class="shop-col">
<div class="shop-item" @click="handleShopSelect(shop.shopId)">
<van-image :src="shop.coverImg || `${publicPath}product-image.png`" class="shop-cover-img" fit="cover" />
<div class="shop-info">
<van-icon name="shop-o" size="20" class="shop-icon" />
<div class="shop-name van-ellipsis">{{ shop.shopName }}</div>
@ -579,8 +586,8 @@ async function handleAb98Bind() {
}
.shop-item {
height: 60px !important;
padding: 12px;
height: auto !important;
padding: 0;
background: white;
border-radius: 8px;
overflow: hidden;
@ -591,7 +598,12 @@ async function handleAb98Bind() {
display: flex;
align-items: center;
justify-content: start;
height: 100%;
padding: 12px;
}
.shop-cover-img {
width: 100%;
height: 80px;
object-fit: cover;
}
.shop-name {

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

@ -1,5 +1,5 @@
import { pinia } from "@/pinia"
import { getOrdersByOpenIdApi } from "@@/apis/shop"
import { getOrdersByOpenIdApi, getOrdersByQyUserIdApi } from "@@/apis/shop"
import type { ShopOrderEntity, ShopOrderGoodsEntity, Goods } from "@@/apis/shop/type"
export interface Order extends ShopOrderEntity {
@ -16,9 +16,11 @@ export const useOrderStore = defineStore("order", () => {
const orderGoods = ref<ShopOrderGoodsEntity[]>([])
const goods = ref<Goods[]>([])
const getOrders = async (openid: string) => {
const getOrders = async (corpid: string, openid: string, qyUserId: number) => {
try {
const { data } = await getOrdersByOpenIdApi(openid)
const { data } = qyUserId ?
await getOrdersByQyUserIdApi(qyUserId)
: await getOrdersByOpenIdApi(corpid, openid);
// 重组订单结构
orders.value = data.orders.map(order => ({

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']
@ -34,8 +35,10 @@ declare module 'vue' {
VanSidebar: typeof import('vant/es')['Sidebar']
VanSidebarItem: typeof import('vant/es')['SidebarItem']
VanStepper: typeof import('vant/es')['Stepper']
VanTab: typeof import('vant/es')['Tab']
VanTabbar: typeof import('vant/es')['Tabbar']
VanTabbarItem: typeof import('vant/es')['TabbarItem']
VanTabs: typeof import('vant/es')['Tabs']
VanTag: typeof import('vant/es')['Tag']
VanUploader: typeof import('vant/es')['Uploader']
}