仓库更改 修改订货数量
Some checks failed
CI / build (18.x) (push) Failing after 40m25s
CI / build (20.x) (push) Failing after 36m6s
CI / deploy-preview (push) Has been skipped
CI / lint (push) Failing after 53m38s
CI / test (push) Successful in 1h9m7s
CI / security (push) Failing after 16m54s
Some checks failed
CI / build (18.x) (push) Failing after 40m25s
CI / build (20.x) (push) Failing after 36m6s
CI / deploy-preview (push) Has been skipped
CI / lint (push) Failing after 53m38s
CI / test (push) Successful in 1h9m7s
CI / security (push) Failing after 16m54s
This commit is contained in:
parent
77dc45b074
commit
7960d136a9
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
dist/
|
||||
text.txt
|
||||
vite.config.js
|
||||
|
||||
@ -149,6 +149,25 @@ export const returnSalesOrderItem = async (data) => {
|
||||
* @param {string} [params.logistics_no] - 快递单号
|
||||
* @returns {Promise<{list: Array, total: number}>}
|
||||
*/
|
||||
/**
|
||||
* 增加销售订单明细的订购数量
|
||||
* @param {Object} data - 请求参数
|
||||
* @param {number} data.sales_order_id - 销售订单ID
|
||||
* @param {number} data.sales_order_item_id - 销售订单明细行ID
|
||||
* @param {number} data.additional_quantity - 增加数量
|
||||
* @param {string} [data.remark] - 备注
|
||||
* @returns {Promise<Object|null>}
|
||||
*/
|
||||
export const modifyOrderQuantity = async (data) => {
|
||||
const response = await request.post(`${API_BASE}/modify-quantity`, {
|
||||
sales_order_id: data.sales_order_id,
|
||||
sales_order_item_id: data.sales_order_item_id,
|
||||
additional_quantity: data.additional_quantity,
|
||||
remark: data.remark || ''
|
||||
})
|
||||
return response?.data || null
|
||||
}
|
||||
|
||||
export const fetchSalesOrderDetails = async ({ page, pageSize, warehouse_id, location_id, status, keyword, shop_type, association_order_no, logistics_no }) => {
|
||||
const params = {
|
||||
page,
|
||||
|
||||
@ -96,26 +96,41 @@
|
||||
<!-- 提交模式:可点击上传图片 -->
|
||||
<template v-else>
|
||||
<!-- 已上传新图片 -->
|
||||
<div v-if="newLiveImageUrl" class="book-image" style="cursor: pointer;" @click="triggerFileSelect"
|
||||
title="点击更换图片">
|
||||
<div v-if="newLiveImageUrl" class="book-image">
|
||||
<img :src="newLiveImageUrl" alt="新书籍封面" width="300" height="300" />
|
||||
<div class="book-image-overlay">
|
||||
<span>点击更换</span>
|
||||
<el-button size="small" type="primary" @click="handleTakePhoto" :disabled="uploadingImage">
|
||||
<el-icon><Camera /></el-icon> 拍照上传
|
||||
</el-button>
|
||||
<el-button size="small" @click="handleLocalUpload" :disabled="uploadingImage">
|
||||
<el-icon><Upload /></el-icon> 本地上传
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 未上传新图片,展示旧数据图片,点击可上传替换 -->
|
||||
<div v-else-if="leftData.liveImage?.[0]" class="book-image" style="cursor: pointer;" @click="triggerFileSelect"
|
||||
title="点击从本地选择图片上传替换">
|
||||
<img :src="leftData.liveImage[0]" alt="旧书籍封面(点击替换)" width="300" height="300" />
|
||||
<!-- 未上传新图片,展示旧数据图片 -->
|
||||
<div v-else-if="leftData.liveImage?.[0]" class="book-image">
|
||||
<img :src="leftData.liveImage[0]" alt="旧书籍封面" width="300" height="300" />
|
||||
<div class="book-image-overlay">
|
||||
<span>点击替换</span>
|
||||
<el-button size="small" type="primary" @click="handleTakePhoto" :disabled="uploadingImage">
|
||||
<el-icon><Camera /></el-icon> 拍照上传
|
||||
</el-button>
|
||||
<el-button size="small" @click="handleLocalUpload" :disabled="uploadingImage">
|
||||
<el-icon><Upload /></el-icon> 本地上传
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 无旧图片 -->
|
||||
<div v-else class="book-image-placeholder" style="cursor: pointer;" @click="triggerFileSelect"
|
||||
title="点击从本地选择图片上传到拼多多空间">
|
||||
<p v-if="uploadingImage">上传中...</p>
|
||||
<p v-else>点击上传新图片</p>
|
||||
<div v-else class="book-image-placeholder">
|
||||
<p v-if="uploadingImage" style="margin-bottom: 12px;">上传中...</p>
|
||||
<p v-else style="margin-bottom: 12px;">暂无图片</p>
|
||||
<div class="placeholder-actions">
|
||||
<el-button size="small" type="primary" @click="handleTakePhoto" :disabled="uploadingImage">
|
||||
<el-icon><Camera /></el-icon> 拍照上传
|
||||
</el-button>
|
||||
<el-button size="small" @click="handleLocalUpload" :disabled="uploadingImage">
|
||||
<el-icon><Upload /></el-icon> 本地上传
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<input ref="fileInputRef" type="file" accept="image/*" style="display: none;" @change="handleFileChange" />
|
||||
</template>
|
||||
@ -223,11 +238,27 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 拍照弹窗 -->
|
||||
<el-dialog v-model="showCameraDialog" title="拍照上传" width="500px" :close-on-click-modal="false"
|
||||
@open="startCamera" @close="stopCamera" append-to-body>
|
||||
<div class="camera-dialog-body">
|
||||
<video ref="cameraVideoRef" autoplay playsinline class="camera-preview"></video>
|
||||
<canvas ref="cameraCanvasRef" style="display: none;"></canvas>
|
||||
<p class="camera-hint">{{ cameraHint }}</p>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="showCameraDialog = false" :disabled="cameraLoading">取消</el-button>
|
||||
<el-button v-if="!capturedPhotoData" type="primary" @click="capturePhoto" :disabled="cameraLoading">拍照</el-button>
|
||||
<el-button v-else type="success" @click="confirmPhoto" :loading="cameraLoading">确认上传</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, computed } from 'vue'
|
||||
import { ref, reactive, watch, computed, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Camera, Upload } from '@element-plus/icons-vue'
|
||||
import { saveAbnormalBook } from '@/api/goodsInfo'
|
||||
import { auditProductLog } from '@/api/submIllegalBook'
|
||||
import { uploadToPddSpace } from '@/utils/pddUpload'
|
||||
@ -431,6 +462,87 @@ const uploadingImage = ref(false)
|
||||
/** 文件选择 input 的 ref */
|
||||
const fileInputRef = ref<HTMLInputElement | null>(null)
|
||||
|
||||
// ============================================
|
||||
// 图片上传
|
||||
// ============================================
|
||||
|
||||
function handleLocalUpload() {
|
||||
fileInputRef.value?.click()
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 拍照上传
|
||||
// ============================================
|
||||
const showCameraDialog = ref(false)
|
||||
const cameraVideoRef = ref<HTMLVideoElement | null>(null)
|
||||
const cameraCanvasRef = ref<HTMLCanvasElement | null>(null)
|
||||
const capturedPhotoData = ref<string | null>(null)
|
||||
const cameraLoading = ref(false)
|
||||
const cameraHint = ref('请将书籍封面放在摄像头前,点击"拍照"')
|
||||
let cameraStream: MediaStream | null = null
|
||||
|
||||
async function handleTakePhoto() {
|
||||
showCameraDialog.value = true
|
||||
}
|
||||
|
||||
async function startCamera() {
|
||||
capturedPhotoData.value = null
|
||||
cameraLoading.value = false
|
||||
cameraHint.value = '正在启动摄像头...'
|
||||
try {
|
||||
cameraStream = await navigator.mediaDevices.getUserMedia({
|
||||
video: { width: { ideal: 640 }, height: { ideal: 480 }, facingMode: 'environment' }
|
||||
})
|
||||
if (cameraVideoRef.value) {
|
||||
cameraVideoRef.value.srcObject = cameraStream
|
||||
}
|
||||
cameraHint.value = '请将书籍封面放在摄像头前,点击"拍照"'
|
||||
} catch (err) {
|
||||
cameraHint.value = '无法访问摄像头,请检查权限'
|
||||
console.error('[拍照] 摄像头启动失败:', err)
|
||||
}
|
||||
}
|
||||
|
||||
function stopCamera() {
|
||||
if (cameraStream) {
|
||||
cameraStream.getTracks().forEach(track => track.stop())
|
||||
cameraStream = null
|
||||
}
|
||||
capturedPhotoData.value = null
|
||||
}
|
||||
|
||||
function capturePhoto() {
|
||||
const video = cameraVideoRef.value
|
||||
const canvas = cameraCanvasRef.value
|
||||
if (!video || !canvas) return
|
||||
canvas.width = video.videoWidth
|
||||
canvas.height = video.videoHeight
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) return
|
||||
ctx.drawImage(video, 0, 0)
|
||||
capturedPhotoData.value = canvas.toDataURL('image/jpeg', 0.92)
|
||||
cameraHint.value = '已拍照,点击"确认上传"提交,或重新拍照请先点取消再打开'
|
||||
}
|
||||
|
||||
async function confirmPhoto() {
|
||||
if (!capturedPhotoData.value) return
|
||||
cameraLoading.value = true
|
||||
try {
|
||||
// dataURL → Blob → File
|
||||
const blob = await fetch(capturedPhotoData.value).then(r => r.blob())
|
||||
const file = new File([blob], `camera_${Date.now()}.jpg`, { type: 'image/jpeg' })
|
||||
const pddUrl = await uploadToPddSpace(file, `camera_${Date.now()}.jpg`)
|
||||
newLiveImageUrl.value = pddUrl
|
||||
ElMessage.success({ message: '拍照上传成功', customClass: 'scan-success-message' })
|
||||
showCameraDialog.value = false
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : '拍照上传失败'
|
||||
ElMessage.error({ message: msg, customClass: 'scan-error-message' })
|
||||
} finally {
|
||||
cameraLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 审核模式 - 图片上传到拼多多空间
|
||||
// ============================================
|
||||
@ -836,11 +948,11 @@ async function handleSubmit() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
letter-spacing: 1px;
|
||||
transition: height 0.25s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.book-image:hover .book-image-overlay {
|
||||
@ -848,14 +960,11 @@ async function handleSubmit() {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.book-image-overlay span {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.book-image-placeholder {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #ddd;
|
||||
@ -870,6 +979,11 @@ async function handleSubmit() {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.placeholder-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.book-info-header {
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -988,4 +1102,23 @@ async function handleSubmit() {
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 拍照弹窗 */
|
||||
.camera-dialog-body {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.camera-preview {
|
||||
width: 100%;
|
||||
max-height: 360px;
|
||||
border-radius: 8px;
|
||||
background: #000;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.camera-hint {
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -216,7 +216,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<el-select v-model="formData.logistics_template_id" placeholder="请选择物流模板" clearable :loading="logisticsLoading" style="width: 100%">
|
||||
<el-option v-for="item in logisticsTemplateOptions" :key="item.id" :label="item.templateName" :value="item.id" />
|
||||
<el-option v-for="item in logisticsTemplateOptions" :key="item.id" :label="item.templateName" :value="String(item.id)" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@ -355,7 +355,7 @@ interface WarehouseFormData {
|
||||
}
|
||||
address_detail: string
|
||||
address?: string
|
||||
logistics_template_id: number | null
|
||||
logistics_template_id: string | null
|
||||
}
|
||||
|
||||
// API 列表响应结构
|
||||
@ -500,7 +500,7 @@ export default defineComponent({
|
||||
// 直接通过 ID 查找(若 handleDetail 成功设值)
|
||||
const id = (detailData.value as any)?.logistics_template_id ?? (detailData.value as any)?.logistics_id
|
||||
if (id != null) {
|
||||
const found = logisticsTemplateOptions.value.find(t => t.id === id)
|
||||
const found = logisticsTemplateOptions.value.find(t => t.id === String(id))
|
||||
if (found) return found.templateName
|
||||
}
|
||||
// 降级:通过 warehouseId 匹配物流模板
|
||||
@ -750,9 +750,10 @@ export default defineComponent({
|
||||
formData.type = source.type ?? row.type
|
||||
formData.status = source.status ?? row.status
|
||||
// 从详情接口获取物流模板ID(若后端返回),否则通过 warehouseId 匹配合适的模板
|
||||
formData.logistics_template_id = (source as any).logistics_template_id ?? (source as any).logistics_id ?? null
|
||||
const rawLogisticsId = (source as any).logistics_template_id ?? (source as any).logistics_id
|
||||
formData.logistics_template_id = rawLogisticsId != null ? String(rawLogisticsId) : null
|
||||
if (formData.logistics_template_id == null) {
|
||||
const matched = logisticsTemplateOptions.value.find(t => t.warehouseId === formData.id)
|
||||
const matched = logisticsTemplateOptions.value.find(t => t.warehouseId === String(formData.id))
|
||||
if (matched) {
|
||||
formData.logistics_template_id = matched.id
|
||||
}
|
||||
@ -874,9 +875,9 @@ export default defineComponent({
|
||||
try {
|
||||
const { list } = await fetchLogisticsList({ page: 1, page_size: 999 })
|
||||
logisticsTemplateOptions.value = list.map((item: any) => ({
|
||||
id: item.id,
|
||||
id: String(item.id),
|
||||
templateName: item.templateName,
|
||||
warehouseId: item.warehouseId
|
||||
warehouseId: String(item.warehouseId)
|
||||
}))
|
||||
} catch (error) {
|
||||
logisticsTemplateOptions.value = []
|
||||
|
||||
@ -114,12 +114,11 @@ async function printWaybillForGroup(group, logisticsCompany = 'YUNDA') {
|
||||
remark: remark
|
||||
})
|
||||
|
||||
console.log('createOrderBatch 返回:', createRes)
|
||||
// 报错之后需要停下来
|
||||
if (createRes?.code !== 0) {
|
||||
console.log('createOrderBatch 返回:', createRes, createRes.code)
|
||||
|
||||
if (String(createRes?.code) !== '200') {
|
||||
throw new Error(createRes?.msg || createRes?.message || '创建快递面单失败,请检查参数后重试')
|
||||
}
|
||||
|
||||
// 校验快递打印机配置
|
||||
const expressPrinter = localStorage.getItem('printer_express')
|
||||
if (!expressPrinter) {
|
||||
|
||||
@ -100,9 +100,11 @@
|
||||
<el-table-column prop="receiver_address" label="收货人地址" min-width="100" show-overflow-tooltip
|
||||
align="center" />
|
||||
|
||||
<el-table-column label="操作" width="100" align="center">
|
||||
<el-table-column label="操作" width="180" align="center">
|
||||
<template #default="{ row: item }">
|
||||
<el-link type="danger" size="small" @click="refundGoods(row, item)">缺货</el-link>
|
||||
<el-divider direction="vertical" />
|
||||
<el-link type="primary" size="small" @click="modifyQuantity(row, item)">修改订购数量</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column label="金额" min-width="100" align="center">
|
||||
@ -303,7 +305,8 @@ import {
|
||||
updateSalesOrder,
|
||||
deleteSalesOrder,
|
||||
confirmSalesOrder,
|
||||
cancelSalesOrder
|
||||
cancelSalesOrder,
|
||||
modifyOrderQuantity
|
||||
} from '@/api/salesOrder'
|
||||
import { createOutbound, createOutboundFromSalesOrder } from '@/api/outbound'
|
||||
import { createWaveOutbound, createWaveOutboundRelease } from '@/api/waveTask'
|
||||
@ -588,6 +591,51 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
/** 修改订购数量 */
|
||||
const modifyQuantity = async (salesOrder: any, item: any): Promise<void> => {
|
||||
try {
|
||||
const { value: inputQty } = await ElMessageBox.prompt(
|
||||
`当前订购数量:${item.quantity}`,
|
||||
'修改订购数量',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputType: 'number',
|
||||
inputPlaceholder: '请输入修改后的数量',
|
||||
inputValidator: (val: string) => {
|
||||
const num = parseInt(val)
|
||||
if (isNaN(num) || num <= 0) {
|
||||
return '请输入大于0的整数'
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
).catch(() => ({ value: '' }))
|
||||
|
||||
if (!inputQty) return
|
||||
|
||||
const additionalQuantity = parseInt(inputQty)
|
||||
if (additionalQuantity <= 0) {
|
||||
ElMessage.warning({ message: '修改数量必须大于0', customClass: 'scan-warning-message' })
|
||||
return
|
||||
}
|
||||
|
||||
await modifyOrderQuantity({
|
||||
sales_order_id: salesOrder.id,
|
||||
sales_order_item_id: item.id,
|
||||
additional_quantity: additionalQuantity
|
||||
})
|
||||
|
||||
ElMessage.success({ message: `成功修改为 ${additionalQuantity} 件`, customClass: 'scan-success-message' })
|
||||
// 清除详情缓存后重新展开
|
||||
delete detailCache.value[salesOrder.id]
|
||||
loadList()
|
||||
} catch (error: any) {
|
||||
if (error === 'cancel') return
|
||||
ElMessage.error({ message: error?.message || '修改订购数量失败', customClass: 'scan-error-message' })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 打开生成出库单弹窗 */
|
||||
@ -741,6 +789,7 @@ export default defineComponent({
|
||||
addItem,
|
||||
removeItem,
|
||||
refundGoods,
|
||||
modifyQuantity,
|
||||
openOutboundDialog,
|
||||
outboundDialogVisible,
|
||||
outboundGenerated,
|
||||
|
||||
@ -247,8 +247,8 @@ module.exports = defineConfig({
|
||||
'/api': {
|
||||
// target: 'http://127.0.0.1:9090',
|
||||
// target: 'http://192.168.101.213:9090',
|
||||
// target: 'https://psi.api.buzhiyushu.cn',
|
||||
target: 'http://127.0.0.1:9090',
|
||||
target: 'https://psi.api.buzhiyushu.cn',
|
||||
// target: 'http://127.0.0.1:9090',
|
||||
changeOrigin: true
|
||||
},
|
||||
'/api/print': {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user