123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636 |
- /**
- * @fileoverview 销售预测表单混入组件
- * @description 提供销售预测表单的数据管理、验证规则和业务逻辑的混入组件,支持新增和编辑模式
- */
- // API接口导入
- import { addForecast, updateForecast, getForecastDetail } from '@/api/forecast'
- // 常量和枚举导入
- import {
- APPROVAL_STATUS,
- APPROVAL_STATUS_OPTIONS,
- FORECAST_FORM_RULES,
- DEFAULT_FORECAST_FORM,
- getApprovalStatusLabel,
- getApprovalStatusType,
- canEdit
- } from '@/constants/forecast'
- // 远程搜索API
- import { getCustomerList, getItemList } from '@/api/common'
- /**
- * 销售预测表单事件常量
- * @readonly
- * @type {Object}
- */
- export const FORECAST_FORM_EVENTS = {
- /** 表单提交成功事件 */
- SUBMIT_SUCCESS: 'submit-success',
- /** 表单取消事件 */
- CANCEL: 'cancel',
- /** 表单加载完成事件 */
- LOADED: 'loaded',
- /** 客户选择变更事件 */
- CUSTOMER_CHANGE: 'customer-change',
- /** 物料选择变更事件 */
- ITEM_CHANGE: 'item-change'
- }
- /**
- * 销售预测表单混入
- * @description 提供销售预测表单的数据管理、验证规则和业务逻辑
- */
- export default {
- /**
- * 组件名称
- */
- name: 'ForecastFormMixin',
- /**
- * 组件属性定义
- * @description 定义组件接收的外部属性及其类型约束
- */
- props: {
- /**
- * 表单可见性控制
- * @description 控制表单的显示和隐藏
- * @type {boolean}
- * @default false
- */
- visible: {
- type: Boolean,
- default: false
- },
- /**
- * 编辑模式标识
- * @description 标识当前表单是否处于编辑模式
- * @type {boolean}
- * @default false
- */
- isEdit: {
- type: Boolean,
- default: false
- },
- /**
- * 初始表单数据
- * @description 用于表单初始化的数据对象
- * @type {Object}
- * @default null
- */
- initialFormData: {
- type: Object,
- default: null
- },
- /**
- * 表单标题
- * @description 自定义表单标题,如果不提供则根据编辑模式自动生成
- * @type {string}
- * @default ''
- */
- title: {
- type: String,
- default: ''
- }
- },
- /**
- * 组件响应式数据
- * @description 定义组件的响应式数据状态
- */
- data() {
- return {
- /**
- * 销售预测表单数据模型
- * @description 存储销售预测表单的所有字段数据
- * @type {Object}
- */
- formData: this.createInitialFormData(),
- /**
- * 保存操作加载状态
- * @description 控制保存按钮的加载状态,防止重复提交
- * @type {boolean}
- */
- saveLoading: false,
- /**
- * 表单加载状态
- * @description 控制表单的加载状态,用于编辑模式下的数据加载
- * @type {boolean}
- */
- formLoading: false,
- /**
- * 客户选项列表
- * @description 客户下拉选择器的选项数据
- * @type {Array<{value: string|number, label: string, customerCode: string}>}
- */
- customerOptions: [],
- /**
- * 客户选项加载状态
- * @description 控制客户选项的加载状态
- * @type {boolean}
- */
- customerLoading: false,
- /**
- * 物料选项列表
- * @description 物料下拉选择器的选项数据
- * @type {Array<{value: string|number, label: string, itemCode: string, specs: string}>}
- */
- itemOptions: [],
- /**
- * 物料选项加载状态
- * @description 控制物料选项的加载状态
- * @type {boolean}
- */
- itemLoading: false,
- /**
- * 审批状态选项列表
- * @description 审批状态下拉选择器的选项数据
- * @type {typeof APPROVAL_STATUS_OPTIONS}
- */
- approvalStatusOptions: APPROVAL_STATUS_OPTIONS,
- /**
- * 表单验证规则
- * @description 表单字段的验证规则
- * @type {typeof FORECAST_FORM_RULES}
- */
- formRules: FORECAST_FORM_RULES
- }
- },
- /**
- * 计算属性
- * @description 组件的响应式计算属性
- */
- computed: {
- /**
- * 表单标题
- * @description 根据编辑模式动态显示表单标题
- * @returns {string} 表单标题文本
- */
- formTitle() {
- if (this.title) {
- return this.title
- }
- return this.isEdit ? '编辑销售预测' : '新增销售预测'
- }
- },
- /**
- * 侦听器
- * @description 监听属性变化并执行相应操作
- */
- watch: {
- /**
- * 监听表单可见性变化
- * @param {boolean} val - 新的可见性值
- */
- visible(val) {
- if (val) {
- this.$nextTick(() => {
- // 表单显示时,初始化表单数据
- if (this.initialFormData) {
- this.formData = this.cleanAndFormatFormData(this.initialFormData)
- } else {
- this.formData = this.createInitialFormData()
- }
- // 如果是编辑模式且有ID,则加载详情数据
- if (this.isEdit && this.formData.id) {
- this.loadForecastDetail(this.formData.id)
- }
- // 如果不是编辑模式,则生成预测编码
- if (!this.isEdit && !this.formData.forecastCode) {
- this.generateForecastCode()
- }
- })
- }
- },
- /**
- * 监听初始表单数据变化
- * @param {Object} val - 新的初始表单数据
- */
- initialFormData(val) {
- if (val && this.visible) {
- this.formData = this.cleanAndFormatFormData(val)
- }
- },
- /**
- * 监听预测ID变化
- * @param {string|number} val - 新的预测ID
- */
- forecastId: {
- handler(val) {
- if (val && this.isEdit && this.visible) {
- this.loadForecastDetail(val)
- }
- },
- immediate: true
- }
- },
- /**
- * 组件方法
- * @description 组件的业务逻辑方法集合
- */
- methods: {
- /**
- * 创建初始表单数据
- * @description 创建销售预测表单的初始数据结构
- * @returns {Object} 初始化的表单数据对象
- * @private
- */
- createInitialFormData() {
- return { ...DEFAULT_FORECAST_FORM }
- },
- /**
- * 清理和格式化表单数据
- * @description 对表单数据进行清理和格式化处理
- * @param {Object} data - 原始表单数据
- * @returns {Object} 清理和格式化后的表单数据
- * @private
- */
- cleanAndFormatFormData(data) {
- // 获取下个月的年份和月份作为默认值
- const now = new Date()
- const currentYear = now.getFullYear()
- const currentMonth = now.getMonth() + 1
-
- let defaultYear, defaultMonth
- if (currentMonth === 12) {
- // 当前是12月,下个月是明年1月
- defaultYear = currentYear + 1
- defaultMonth = 1
- } else {
- // 其他月份,直接 +1
- defaultYear = currentYear
- defaultMonth = currentMonth + 1
- }
-
- return {
- id: data.id || null,
- forecastCode: String(data.forecastCode || ''),
- year: data.year ? data.year.toString() : defaultYear.toString(),
- month: Number(data.month) || defaultMonth,
- customerId: data.customerId ? data.customerId.toString() : null,
- customerCode: String(data.customerCode || ''),
- customerName: String(data.customerName || ''),
- brandId: Number(data.brandId) || null,
- brandCode: String(data.brandCode || ''),
- brandName: String(data.brandName || ''),
- itemId: data.itemId ? data.itemId.toString() : null,
- itemCode: String(data.itemCode || ''),
- itemName: String(data.itemName || ''),
- specs: String(data.specs || ''),
- forecastQuantity: data.forecastQuantity !== undefined && data.forecastQuantity !== null && data.forecastQuantity !== '' ? Number(data.forecastQuantity) : null,
- currentInventory: Number(data.currentInventory) || null,
- approvalStatus: Number(data.approvalStatus) || APPROVAL_STATUS.PENDING,
- approvedName: String(data.approvedName || ''),
- approvedTime: data.approvedTime || null,
- createTime: data.createTime || null,
- updateTime: data.updateTime || null
- }
- },
- /**
- * 加载销售预测详情
- * @description 根据ID加载销售预测详情数据
- * @param {string|number} id - 销售预测ID
- * @returns {Promise<void>}
- * @private
- */
- async loadForecastDetail(id) {
- if (!id) return
- try {
- this.formLoading = true
- const res = await getForecastDetail(id)
- if (res.data && res.data.success && res.data.data) {
- const detailData = res.data.data
- this.formData = this.cleanAndFormatFormData(detailData)
- // 加载客户选项数据,确保客户下拉框能正确显示
- if (this.formData.customerId) {
- await this.loadCustomerOption(this.formData.customerId, this.formData.customerName)
- }
- // 加载物料选项数据,确保物料下拉框能正确显示
- if (this.formData.itemId) {
- await this.loadItemOption(this.formData.itemId, this.formData.itemName, this.formData.itemCode, this.formData.specs)
- }
- // 触发加载完成事件
- this.$emit(FORECAST_FORM_EVENTS.LOADED, this.formData)
- } else {
- this.$message.error(res.data?.msg || '获取详情失败')
- }
- } catch (error) {
- console.error('获取销售预测详情失败:', error)
- this.$message.error('获取详情失败,请稍后重试')
- } finally {
- this.formLoading = false
- }
- },
- /**
- * 加载单个客户选项
- * @description 为编辑模式加载特定客户的选项数据
- * @param {string|number} customerId - 客户ID
- * @param {string} customerName - 客户名称
- * @returns {Promise<void>}
- */
- async loadCustomerOption(customerId, customerName) {
- if (!customerId) return
- try {
- // customer-select组件会自动处理回显,我们只需要确保formData中有正确的值
- // 组件的watch会监听value变化并调用loadCustomerById方法
-
- } catch (error) {
- console.error('加载客户选项失败:', error)
- }
- },
- /**
- * 远程搜索客户
- * @description 根据关键字远程搜索客户数据
- * @param {string} query - 搜索关键字
- * @returns {Promise<void>}
- */
- async remoteSearchCustomer(query) {
- if (query === '') {
- this.customerOptions = []
- return
- }
- try {
- this.customerLoading = true
- const res = await getCustomerList({
- current: 1,
- size: 10,
- customerName: query
- })
- if (res.data && res.data.success && res.data.data) {
- const { records } = res.data.data
- this.customerOptions = records.map(item => ({
- value: item.id,
- label: item.customerName,
- customerCode: item.customerCode
- }))
- }
- } catch (error) {
- console.error('搜索客户失败:', error)
- } finally {
- this.customerLoading = false
- }
- },
- /**
- * 加载单个物料选项
- * @description 为编辑模式加载特定物料的选项数据
- * @param {string|number} itemId - 物料ID
- * @param {string} itemName - 物料名称
- * @param {string} itemCode - 物料编码
- * @param {string} specs - 规格
- * @returns {Promise<void>}
- */
- async loadItemOption(itemId, itemName, itemCode, specs) {
- if (!itemId) return
- try {
- // 如果选项中已经存在该物料,则不需要重新加载
- const existingOption = this.itemOptions.find(option => option.id === itemId)
- if (existingOption) return
- // 添加当前物料到选项中
- this.itemOptions = [{
- id: itemId,
- code: itemCode,
- name: itemName,
- specs: specs
- }]
- } catch (error) {
- console.error('加载物料选项失败:', error)
- }
- },
- /**
- * 远程搜索物料
- * @description 根据关键字远程搜索物料数据
- * @param {string} query - 搜索关键字
- * @returns {Promise<void>}
- */
- async remoteSearchItem(query) {
- if (query === '') {
- this.itemOptions = []
- return
- }
- try {
- this.itemLoading = true
- const res = await getItemList({
- current: 1,
- size: 10,
- itemName: query
- })
- if (res.data && res.data.success && res.data.data) {
- const { records } = res.data.data
- this.itemOptions = records.map(item => ({
- value: item.id,
- label: item.itemName,
- itemCode: item.itemCode,
- specs: item.specs
- }))
- }
- } catch (error) {
- console.error('搜索物料失败:', error)
- } finally {
- this.itemLoading = false
- }
- },
- /**
- * 客户选择变化处理
- * @description 处理客户选择变化,更新表单中的客户相关字段
- * @param {string|number} customerId - 客户ID
- * @returns {void}
- */
- handleCustomerChange(customerId) {
- const customer = this.customerOptions.find(item => item.value === customerId)
- if (customer) {
- this.formData.customerId = customer.value
- this.formData.customerCode = customer.customerCode
- this.formData.customerName = customer.label
- // 触发客户变更事件
- this.$emit(FORECAST_FORM_EVENTS.CUSTOMER_CHANGE, customer)
- }
- },
- /**
- * 物料选择变化处理
- * @description 处理物料选择变化,更新表单中的物料相关字段
- * @param {string|number} itemId - 物料ID
- * @returns {void}
- */
- handleItemChange(itemId) {
- const item = this.itemOptions.find(option => option.value === itemId)
- if (item) {
- this.formData.itemId = item.value
- this.formData.itemCode = item.itemCode
- this.formData.itemName = item.label
- this.formData.specs = item.specs
- // 触发物料变更事件
- this.$emit(FORECAST_FORM_EVENTS.ITEM_CHANGE, item)
- }
- },
- /**
- * 生成预测编码
- * @description 自动生成销售预测编码
- * @returns {void}
- */
- generateForecastCode() {
- const now = new Date()
- const year = now.getFullYear()
- const month = String(now.getMonth() + 1).padStart(2, '0')
- const timestamp = now.getTime().toString().slice(-6)
- this.formData.forecastCode = `FC-${year}-${month}-${timestamp}`
- },
- /**
- * 表单提交
- * @description 提交表单数据,根据编辑模式调用不同的API
- * @returns {Promise<void>}
- */
- async submitForm() {
- try {
- // 表单验证
- await this.$refs.form.validate()
- this.saveLoading = true
- // 准备提交数据
- const submitData = this.prepareSubmitData()
- let res
- if (this.isEdit) {
- res = await updateForecast(submitData)
- } else {
- res = await addForecast(submitData)
- }
- if (res.data && res.data.success) {
- this.$message.success(res.data.msg || (this.isEdit ? '修改成功' : '添加成功'))
-
- // 触发提交成功事件
- this.$emit(FORECAST_FORM_EVENTS.SUBMIT_SUCCESS, res.data.data)
-
- // 关闭表单
- this.closeForm()
- } else {
- this.$message.error(res.data?.msg || (this.isEdit ? '修改失败' : '添加失败'))
- }
- } catch (error) {
- if (error.fields) {
- // 表单验证失败
- return
- }
- console.error('保存失败:', error)
- this.$message.error('保存失败,请稍后重试')
- } finally {
- this.saveLoading = false
- }
- },
- /**
- * 准备提交数据
- * @description 复制表单数据并进行清理和格式化处理
- * @returns {Object} 准备好的提交数据
- * @private
- */
- prepareSubmitData() {
- const submitData = { ...this.formData }
- // 转换年份为数字
- if (submitData.year && typeof submitData.year === 'string') {
- submitData.year = parseInt(submitData.year, 10)
- }
- // 确保数值字段为数字类型
- if (submitData.forecastQuantity) {
- submitData.forecastQuantity = Number(submitData.forecastQuantity)
- }
- if (submitData.currentInventory) {
- submitData.currentInventory = Number(submitData.currentInventory)
- }
- return submitData
- },
- /**
- * 关闭表单
- * @description 关闭表单并重置数据
- * @returns {void}
- */
- closeForm() {
- // 触发取消事件
- this.$emit(FORECAST_FORM_EVENTS.CANCEL)
-
- // 更新可见性
- this.$emit('update:visible', false)
-
- // 重置表单数据
- this.formData = this.createInitialFormData()
-
- // 重置表单验证
- if (this.$refs.form) {
- this.$refs.form.resetFields()
- }
- },
- /**
- * 获取审批状态标签
- * @description 根据审批状态获取对应的标签文本
- * @param {number} status - 审批状态
- * @returns {string} 状态标签
- */
- getApprovalStatusLabel,
- /**
- * 获取审批状态类型
- * @description 根据审批状态获取对应的类型(用于标签样式)
- * @param {number} status - 审批状态
- * @returns {string} 状态类型
- */
- getApprovalStatusType,
- /**
- * 判断是否可以编辑
- * @description 根据审批状态判断记录是否可以编辑
- * @param {number} status - 审批状态
- * @returns {boolean} 是否可编辑
- */
- canEdit
- }
- }
|