采购人根据历史上传记录来
This commit is contained in:
@ -5,29 +5,19 @@ import traceback
|
|||||||
|
|
||||||
inbound_buy_bp = Blueprint('stock_buy', __name__)
|
inbound_buy_bp = Blueprint('stock_buy', __name__)
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# 0. 基础物料搜索
|
# 0. 基础物料搜索
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@inbound_buy_bp.route('/search-base', methods=['GET'])
|
@inbound_buy_bp.route('/search-base', methods=['GET'])
|
||||||
def search_base():
|
def search_base():
|
||||||
"""
|
|
||||||
供前端下拉框远程搜索使用
|
|
||||||
Query Param: keyword (名称或规格)
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
keyword = request.args.get('keyword', '')
|
keyword = request.args.get('keyword', '')
|
||||||
data = BuyInboundService.search_base_material(keyword)
|
data = BuyInboundService.search_base_material(keyword)
|
||||||
return jsonify({
|
return jsonify({"code": 200, "msg": "success", "data": data})
|
||||||
"code": 200,
|
|
||||||
"msg": "success",
|
|
||||||
"data": data
|
|
||||||
})
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return jsonify({"code": 500, "msg": str(e)}), 500
|
return jsonify({"code": 500, "msg": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# 1. 获取列表
|
# 1. 获取列表
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@ -37,8 +27,6 @@ def get_list():
|
|||||||
page = request.args.get('page', 1, type=int)
|
page = request.args.get('page', 1, type=int)
|
||||||
limit = request.args.get('pageSize', 15, type=int)
|
limit = request.args.get('pageSize', 15, type=int)
|
||||||
keyword = request.args.get('keyword', '')
|
keyword = request.args.get('keyword', '')
|
||||||
|
|
||||||
# 获取状态列表参数
|
|
||||||
statuses_str = request.args.get('statuses', '')
|
statuses_str = request.args.get('statuses', '')
|
||||||
statuses = statuses_str.split(',') if statuses_str else []
|
statuses = statuses_str.split(',') if statuses_str else []
|
||||||
|
|
||||||
@ -48,7 +36,6 @@ def get_list():
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return jsonify({"code": 500, "msg": str(e)}), 500
|
return jsonify({"code": 500, "msg": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# 2. 新增入库
|
# 2. 新增入库
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@ -58,19 +45,12 @@ def submit():
|
|||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if not data:
|
if not data:
|
||||||
return jsonify({"code": 400, "msg": "No data"}), 400
|
return jsonify({"code": 400, "msg": "No data"}), 400
|
||||||
|
|
||||||
new_stock = BuyInboundService.handle_inbound(data)
|
new_stock = BuyInboundService.handle_inbound(data)
|
||||||
|
return jsonify({"code": 200, "msg": "入库成功", "data": new_stock.to_dict()})
|
||||||
return jsonify({
|
|
||||||
"code": 200,
|
|
||||||
"msg": "入库成功",
|
|
||||||
"data": new_stock.to_dict()
|
|
||||||
})
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return jsonify({"code": 500, "msg": str(e)}), 500
|
return jsonify({"code": 500, "msg": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# 3. 更新入库
|
# 3. 更新入库
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@ -83,7 +63,6 @@ def update_buy(id):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"code": 500, "msg": str(e)}), 500
|
return jsonify({"code": 500, "msg": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# 4. 删除
|
# 4. 删除
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@ -95,26 +74,8 @@ def delete_buy(id):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"code": 500, "msg": str(e)}), 500
|
return jsonify({"code": 500, "msg": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# 5. 获取关联的出库历史
|
# 5. 供应商建议 (基于 base_id)
|
||||||
# ------------------------------------------------------------------
|
|
||||||
@inbound_buy_bp.route('/<int:id>/history', methods=['GET'])
|
|
||||||
def get_history(id):
|
|
||||||
try:
|
|
||||||
history = BuyInboundService.get_outbound_history(id)
|
|
||||||
return jsonify({
|
|
||||||
"code": 200,
|
|
||||||
"msg": "success",
|
|
||||||
"data": history
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
traceback.print_exc()
|
|
||||||
return jsonify({"code": 500, "msg": str(e)}), 500
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# 6. 供应商建议
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@inbound_buy_bp.route('/suggestions/suppliers', methods=['GET'])
|
@inbound_buy_bp.route('/suggestions/suppliers', methods=['GET'])
|
||||||
def get_supplier_suggestions():
|
def get_supplier_suggestions():
|
||||||
@ -124,18 +85,17 @@ def get_supplier_suggestions():
|
|||||||
data = BuyInboundService.get_history_suppliers(base_id)
|
data = BuyInboundService.get_history_suppliers(base_id)
|
||||||
return jsonify({"code": 200, "msg": "success", "data": data})
|
return jsonify({"code": 200, "msg": "success", "data": data})
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# 7. 系统用户建议 (采购人)
|
# 6. 采购人建议 (全局,基于 stock_buy 表)
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@inbound_buy_bp.route('/suggestions/users', methods=['GET'])
|
@inbound_buy_bp.route('/suggestions/users', methods=['GET'])
|
||||||
def get_user_suggestions():
|
def get_user_suggestions():
|
||||||
keyword = request.args.get('keyword', '')
|
keyword = request.args.get('keyword', '')
|
||||||
data = BuyInboundService.search_system_users(keyword)
|
data = BuyInboundService.get_history_purchasers(keyword)
|
||||||
return jsonify({"code": 200, "msg": "success", "data": data})
|
return jsonify({"code": 200, "msg": "success", "data": data})
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# 8. [新增] 链接建议
|
# 7. 链接建议 (基于 base_id)
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@inbound_buy_bp.route('/suggestions/links', methods=['GET'])
|
@inbound_buy_bp.route('/suggestions/links', methods=['GET'])
|
||||||
def get_link_suggestions():
|
def get_link_suggestions():
|
||||||
|
|||||||
@ -1,13 +1,6 @@
|
|||||||
from app.extensions import db
|
from app.extensions import db
|
||||||
from app.models.inbound.buy import StockBuy
|
from app.models.inbound.buy import StockBuy
|
||||||
from app.models.base import MaterialBase
|
from app.models.base import MaterialBase
|
||||||
|
|
||||||
# 尝试导入出库模型,如果不存在则忽略
|
|
||||||
try:
|
|
||||||
from app.models.outbound import TransOutbound
|
|
||||||
except ImportError:
|
|
||||||
TransOutbound = None
|
|
||||||
|
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from sqlalchemy import or_, func, text, and_
|
from sqlalchemy import or_, func, text, and_
|
||||||
import traceback
|
import traceback
|
||||||
@ -53,15 +46,12 @@ class BuyInboundService:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def search_base_material(keyword):
|
def search_base_material(keyword):
|
||||||
try:
|
try:
|
||||||
# 只查询已启用的物料
|
|
||||||
query = MaterialBase.query.filter(MaterialBase.is_enabled == True)
|
query = MaterialBase.query.filter(MaterialBase.is_enabled == True)
|
||||||
|
|
||||||
if keyword:
|
if keyword:
|
||||||
query = query.filter(
|
query = query.filter(
|
||||||
or_(
|
or_(
|
||||||
MaterialBase.name.ilike(f'%{keyword}%'),
|
MaterialBase.name.ilike(f'%{keyword}%'),
|
||||||
MaterialBase.spec_model.ilike(f'%{keyword}%'),
|
MaterialBase.spec_model.ilike(f'%{keyword}%')
|
||||||
MaterialBase.pinyin.ilike(f'%{keyword}%')
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
query = query.order_by(MaterialBase.id.desc()).limit(20)
|
query = query.order_by(MaterialBase.id.desc()).limit(20)
|
||||||
@ -342,50 +332,61 @@ class BuyInboundService:
|
|||||||
return {"total": 0, "items": []}
|
return {"total": 0, "items": []}
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 6. 供应商历史查询 (根据 base_id)
|
# 6. 供应商历史查询 (基于 base_id)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_history_suppliers(base_id):
|
def get_history_suppliers(base_id):
|
||||||
"""返回该物料关联的供应商列表(去重)"""
|
"""返回该物料在 stock_buy 表中关联过的供应商列表"""
|
||||||
try:
|
try:
|
||||||
|
# 去重查询
|
||||||
query = db.session.query(StockBuy.supplier_name).filter(
|
query = db.session.query(StockBuy.supplier_name).filter(
|
||||||
StockBuy.base_id == base_id,
|
StockBuy.base_id == base_id,
|
||||||
StockBuy.supplier_name.isnot(None),
|
StockBuy.supplier_name.isnot(None),
|
||||||
StockBuy.supplier_name != ''
|
StockBuy.supplier_name != ''
|
||||||
).distinct().order_by(StockBuy.supplier_name)
|
).distinct().order_by(StockBuy.supplier_name)
|
||||||
|
|
||||||
suppliers = [row[0] for row in query.all()]
|
suppliers = [row[0] for row in query.all()]
|
||||||
return suppliers
|
return suppliers
|
||||||
except Exception:
|
except Exception:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 7. 系统用户搜索 (全局)
|
# 7. 采购人/邮箱历史查询 (全局,从 stock_buy 获取)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def search_system_users(keyword):
|
def get_history_purchasers(keyword):
|
||||||
"""搜索系统用户(活跃状态)"""
|
"""
|
||||||
from app.models.system import SysUser
|
从 stock_buy 表中提取历史采购人和邮箱。
|
||||||
|
不绑定 base_id,因为采购人通常是全局的。
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
query = SysUser.query.filter(SysUser.status == 'active')
|
# 查询 buyer_name 和 buyer_email,并去重
|
||||||
|
query = db.session.query(StockBuy.buyer_name, StockBuy.buyer_email) \
|
||||||
|
.filter(StockBuy.buyer_name.isnot(None), StockBuy.buyer_name != '')
|
||||||
|
|
||||||
if keyword:
|
if keyword:
|
||||||
kw = f'%{keyword}%'
|
kw = f'%{keyword}%'
|
||||||
query = query.filter(db.or_(
|
query = query.filter(or_(
|
||||||
SysUser.username.ilike(kw),
|
StockBuy.buyer_name.ilike(kw),
|
||||||
SysUser.email.ilike(kw)
|
StockBuy.buyer_email.ilike(kw)
|
||||||
))
|
))
|
||||||
query = query.order_by(SysUser.username)
|
|
||||||
|
# 按名字去重,取最新的记录(这里简单做 distinct,具体业务如果一个人有多个邮箱可能需要更复杂逻辑,这里简化为 distinct 组合)
|
||||||
|
results = query.distinct().limit(20).all()
|
||||||
|
|
||||||
users = []
|
users = []
|
||||||
for u in query.limit(20).all():
|
for row in results:
|
||||||
users.append({
|
users.append({
|
||||||
'value': u.username,
|
'value': row.buyer_name, # 前端 autocomplete 显示的值
|
||||||
'email': u.email
|
'email': row.buyer_email or ''
|
||||||
})
|
})
|
||||||
return users
|
return users
|
||||||
except Exception:
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 8. [新增] 链接建议 (根据 base_id)
|
# 8. 链接建议 (基于 base_id)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_history_links(base_id, link_type='original'):
|
def get_history_links(base_id, link_type='original'):
|
||||||
|
|||||||
@ -80,7 +80,7 @@ export function getUserSuggestions(params: any) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10. [新增] 链接建议
|
// 10. 链接建议
|
||||||
export function getLinkSuggestions(params: any) {
|
export function getLinkSuggestions(params: any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/inbound/buy/suggestions/links',
|
url: '/inbound/buy/suggestions/links',
|
||||||
|
|||||||
@ -98,13 +98,6 @@
|
|||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #default="scope" v-else-if="col.prop === 'qty_stock'">
|
|
||||||
<span class="stock-num">{{ scope.row.qty_stock }}</span>
|
|
||||||
</template>
|
|
||||||
<template #default="scope" v-else-if="col.prop === 'qty_available'">
|
|
||||||
<span class="avail-num">{{ scope.row.qty_available }}</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #default="scope" v-else-if="['arrival_photo', 'inspection_report'].includes(col.prop)">
|
<template #default="scope" v-else-if="['arrival_photo', 'inspection_report'].includes(col.prop)">
|
||||||
<div v-if="getImagesOnly(scope.row[col.prop]).length > 0" style="display: flex; align-items: center; justify-content: center;">
|
<div v-if="getImagesOnly(scope.row[col.prop]).length > 0" style="display: flex; align-items: center; justify-content: center;">
|
||||||
<el-image
|
<el-image
|
||||||
@ -399,8 +392,10 @@
|
|||||||
@select="handlePurchaserSelect"
|
@select="handlePurchaserSelect"
|
||||||
>
|
>
|
||||||
<template #default="{ item }">
|
<template #default="{ item }">
|
||||||
<span>{{ item.value }}</span>
|
<div style="display: flex; justify-content: space-between;">
|
||||||
<span v-if="item.email" style="float: right; color: #999; font-size: 12px; margin-left:10px">{{ item.email }}</span>
|
<span style="font-weight: 500">{{ item.value }}</span>
|
||||||
|
<span v-if="item.email" style="color: #999; font-size: 12px;">{{ item.email }}</span>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-autocomplete>
|
</el-autocomplete>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -453,7 +448,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
|
||||||
<el-dialog v-model="dialogVisibleImage" append-to-body width="50%"><img style="width: 100%" :src="dialogImageUrl" alt="Preview Image" /></el-dialog>
|
<el-dialog v-model="dialogVisibleImage" append-to-body width="50%"><img style="width: 100%" :src="dialogImageUrl" alt="Preview Image" /></el-dialog>
|
||||||
<el-dialog v-model="cameraDialogVisible" title="拍照上传" width="500px" append-to-body destroy-on-close :close-on-click-modal="false">
|
<el-dialog v-model="cameraDialogVisible" title="拍照上传" width="500px" append-to-body destroy-on-close :close-on-click-modal="false">
|
||||||
<WebRtcCamera
|
<WebRtcCamera
|
||||||
@ -498,9 +492,6 @@ import {
|
|||||||
import {getLabelPreview, executePrint} from '@/api/common/print'
|
import {getLabelPreview, executePrint} from '@/api/common/print'
|
||||||
import WebRtcCamera from '@/components/Camera/WebRtcCamera.vue'
|
import WebRtcCamera from '@/components/Camera/WebRtcCamera.vue'
|
||||||
|
|
||||||
// 获取环境变量中的 API Base URL,用于图片拼接
|
|
||||||
const apiBaseUrl = import.meta.env.VITE_APP_BASE_API || ''
|
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
// 状态与变量
|
// 状态与变量
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
@ -574,7 +565,6 @@ const stockColumns = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const allColumns = [...baseColumns, ...stockColumns]
|
const allColumns = [...baseColumns, ...stockColumns]
|
||||||
const STORAGE_KEY_COLS = 'stock_buy_visible_columns_v2'
|
|
||||||
|
|
||||||
const defaultColumns = [
|
const defaultColumns = [
|
||||||
'material_name', 'material_type', 'category', 'spec_model', 'unit',
|
'material_name', 'material_type', 'category', 'spec_model', 'unit',
|
||||||
@ -601,7 +591,6 @@ const form = reactive({
|
|||||||
// 1. 供应商建议 (基于 base_id)
|
// 1. 供应商建议 (基于 base_id)
|
||||||
const fetchSupplierSuggestions = async (query: string, cb: any) => {
|
const fetchSupplierSuggestions = async (query: string, cb: any) => {
|
||||||
if (!form.base_id) {
|
if (!form.base_id) {
|
||||||
// 如果没有选物料,不给建议,或者可以给空
|
|
||||||
cb([])
|
cb([])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -623,14 +612,13 @@ const handleSupplierSelect = (item: any) => {
|
|||||||
form.supplier_name = item.value
|
form.supplier_name = item.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 采购人建议 (全局搜索 + 系统用户)
|
// 2. 采购人建议 (全局,来自历史 buy 表)
|
||||||
const fetchUserSuggestions = async (query: string, cb: any) => {
|
const fetchUserSuggestions = async (query: string, cb: any) => {
|
||||||
try {
|
try {
|
||||||
const res: any = await getUserSuggestions({ keyword: query })
|
const res: any = await getUserSuggestions({ keyword: query })
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
// 假设后端返回 [{value: '张三', email: 'zhangsan@xxx.com'}, ...]
|
// res.data = [{value: '张三', email: 'xx@xx.com'}]
|
||||||
const users = res.data.map((user: any) => ({ value: user.value, email: user.email }))
|
cb(res.data)
|
||||||
cb(users)
|
|
||||||
} else {
|
} else {
|
||||||
cb([])
|
cb([])
|
||||||
}
|
}
|
||||||
@ -641,7 +629,7 @@ const fetchUserSuggestions = async (query: string, cb: any) => {
|
|||||||
const querySearchPurchaser = (qs: string, cb: any) => fetchUserSuggestions(qs, cb)
|
const querySearchPurchaser = (qs: string, cb: any) => fetchUserSuggestions(qs, cb)
|
||||||
const handlePurchaserSelect = (item: any) => {
|
const handlePurchaserSelect = (item: any) => {
|
||||||
form.purchaser = item.value
|
form.purchaser = item.value
|
||||||
// 核心:选中采购人时,自动填入邮箱
|
// 自动填充邮箱
|
||||||
if (item.email) {
|
if (item.email) {
|
||||||
form.purchaser_email = item.email
|
form.purchaser_email = item.email
|
||||||
}
|
}
|
||||||
@ -653,7 +641,6 @@ const fetchLinkSuggestions = async (query: string, cb: any, type: 'original' | '
|
|||||||
try {
|
try {
|
||||||
const res: any = await getLinkSuggestions({ base_id: form.base_id, type })
|
const res: any = await getLinkSuggestions({ base_id: form.base_id, type })
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
// 后端返回 ['http://...', 'http://...']
|
|
||||||
const links = res.data.map((link: string) => ({ value: link }))
|
const links = res.data.map((link: string) => ({ value: link }))
|
||||||
const filtered = query ? links.filter((item:any) => item.value.toLowerCase().includes(query.toLowerCase())) : links
|
const filtered = query ? links.filter((item:any) => item.value.toLowerCase().includes(query.toLowerCase())) : links
|
||||||
cb(filtered)
|
cb(filtered)
|
||||||
@ -686,10 +673,6 @@ const onMaterialSelected = (val: number) => {
|
|||||||
form.category = item.category
|
form.category = item.category
|
||||||
form.unit = item.unit
|
form.unit = item.unit
|
||||||
form.material_type = item.type
|
form.material_type = item.type
|
||||||
// 切换物料后,清空跟物料相关的供应商、链接,因为它们不再适用新物料
|
|
||||||
// form.supplier_name = '' // 可选:是否清空
|
|
||||||
// form.source_link = ''
|
|
||||||
// form.detail_link = ''
|
|
||||||
checkHistoryAndSetMode(item.id)
|
checkHistoryAndSetMode(item.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -699,7 +682,6 @@ const onMaterialSelected = (val: number) => {
|
|||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
const validateUnique = (rule: any, value: string, callback: any) => {
|
const validateUnique = (rule: any, value: string, callback: any) => {
|
||||||
if (!value) return callback()
|
if (!value) return callback()
|
||||||
// 前端仅做当前页面的简单重复提示,真正的校验在后端
|
|
||||||
const isDuplicate = tableData.value.some((row: any) => {
|
const isDuplicate = tableData.value.some((row: any) => {
|
||||||
if (dialogStatus.value === 'update' && row.id === form.id) return false
|
if (dialogStatus.value === 'update' && row.id === form.id) return false
|
||||||
if (rule.field === 'serial_number' && row.serial_number === value) return true
|
if (rule.field === 'serial_number' && row.serial_number === value) return true
|
||||||
@ -724,12 +706,11 @@ const rules = {
|
|||||||
// 自动计算批号逻辑
|
// 自动计算批号逻辑
|
||||||
const checkHistoryAndSetMode = async (baseId: number) => {
|
const checkHistoryAndSetMode = async (baseId: number) => {
|
||||||
try {
|
try {
|
||||||
const res: any = await getBuyList({page: 1, pageSize: 1000}) // 获取最近数据
|
const res: any = await getBuyList({page: 1, pageSize: 1000})
|
||||||
const historyItems = (res.data.items || []).filter((item: any) => item.base_id === baseId)
|
const historyItems = (res.data.items || []).filter((item: any) => item.base_id === baseId)
|
||||||
|
|
||||||
if (historyItems.length > 0) {
|
if (historyItems.length > 0) {
|
||||||
modeLocked.value = true
|
modeLocked.value = true
|
||||||
// 找最新的那条记录
|
|
||||||
const latest = historyItems.sort((a: any, b: any) => b.id - a.id)[0]
|
const latest = historyItems.sort((a: any, b: any) => b.id - a.id)[0]
|
||||||
if (latest.serial_number) {
|
if (latest.serial_number) {
|
||||||
entryMode.value = 'serial'
|
entryMode.value = 'serial'
|
||||||
@ -738,7 +719,6 @@ const checkHistoryAndSetMode = async (baseId: number) => {
|
|||||||
} else {
|
} else {
|
||||||
entryMode.value = 'batch'
|
entryMode.value = 'batch'
|
||||||
form.serial_number = ''
|
form.serial_number = ''
|
||||||
// 自动递增批号
|
|
||||||
form.batch_number = incrementBatchNumber(latest.batch_number || '000000')
|
form.batch_number = incrementBatchNumber(latest.batch_number || '000000')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -807,7 +787,7 @@ const handleUpdate = (row: any) => {
|
|||||||
source_link: row.source_link, detail_link: row.detail_link,
|
source_link: row.source_link, detail_link: row.detail_link,
|
||||||
arrival_photo: row.arrival_photo || [], inspection_report: row.inspection_report || []
|
arrival_photo: row.arrival_photo || [], inspection_report: row.inspection_report || []
|
||||||
})
|
})
|
||||||
// 核心:回显图片时,使用 getImageUrl 补全路径
|
// 回显图片,补全路径
|
||||||
arrivalFileList.value = form.arrival_photo.map(url => ({ name: url.split('/').pop(), url: getImageUrl(url) }))
|
arrivalFileList.value = form.arrival_photo.map(url => ({ name: url.split('/').pop(), url: getImageUrl(url) }))
|
||||||
const reports = form.inspection_report || []
|
const reports = form.inspection_report || []
|
||||||
const reportImgs = reports.filter(r => !isExternalLink(r))
|
const reportImgs = reports.filter(r => !isExternalLink(r))
|
||||||
@ -844,11 +824,9 @@ const submitForm = async () => {
|
|||||||
}
|
}
|
||||||
} else { await updateBuyInbound(form.id!, payload); ElMessage.success('更新成功') }
|
} else { await updateBuyInbound(form.id!, payload); ElMessage.success('更新成功') }
|
||||||
|
|
||||||
|
|
||||||
await fetchData()
|
await fetchData()
|
||||||
visible.value = false
|
visible.value = false
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
// 重点:捕获后端唯一性校验错误
|
|
||||||
ElMessage.error(e.msg || '操作失败')
|
ElMessage.error(e.msg || '操作失败')
|
||||||
} finally { submitting.value = false }
|
} finally { submitting.value = false }
|
||||||
}
|
}
|
||||||
@ -859,15 +837,16 @@ const submitForm = async () => {
|
|||||||
// 图片/文件处理 (核心修复)
|
// 图片/文件处理 (核心修复)
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
|
|
||||||
// 1. 路径补全:如果是http开头则直接用,否则拼接 apiBaseUrl
|
// 1. 路径补全
|
||||||
const getImageUrl = (url: string) => {
|
const getImageUrl = (url: string) => {
|
||||||
if (!url) return ''
|
if (!url) return ''
|
||||||
if (url.startsWith('http') || url.startsWith('https') || url.startsWith('blob:')) {
|
if (url.startsWith('http') || url.startsWith('https') || url.startsWith('blob:')) {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
// 拼接 API 基础路径,例如 http://localhost:5000 + /static/files/xxx.jpg
|
// 获取 VITE_APP_BASE_API,如果未设置则默认为空
|
||||||
// 注意处理斜杠,防止双斜杠
|
const apiBase = import.meta.env.VITE_APP_BASE_API || ''
|
||||||
const baseUrl = apiBaseUrl.endsWith('/') ? apiBaseUrl.slice(0, -1) : apiBaseUrl
|
// 去除末尾斜杠
|
||||||
|
const baseUrl = apiBase.endsWith('/') ? apiBase.slice(0, -1) : apiBase
|
||||||
const path = url.startsWith('/') ? url : '/' + url
|
const path = url.startsWith('/') ? url : '/' + url
|
||||||
return baseUrl + path
|
return baseUrl + path
|
||||||
}
|
}
|
||||||
@ -892,16 +871,14 @@ const customUpload = async (options: any, targetField: 'arrival_photo' | 'inspec
|
|||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
const newUrl = res.data.url // 后端返回的相对路径
|
const newUrl = res.data.url // 后端返回的相对路径
|
||||||
|
|
||||||
// 1. 存入表单数据
|
// 1. 存入表单数据 (相对路径)
|
||||||
form[targetField].push(newUrl)
|
form[targetField].push(newUrl)
|
||||||
|
|
||||||
// 2. 核心修复:显式更新 fileList 以确保缩略图显示
|
// 2. 显式更新 fileList (完整路径,用于显示)
|
||||||
// 需要拼接完整路径用于展示
|
|
||||||
const fullUrl = getImageUrl(newUrl)
|
const fullUrl = getImageUrl(newUrl)
|
||||||
const fileObj = { name: file.name, url: fullUrl, status: 'success', uid: file.uid }
|
const fileObj = { name: file.name, url: fullUrl, status: 'success', uid: file.uid }
|
||||||
|
|
||||||
if (targetField === 'arrival_photo') {
|
if (targetField === 'arrival_photo') {
|
||||||
// 替换或追加
|
|
||||||
const idx = arrivalFileList.value.findIndex(f => f.uid === file.uid)
|
const idx = arrivalFileList.value.findIndex(f => f.uid === file.uid)
|
||||||
if (idx > -1) arrivalFileList.value[idx] = fileObj
|
if (idx > -1) arrivalFileList.value[idx] = fileObj
|
||||||
else arrivalFileList.value.push(fileObj)
|
else arrivalFileList.value.push(fileObj)
|
||||||
@ -925,9 +902,8 @@ const customUpload = async (options: any, targetField: 'arrival_photo' | 'inspec
|
|||||||
|
|
||||||
const handleRemoveImage = async (uploadFile: any, targetField: 'arrival_photo' | 'inspection_report') => {
|
const handleRemoveImage = async (uploadFile: any, targetField: 'arrival_photo' | 'inspection_report') => {
|
||||||
try {
|
try {
|
||||||
// 这里需要反向查找,因为 uploadFile.url 可能是带域名的完整路径,而 form 里存的是相对路径
|
|
||||||
// 简单比对末尾文件名
|
|
||||||
const filename = uploadFile.url.split('/').pop()
|
const filename = uploadFile.url.split('/').pop()
|
||||||
|
// 尝试匹配完整路径或文件名
|
||||||
const urlToRemove = form[targetField].find(u => u.endsWith(filename)) || uploadFile.url
|
const urlToRemove = form[targetField].find(u => u.endsWith(filename)) || uploadFile.url
|
||||||
|
|
||||||
form[targetField] = form[targetField].filter(u => u !== urlToRemove)
|
form[targetField] = form[targetField].filter(u => u !== urlToRemove)
|
||||||
@ -966,7 +942,7 @@ const handleCameraConfirm = async (file: File) => {
|
|||||||
// 更新表单
|
// 更新表单
|
||||||
form[field].push(newUrl)
|
form[field].push(newUrl)
|
||||||
|
|
||||||
// 更新文件列表 (使用 getImageUrl 补全显示)
|
// 更新文件列表 (完整路径)
|
||||||
const fileObj = { name: file.name, url: getImageUrl(newUrl) }
|
const fileObj = { name: file.name, url: getImageUrl(newUrl) }
|
||||||
if (field === 'arrival_photo') {
|
if (field === 'arrival_photo') {
|
||||||
arrivalFileList.value.push(fileObj)
|
arrivalFileList.value.push(fileObj)
|
||||||
|
|||||||
Reference in New Issue
Block a user