Files
klp-oa/klp-ui/src/components/CoilSelector/index.vue
砂糖 a9fb6d51b1 fix: 修复表单禁用状态和加载逻辑问题
修复发货表单字段的禁用状态逻辑,移除多余的readonly属性
调整取消分卷操作的加载状态处理,确保在finally中重置状态
优化钢卷选择器组件,增加切边和包装筛选及显示列
将结算方式输入框改为下拉选择框,提升用户体验
2026-01-29 15:04:06 +08:00

626 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="coil-selector-container">
<div v-if="useTrigger" class="trigger-container">
<!-- 选择按钮 -->
<div @click="handleOpen">
<slot>
<el-button type="primary" size="small" class="select-button">
<i class="el-icon-search"></i>
<span v-if="selectedCoil">
<span>{{ selectedCoil.currentCoilNo }}</span>
<span>({{ selectedCoil.itemName }})</span>
<span>[{{ selectedCoil.netWeight }}t]</span>
</span>
<span v-else>{{ placeholder }}</span>
</el-button>
</slot>
</div>
</div>
<el-dialog title="选择钢卷" :visible.sync="dialogVisible" :width="dialogWidth" :close-on-click-modal="false"
@close="handleClose" append-to-body>
<!-- 搜索区域 -->
<el-form v-if="!rangeMode" :model="queryParams" class="search-form">
<!-- <el-form-item label="类型">
<el-select v-model="queryParams.selectType" placeholder="请选择类型" size="small">
<el-option label="成品" value="product" />
<el-option label="原料" value="raw_material" />
</el-select>
</el-form-item> -->
<el-form-item label="卷号">
<el-input v-model="queryParams.currentCoilNo" placeholder="请输入卷号" clearable size="small"
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="物料">
<muti-select v-model="queryParams.itemName" :options="dict.type.coil_itemname" placeholder="请选择物料"
clearable />
</el-form-item>
<el-form-item label="规格">
<memo-input storageKey="coilSpec" v-model="queryParams.itemSpecification" placeholder="请输入规格" clearable
size="small" @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="材质">
<muti-select v-model="queryParams.itemMaterial" :options="dict.type.coil_material" placeholder="请选择材质"
clearable />
</el-form-item>
<el-form-item label="厂家">
<muti-select v-model="queryParams.itemManufacturer" :options="dict.type.coil_manufacturer" placeholder="请选择厂家"
clearable />
</el-form-item>
<el-form-item label="切边" prop="trimmingRequirement" v-if="orderBy">
<el-select v-model="queryParams.trimmingRequirement" placeholder="请选择切边" clearable style="width: 100%">
<el-option label="净边" value="净边" />
<el-option label="毛边" value="毛边" />
</el-select>
</el-form-item>
<el-form-item label="包装" prop="packagingRequirement" v-if="orderBy">
<el-select v-model="queryParams.packagingRequirement" placeholder="请选择包装" clearable style="width: 100%">
<el-option label="裸包" value="裸包" />
<el-option label="普包" value="普包" />
<el-option label="简包" value="简包" />
</el-select>
</el-form-item>
<el-form-item label="实际库区" v-if="orderBy">
<actual-warehouse-select v-model="queryParams.actualWarehouseId" placeholder="请选择实际库区" canSelectLevel2
canSelectDisabled />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="small" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 数据表格 -->
<el-table v-if="!salesRestricted" v-loading="loading" :data="coilList" @row-click="handleRowClick" height="400px"
style="width: 100%" :row-class-name="getRowClassName">
<!-- 自定义列 -->
<el-table-column v-for="column in renderColumns" :label="column.label" :align="column.align" :prop="column.prop"
:width="column.width" :show-overflow-tooltip="column.showOverflowTooltip" />
<el-table-column v-if="orderBy" label="切边" prop="trimmingRequirement"></el-table-column>
<el-table-column v-if="orderBy" label="包装" prop="packagingRequirement"></el-table-column>
</el-table>
<div v-else>
<el-tabs type="card" v-model="currentTab" @tab-click="getList">
<el-tab-pane label="全部钢卷" name="all"></el-tab-pane>
<el-tab-pane label="我的钢卷" name="my"></el-tab-pane>
</el-tabs>
<!-- 增加 row-class-name 绑定动态设置行样式 -->
<el-table v-loading="loading" :data="coilList" @row-click="handleRowClick" height="400px" style="width: 100%"
:row-class-name="getRowClassName">
<!-- 自定义列 -->
<el-table-column v-for="column in renderColumns" :label="column.label" :align="column.align"
:prop="column.prop" :width="column.width" :show-overflow-tooltip="column.showOverflowTooltip" />
<el-table-column v-if="orderBy" label="切边" prop="trimmingRequirement"></el-table-column>
<el-table-column v-if="orderBy" label="包装" prop="packagingRequirement"></el-table-column>
</el-table>
</div>
<!-- 分页 -->
<pagination v-if="!rangeMode" v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize" @pagination="getList" />
<el-table v-if="multiple && selectedCoils.length > 0" :data="selectedCoils">
<el-table-column v-for="column in renderColumns" :label="column.label" :align="column.align" :prop="column.prop"
:width="column.width" :show-overflow-tooltip="column.showOverflowTooltip" />
<el-table-column label="操作" width="50">
<template slot-scope="scope">
<el-button size="mini" @click="handleRemove(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="handleConfirm">确认选择</el-button>
<el-button @click="handleClose">取消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listMaterialCoil } from '@/api/wms/coil';
import MemoInput from '@/components/MemoInput/index.vue';
import MutiSelect from '@/components/MutiSelect/index.vue';
import { defaultColumns } from './data';
import ActualWarehouseSelect from '@/components/KLPService/ActualWarehouseSelect/index.vue';
export default {
name: 'CoilSelector',
components: {
MemoInput,
MutiSelect,
ActualWarehouseSelect
},
dicts: ['coil_itemname', 'coil_material', 'coil_manufacturer'],
props: {
// 非触发器模式下,外部控制显隐(触发器模式下无效)
visible: {
type: Boolean,
default: false
},
dialogWidth: {
type: String,
default: '1000px'
},
// 过滤条件(可以预设一些查询条件)
filters: {
type: Object,
default: () => ({})
},
placeholder: {
type: String,
default: '请选择钢卷'
},
value: {
type: [String, Number],
default: ''
},
// 是否使用内部触发器(按钮)
useTrigger: {
type: Boolean,
default: true
},
// 初始选中的钢卷数据(支持外部传入已选数据)
initialCoil: {
type: Object,
default: null
},
rangeMode: {
type: Boolean,
default: false
},
// 范围选择模式不再通过list借口获取而是传入可以选择的钢卷数据
rangeData: {
type: Array,
default: () => []
},
// 自定义钢卷列的配置
coilColumn: {
type: Array,
default: () => []
},
// 销售视角:钢卷受限访问
salesRestricted: {
type: Boolean,
default: false
},
// 是否根据实际库区查询钢卷
orderBy: {
type: Boolean,
default: false
},
multiple: {
type: Boolean,
default: false
}
},
data() {
return {
loading: false,
coilList: [],
total: 0,
// 内部显隐控制变量(触发器模式下使用)
innerVisible: false,
// 选中的钢卷数据
selectedCoil: null,
queryParams: {
pageNum: 1,
pageSize: 10,
currentCoilNo: null,
grade: null,
itemSpecification: null,
itemMaterial: null,
itemManufacturer: null,
actualWarehouseId: null,
selectType: 'product',
status: 0, // 不包含已发货的钢卷
dataType: 1 // 只查询当前数据,不查询历史数据
},
columns: defaultColumns,
currentTab: 'my',
selectedCoils: [],
};
},
computed: {
// 根据模式决定对话框显隐逻辑
dialogVisible: {
get() {
// 触发器模式:使用内部变量
if (this.useTrigger) {
return this.innerVisible;
}
// 非触发器模式使用外部传入的visible属性
return this.visible;
},
set(val) {
if (this.useTrigger) {
// 触发器模式:更新内部变量
this.innerVisible = val;
} else {
// 非触发器模式:通知父组件更新
this.$emit('update:visible', val);
}
}
},
currentUserId() {
return this.$store.getters.id;
},
renderColumns() {
// 如果有自定义列配置,使用它;否则使用默认列
return this.coilColumn.length > 0 ? this.coilColumn : this.columns;
},
// 已选钢卷ID集合用于快速判断
selectedCoilIds() {
if (this.multiple) {
return new Set(this.selectedCoils.map(item => item.coilId));
} else {
return this.selectedCoil ? new Set([this.selectedCoil.coilId]) : new Set();
}
}
},
watch: {
// 监听对话框显隐状态变化,触发数据加载
dialogVisible(val) {
if (val) {
this.resetQuery();
this.getList();
}
},
// 非触发器模式下监听外部visible属性变化
visible(val) {
if (!this.useTrigger && val !== this.dialogVisible) {
this.dialogVisible = val;
}
},
// 监听初始钢卷数据变化
initialCoil(val) {
if (val && typeof val === 'object') {
this.selectedCoil = val;
// 同步到v-model
if (this.useTrigger && val.coilId) {
this.$emit('input', val.coilId);
this.$emit('change', val.coilId);
}
}
},
// 监听v-model值变化外部修改时同步
value(val) {
if (!val) {
this.handleClearSelection();
return;
}
if (val && !this.selectedCoil?.coilId) {
// 如果传入了coilId但还没有选中数据尝试通过id匹配可选功能
this.matchCoilById(val);
}
}
},
created() {
// 初始化时如果有初始钢卷数据,设置选中状态
if (this.initialCoil) {
this.selectedCoil = this.initialCoil;
}
},
methods: {
// 动态生成表格行类名 - 综合处理选中、禁用等状态
getRowClassName({ row }) {
const classNames = [];
// 检查是否为已选中的钢卷
if (this.selectedCoilIds.has(row.coilId)) {
classNames.push('selected-coil-row');
}
// 销售受限模式下,判断当前行是否有权限
if (this.salesRestricted && row.saleId !== this.currentUserId) {
classNames.push('disabled-coil-row'); // 禁用行类名
}
return classNames.join(' ');
},
handleRemove(row) {
this.selectedCoils = this.selectedCoils.filter(item => item.coilId !== row.coilId);
},
handleConfirm() {
this.$emit('confirm', this.selectedCoils);
this.selectedCoils = [];
this.handleClose();
},
// 获取钢卷列表
async getList() {
// 如果是范围模式,直接使用传入数据
if (this.rangeMode) {
this.coilList = this.rangeData || [];
this.total = this.coilList.length;
return;
}
try {
this.loading = true;
// 设置筛选条件
const queryPayload = {
...this.queryParams,
...this.filters,
};
// 处于销售视角且my视图时只查询当前用户的钢卷
console.log('this.salesRestricted', this.salesRestricted, this.currentTab, this.currentUserId);
if (this.salesRestricted && this.currentTab === 'my') {
queryPayload.saleId = this.currentUserId;
}
const response = await listMaterialCoil(queryPayload);
if (response.code === 200) {
this.coilList = response.rows || [];
this.total = response.total || 0;
// 如果有初始coilId尝试匹配数据
if (this.value && !this.selectedCoil) {
this.matchCoilById(this.value);
}
} else {
this.$message.warning(`获取钢卷列表失败:${response.msg || '未知错误'}`);
}
} catch (error) {
console.error('获取钢卷列表异常', error);
this.$message.error('获取钢卷列表失败,请重试');
} finally {
this.loading = false;
}
},
// 根据coilId匹配钢卷数据
matchCoilById(coilId) {
const matchedCoil = this.coilList.find(item => item.coilId == coilId);
if (matchedCoil) {
this.selectedCoil = matchedCoil;
}
},
// 触发器模式:打开对话框
handleOpen() {
this.innerVisible = true;
},
// 搜索
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
// 重置搜索条件
resetQuery() {
this.queryParams = {
pageNum: 1,
pageSize: 10,
currentCoilNo: null,
grade: null,
dataType: 1,
selectType: 'raw_material',
};
this.getList();
},
// 点击表格行选择 - 新增禁用行拦截
handleRowClick(row) {
// 销售受限模式下,拦截无权限行的点击
if (this.salesRestricted && row.saleId !== this.currentUserId) {
this.$message.warning('您没有权限选择此钢卷');
return; // 终止后续逻辑
}
this.handleSelect(row);
},
// 选择钢卷(确认选择)
handleSelect(row) {
if (!row) {
this.$message.warning('请选择有效的钢卷数据');
return;
}
if (this.salesRestricted && row.saleId != this.currentUserId) {
this.$message.warning('您没有权限选择此钢卷');
return;
}
if (this.multiple) {
// 检查是否已经选择
if (this.selectedCoils.some(item => item.coilId === row.coilId)) {
// 再次点击删除
this.handleRemove(row);
return;
}
this.selectedCoils.push(row);
return;
// return;
} else {
this.selectedCoil = row;
}
// 触发自定义事件,通知父组件选中结果(返回完整行数据)
this.$emit('select', row);
// 触发器模式下支持v-model双向绑定
if (this.useTrigger) {
this.$emit('input', row.coilId);
this.$emit('change', row.coilId); // 兼容v-model的change事件
}
// 选择后关闭对话框
this.handleClose();
},
// 清除选中状态
handleClearSelection() {
this.selectedCoil = null;
this.selectedCoils = []; // 清空多选数据
this.$emit('input', '');
this.$emit('change', '');
this.$emit('clear', true); // 触发清除事件
},
// 关闭对话框
handleClose() {
this.dialogVisible = false;
// 触发关闭事件,通知父组件
this.$emit('close');
}
}
};
</script>
<style scoped lang="scss">
.coil-selector-container {
width: 100%;
}
.trigger-container {
display: flex;
flex-direction: column;
gap: 8px;
}
// 未选择状态样式
.no-selection {
padding: 8px 12px;
border: 1px dashed #dcdcdc;
border-radius: 4px;
color: #999;
background-color: #f9f9f9;
width: fit-content;
}
// 已选择状态样式
.selected-coil-info {
padding: 12px;
border: 1px solid #e6f7ff;
border-radius: 6px;
background-color: #f0f9ff;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 16px 24px;
width: fit-content;
max-width: 100%;
.info-item {
display: flex;
align-items: center;
gap: 6px;
.label {
color: #666;
font-size: 13px;
}
.value {
color: #333;
font-size: 14px;
font-weight: 500;
white-space: nowrap;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
}
}
.clear-selection {
color: #f56c6c;
margin-left: 12px;
padding: 0 8px;
&:hover {
color: #ff4d4f;
background-color: transparent;
}
}
}
// 选择按钮样式
.select-button {
margin-top: 4px;
}
.search-form {
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 16px;
}
::v-deep .el-dialog__body {
padding: 20px;
max-height: calc(100vh - 200px);
overflow-y: auto;
}
::v-deep .el-table {
--el-table-row-hover-bg-color: var(--el-color-primary-light-9);
}
::v-deep .el-table__row.current-row {
background-color: var(--el-color-primary-light-8) !important;
}
// 核心:已选钢卷样式(黄色背景)
::v-deep .el-table .selected-coil-row {
background-color: #fffbe6 !important;
/* 浅黄色背景 */
&:hover>td {
background-color: #fff8d9 !important;
/* hover时稍深一点的黄色 */
}
// 当同时是当前行时
&.current-row {
background-color: #fff8d9 !important;
}
}
// 核心:禁用行样式(销售权限受限)
::v-deep .el-table .disabled-coil-row {
background-color: #f8f8f8 !important;
color: #c0c4cc !important;
cursor: not-allowed !important;
// 覆盖hover高亮
&:hover>td {
background-color: #f8f8f8 !important;
}
// 覆盖当前行高亮
&.current-row {
background-color: #f8f8f8 !important;
}
// 禁止单元格交互
td {
pointer-events: none;
user-select: none; // 禁止文本选中
}
// 当同时是已选行时(优先级:禁用 > 已选)
&.selected-coil-row {
background-color: #f5f5f5 !important;
&:hover>td {
background-color: #f5f5f5 !important;
}
}
}
.dialog-footer {
text-align: right;
}
// 响应式调整
@media (max-width: 768px) {
.selected-coil-info {
gap: 12px;
padding: 10px;
}
.info-item {
flex: 1 1 calc(50% - 12px);
}
}
</style>