feat: 添加参考价格列 + 修复公司筛选跨域问题 + CLIP模型持久化

## 新增功能
- material_base 表新增 reference_price 列(NUMERIC(10,2))
- 基础信息 list.vue / buyOdoo.vue 页面增加「参考价格」列展示和编辑
- 新增 material_list:referencePrice 权限元素,支持按角色控制可见性

## Bug 修复
- 入库三页面(buy/product/semi)公司筛选:非超管用户 company=ALL 不再传给旧版后端
- 基础信息两页面(list/buyOdoo):buyOdoo 增加 v-if=isSuperAdmin 与 list.vue 行为统一
- company=ALL 默认值在各页面 getList/fetchData 中自动过滤,兼容旧版后端

## 运维优化
- docker-compose.prod.yml 增加 models_prod 卷挂载,CLIP模型持久化免重复上传
- deploy_code.sh 增加 models_prod 目录检查与模型文件存在性告警
This commit is contained in:
yueli
2026-07-13 17:30:52 +08:00
parent 4f5965db02
commit 760f78e016
10 changed files with 112 additions and 24 deletions

View File

@ -231,8 +231,8 @@
</el-link>
</template>
<template #default="scope" v-else-if="['unit_price', 'post_tax_unit_price', 'total_price'].includes(col.prop)">
<span class="money-text">{{ formatMoney(scope.row[col.prop], scope.row.currency) }}</span>
<template #default="scope" v-else-if="['unit_price', 'total_price'].includes(col.prop)">
<span class="money-text">{{ formatMoney(scope.row[col.prop]) }}</span>
</template>
</el-table-column>
</template>
@ -767,7 +767,6 @@ const hasFormFieldPermission = (fieldName: string) => {
available_quantity: 'inbound_buy:available_quantity',
warehouse_location: 'inbound_buy:warehouse_location',
unit_price: 'inbound_buy:unit_price',
post_tax_unit_price: 'inbound_buy:post_tax_unit_price',
tax_rate: 'inbound_buy:tax_rate',
total_price: 'inbound_buy:total_price',
currency: 'inbound_buy:currency',
@ -889,7 +888,6 @@ const fieldOptions = computed(() => {
{ value: 'qty_stock', label: '库存数', perm: 'inbound_buy:qty_stock' },
{ value: 'qty_available', label: '可用数', perm: 'inbound_buy:qty_available' },
{ value: 'unit_price', label: '不含税单价', perm: 'inbound_buy:unit_price' },
{ value: 'post_tax_unit_price', label: '含税单价', perm: 'inbound_buy:post_tax_unit_price' },
{ value: 'total_price', label: '不含税总价', perm: 'inbound_buy:total_price' },
{ value: 'tax_rate', label: '税率', perm: 'inbound_buy:tax_rate' },
{ value: 'currency', label: '币种', perm: 'inbound_buy:currency' },
@ -941,7 +939,6 @@ const stockColumns = [
{prop: 'tax_rate', label: '税率', minWidth: '80'},
{prop: 'unit_price', label: '不含税单价', minWidth: '120'},
{prop: 'post_tax_unit_price', label: '含税单价', minWidth: '120'},
{prop: 'total_price', label: '不含税总价', minWidth: '120'},
{prop: 'currency', label: '币种', minWidth: '80'},
@ -979,7 +976,6 @@ const permissionMap: Record<string, string> = {
warehouse_loc: 'inbound_buy:warehouse_loc',
tax_rate: 'inbound_buy:tax_rate',
unit_price: 'inbound_buy:unit_price',
post_tax_unit_price: 'inbound_buy:post_tax_unit_price',
total_price: 'inbound_buy:total_price',
currency: 'inbound_buy:currency',
exchange_rate: 'inbound_buy:exchange_rate',
@ -1344,6 +1340,10 @@ const fetchData = async () => {
isAsc: queryParams.isAsc,
advancedFilters: JSON.stringify(queryParams.advancedFilters)
}
// 兼容旧版后端:company=ALL 时不传过滤参数
if (params.company === 'ALL') {
delete params.company
}
const res: any = await getBuyList(params)
tableData.value = res.data.items || []
total.value = res.data.total || 0
@ -1684,7 +1684,7 @@ const resetAdvancedFilter = () => {
fetchData()
}
const isColumnSortable = (prop: string) => {
const sortableColumns = ['company_name', 'material_name', 'material_type', 'category', 'spec_model', 'unit', 'sku', 'barcode', 'inbound_date', 'serial_number', 'batch_number', 'status', 'inspection_status', 'qty_inbound', 'qty_stock', 'qty_available', 'warehouse_loc', 'unit_price', 'post_tax_unit_price', 'total_price', 'tax_rate', 'currency', 'exchange_rate', 'supplier_name', 'purchaser', 'purchaser_email', 'source_link', 'detail_link']
const sortableColumns = ['company_name', 'material_name', 'material_type', 'category', 'spec_model', 'unit', 'sku', 'barcode', 'inbound_date', 'serial_number', 'batch_number', 'status', 'inspection_status', 'qty_inbound', 'qty_stock', 'qty_available', 'warehouse_loc', 'unit_price', 'total_price', 'tax_rate', 'currency', 'exchange_rate', 'supplier_name', 'purchaser', 'purchaser_email', 'source_link', 'detail_link']
return sortableColumns.includes(prop)
}
const handleSortChange = ({ column, prop, order }: any) => {
@ -1777,12 +1777,12 @@ const resetForm = () => {
const getStatusType = (status: string) => { const map: any = {'在库': 'success', '出库': 'info', '损耗': 'danger'}; return map[status] || 'warning' }
// 列表金额显示增加千分位处理,并保留2位小数
const formatMoney = (val: any, currency = '¥') => {
const formatMoney = (val: any) => {
const num = Number(val);
if (isNaN(num)) return '-';
const parts = num.toFixed(2).split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return `${currency} ${parts.join('.')}`;
return parts.join('.');
}
onMounted(() => {

View File

@ -3,6 +3,7 @@
<div class="header-tools">
<div class="left-tools">
<el-select
v-if="isSuperAdmin"
v-model="queryParams.company"
placeholder="所属公司"
class="filter-item-select"
@ -11,6 +12,7 @@
@change="fetchData"
style="width: 160px;"
>
<el-option label="全部 (跨域)" value="ALL" />
<el-option v-for="item in companyOptions" :key="item" :label="item" :value="item" />
</el-select>
@ -620,6 +622,7 @@ const debounce = (fn: Function, delay: number = 500) => {
// ------------------------------------
const userStore = useUserStore()
const isSuperAdmin = computed(() => userStore.role === 'SUPER_ADMIN')
const router = useRouter()
// 在新标签页打开基础信息编辑
@ -647,7 +650,7 @@ const formRef = ref()
// 上传锁定状态
const isUploading = ref(false)
const queryParams = reactive({ page: 1, pageSize: 20, keyword: '', searchField: 'all', sku: '', category: '', material_type: '', statuses: ['在库', '借库'], company: '', orderByColumn: '', isAsc: '', advancedFilters: [] })
const queryParams = reactive({ page: 1, pageSize: 20, keyword: '', searchField: 'all', sku: '', category: '', material_type: '', statuses: ['在库', '借库'], company: 'ALL', orderByColumn: '', isAsc: '', advancedFilters: [] })
const categoryOptions = ref<string[]>([])
const categoryTreeOptions = ref<{ value: string; label: string; children?: any[] }[]>([])
@ -1104,6 +1107,10 @@ const fetchData = async () => {
isAsc: queryParams.isAsc,
advancedFilters: queryParams.advancedFilters.length > 0 ? JSON.stringify(queryParams.advancedFilters) : ''
}
// 兼容旧版后端:company=ALL 时不传过滤参数
if (params.company === 'ALL') {
delete params.company
}
const res: any = await getProductList(params)
tableData.value = res.data.items || []
total.value = res.data.total || 0
@ -1176,7 +1183,7 @@ const resetQuery = () => {
queryParams.sku = ''
queryParams.category = ''
queryParams.material_type = ''
queryParams.company = ''
queryParams.company = isSuperAdmin.value ? 'ALL' : ''
queryParams.page = 1
queryParams.orderByColumn = ''
queryParams.isAsc = ''
@ -1458,7 +1465,7 @@ const resetForm = () => {
}
const getStatusType = (s:string) => ({'在库':'success','出库':'info','借库':'warning','损耗':'danger'}[s]||'warning')
const getQualityType = (s:string) => ({'合格':'success','不合格':'danger','待检':'info'}[s]||'info')
const formatMoney = (val:any) => isNaN(Number(val)) ? '-' : `¥ ${Number(val).toFixed(2)}`
const formatMoney = (val:any) => isNaN(Number(val)) ? '-' : Number(val).toFixed(2)
onMounted(() => {
// 先根据权限初始化列显示状态
initColumnPermissions()

View File

@ -4,6 +4,7 @@
<div class="left-tools">
<el-select
v-if="isSuperAdmin"
v-model="queryParams.company"
placeholder="所属公司"
class="filter-item-select"
@ -12,6 +13,7 @@
@change="fetchData"
style="width: 160px;"
>
<el-option label="全部 (跨域)" value="ALL" />
<el-option v-for="item in companyOptions" :key="item" :label="item" :value="item" />
</el-select>
@ -688,6 +690,7 @@ const vLoadmore = {
// 状态与变量
// ------------------------------------
const userStore = useUserStore()
const isSuperAdmin = computed(() => userStore.role === 'SUPER_ADMIN')
const router = useRouter()
// 在新标签页打开基础信息编辑
@ -716,7 +719,7 @@ const formRef = ref()
// 上传锁定状态
const isUploading = ref(false)
const queryParams = reactive({ page: 1, pageSize: 20, keyword: '', searchField: 'all', sku: '', category: '', material_type: '', statuses: ['在库', '借库'], company: '', orderByColumn: '', isAsc: '', advancedFilters: [] })
const queryParams = reactive({ page: 1, pageSize: 20, keyword: '', searchField: 'all', sku: '', category: '', material_type: '', statuses: ['在库', '借库'], company: 'ALL', orderByColumn: '', isAsc: '', advancedFilters: [] })
const categoryOptions = ref<string[]>([])
const categoryTreeOptions = ref<{ value: string; label: string; children?: any[] }[]>([])
@ -1220,6 +1223,10 @@ const fetchData = async () => {
isAsc: queryParams.isAsc,
advancedFilters: queryParams.advancedFilters.length > 0 ? JSON.stringify(queryParams.advancedFilters) : ''
}
// 兼容旧版后端:company=ALL 时不传过滤参数
if (params.company === 'ALL') {
delete params.company
}
const res: any = await getSemiList(params)
tableData.value = res.data.items || []
total.value = res.data.total || 0
@ -1292,7 +1299,7 @@ const resetQuery = () => {
queryParams.sku = ''
queryParams.category = ''
queryParams.material_type = ''
queryParams.company = ''
queryParams.company = isSuperAdmin.value ? 'ALL' : ''
queryParams.page = 1
queryParams.orderByColumn = ''
queryParams.isAsc = ''
@ -1568,7 +1575,7 @@ const resetForm = () => {
}
const getStatusType = (status: string) => { const map: any = { '在库': 'success', '出库': 'info', '借库': 'warning', '损耗': 'danger' }; return map[status] || 'warning' }
const getQualityType = (status: string) => { const map: any = { '合格': 'success', '不合格': 'danger', '待检': 'info', '返修中': 'warning' }; return map[status] || 'info' }
const formatMoney = (val: any) => isNaN(Number(val)) ? '-' : `¥ ${Number(val).toFixed(2)}`
const formatMoney = (val: any) => isNaN(Number(val)) ? '-' : Number(val).toFixed(2)
onMounted(() => {
// 先根据权限初始化列显示状态