feat(approval): 添加审批中心功能,包括审批列表和提交审批

新增审批中心页面,支持管理员查看审批列表,包含搜索、分页和状态筛选功能。同时优化了提交审批页面,增加退货备注字段,并改进上传文件后的反馈提示。
This commit is contained in:
dzq 2025-04-09 10:28:48 +08:00
parent 42eae0b1cd
commit 9c81c228ee
7 changed files with 312 additions and 6 deletions

View File

@ -1,5 +1,13 @@
import { request } from '@/http/axios'
import { SubmitApprovalRequestData, SubmitApprovalResponseData } from './type'
import { SubmitApprovalRequestData, SubmitApprovalResponseData, SearchApiReturnApprovalQuery, ApiResponsePageData, ReturnApprovalEntity } from './type'
export const getApprovalListApi = (params: SearchApiReturnApprovalQuery) => {
return request<ApiResponsePageData<ReturnApprovalEntity>>({
url: 'approval/list',
method: 'get',
params
})
}
export const submitApprovalApi = (data: SubmitApprovalRequestData) => {
return request<SubmitApprovalResponseData>({

View File

@ -2,6 +2,55 @@ export interface SubmitApprovalRequestData {
orderGoodsId: number
returnQuantity: number
returnImages: string
returnRemark: string
}
export interface SearchApiReturnApprovalQuery {
pageNum: number
pageSize: number
approvalId?: number
orderId?: number
goodsId?: number
status?: number
startTime?: string
endTime?: string
}
export interface ApiResponsePageData<T> {
code: number
msg: string
data: {
total: number
rows: T[]
}
}
export interface ReturnApprovalEntity {
approvalId: number
orderId: number
goodsId: number
/** 关联订单商品ID */
orderGoodsId: number
/** 归还数量 */
returnQuantity: number
/** 商品单价 */
goodsPrice: number
status: number
returnAmount: number
/** 归还图片路径数组 */
returnImages: string
/** 审核图片路径数组 */
auditImages: string
/** 归还说明 */
returnRemark: string
/** 审核说明 */
auditRemark: string
createTime: string
updateTime: string
/** 商品名称 */
goodsName: string
/** 封面图URL */
coverImg: string
}
export type SubmitApprovalResponseData = ApiResponseMsgData<{

218
src/pages/approval/list.vue Normal file
View File

@ -0,0 +1,218 @@
<template>
<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
:value="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-form>
<!-- 审批列表 -->
<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}`"
>
<template #label>
<div>商品名称{{ item.goodsName }}</div>
<div>申请时间{{ item.createTime }}</div>
<van-tag :type="statusTagType(item.status)">
{{ statusMap[item.status] }}
</van-tag>
</template>
</van-cell>
</van-list>
</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 type { PickerConfirmEventParams } from 'vant/es';
//
const searchParams = reactive<SearchApiReturnApprovalQuery>({
pageNum: 1,
pageSize: 10,
})
//
const showStatusPicker = ref(false)
const statusOptions = [
{ text: '全部', value: undefined },
{ text: '待审核', value: 0 },
{ text: '已通过', value: 1 },
{ text: '已拒绝', value: 2 },
]
const statusMap: { [key: number]: string } = {
0: '待审核',
1: '已通过',
2: '已拒绝'
}
const statusText = ref('')
//
const showDatePicker = ref(false)
const dateRangeText = ref('')
//
const list = ref<ReturnApprovalEntity[]>([])
const loading = ref(false)
const finished = ref(false)
//
const statusTagType = (status: number) => {
switch (status) {
case 0: return 'warning'
case 1: return 'success'
case 2: return 'danger'
default: return 'default'
}
}
//
const onStatusConfirm = (e: PickerConfirmEventParams) => {
const { selectedOptions } = e;
searchParams.status = Number(selectedOptions[0]?.value);
showStatusPicker.value = false;
// statusText
statusText.value = String(selectedOptions[0]?.text || '');
}
//
const onDateConfirm = (values: Date[]) => {
const [start, end] = values
searchParams.startTime = start.toISOString().split('T')[0]
searchParams.endTime = end.toISOString().split('T')[0]
dateRangeText.value = `${searchParams.startTime}${searchParams.endTime}`
showDatePicker.value = false
}
//
const handleSearch = () => {
list.value = []
searchParams.pageNum = 1
onLoad()
}
//
const handleReset = () => {
Object.assign(searchParams, {
pageNum: 1,
pageSize: 10,
approvalId: undefined,
orderId: undefined,
goodsId: undefined,
status: undefined,
startTime: undefined,
endTime: undefined
})
statusText.value = ''
dateRangeText.value = ''
handleSearch()
}
//
const onLoad = async () => {
try {
const { data } = await getApprovalListApi(searchParams)
list.value.push(...data.rows)
loading.value = false
if (list.value.length >= data.total) {
finished.value = true
} else {
searchParams.pageNum++
}
} catch (error) {
console.error('获取审批列表失败', error)
finished.value = true
}
}
</script>
<style scoped>
.approval-list-container {
padding: 12px;
}
.van-cell__title {
font-size: 14px;
color: #333;
}
.van-cell__label {
margin-top: 8px;
color: #666;
font-size: 12px;
}
</style>

View File

@ -13,7 +13,8 @@ const route = useRoute()
const formData = ref<SubmitApprovalRequestData>({
orderGoodsId: Number(route.query.orderGoodsId),
returnQuantity: 1,
returnImages: ''
returnImages: '',
returnRemark: ''
})
watch(() => route.query.orderGoodsId, (newVal) => {
@ -71,10 +72,9 @@ const handleFileUpload = async (items: UploaderFileListItem | UploaderFileListIt
const urls = await Promise.all(uploadPromises)
files.forEach((item, index) => {
item.status = 'done'
item.message = ''
item.message = '上传成功'
item.url = urls[index].url
})
showToast('上传成功')
} catch (error) {
showConfirmDialog({
title: '上传失败',
@ -96,7 +96,13 @@ const handleSubmit = async () => {
if (code === 0) {
showSuccessToast('提交成功')
router.push('/order/' + orderId)
try {
await showConfirmDialog({
title: "提交成功",
message: `退货申请已提交,等待管理员审核`
})
} catch (error) { }
router.push('/order/' + orderId.value)
}
} catch (error) {
console.error('提交失败:', error)
@ -118,6 +124,7 @@ const handleSubmit = async () => {
<van-cell-group>
<!-- 移除订单ID和商品ID的输入框 -->
<van-field v-model="formData.returnQuantity" label="退还数量" type="number" :min="1" />
<van-field v-model="formData.returnRemark" label="退货备注" type="textarea" rows="2" autosize />
</van-cell-group>
<van-cell-group class="upload-section">

View File

@ -75,6 +75,7 @@ const balance = computed(() => wxStore.balance)
<van-cell-group>
<van-cell title="订单列表" is-link @click="router.push('/order-list')" />
<van-cell title="柜机管理" is-link @click="router.push('/cabinet')" v-if="wxStore.isCabinetAdmin"/>
<van-cell title="审批中心" is-link @click="router.push('/approval/list')" v-if="wxStore.isCabinetAdmin"/>
</van-cell-group>
</div>
</template>

View File

@ -87,6 +87,25 @@ export const routes: RouteRecordRaw[] = [
}
}
},
{
path: '/approval/list',
component: () => import('@/pages/approval/list.vue'),
name: "Approval",
meta: {
title: '审批中心',
keepAlive: true,
layout: {
navBar: {
showNavBar: false,
showLeftArrow: false
},
tabbar: {
showTabbar: false,
icon: "home-o"
}
}
}
},
{
path: "/",
component: () => import("@/pages/product/ProductList.vue"),

View File

@ -17,12 +17,16 @@ declare module 'vue' {
VanConfigProvider: typeof import('vant/es')['ConfigProvider']
VanDivider: typeof import('vant/es')['Divider']
VanField: typeof import('vant/es')['Field']
VanForm: typeof import('vant/es')['Form']
VanGrid: typeof import('vant/es')['Grid']
VanGridItem: typeof import('vant/es')['GridItem']
VanIcon: typeof import('vant/es')['Icon']
VanImage: typeof import('vant/es')['Image']
VanList: typeof import('vant/es')['List']
VanLoading: typeof import('vant/es')['Loading']
VanNavBar: typeof import('vant/es')['NavBar']
VanPicker: typeof import('vant/es')['Picker']
VanPopup: typeof import('vant/es')['Popup']
VanRadio: typeof import('vant/es')['Radio']
VanRadioGroup: typeof import('vant/es')['RadioGroup']
VanSidebar: typeof import('vant/es')['Sidebar']