2 Commits

4 changed files with 115 additions and 39 deletions

Binary file not shown.

View File

@ -252,7 +252,8 @@ class MaterialBaseService:
category = filters.get('category')
if category is not None and category != '':
query = query.filter(MaterialBase.category.ilike(category.strip()))
# 在末尾拼接 '%' 实现前缀模糊匹配
query = query.filter(MaterialBase.category.ilike(f"{category.strip()}%"))
type_val = filters.get('type')
if type_val is not None and type_val != '':
@ -750,7 +751,8 @@ class MaterialBaseService:
category = filters.get('category')
if category is not None and category != '':
filter_conditions.append(MaterialBase.category.ilike(category.strip()))
# 同样在末尾拼接 '%'
filter_conditions.append(MaterialBase.category.ilike(f"{category.strip()}%"))
type_val = filters.get('type')
if type_val is not None and type_val != '':
filter_conditions.append(MaterialBase.material_type.ilike(type_val.strip()))

View File

@ -239,7 +239,7 @@ const handleLogout = () => {
<footer v-if="!isLoginPage" class="app-footer">
<span class="version-tag">
<el-icon style="vertical-align: middle; margin-right: 4px"><InfoFilled /></el-icon>
当前版本:V3.35识图版
当前版本:V3.36
</span>
</footer>

View File

@ -32,19 +32,16 @@
<el-option v-for="item in companyOptions" :key="item" :label="item" :value="item" />
</el-select>
<el-select
v-model="queryParams.category"
<el-cascader
v-model="searchCategoryPath"
:options="categoryTreeOptions"
:props="{ checkStrictly: true }"
placeholder="类别"
clearable
filterable
allow-create
default-first-option
style="width: 240px; margin-right: 10px;"
@change="handleQuery"
popper-class="long-dropdown"
>
<el-option v-for="item in categoryOptions" :key="item" :label="item" :value="item" />
</el-select>
/>
<el-select
v-model="queryParams.type"
@ -171,21 +168,31 @@
<el-button circle :icon="Setting" style="margin-left: 8px" title="列设置" />
</template>
<div class="column-setting-list">
<div style="font-weight: bold; margin-bottom: 5px; border-bottom: 1px solid #eee; padding-bottom: 5px">
列展示设置
<div style="display: flex; justify-content: space-between; align-items: center; font-weight: bold; margin-bottom: 5px; border-bottom: 1px solid #eee; padding-bottom: 5px">
<span>列展示设置</span>
<el-checkbox
:model-value="isAllSelected"
:indeterminate="isIndeterminate"
@change="handleCheckAllChange"
>
全选
</el-checkbox>
</div>
<el-checkbox v-model="columns.id.visible" label="ID" />
<el-checkbox v-model="columns.companyName.visible" label="所属公司" />
<el-checkbox v-model="columns.name.visible" label="名称" />
<el-checkbox v-model="columns.commonName.visible" label="名" />
<el-checkbox v-model="columns.category.visible" label="类别" />
<el-checkbox v-model="columns.type.visible" label="类" />
<el-checkbox v-model="columns.spec.visible" label="规格型号" />
<el-checkbox v-model="columns.unit.visible" label="单位" />
<el-checkbox v-model="columns.inventory.visible" label="库存数" />
<el-checkbox v-model="columns.available.visible" label="可用数" />
<el-checkbox v-model="columns.files.visible" label="资料" />
<el-checkbox v-model="columns.isEnabled.visible" label="状态" />
<el-checkbox v-if="hasColPermission('id')" v-model="columns.id.visible" label="ID" />
<el-checkbox v-if="hasColPermission('companyName')" v-model="columns.companyName.visible" label="所属公司" />
<el-checkbox v-if="hasColPermission('name')" v-model="columns.name.visible" label="名" />
<el-checkbox v-if="hasColPermission('commonName')" v-model="columns.commonName.visible" label="俗名" />
<el-checkbox v-if="hasColPermission('category')" v-model="columns.category.visible" label="类" />
<el-checkbox v-if="hasColPermission('type')" v-model="columns.type.visible" label="类型" />
<el-checkbox v-if="hasColPermission('spec')" v-model="columns.spec.visible" label="规格型号" />
<el-checkbox v-if="hasColPermission('unit')" v-model="columns.unit.visible" label="单位" />
<el-checkbox v-if="hasColPermission('inventory')" v-model="columns.inventory.visible" label="库存数" />
<el-checkbox v-if="hasColPermission('available')" v-model="columns.available.visible" label="可用数" />
<el-checkbox v-if="hasColPermission('files')" v-model="columns.files.visible" label="资料" />
<el-checkbox v-if="hasColPermission('isEnabled')" v-model="columns.isEnabled.visible" label="状态" />
<el-checkbox v-if="hasColPermission('isInspectionRequired')" v-model="columns.isInspectionRequired.visible" label="强制质检" />
<el-checkbox v-if="hasColPermission('warningStatus')" v-model="columns.warningStatus.visible" label="预警状态" />
</div>
</el-popover>
</div>
@ -306,7 +313,7 @@
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="userStore.hasPermission('material_list:view_warning')" label="预警状态" width="120" align="center">
<el-table-column v-if="columns.warningStatus.visible" label="预警状态" width="120" align="center">
<template #default="{ row }">
<template v-if="row.warningStatus === 2">
<el-tag type="danger" size="small">红色预警</el-tag>
@ -339,7 +346,7 @@
<el-pagination
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
:page-sizes="[10, 20, 50, 100]"
:page-sizes="[50, 100, 200, 500]"
:background="true"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@ -884,7 +891,8 @@ const columns = reactive({
available: { visible: true },
files: { visible: true },
isEnabled: { visible: true },
isInspectionRequired: { visible: true }
isInspectionRequired: { visible: true },
warningStatus: { visible: true }
});
// 列与权限Code的映射关系数据库中的code
@ -901,22 +909,78 @@ const permissionMap: Record<string, string> = {
available: 'material_list:availableCount',
files: 'material_list:files',
isEnabled: 'material_list:isEnabled',
isInspectionRequired: 'material_list:operation'
isInspectionRequired: 'material_list:operation',
warningStatus: 'material_list:view_warning'
};
// 根据用户权限初始化列显示状态
// ================= 全选与本地缓存逻辑 =================
// 获取唯一缓存 Key (加上用户名,防止同一个浏览器切换账号时设置错乱)
const getStorageKey = () => `MOM_BASIC_INFO_COLS_${userStore.username || 'DEFAULT'}`;
// 辅助方法:判断当前用户是否有某列的权限
const hasColPermission = (key: string) => {
if (userStore.role === 'SUPER_ADMIN' || userStore.username === 'IRIS') return true;
const code = permissionMap[key];
return code ? !!userStore.hasPermission(code) : true;
};
// 计算属性:判断是否"全选"了所有【有权限】的列
const isAllSelected = computed(() => {
const allowedKeys = Object.keys(columns).filter(k => hasColPermission(k));
return allowedKeys.length > 0 && allowedKeys.every(k => columns[k as keyof typeof columns].visible);
});
// 计算属性:判断是否"半选" (Element UI 中 checkbox 的 indeterminate 状态)
const isIndeterminate = computed(() => {
const allowedKeys = Object.keys(columns).filter(k => hasColPermission(k));
const checkedCount = allowedKeys.filter(k => columns[k as keyof typeof columns].visible).length;
return checkedCount > 0 && checkedCount < allowedKeys.length;
});
// 事件:点击"全选"复选框时触发
const handleCheckAllChange = (val: boolean) => {
Object.keys(columns).forEach(key => {
// 只有用户有权限的列,才会被全选/全不选操作控制
if (hasColPermission(key)) {
columns[key as keyof typeof columns].visible = val;
}
});
};
// 监听:只要列展示状态发生变化,就自动保存到浏览器本地
watch(columns, (newVal) => {
localStorage.setItem(getStorageKey(), JSON.stringify(newVal));
}, { deep: true });
// ================= 修改:权限初始化与读取缓存 =================
// 修改你原有的 initColumnPermissions 函数
const initColumnPermissions = () => {
// 超级管理员跳过权限检查,显示所有列
if (userStore.role === 'SUPER_ADMIN' || userStore.username === 'IRIS') {
return;
// 1. 尝试从本地缓存读取用户上次的设置
const cachedData = localStorage.getItem(getStorageKey());
let parsedCache: Record<string, any> | null = null;
if (cachedData) {
try {
parsedCache = JSON.parse(cachedData);
} catch (e) {
console.error('解析列缓存失败', e);
}
}
// 普通用户:严格执行列级权限控制,没有权限的列必须隐藏
// 2. 遍历列进行权限判断与缓存赋值
Object.keys(columns).forEach(key => {
const code = permissionMap[key];
if (code) {
// 如果不具备该权限,必须设为 false
columns[key].visible = !!userStore.hasPermission(code);
const colKey = key as keyof typeof columns;
const hasPerm = hasColPermission(colKey);
if (!hasPerm) {
// 【权限最高】如果没有权限,强制隐藏,无视任何缓存
columns[colKey].visible = false;
} else {
// 如果有权限,且存在本地缓存,则使用本地缓存的值
if (parsedCache && parsedCache[colKey] !== undefined) {
columns[colKey].visible = parsedCache[colKey].visible;
}
}
});
};
@ -939,6 +1003,16 @@ const categoryOptions = ref<string[]>([]);
const typeOptions = ref<string[]>([]);
const categoryTreeOptions = ref<CascaderOption[]>([]);
// 用于搜索栏级联选择器的数据绑定中转
const searchCategoryPath = computed({
get() {
return queryParams.category ? queryParams.category.split('/') : [];
},
set(val: string[] | null) {
queryParams.category = val && val.length > 0 ? val.join('/') : '';
}
});
// 类别级联选择器的 ref
const categoryCascaderRef = ref<any>(null);
@ -954,7 +1028,7 @@ const tempCategorySuffix = ref<string>('');
const queryParams = reactive<QueryParams>({
pageNum: 1,
pageSize: 10,
pageSize: 100,
keyword: '',
searchField: 'all',
category: '',