|
|
@@ -0,0 +1,380 @@
|
|
|
+<template>
|
|
|
+ <el-select
|
|
|
+ v-model="currentValue"
|
|
|
+ :placeholder="placeholder"
|
|
|
+ :disabled="disabled"
|
|
|
+ :clearable="clearable"
|
|
|
+ :filterable="true"
|
|
|
+ :remote="true"
|
|
|
+ :remote-method="remoteSearch"
|
|
|
+ :loading="loading"
|
|
|
+ :size="size"
|
|
|
+ :class="selectClass"
|
|
|
+ @change="handleChange"
|
|
|
+ @clear="handleClear"
|
|
|
+ @focus="handleFocus"
|
|
|
+ @blur="handleBlur"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in options"
|
|
|
+ :key="item.Item_ID"
|
|
|
+ :label="item.Item_Name"
|
|
|
+ :value="item.Item_ID"
|
|
|
+ >
|
|
|
+ <div style="display: flex; justify-content: space-between">
|
|
|
+ <span>{{ item.Item_Code }} - {{ item.Item_Name }}</span>
|
|
|
+ <span style="color: #8492a6; font-size: 13px">
|
|
|
+ {{ item.Item_PECS || '无规格' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </el-option>
|
|
|
+ </el-select>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { getItemList } from '@/api/common/index'
|
|
|
+import { COMMON_EVENTS, MATERIAL_SELECT_EVENTS } from './events'
|
|
|
+
|
|
|
+/**
|
|
|
+ * 物料选择组件
|
|
|
+ * @description 基于Element UI Select的物料选择组件,支持远程搜索和数据回显
|
|
|
+ * @component
|
|
|
+ */
|
|
|
+export default {
|
|
|
+ name: 'MaterialSelect',
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组件属性定义
|
|
|
+ * @description 定义组件接收的外部属性
|
|
|
+ */
|
|
|
+ props: {
|
|
|
+ /**
|
|
|
+ * 绑定值
|
|
|
+ * @description 当前选中的物料ID
|
|
|
+ */
|
|
|
+ value: {
|
|
|
+ type: [String, Number],
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 占位符文本
|
|
|
+ * @description 输入框的占位符提示文本
|
|
|
+ */
|
|
|
+ placeholder: {
|
|
|
+ type: String,
|
|
|
+ default: '请选择物料'
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 是否禁用
|
|
|
+ * @description 控制组件是否处于禁用状态
|
|
|
+ */
|
|
|
+ disabled: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 是否可清空
|
|
|
+ * @description 控制是否显示清空按钮
|
|
|
+ */
|
|
|
+ clearable: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 组件尺寸
|
|
|
+ * @description 控制组件的显示尺寸
|
|
|
+ */
|
|
|
+ size: {
|
|
|
+ type: String,
|
|
|
+ default: 'small',
|
|
|
+ validator: (value) => ['large', 'medium', 'small', 'mini'].includes(value)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组件响应式数据
|
|
|
+ * @description 定义组件的内部状态数据
|
|
|
+ * @returns {Object} 组件数据对象
|
|
|
+ */
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ /**
|
|
|
+ * 当前选中值
|
|
|
+ * @type {string|number}
|
|
|
+ */
|
|
|
+ currentValue: this.value,
|
|
|
+ /**
|
|
|
+ * 物料选项列表
|
|
|
+ */
|
|
|
+ options: [],
|
|
|
+ /**
|
|
|
+ * 加载状态
|
|
|
+ * @type {boolean}
|
|
|
+ */
|
|
|
+ loading: false,
|
|
|
+ /**
|
|
|
+ * 搜索防抖定时器
|
|
|
+ * @type {number|null}
|
|
|
+ */
|
|
|
+ searchTimer: null
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算属性
|
|
|
+ * @description 组件的响应式计算属性
|
|
|
+ */
|
|
|
+ computed: {
|
|
|
+ /**
|
|
|
+ * 选择器样式类
|
|
|
+ * @description 返回与Avue组件一致的样式类名
|
|
|
+ * @returns {Object} 样式类对象
|
|
|
+ */
|
|
|
+ selectClass() {
|
|
|
+ return {
|
|
|
+ 'avue-select': true,
|
|
|
+ 'el-select': true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 监听器
|
|
|
+ * @description 监听属性变化并执行相应的处理逻辑
|
|
|
+ */
|
|
|
+ watch: {
|
|
|
+ /**
|
|
|
+ * 监听value属性变化
|
|
|
+ * @description 当外部传入的value发生变化时,同步更新内部状态并加载物料信息
|
|
|
+ * @param {string|number} newVal - 新的物料ID值
|
|
|
+ * @param {string|number} oldVal - 旧的物料ID值
|
|
|
+ */
|
|
|
+ value(newVal, oldVal) {
|
|
|
+ if (newVal !== oldVal) {
|
|
|
+ this.currentValue = newVal
|
|
|
+ // 当value变化时,如果有值且当前选项为空,则加载对应的物料信息
|
|
|
+ if (newVal && this.options.length === 0) {
|
|
|
+ this.loadMaterialById(newVal)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 监听currentValue变化
|
|
|
+ * @description 当内部值发生变化时,向父组件发送input事件
|
|
|
+ * @param {string|number} newVal - 新的值
|
|
|
+ */
|
|
|
+ currentValue(newVal) {
|
|
|
+ this.$emit(COMMON_EVENTS.INPUT, newVal)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组件挂载钩子
|
|
|
+ * @description 组件挂载完成后执行的初始化逻辑
|
|
|
+ */
|
|
|
+ mounted() {
|
|
|
+ // 组件挂载时,如果有初始值,加载对应的物料信息
|
|
|
+ if (this.value) {
|
|
|
+ this.loadMaterialById(this.value)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组件销毁钩子
|
|
|
+ * @description 组件销毁前清理定时器等资源
|
|
|
+ */
|
|
|
+ beforeDestroy() {
|
|
|
+ if (this.searchTimer) {
|
|
|
+ clearTimeout(this.searchTimer)
|
|
|
+ this.searchTimer = null
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组件方法
|
|
|
+ * @description 组件的业务逻辑方法集合
|
|
|
+ */
|
|
|
+ methods: {
|
|
|
+ /**
|
|
|
+ * 远程搜索方法
|
|
|
+ * @description 根据输入关键词远程搜索物料数据
|
|
|
+ * @param {string} keyword - 搜索关键词
|
|
|
+ * @returns {void}
|
|
|
+ */
|
|
|
+ remoteSearch(keyword) {
|
|
|
+ // 清除之前的定时器
|
|
|
+ if (this.searchTimer) {
|
|
|
+ clearTimeout(this.searchTimer)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置防抖定时器
|
|
|
+ this.searchTimer = setTimeout(async () => {
|
|
|
+ if (!keyword) {
|
|
|
+ this.options = []
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ await this.searchMaterials(keyword)
|
|
|
+ }, 300)
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 搜索物料
|
|
|
+ * @description 调用API搜索物料数据
|
|
|
+ * @param {string} keyword - 搜索关键词
|
|
|
+ * @returns {Promise<void>}
|
|
|
+ * @throws {Error} 当API调用失败时抛出异常
|
|
|
+ */
|
|
|
+ async searchMaterials(keyword) {
|
|
|
+ try {
|
|
|
+ this.loading = true
|
|
|
+
|
|
|
+ const response = await getItemList(1, 20, {
|
|
|
+ itemName: keyword
|
|
|
+ })
|
|
|
+
|
|
|
+ if (response?.data?.success && response.data.data?.records) {
|
|
|
+ this.options = response.data.data.records
|
|
|
+ } else {
|
|
|
+ this.options = []
|
|
|
+ const errorMsg = response?.data?.msg || '搜索物料失败'
|
|
|
+ this.$message.warning(errorMsg)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('搜索物料API调用失败:', error)
|
|
|
+ this.options = []
|
|
|
+ this.$message.error('网络错误,搜索物料失败')
|
|
|
+ throw error
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据ID加载物料信息
|
|
|
+ * @description 通过物料ID查询并加载单个物料的详细信息,用于数据回显
|
|
|
+ * @param {string|number} itemId - 物料ID
|
|
|
+ * @returns {Promise<void>}
|
|
|
+ * @throws {Error} 当API调用失败时抛出异常
|
|
|
+ */
|
|
|
+ async loadMaterialById(itemId) {
|
|
|
+ if (!itemId) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.loading = true
|
|
|
+
|
|
|
+ const response = await getItemList(1, 200, {
|
|
|
+ id: itemId
|
|
|
+ })
|
|
|
+
|
|
|
+ if (response?.data?.success && response.data.data?.records) {
|
|
|
+ const allMaterials = response.data.data.records
|
|
|
+ const material = allMaterials.find(item => item.Item_ID == itemId)
|
|
|
+
|
|
|
+ if (material) {
|
|
|
+ // 只保留匹配的物料选项
|
|
|
+ this.options = [material]
|
|
|
+ // 确保选中状态
|
|
|
+ this.currentValue = String(material.Item_ID)
|
|
|
+ console.log('物料回显成功:', material.Item_Name, 'ID:', material.Item_ID)
|
|
|
+ } else {
|
|
|
+ // 如果在当前页没找到,可能需要查询更多页面
|
|
|
+ console.warn(`未找到物料ID为 ${itemId} 的物料信息`)
|
|
|
+ this.options = []
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ const errorMsg = response?.data?.msg || '获取物料列表失败'
|
|
|
+ console.error('获取物料列表失败:', errorMsg)
|
|
|
+ this.options = []
|
|
|
+ this.$message.warning(errorMsg)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载物料信息API调用失败:', error)
|
|
|
+ this.options = []
|
|
|
+ this.$message.error('网络错误,加载物料信息失败')
|
|
|
+ throw error
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 选择变化处理
|
|
|
+ * @description 当用户选择物料时触发,更新内部状态并向父组件发送相关事件
|
|
|
+ * @param {string|number} value - 选中的物料ID
|
|
|
+ * @returns {void}
|
|
|
+ */
|
|
|
+ handleChange(value) {
|
|
|
+ this.currentValue = value
|
|
|
+ this.$emit(COMMON_EVENTS.CHANGE, value)
|
|
|
+
|
|
|
+ // 查找选中的物料对象
|
|
|
+ const selectedMaterial = this.options.find(item => item.Item_ID == value)
|
|
|
+
|
|
|
+ if (selectedMaterial) {
|
|
|
+ // 发送物料选择事件,包含完整的物料信息
|
|
|
+ this.$emit(MATERIAL_SELECT_EVENTS.MATERIAL_SELECTED, {
|
|
|
+ itemId: selectedMaterial.Item_ID,
|
|
|
+ itemCode: selectedMaterial.Item_Code,
|
|
|
+ itemName: selectedMaterial.Item_Name,
|
|
|
+ specification: selectedMaterial.Item_PECS,
|
|
|
+ materialData: selectedMaterial // 传递完整的物料对象
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ // 如果没有找到对应的物料,发送空数据
|
|
|
+ this.$emit(MATERIAL_SELECT_EVENTS.MATERIAL_SELECTED, {
|
|
|
+ itemId: '',
|
|
|
+ itemCode: '',
|
|
|
+ itemName: '',
|
|
|
+ specification: '',
|
|
|
+ materialData: null
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清空处理
|
|
|
+ */
|
|
|
+ handleClear() {
|
|
|
+ this.currentValue = ''
|
|
|
+ this.options = []
|
|
|
+ this.$emit(COMMON_EVENTS.CLEAR)
|
|
|
+ this.$emit(MATERIAL_SELECT_EVENTS.MATERIAL_SELECTED, {
|
|
|
+ itemId: '',
|
|
|
+ itemCode: '',
|
|
|
+ itemName: '',
|
|
|
+ specification: ''
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获得焦点处理
|
|
|
+ */
|
|
|
+ handleFocus() {
|
|
|
+ this.$emit(COMMON_EVENTS.FOCUS)
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 失去焦点处理
|
|
|
+ */
|
|
|
+ handleBlur() {
|
|
|
+ this.$emit(COMMON_EVENTS.BLUR)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* 确保样式与Avue select保持一致 */
|
|
|
+.avue-select {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+/* 选项样式优化 */
|
|
|
+.el-select-dropdown__item {
|
|
|
+ height: auto;
|
|
|
+ line-height: 1.5;
|
|
|
+ padding: 8px 20px;
|
|
|
+}
|
|
|
+</style>
|