feat(approval): 优化退货审批流程并添加文件上传功能
- 在 `.env` 配置文件中启用调试工具 - 在 `.env.development`、`.env.staging` 和 `.env.production` 中添加后端地址配置 - 修改 `SubmitApprovalRequestData` 接口,合并 `orderId` 和 `goodsId` 为 `orderGoodsId` - 在 `approval/submit.vue` 中添加文件上传功能,并优化表单提交逻辑 - 在 `order/index.vue` 中添加订单商品状态显示和退款按钮条件判断
This commit is contained in:
parent
bc46f40285
commit
42eae0b1cd
2
.env
2
.env
|
@ -7,4 +7,4 @@ VITE_APP_TITLE = 借还柜
|
||||||
VITE_ROUTER_HISTORY = hash
|
VITE_ROUTER_HISTORY = hash
|
||||||
|
|
||||||
## 是否开启 console 调试工具
|
## 是否开启 console 调试工具
|
||||||
VITE_CONSOLE = false
|
VITE_CONSOLE = true
|
||||||
|
|
|
@ -5,3 +5,6 @@ VITE_BASE_URL = http://localhost:8090/api
|
||||||
|
|
||||||
## 开发环境域名和静态资源公共路径(一般 / 或 ./ 都可以)
|
## 开发环境域名和静态资源公共路径(一般 / 或 ./ 都可以)
|
||||||
VITE_PUBLIC_PATH = /
|
VITE_PUBLIC_PATH = /
|
||||||
|
|
||||||
|
# 后端地址
|
||||||
|
VITE_APP_BASE_API = '/dev-api'
|
|
@ -4,3 +4,6 @@
|
||||||
VITE_BASE_URL = '/shop-api/api'
|
VITE_BASE_URL = '/shop-api/api'
|
||||||
## 打包构建静态资源公共路径(例如部署到 https://un-pany.github.io/mobvue/ 域名下就需要填写 /mobvue/)
|
## 打包构建静态资源公共路径(例如部署到 https://un-pany.github.io/mobvue/ 域名下就需要填写 /mobvue/)
|
||||||
VITE_PUBLIC_PATH = /shop/
|
VITE_PUBLIC_PATH = /shop/
|
||||||
|
|
||||||
|
# 后端地址
|
||||||
|
VITE_APP_BASE_API = '/shop-back-end'
|
|
@ -5,3 +5,6 @@ VITE_BASE_URL = '/shop-api/api'
|
||||||
|
|
||||||
## 打包构建静态资源公共路径(例如部署到 https://un-pany.github.io/ 域名下就需要填写 /)
|
## 打包构建静态资源公共路径(例如部署到 https://un-pany.github.io/ 域名下就需要填写 /)
|
||||||
VITE_PUBLIC_PATH = /shop/
|
VITE_PUBLIC_PATH = /shop/
|
||||||
|
|
||||||
|
# 后端地址
|
||||||
|
VITE_APP_BASE_API = '/shop-back-end'
|
|
@ -1,6 +1,5 @@
|
||||||
export interface SubmitApprovalRequestData {
|
export interface SubmitApprovalRequestData {
|
||||||
orderId: number
|
orderGoodsId: number
|
||||||
goodsId: number
|
|
||||||
returnQuantity: number
|
returnQuantity: number
|
||||||
returnImages: string
|
returnImages: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,41 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { showConfirmDialog, showSuccessToast } from 'vant'
|
import { showConfirmDialog, showSuccessToast, showToast, UploaderFileListItem } from 'vant'
|
||||||
|
import axios from "axios"
|
||||||
import { submitApprovalApi } from '@/common/apis/approval'
|
import { submitApprovalApi } from '@/common/apis/approval'
|
||||||
import type { SubmitApprovalRequestData } from '@/common/apis/approval/type'
|
import type { SubmitApprovalRequestData } from '@/common/apis/approval/type'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
const { VITE_APP_BASE_API } = import.meta.env;
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const formData = ref<SubmitApprovalRequestData>({
|
const formData = ref<SubmitApprovalRequestData>({
|
||||||
orderId: Number(route.query.orderId),
|
orderGoodsId: Number(route.query.orderGoodsId),
|
||||||
goodsId: Number(route.query.goodsId),
|
|
||||||
returnQuantity: 1,
|
returnQuantity: 1,
|
||||||
returnImages: ''
|
returnImages: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(() => route.query.orderGoodsId, (newVal) => {
|
||||||
|
formData.value.orderGoodsId = newVal ? Number(newVal) : NaN
|
||||||
|
})
|
||||||
|
|
||||||
|
const orderId = ref(Number(route.query.orderId))
|
||||||
|
console.log('orderId: ', orderId.value)
|
||||||
|
watch(() => route.query.orderId, (newVal) => {
|
||||||
|
orderId.value = newVal? Number(newVal) : NaN
|
||||||
|
console.log('orderId: ', orderId.value)
|
||||||
|
})
|
||||||
|
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
const fileList = ref<Array<{ url: string }>>([])
|
const fileList = ref<UploaderFileListItem[]>([])
|
||||||
|
const uploading = ref(false)
|
||||||
|
|
||||||
const validateForm = () => {
|
const validateForm = () => {
|
||||||
if (!formData.value.orderId || isNaN(formData.value.orderId)) {
|
if (!formData.value.orderGoodsId || isNaN(formData.value.orderGoodsId)) {
|
||||||
showConfirmDialog({ title: '错误', message: '订单ID参数错误' })
|
showConfirmDialog({ title: '错误', message: '订单ID参数错误' })
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (!formData.value.goodsId || isNaN(formData.value.goodsId)) {
|
|
||||||
showConfirmDialog({ title: '错误', message: '商品ID参数错误' })
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (formData.value.returnQuantity < 1) {
|
if (formData.value.returnQuantity < 1) {
|
||||||
showConfirmDialog({ title: '提示', message: '退还数量至少为1' })
|
showConfirmDialog({ title: '提示', message: '退还数量至少为1' })
|
||||||
return false
|
return false
|
||||||
|
@ -34,20 +43,63 @@ const validateForm = () => {
|
||||||
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
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
showToast('上传成功')
|
||||||
|
} catch (error) {
|
||||||
|
showConfirmDialog({
|
||||||
|
title: '上传失败',
|
||||||
|
message: error instanceof Error ? error.message : '未知错误'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
uploading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!validateForm()) return
|
if (!validateForm()) return
|
||||||
|
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
formData.value.returnImages = fileList.value.map(f => f.url).join(',')
|
formData.value.returnImages = fileList.value.map(item => item.url).join(',')
|
||||||
|
|
||||||
const { code } = await submitApprovalApi(formData.value)
|
const { code, data } = await submitApprovalApi(formData.value)
|
||||||
|
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
showSuccessToast('提交成功')
|
showSuccessToast('提交成功')
|
||||||
router.push({ name: 'approval-result', query: { id: formData.value.orderId } })
|
router.push('/order/' + orderId)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('提交失败:', error)
|
||||||
showConfirmDialog({
|
showConfirmDialog({
|
||||||
title: '提交失败',
|
title: '提交失败',
|
||||||
message: error instanceof Error ? error.message : '网络请求异常'
|
message: error instanceof Error ? error.message : '网络请求异常'
|
||||||
|
@ -60,46 +112,24 @@ const handleSubmit = async () => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="approval-container">
|
<div class="approval-container">
|
||||||
<van-nav-bar
|
<van-nav-bar title="退货审批" left-text="返回" left-arrow fixed @click-left="() => router.go(-1)" />
|
||||||
title="退货审批"
|
|
||||||
left-text="返回"
|
|
||||||
left-arrow
|
|
||||||
fixed
|
|
||||||
@click-left="() => router.go(-1)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<van-cell-group>
|
<van-cell-group>
|
||||||
<!-- 移除订单ID和商品ID的输入框 -->
|
<!-- 移除订单ID和商品ID的输入框 -->
|
||||||
<van-field
|
<van-field v-model="formData.returnQuantity" label="退还数量" type="number" :min="1" />
|
||||||
v-model="formData.returnQuantity"
|
|
||||||
label="退还数量"
|
|
||||||
type="number"
|
|
||||||
:min="1"
|
|
||||||
/>
|
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
|
|
||||||
<van-cell-group class="upload-section">
|
<van-cell-group class="upload-section">
|
||||||
<van-cell title="凭证图片">
|
<van-cell title="凭证图片">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<van-uploader
|
<van-uploader v-model="fileList" multiple max-count="3" :after-read="handleFileUpload" />
|
||||||
v-model="fileList"
|
|
||||||
multiple
|
|
||||||
max-count="3"
|
|
||||||
:after-read="() => {}"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</van-cell>
|
</van-cell>
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
|
|
||||||
<div class="submit-bar">
|
<div class="submit-bar">
|
||||||
<van-button
|
<van-button type="primary" block :loading="submitting" loading-text="提交中..." @click="handleSubmit">
|
||||||
type="primary"
|
|
||||||
block
|
|
||||||
:loading="submitting"
|
|
||||||
loading-text="提交中..."
|
|
||||||
@click="handleSubmit"
|
|
||||||
>
|
|
||||||
提交申请
|
提交申请
|
||||||
</van-button>
|
</van-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -30,13 +30,22 @@ const order = computed(() => {
|
||||||
return orderStore.orders.find(o => o.orderId === orderId.value)
|
return orderStore.orders.find(o => o.orderId === orderId.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const orderGoodsStatusMap: Record<number, string> = {
|
||||||
|
5: '审核中',
|
||||||
|
2: '已退货',
|
||||||
|
6: '退货未通过'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOrderGoodsStatusText = (status: number) => {
|
||||||
|
return orderGoodsStatusMap[status] || ''
|
||||||
|
}
|
||||||
|
|
||||||
const handleRefund = (item: any) => {
|
const handleRefund = (item: any) => {
|
||||||
if (order.value?.status !== 4) return
|
|
||||||
router.push({
|
router.push({
|
||||||
path: '/approval/submit',
|
path: '/approval/submit',
|
||||||
query: {
|
query: {
|
||||||
goodsId: item.goodsInfo.goodsId,
|
orderGoodsId: item.orderGoods.orderGoodsId,
|
||||||
orderId: order.value.orderId
|
orderId: orderId.value,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -85,8 +94,8 @@ onMounted(() => {
|
||||||
<div class="action-row">
|
<div class="action-row">
|
||||||
<p>数量: {{ item.orderGoods.quantity }}</p>
|
<p>数量: {{ item.orderGoods.quantity }}</p>
|
||||||
<p>小计: ¥{{ (item.orderGoods.price * item.orderGoods.quantity).toFixed(2) }}</p>
|
<p>小计: ¥{{ (item.orderGoods.price * item.orderGoods.quantity).toFixed(2) }}</p>
|
||||||
|
<template v-if="item.orderGoods.status === 1">
|
||||||
<van-button
|
<van-button
|
||||||
v-if="order.status === 4"
|
|
||||||
type="primary"
|
type="primary"
|
||||||
size="mini"
|
size="mini"
|
||||||
class="refund-btn"
|
class="refund-btn"
|
||||||
|
@ -94,6 +103,14 @@ onMounted(() => {
|
||||||
>
|
>
|
||||||
退还
|
退还
|
||||||
</van-button>
|
</van-button>
|
||||||
|
</template>
|
||||||
|
<span
|
||||||
|
v-if="[2,5,6].includes(item.orderGoods.status)"
|
||||||
|
class="status-text"
|
||||||
|
:class="`status-${item.orderGoods.status}`"
|
||||||
|
>
|
||||||
|
{{ getOrderGoodsStatusText(item.orderGoods.status) }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</van-cell>
|
</van-cell>
|
||||||
|
@ -171,12 +188,15 @@ goods-info {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #ee0a24;
|
background: #ee0a24;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
padding: 0 12px;
|
padding: 8px 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
min-width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-row {
|
.action-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -30,5 +30,6 @@ declare module 'vue' {
|
||||||
VanTabbar: typeof import('vant/es')['Tabbar']
|
VanTabbar: typeof import('vant/es')['Tabbar']
|
||||||
VanTabbarItem: typeof import('vant/es')['TabbarItem']
|
VanTabbarItem: typeof import('vant/es')['TabbarItem']
|
||||||
VanTag: typeof import('vant/es')['Tag']
|
VanTag: typeof import('vant/es')['Tag']
|
||||||
|
VanUploader: typeof import('vant/es')['Uploader']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue