修改基础信息启用停用内容,进行修复
This commit is contained in:
@ -0,0 +1,57 @@
|
|||||||
|
# app/models/base.py
|
||||||
|
from app.extensions import db
|
||||||
|
|
||||||
|
class MaterialBase(db.Model):
|
||||||
|
"""
|
||||||
|
基础信息表模型
|
||||||
|
对应数据库表: material_base
|
||||||
|
"""
|
||||||
|
__tablename__ = 'material_base'
|
||||||
|
|
||||||
|
# 1. 基础字段
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(255), nullable=False, comment='基础信息名称')
|
||||||
|
category = db.Column(db.String(100), comment='类别')
|
||||||
|
material_type = db.Column(db.String(100), comment='类型')
|
||||||
|
spec_model = db.Column(db.String(255), comment='规格型号')
|
||||||
|
unit = db.Column(db.String(50), comment='计量单位')
|
||||||
|
|
||||||
|
# 可见等级
|
||||||
|
visibility_level = db.Column(db.Integer, default=0, comment='信息可见等级')
|
||||||
|
|
||||||
|
# 链接与图片
|
||||||
|
manual_link = db.Column(db.Text, comment='通用说明书')
|
||||||
|
product_image = db.Column(db.Text, comment='通用产品图')
|
||||||
|
|
||||||
|
# 启用状态
|
||||||
|
is_enabled = db.Column(db.Boolean, default=True, comment='是否启用')
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 关联关系区域
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
# 1. 关联采购库存 (StockBuy)
|
||||||
|
stock_buys = db.relationship('StockBuy', back_populates='material', lazy='dynamic')
|
||||||
|
|
||||||
|
# 2. 关联半成品库存 (StockSemi)
|
||||||
|
stock_semis = db.relationship('StockSemi', back_populates='material', lazy='dynamic')
|
||||||
|
|
||||||
|
# 3. 关联成品库存 (StockProduct)
|
||||||
|
stock_products = db.relationship('StockProduct', back_populates='material', lazy='dynamic')
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""
|
||||||
|
序列化方法
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'name': self.name,
|
||||||
|
'category': self.category,
|
||||||
|
'type': self.material_type, # 前端字段映射
|
||||||
|
'spec': self.spec_model, # 前端字段映射
|
||||||
|
'unit': self.unit,
|
||||||
|
'visibilityLevel': self.visibility_level,
|
||||||
|
'generalManual': self.manual_link,
|
||||||
|
'generalImage': self.product_image,
|
||||||
|
'isEnabled': 1 if self.is_enabled else 0,
|
||||||
|
}
|
||||||
@ -1,57 +0,0 @@
|
|||||||
# app/models/material.py
|
|
||||||
from app.extensions import db
|
|
||||||
|
|
||||||
class MaterialBase(db.Model):
|
|
||||||
"""
|
|
||||||
基础信息表模型
|
|
||||||
对应数据库表: material_base
|
|
||||||
"""
|
|
||||||
__tablename__ = 'material_base'
|
|
||||||
|
|
||||||
# 1. 基础字段
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
name = db.Column(db.String(255), nullable=False, comment='基础信息名称')
|
|
||||||
category = db.Column(db.String(100), comment='类别')
|
|
||||||
material_type = db.Column(db.String(100), comment='类型')
|
|
||||||
spec_model = db.Column(db.String(255), comment='规格型号')
|
|
||||||
unit = db.Column(db.String(50), comment='计量单位')
|
|
||||||
|
|
||||||
# 可见等级
|
|
||||||
visibility_level = db.Column(db.Integer, default=0, comment='信息可见等级')
|
|
||||||
|
|
||||||
# 链接与图片
|
|
||||||
manual_link = db.Column(db.Text, comment='通用说明书')
|
|
||||||
product_image = db.Column(db.Text, comment='通用产品图')
|
|
||||||
|
|
||||||
# 启用状态
|
|
||||||
is_enabled = db.Column(db.Boolean, default=True, comment='是否启用')
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 关联关系区域
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
# 1. 关联采购库存 (StockBuy)
|
|
||||||
stock_buys = db.relationship('StockBuy', back_populates='material', lazy='dynamic')
|
|
||||||
|
|
||||||
# 2. 关联半成品库存 (StockSemi)
|
|
||||||
stock_semis = db.relationship('StockSemi', back_populates='material', lazy='dynamic')
|
|
||||||
|
|
||||||
# 3. 关联成品库存 (StockProduct)
|
|
||||||
stock_products = db.relationship('StockProduct', back_populates='material', lazy='dynamic')
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
"""
|
|
||||||
序列化方法
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
'id': self.id,
|
|
||||||
'name': self.name,
|
|
||||||
'category': self.category,
|
|
||||||
'type': self.material_type, # 前端字段映射
|
|
||||||
'spec': self.spec_model, # 前端字段映射
|
|
||||||
'unit': self.unit,
|
|
||||||
'visibilityLevel': self.visibility_level,
|
|
||||||
'generalManual': self.manual_link,
|
|
||||||
'generalImage': self.product_image,
|
|
||||||
'isEnabled': 1 if self.is_enabled else 0,
|
|
||||||
}
|
|
||||||
@ -19,9 +19,10 @@ export function addMaterialBase(data: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 修改基础信息 (包含状态启用/禁用)
|
// 3. 修改基础信息 (包含状态启用/禁用)
|
||||||
|
// 【修复点】: 必须在 URL 中拼接 data.id,否则后端会报 405 Method Not Allowed
|
||||||
export function updateMaterialBase(data: any) {
|
export function updateMaterialBase(data: any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/inbound/base/',
|
url: `/inbound/base/${data.id}`,
|
||||||
method: 'put',
|
method: 'put',
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
@ -30,7 +31,7 @@ export function updateMaterialBase(data: any) {
|
|||||||
// 4. 删除基础信息
|
// 4. 删除基础信息
|
||||||
export function delMaterialBase(id: number) {
|
export function delMaterialBase(id: number) {
|
||||||
return request({
|
return request({
|
||||||
url: `/inbound/base/${id}`, // 注意这里是反引号,用于拼接 URL
|
url: `/inbound/base/${id}`,
|
||||||
method: 'delete'
|
method: 'delete'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -118,60 +118,63 @@
|
|||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="visible"
|
v-model="visible"
|
||||||
:title="dialogStatus === 'create' ? '新增采购入库' : '编辑入库信息'"
|
:title="dialogStatus === 'create' ? '新增采购入库' : '编辑入库信息'"
|
||||||
width="1050px"
|
width="1000px"
|
||||||
top="5vh"
|
top="4vh"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
class="stylish-dialog"
|
class="stylish-dialog compact-layout"
|
||||||
>
|
>
|
||||||
<el-form :model="form" label-width="110px" ref="formRef" :rules="rules" size="large" class="stylish-form">
|
<div class="dialog-scroll-container">
|
||||||
|
<el-form :model="form" label-width="100px" ref="formRef" :rules="rules" size="default" class="stylish-form">
|
||||||
|
|
||||||
<div class="form-card basic-card">
|
<div class="form-card basic-card">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<el-icon class="icon"><Box /></el-icon>
|
<el-icon class="icon"><Box /></el-icon>
|
||||||
<span>1. 基础信息</span>
|
<span>1. 基础信息</span>
|
||||||
<span class="sub-title" v-if="dialogStatus === 'create'"> (请先搜索选择物料)</span>
|
<span class="sub-title" v-if="dialogStatus === 'create'"> (请先搜索锁定物料)</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<el-row :gutter="24" v-if="dialogStatus === 'create'" style="margin-bottom: 20px;">
|
<el-row :gutter="24" v-if="dialogStatus === 'create'" style="margin-bottom: 15px;">
|
||||||
<el-col :span="14">
|
<el-col :span="10">
|
||||||
<el-form-item label="物料搜索" prop="base_id" class="highlight-label">
|
<el-form-item label="物料搜索" prop="base_id" class="highlight-label">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="form.base_id"
|
v-model="form.base_id"
|
||||||
filterable
|
filterable
|
||||||
remote
|
remote
|
||||||
reserve-keyword
|
reserve-keyword
|
||||||
placeholder="输入名称 / 规格型号进行模糊搜索..."
|
placeholder="输入名称或规格..."
|
||||||
:remote-method="handleSearchMaterial"
|
:remote-method="handleSearchMaterial"
|
||||||
|
@visible-change="handleMaterialDropdownVisible"
|
||||||
:loading="searchLoading"
|
:loading="searchLoading"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@change="onMaterialSelected"
|
@change="onMaterialSelected"
|
||||||
size="large"
|
default-first-option
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in materialOptions"
|
v-for="item in materialOptions"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:label="item.name + ' [' + item.spec + ']'"
|
:label="item.name"
|
||||||
:value="item.id"
|
:value="item.id"
|
||||||
>
|
>
|
||||||
<div class="option-item">
|
<div class="option-item">
|
||||||
<span class="opt-name">{{ item.name }}</span>
|
<span class="opt-name">{{ item.name }}</span>
|
||||||
<span class="opt-spec">{{ item.spec }}</span>
|
<span class="opt-spec">{{ item.spec }}</span>
|
||||||
|
<el-tag v-if="item.isHistory" size="small" type="info" effect="plain">历史</el-tag>
|
||||||
</div>
|
</div>
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="10">
|
<el-col :span="14" style="display: flex; align-items: center;">
|
||||||
<div class="info-alert">
|
<span class="search-tip">
|
||||||
<el-icon><InfoFilled /></el-icon> 仅展示状态为“启用”的基础物料
|
<el-icon><InfoFilled /></el-icon> 点击可查看最近使用的物料,或输入关键词从云端搜索。
|
||||||
</div>
|
</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<div class="read-only-grid">
|
<div class="read-only-grid">
|
||||||
<el-row :gutter="24">
|
<el-row :gutter="20">
|
||||||
<el-col :span="8"><el-form-item label="名称"><el-input v-model="form.material_name" disabled class="is-text-view" /></el-form-item></el-col>
|
<el-col :span="8"><el-form-item label="名称"><el-input v-model="form.material_name" disabled class="is-text-view" /></el-form-item></el-col>
|
||||||
<el-col :span="8"><el-form-item label="规格型号"><el-input v-model="form.spec_model" disabled class="is-text-view" /></el-form-item></el-col>
|
<el-col :span="8"><el-form-item label="规格型号"><el-input v-model="form.spec_model" disabled class="is-text-view" /></el-form-item></el-col>
|
||||||
<el-col :span="8"><el-form-item label="单位"><el-input v-model="form.unit" disabled class="is-text-view" /></el-form-item></el-col>
|
<el-col :span="8"><el-form-item label="单位"><el-input v-model="form.unit" disabled class="is-text-view" /></el-form-item></el-col>
|
||||||
@ -189,7 +192,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<el-row :gutter="24">
|
<el-row :gutter="20">
|
||||||
<el-col :span="6"><el-form-item label="编码/SKU" prop="sku"><el-input v-model="form.sku" placeholder="选填" /></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="编码/SKU" prop="sku"><el-input v-model="form.sku" placeholder="选填" /></el-form-item></el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="入库日期" prop="in_date">
|
<el-form-item label="入库日期" prop="in_date">
|
||||||
@ -202,16 +205,16 @@
|
|||||||
|
|
||||||
<div class="identity-panel">
|
<div class="identity-panel">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="24" style="margin-bottom: 12px;">
|
<el-col :span="24" style="margin-bottom: 8px;">
|
||||||
<el-radio-group v-model="entryMode" @change="handleEntryModeChange" :disabled="modeLocked" class="custom-radio-group">
|
<el-radio-group v-model="entryMode" @change="handleEntryModeChange" :disabled="modeLocked" size="small" class="custom-radio-group">
|
||||||
<el-radio-button label="batch">按批号入库 (Batch)</el-radio-button>
|
<el-radio-button label="batch">按批号入库 (Batch)</el-radio-button>
|
||||||
<el-radio-button label="serial">按序列号入库 (SN)</el-radio-button>
|
<el-radio-button label="serial">按序列号入库 (SN)</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
<span v-if="modeLocked" class="locked-msg"><el-icon><Lock /></el-icon> 根据历史记录已锁定入库方式</span>
|
<span v-if="modeLocked" class="locked-msg"><el-icon><Lock /></el-icon> 历史锁定</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row :gutter="24">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="批号" prop="batch_number">
|
<el-form-item label="批号" prop="batch_number">
|
||||||
<el-input
|
<el-input
|
||||||
@ -239,7 +242,7 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-row :gutter="24" style="margin-top: 15px;">
|
<el-row :gutter="20" style="margin-top: 10px;">
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="入库数量" prop="in_quantity">
|
<el-form-item label="入库数量" prop="in_quantity">
|
||||||
<el-input-number v-model="form.in_quantity" :min="1" controls-position="right" style="width:100%" class="strong-input" />
|
<el-input-number v-model="form.in_quantity" :min="1" controls-position="right" style="width:100%" class="strong-input" />
|
||||||
@ -282,8 +285,23 @@
|
|||||||
|
|
||||||
<div class="divider-text">商务与采购信息</div>
|
<div class="divider-text">商务与采购信息</div>
|
||||||
|
|
||||||
<el-row :gutter="24">
|
<el-row :gutter="20">
|
||||||
<el-col :span="6"><el-form-item label="币种"><el-input v-model="form.currency" /></el-form-item></el-col>
|
<el-col :span="6">
|
||||||
|
<el-form-item label="币种">
|
||||||
|
<el-autocomplete
|
||||||
|
v-model="form.currency"
|
||||||
|
:fetch-suggestions="querySearchCurrency"
|
||||||
|
placeholder="币种"
|
||||||
|
style="width: 100%"
|
||||||
|
:trigger-on-focus="true"
|
||||||
|
>
|
||||||
|
<template #default="{ item }">
|
||||||
|
<span>{{ item.value }}</span>
|
||||||
|
<span style="float:right; color:#999; font-size:12px">{{ item.desc }}</span>
|
||||||
|
</template>
|
||||||
|
</el-autocomplete>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
<el-col :span="6"><el-form-item label="汇率"><el-input-number v-model="form.exchange_rate" :precision="2" controls-position="right" style="width:100%"/></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="汇率"><el-input-number v-model="form.exchange_rate" :precision="2" controls-position="right" style="width:100%"/></el-form-item></el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="含税单价" prop="unit_price">
|
<el-form-item label="含税单价" prop="unit_price">
|
||||||
@ -297,13 +315,49 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row :gutter="24">
|
<el-row :gutter="20">
|
||||||
<el-col :span="8"><el-form-item label="供应商"><el-input v-model="form.supplier_name" placeholder="供应商全称" /></el-form-item></el-col>
|
<el-col :span="8">
|
||||||
<el-col :span="8"><el-form-item label="采购人"><el-input v-model="form.purchaser" /></el-form-item></el-col>
|
<el-form-item label="供应商">
|
||||||
<el-col :span="8"><el-form-item label="采购邮箱"><el-input v-model="form.purchaser_email" /></el-form-item></el-col>
|
<el-autocomplete
|
||||||
|
v-model="form.supplier_name"
|
||||||
|
:fetch-suggestions="querySearchSupplier"
|
||||||
|
placeholder="输入或选择供应商"
|
||||||
|
style="width: 100%"
|
||||||
|
clearable
|
||||||
|
:trigger-on-focus="true"
|
||||||
|
@select="handleSupplierSelect"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="采购人">
|
||||||
|
<el-autocomplete
|
||||||
|
v-model="form.purchaser"
|
||||||
|
:fetch-suggestions="querySearchPurchaser"
|
||||||
|
placeholder="输入采购人"
|
||||||
|
style="width: 100%"
|
||||||
|
clearable
|
||||||
|
:trigger-on-focus="true"
|
||||||
|
@select="handlePurchaserSelect"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="采购邮箱">
|
||||||
|
<el-autocomplete
|
||||||
|
v-model="form.purchaser_email"
|
||||||
|
:fetch-suggestions="querySearchEmail"
|
||||||
|
placeholder="输入邮箱"
|
||||||
|
style="width: 100%"
|
||||||
|
clearable
|
||||||
|
:trigger-on-focus="true"
|
||||||
|
@select="handleEmailSelect"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row :gutter="24">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12"><el-form-item label="原始链接"><el-input v-model="form.source_link" placeholder="http://" /></el-form-item></el-col>
|
<el-col :span="12"><el-form-item label="原始链接"><el-input v-model="form.source_link" placeholder="http://" /></el-form-item></el-col>
|
||||||
<el-col :span="12"><el-form-item label="详情链接"><el-input v-model="form.detail_link" placeholder="http://" /></el-form-item></el-col>
|
<el-col :span="12"><el-form-item label="详情链接"><el-input v-model="form.detail_link" placeholder="http://" /></el-form-item></el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@ -311,6 +365,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</el-form>
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
@ -392,7 +447,7 @@ const stockColumns = [
|
|||||||
const allColumns = [...baseColumns, ...stockColumns]
|
const allColumns = [...baseColumns, ...stockColumns]
|
||||||
|
|
||||||
// 表头持久化
|
// 表头持久化
|
||||||
const STORAGE_KEY = 'stock_buy_visible_columns'
|
const STORAGE_KEY_COLS = 'stock_buy_visible_columns'
|
||||||
const defaultColumns = [
|
const defaultColumns = [
|
||||||
'material_name', 'category', 'material_type', 'spec_model', 'unit',
|
'material_name', 'category', 'material_type', 'spec_model', 'unit',
|
||||||
'inbound_date', 'serial_number', 'batch_number', 'status', 'inspection_status',
|
'inbound_date', 'serial_number', 'batch_number', 'status', 'inspection_status',
|
||||||
@ -401,7 +456,7 @@ const defaultColumns = [
|
|||||||
|
|
||||||
const getSavedColumns = () => {
|
const getSavedColumns = () => {
|
||||||
try {
|
try {
|
||||||
const saved = localStorage.getItem(STORAGE_KEY)
|
const saved = localStorage.getItem(STORAGE_KEY_COLS)
|
||||||
return saved ? JSON.parse(saved) : defaultColumns
|
return saved ? JSON.parse(saved) : defaultColumns
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return defaultColumns
|
return defaultColumns
|
||||||
@ -411,7 +466,7 @@ const getSavedColumns = () => {
|
|||||||
const visibleColumnProps = ref(getSavedColumns())
|
const visibleColumnProps = ref(getSavedColumns())
|
||||||
|
|
||||||
watch(visibleColumnProps, (newVal) => {
|
watch(visibleColumnProps, (newVal) => {
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(newVal))
|
localStorage.setItem(STORAGE_KEY_COLS, JSON.stringify(newVal))
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
|
|
||||||
|
|
||||||
@ -429,6 +484,163 @@ const form = reactive({
|
|||||||
source_link: '', detail_link: '', arrival_photo: ''
|
source_link: '', detail_link: '', arrival_photo: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
// 历史记录管理器 (Local Storage)
|
||||||
|
// ------------------------------------
|
||||||
|
const HISTORY_KEYS = {
|
||||||
|
SUPPLIER: 'history_suppliers',
|
||||||
|
PURCHASER: 'history_purchasers',
|
||||||
|
EMAIL: 'history_emails',
|
||||||
|
MATERIAL: 'history_materials'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存历史 (String 类型)
|
||||||
|
const saveToHistory = (key: string, value: string) => {
|
||||||
|
if (!value) return
|
||||||
|
try {
|
||||||
|
const existing = localStorage.getItem(key)
|
||||||
|
let list = existing ? JSON.parse(existing) : []
|
||||||
|
// 移除旧的,添加到前面
|
||||||
|
list = list.filter((i: string) => i !== value)
|
||||||
|
list.unshift(value)
|
||||||
|
if (list.length > 20) list = list.slice(0, 20) // 最多存20条
|
||||||
|
localStorage.setItem(key, JSON.stringify(list))
|
||||||
|
} catch (e) { console.error('save history failed', e) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取历史 (String 类型)
|
||||||
|
const getHistoryList = (key: string): any[] => {
|
||||||
|
try {
|
||||||
|
const existing = localStorage.getItem(key)
|
||||||
|
const list = existing ? JSON.parse(existing) : []
|
||||||
|
return list.map((v: string) => ({ value: v }))
|
||||||
|
} catch (e) { return [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存物料历史 (Object 类型)
|
||||||
|
const saveMaterialHistory = (item: any) => {
|
||||||
|
if (!item || !item.id) return
|
||||||
|
const key = HISTORY_KEYS.MATERIAL
|
||||||
|
try {
|
||||||
|
const existing = localStorage.getItem(key)
|
||||||
|
let list = existing ? JSON.parse(existing) : []
|
||||||
|
// 必须保存完整对象,因为下拉框需要显示 name, spec 等
|
||||||
|
list = list.filter((i: any) => i.id !== item.id)
|
||||||
|
list.unshift({ ...item, isHistory: true }) // 标记为历史
|
||||||
|
if (list.length > 10) list = list.slice(0, 10)
|
||||||
|
localStorage.setItem(key, JSON.stringify(list))
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMaterialHistory = () => {
|
||||||
|
try {
|
||||||
|
const existing = localStorage.getItem(HISTORY_KEYS.MATERIAL)
|
||||||
|
return existing ? JSON.parse(existing) : []
|
||||||
|
} catch (e) { return [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
// Autocomplete 建议逻辑 (混合模式:历史+当前表格)
|
||||||
|
// ------------------------------------
|
||||||
|
const createFilter = (queryString: string) => {
|
||||||
|
return (item: any) => {
|
||||||
|
return (item.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数:从当前表格提取
|
||||||
|
const getTableDataUnique = (field: string) => {
|
||||||
|
const uniqueItems = Array.from(new Set(tableData.value.map((i: any) => i[field]).filter(Boolean)))
|
||||||
|
return uniqueItems.map(i => ({ value: i }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用查询: 历史记录 + 当前页面数据
|
||||||
|
const mixedSearch = (queryString: string, tableField: string, storageKey: string, cb: any) => {
|
||||||
|
const tableList = getTableDataUnique(tableField)
|
||||||
|
const historyList = getHistoryList(storageKey)
|
||||||
|
|
||||||
|
// 合并去重
|
||||||
|
const map = new Map()
|
||||||
|
historyList.forEach(i => map.set(i.value, i))
|
||||||
|
tableList.forEach(i => map.set(i.value, i)) // 表格数据优先(虽然value一样没区别)
|
||||||
|
|
||||||
|
const allList = Array.from(map.values())
|
||||||
|
const results = queryString ? allList.filter(createFilter(queryString)) : allList
|
||||||
|
cb(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 供应商
|
||||||
|
const querySearchSupplier = (qs: string, cb: any) => mixedSearch(qs, 'supplier_name', HISTORY_KEYS.SUPPLIER, cb)
|
||||||
|
const handleSupplierSelect = (item: any) => saveToHistory(HISTORY_KEYS.SUPPLIER, item.value)
|
||||||
|
|
||||||
|
// 2. 采购人
|
||||||
|
const querySearchPurchaser = (qs: string, cb: any) => mixedSearch(qs, 'purchaser', HISTORY_KEYS.PURCHASER, cb)
|
||||||
|
const handlePurchaserSelect = (item: any) => saveToHistory(HISTORY_KEYS.PURCHASER, item.value)
|
||||||
|
|
||||||
|
// 3. 邮箱
|
||||||
|
const querySearchEmail = (qs: string, cb: any) => mixedSearch(qs, 'purchaser_email', HISTORY_KEYS.EMAIL, cb)
|
||||||
|
const handleEmailSelect = (item: any) => saveToHistory(HISTORY_KEYS.EMAIL, item.value)
|
||||||
|
|
||||||
|
// 4. 币种 (固定+过滤)
|
||||||
|
const currencyOptions = [
|
||||||
|
{ value: 'CNY', desc: '人民币' },
|
||||||
|
{ value: 'USD', desc: '美元' },
|
||||||
|
{ value: 'EUR', desc: '欧元' }
|
||||||
|
]
|
||||||
|
const querySearchCurrency = (queryString: string, cb: any) => {
|
||||||
|
const results = queryString ? currencyOptions.filter(createFilter(queryString)) : currencyOptions
|
||||||
|
cb(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
// 物料搜索逻辑 (支持历史回显 + API)
|
||||||
|
// ------------------------------------
|
||||||
|
|
||||||
|
// 下拉框展开时触发
|
||||||
|
const handleMaterialDropdownVisible = (visible: boolean) => {
|
||||||
|
if (visible) {
|
||||||
|
// 只有当列表为空时(即没有进行API搜索时),才填充历史记录
|
||||||
|
// 这样不会覆盖用户正在搜的结果
|
||||||
|
if (materialOptions.value.length === 0) {
|
||||||
|
materialOptions.value = getMaterialHistory()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSearchMaterial = async (query: string) => {
|
||||||
|
if (query) {
|
||||||
|
searchLoading.value = true
|
||||||
|
try {
|
||||||
|
const res: any = await searchMaterialBase(query)
|
||||||
|
// 给 API 返回的数据加个标记,区分历史
|
||||||
|
const apiResults = (res.data || []).map((i: any) => ({ ...i, isHistory: false }))
|
||||||
|
materialOptions.value = apiResults
|
||||||
|
} finally {
|
||||||
|
searchLoading.value = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 搜索词清空时,恢复历史记录
|
||||||
|
materialOptions.value = getMaterialHistory()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中物料后 -> 存入历史 -> 填充表单
|
||||||
|
const onMaterialSelected = (val: number) => {
|
||||||
|
const item = materialOptions.value.find(i => i.id === val)
|
||||||
|
if (item) {
|
||||||
|
// 存入历史
|
||||||
|
saveMaterialHistory(item)
|
||||||
|
|
||||||
|
form.material_name = item.name
|
||||||
|
form.spec_model = item.spec
|
||||||
|
form.category = item.category
|
||||||
|
form.unit = item.unit
|
||||||
|
form.material_type = item.type
|
||||||
|
checkHistoryAndSetMode(item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
// 逻辑校验规则
|
// 逻辑校验规则
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
@ -521,32 +733,6 @@ const handleEntryModeChange = (val: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSearchMaterial = async (query: string) => {
|
|
||||||
if (query) {
|
|
||||||
searchLoading.value = true
|
|
||||||
try {
|
|
||||||
const res: any = await searchMaterialBase(query)
|
|
||||||
materialOptions.value = res.data || []
|
|
||||||
} finally {
|
|
||||||
searchLoading.value = false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
materialOptions.value = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onMaterialSelected = (val: number) => {
|
|
||||||
const item = materialOptions.value.find(i => i.id === val)
|
|
||||||
if (item) {
|
|
||||||
form.material_name = item.name
|
|
||||||
form.spec_model = item.spec
|
|
||||||
form.category = item.category
|
|
||||||
form.unit = item.unit
|
|
||||||
form.material_type = item.type
|
|
||||||
checkHistoryAndSetMode(item.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => [form.in_quantity, form.unit_price], () => {
|
watch(() => [form.in_quantity, form.unit_price], () => {
|
||||||
form.total_price = Number((form.in_quantity * form.unit_price).toFixed(4))
|
form.total_price = Number((form.in_quantity * form.unit_price).toFixed(4))
|
||||||
})
|
})
|
||||||
@ -568,6 +754,8 @@ const handleCreate = () => {
|
|||||||
entryMode.value = 'batch'
|
entryMode.value = 'batch'
|
||||||
form.batch_number = ''
|
form.batch_number = ''
|
||||||
visible.value = true
|
visible.value = true
|
||||||
|
// 每次打开弹窗时,先清空选项,让下拉时触发“历史加载”
|
||||||
|
materialOptions.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUpdate = (row: any) => {
|
const handleUpdate = (row: any) => {
|
||||||
@ -614,6 +802,14 @@ const handleUpdate = (row: any) => {
|
|||||||
form.detail_link = row.detail_link
|
form.detail_link = row.detail_link
|
||||||
form.arrival_photo = row.arrival_photo
|
form.arrival_photo = row.arrival_photo
|
||||||
|
|
||||||
|
// 编辑模式下,把当前物料塞入选项,防止显示为 ID
|
||||||
|
materialOptions.value = [{
|
||||||
|
id: row.base_id,
|
||||||
|
name: row.material_name,
|
||||||
|
spec: row.spec_model,
|
||||||
|
category: row.category
|
||||||
|
}]
|
||||||
|
|
||||||
visible.value = true
|
visible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -635,6 +831,12 @@ const submitForm = async () => {
|
|||||||
await updateBuyInbound(form.id!, payload)
|
await updateBuyInbound(form.id!, payload)
|
||||||
ElMessage.success('更新成功')
|
ElMessage.success('更新成功')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 核心修改:提交成功时,保存供应商等信息到历史记录
|
||||||
|
saveToHistory(HISTORY_KEYS.SUPPLIER, form.supplier_name)
|
||||||
|
saveToHistory(HISTORY_KEYS.PURCHASER, form.purchaser)
|
||||||
|
saveToHistory(HISTORY_KEYS.EMAIL, form.purchaser_email)
|
||||||
|
|
||||||
await fetchData()
|
await fetchData()
|
||||||
visible.value = false
|
visible.value = false
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@ -688,7 +890,7 @@ onMounted(() => fetchData())
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
/* 全局布局 */
|
/* 全局布局 */
|
||||||
.buy-module {
|
.buy-module {
|
||||||
background: #f5f7fa; /* 整体背景微灰,突出内容 */
|
background: #f5f7fa;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
@ -727,21 +929,34 @@ onMounted(() => fetchData())
|
|||||||
.avail-num { font-weight: bold; color: #67C23A; font-size: 15px; }
|
.avail-num { font-weight: bold; color: #67C23A; font-size: 15px; }
|
||||||
.sum-tag { margin-left: 4px; transform: scale(0.9); }
|
.sum-tag { margin-left: 4px; transform: scale(0.9); }
|
||||||
|
|
||||||
/* 弹窗与表单美化 */
|
/* 弹窗核心样式调整 */
|
||||||
|
:deep(.el-dialog__body) {
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 内部滚动容器 */
|
||||||
|
.dialog-scroll-container {
|
||||||
|
padding: 15px 20px;
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.stylish-form .form-card {
|
.stylish-form .form-card {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid #e4e7ed;
|
border: 1px solid #e4e7ed;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 15px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
background: #fcfcfc;
|
background: #fcfcfc;
|
||||||
padding: 12px 20px;
|
padding: 10px 20px;
|
||||||
border-bottom: 1px solid #ebeef5;
|
border-bottom: 1px solid #ebeef5;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 15px;
|
font-size: 14px;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -749,10 +964,11 @@ onMounted(() => fetchData())
|
|||||||
.card-title .icon { margin-right: 8px; font-size: 18px; color: #409EFF; }
|
.card-title .icon { margin-right: 8px; font-size: 18px; color: #409EFF; }
|
||||||
.card-title .sub-title { font-size: 12px; color: #909399; font-weight: normal; margin-left: 10px; }
|
.card-title .sub-title { font-size: 12px; color: #909399; font-weight: normal; margin-left: 10px; }
|
||||||
|
|
||||||
.card-content { padding: 20px; }
|
.card-content { padding: 15px 20px; }
|
||||||
|
|
||||||
/* 基础信息卡片 (蓝色调,示读) */
|
/* 基础信息卡片 */
|
||||||
.basic-card { border-left: 4px solid #409EFF; }
|
.basic-card { border-left: 4px solid #409EFF; }
|
||||||
|
.search-tip { color: #909399; font-size: 12px; margin-left: 10px; display: flex; align-items: center; gap: 4px; }
|
||||||
.is-text-view :deep(.el-input__wrapper) {
|
.is-text-view :deep(.el-input__wrapper) {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
background-color: #f5f7fa;
|
background-color: #f5f7fa;
|
||||||
@ -760,7 +976,7 @@ onMounted(() => fetchData())
|
|||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
.is-text-view :deep(.el-input__inner) { color: #606266; font-weight: 500; }
|
.is-text-view :deep(.el-input__inner) { color: #606266; font-weight: 500; font-size: 13px; }
|
||||||
|
|
||||||
/* 入库信息卡片 */
|
/* 入库信息卡片 */
|
||||||
.inbound-card { border-left: 4px solid #67C23A; }
|
.inbound-card { border-left: 4px solid #67C23A; }
|
||||||
@ -770,8 +986,8 @@ onMounted(() => fetchData())
|
|||||||
background: #fffbf0;
|
background: #fffbf0;
|
||||||
border: 1px dashed #e6a23c;
|
border: 1px dashed #e6a23c;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 15px;
|
padding: 12px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
.custom-radio-group { margin-bottom: 10px; }
|
.custom-radio-group { margin-bottom: 10px; }
|
||||||
.locked-msg { font-size: 12px; color: #e6a23c; margin-left: 15px; }
|
.locked-msg { font-size: 12px; color: #e6a23c; margin-left: 15px; }
|
||||||
@ -784,9 +1000,9 @@ onMounted(() => fetchData())
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 30px 0 20px;
|
margin: 20px 0 15px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.divider-text::before, .divider-text::after {
|
.divider-text::before, .divider-text::after {
|
||||||
@ -798,10 +1014,17 @@ onMounted(() => fetchData())
|
|||||||
.divider-text::after { margin-left: 15px; }
|
.divider-text::after { margin-left: 15px; }
|
||||||
|
|
||||||
/* 底部按钮 */
|
/* 底部按钮 */
|
||||||
.dialog-footer { display: flex; justify-content: flex-end; gap: 15px; margin-top: 20px; }
|
.dialog-footer {
|
||||||
.info-alert { font-size: 12px; color: #909399; margin-top: 10px; display: flex; align-items: center; gap: 5px; }
|
display: flex;
|
||||||
.option-item { display: flex; justify-content: space-between; width: 100%; }
|
justify-content: flex-end;
|
||||||
|
gap: 15px;
|
||||||
|
padding: 15px 20px;
|
||||||
|
background: #fff;
|
||||||
|
border-top: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item { display: flex; justify-content: space-between; width: 100%; align-items: center;}
|
||||||
.opt-name { font-weight: bold; }
|
.opt-name { font-weight: bold; }
|
||||||
.opt-spec { color: #8492a6; font-size: 13px; }
|
.opt-spec { color: #8492a6; font-size: 13px; margin-right: 10px; }
|
||||||
.total-price-input :deep(.el-input__inner) { color: #F56C6C; font-weight: bold; }
|
.total-price-input :deep(.el-input__inner) { color: #F56C6C; font-weight: bold; }
|
||||||
</style>
|
</style>
|
||||||
Reference in New Issue
Block a user