仓库更改 修改订货数量
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:
97694731 2026-07-01 18:11:39 +08:00
parent 77dc45b074
commit 7960d136a9
7 changed files with 238 additions and 36 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
dist/
text.txt
vite.config.js

View File

@ -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,

View File

@ -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>

View File

@ -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 = []

View File

@ -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) {
@ -132,7 +131,7 @@ async function printWaybillForGroup(group, logisticsCompany = 'YUNDA') {
const LODOP = await createPrintTask(logisticsCompany, createRes.expressDeliveryOrder)
LODOP.SET_PRINTER_INDEX(expressPrinter)
LODOP.PRINT()
mailNo = createRes.expressDeliveryOrder.waybillNo;
} else {
@ -196,13 +195,13 @@ async function printWaybillForGroup(group, logisticsCompany = 'YUNDA') {
// 6. 提交公司订单回填(只提交一次)
if (logisticsCompany === 'YZXB' || logisticsCompany == 'YTO' || logisticsCompany == 'JTSD') {
if(logisticsCompany === 'YZXB'){
if (logisticsCompany === 'YZXB') {
await submitCompanyOrder({
code: 'POSTBBZ',
orderNo: createRes.expressDeliveryOrder.waybillNo || createRes.expressDeliveryOrder.waybill_no,
erpOrderId: createRes.expressDeliveryOrder.erpOrderId.toString()
})
}else {
} else {
await submitCompanyOrder({
code: logisticsCompany,
orderNo: createRes.expressDeliveryOrder.waybillNo || createRes.expressDeliveryOrder.waybill_no,

View File

@ -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,

View File

@ -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': {