全局审计日fix: 使用鸭子类型强制安全解包 SQLAlchemy Row 对象,彻底解决 to_dict 报错志

This commit is contained in:
dxc
2026-03-11 13:11:16 +08:00
parent ac97c6066b
commit d2d9abe201
8 changed files with 434 additions and 13 deletions

View File

@ -51,4 +51,13 @@ export function delMaterialBase(id: number) {
url: `/inbound/base/${id}`,
method: 'delete'
})
}
// 5. 批量设置预警
export function batchSetWarning(data: any[]) {
return request({
url: '/inbound/base/warning/batch-set',
method: 'post',
data
})
}

View File

@ -172,6 +172,19 @@ const routes: Array<RouteRecordRaw> = [
name: 'OpRecords',
component: () => import('@/views/transaction/records.vue'),
meta: { title: '借还记录' }
},
// ★ [新增] 报废管理
{
path: 'scrap/index',
name: 'ScrapList',
component: () => import('@/views/operation/scrap/index.vue'), // ✅ 正确路径
meta: { title: '报废记录' }
},
{
path: 'scrap/create',
name: 'ScrapCreate',
component: () => import('@/views/operation/scrap/create.vue'), // ✅ 正确路径
meta: { title: '新建报废' }
}
]
},

View File

@ -98,6 +98,17 @@
<el-icon style="margin-right: 5px"><Download /></el-icon>导出库存统计
</el-button>
<!-- 批量设置预警按钮 (需要 edit_warning 权限) -->
<el-button
v-if="userStore.hasPermission('material_list:edit_warning')"
type="warning"
plain
@click="handleBatchSetWarning"
style="margin-right: 10px"
>
<el-icon style="margin-right: 5px"><Bell /></el-icon>批量设置预警
</el-button>
<el-button v-if="userStore.hasPermission('material_list:operation')" type="primary" @click="handleAdd" style="margin-right: 10px">
<el-icon style="margin-right: 5px"><Plus /></el-icon>新增
</el-button>
@ -148,6 +159,7 @@
border
stripe
:size="tableSize"
:row-class-name="tableRowClassName"
@sort-change="handleSortChange"
style="width: 100%; margin-top: 15px"
>
@ -243,9 +255,10 @@
/>
</template>
</el-table-column>
<el-table-column v-if="userStore.hasPermission('material_list:operation')" label="操作" min-width="150" fixed="right" align="center">
<el-table-column v-if="userStore.hasPermission('material_list:operation')" label="操作" min-width="200" fixed="right" align="center">
<template #default="scope">
<el-button v-if="userStore.hasPermission('material_list:operation')" link type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button v-if="userStore.hasPermission('material_list:edit_warning')" link type="warning" size="small" @click="handleSetSingleWarning(scope.row)">设置预警</el-button>
<el-button v-if="userStore.hasPermission('material_list:operation')" link type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
@ -440,13 +453,43 @@
@cancel="cameraDialogVisible = false"
/>
</el-dialog>
<!-- 预警设置弹窗 -->
<el-dialog v-model="warningDialog.visible" :title="warningDialog.title" width="500px" append-to-body destroy-on-close>
<el-form ref="warningFormRef" :model="warningForm" :rules="warningRules" label-width="100px">
<el-alert
v-if="warningDialog.selectedCount > 1"
:title="`正在批量设置 ${warningDialog.selectedCount} 条物料的预警`"
type="info"
:closable="false"
style="margin-bottom: 15px"
/>
<el-form-item label="启用预警" prop="isEnabled">
<el-switch v-model="warningForm.isEnabled" />
</el-form-item>
<el-form-item label="红色阈值" prop="redThreshold" v-if="warningForm.isEnabled">
<el-input-number v-model="warningForm.redThreshold" :min="0" :precision="2" placeholder="库存此值为红色预警" style="width: 100%" />
<div class="form-tip">库存数量 ≤ 此值时显示红色预警</div>
</el-form-item>
<el-form-item label="黄色阈值" prop="yellowThreshold" v-if="warningForm.isEnabled">
<el-input-number v-model="warningForm.yellowThreshold" :min="0" :precision="2" placeholder="库存此值为黄色预警" style="width: 100%" />
<div class="form-tip">红色阈值 &lt; 库存 ≤ 此值时显示黄色预警</div>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="warningDialog.visible = false">取 消</el-button>
<el-button type="primary" @click="submitWarning" :loading="warningLoading">确 定</el-button>
</div>
</template>
</el-dialog>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, nextTick, computed } from 'vue';
import { Plus, Document, Refresh, Setting, Rank, Camera, Link, Download } from '@element-plus/icons-vue';
import { Plus, Document, Refresh, Setting, Rank, Camera, Link, Download, Bell } from '@element-plus/icons-vue';
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus';
import type { FormInstance, FormRules } from 'element-plus';
import { useUserStore } from '@/stores/user';
@ -457,7 +500,8 @@ import {
updateMaterialBase,
delMaterialBase,
getMaterialBaseOptions,
exportAssetStatistics // 导入导出API
exportAssetStatistics,
batchSetWarning
} from '@/api/material_base';
import { uploadFile, deleteFile } from '@/api/common/upload';
import WebRtcCamera from '@/components/Camera/WebRtcCamera.vue';
@ -545,6 +589,39 @@ const cameraDialogVisible = ref(false);
const cameraRef = ref<InstanceType<typeof WebRtcCamera> | null>(null);
const currentCameraField = ref<'generalImage' | 'generalManual'>('generalImage');
// 预警设置相关
const warningDialog = reactive({
visible: false,
title: '设置预警',
selectedCount: 0,
selectedIds: [] as number[]
});
const warningFormRef = ref<FormInstance>();
const warningLoading = ref(false);
const warningForm = reactive({
isEnabled: false,
redThreshold: undefined as number | undefined,
yellowThreshold: undefined as number | undefined
});
const warningRules = {
yellowThreshold: [
{
validator: (rule: any, value: any, callback: any) => {
if (warningForm.isEnabled && warningForm.redThreshold !== undefined && value !== undefined) {
if (value <= warningForm.redThreshold) {
callback(new Error('黄色阈值必须大于红色阈值'));
} else {
callback();
}
} else {
callback();
}
},
trigger: 'blur'
}
]
};
const columns = reactive({
id: { visible: false },
companyName: { visible: true },
@ -1017,6 +1094,83 @@ const handleDelete = (row: MaterialBaseVO) => {
}).catch(() => {});
};
// --- 预警设置函数 ---
// 批量设置预警
const handleBatchSetWarning = () => {
// 获取已选择的行(如果有选中的行则使用选中的行,否则使用当前页所有行)
const selectedRows = tableData.value.filter((row: MaterialBaseVO) => (row as any)._checked);
if (selectedRows.length > 0) {
warningDialog.selectedIds = selectedRows.map((row: MaterialBaseVO) => row.id);
warningDialog.selectedCount = selectedRows.length;
} else {
warningDialog.selectedIds = tableData.value.map((row: MaterialBaseVO) => row.id);
warningDialog.selectedCount = tableData.value.length;
}
// 重置表单
warningForm.isEnabled = false;
warningForm.redThreshold = undefined;
warningForm.yellowThreshold = undefined;
warningDialog.title = '批量设置预警';
warningDialog.visible = true;
};
// 单条设置预警
const handleSetSingleWarning = (row: MaterialBaseVO) => {
warningDialog.selectedIds = [row.id];
warningDialog.selectedCount = 1;
// 如果已有预警设置则回显
warningForm.isEnabled = row.warningEnabled || false;
warningForm.redThreshold = row.warningRed;
warningForm.yellowThreshold = row.warningYellow;
warningDialog.title = '设置预警';
warningDialog.visible = true;
};
// 提交预警设置
const submitWarning = async () => {
if (!warningFormRef.value) return;
await warningFormRef.value.validate();
warningLoading.value = true;
try {
const data = warningDialog.selectedIds.map(baseId => ({
baseId,
isEnabled: warningForm.isEnabled,
redThreshold: warningForm.redThreshold,
yellowThreshold: warningForm.yellowThreshold
}));
await batchSetWarning(data);
ElMessage.success('预警设置成功');
warningDialog.visible = false;
getList();
} catch (error: any) {
ElMessage.error(error?.msg || '设置失败');
} finally {
warningLoading.value = false;
}
};
// 表格行样式(根据预警状态)
const tableRowClassName = ({ row }: { row: MaterialBaseVO }) => {
// 只有拥有 view_warning 权限且有预警状态时才显示特殊样式
if (!userStore.hasPermission('material_list:view_warning')) return '';
const status = (row as any).warningStatus;
if (status === 2) {
return 'warning-row-red'; // 红色预警
} else if (status === 1) {
return 'warning-row-yellow'; // 黄色预警
}
return '';
};
// --- 文件上传辅助函数 ---
const getImageUrl = (url: string) => { return !url ? '' : (url.startsWith('http') ? url : url) }
@ -1189,6 +1343,27 @@ onMounted(() => {
.file-preview-cell { display: flex; align-items: center; justify-content: center; position: relative; }
.more-badge { position: absolute; top: -5px; right: -5px; background: #909399; color: #fff; border-radius: 10px; padding: 0 4px; font-size: 10px; transform: scale(0.9); }
/* 预警行样式 */
:deep(.warning-row-red) {
background-color: rgba(245, 108, 108, 0.15) !important;
}
:deep(.warning-row-red td) {
background-color: transparent !important;
}
:deep(.warning-row-yellow) {
background-color: rgba(230, 162, 60, 0.15) !important;
}
:deep(.warning-row-yellow td) {
background-color: transparent !important;
}
/* 表单提示文字 */
.form-tip {
font-size: 12px;
color: #909399;
margin-top: 4px;
line-height: 1.4;
}
</style>
<style>