order-form-mixin.js 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274
  1. /**
  2. * @fileoverview 订单表单混入组件
  3. * @description 提供订单表单的数据管理、验证规则和业务逻辑的混入组件,支持新增和编辑模式
  4. */
  5. // API接口导入
  6. import { add, update, getDetail } from '@/api/order/order'
  7. import { getList as getOrderItemList } from '@/api/order/order-item'
  8. import { createSalesOrder } from '@/api/order/sales-order'
  9. // 常量和枚举导入
  10. import {
  11. ORDER_TYPES,
  12. ORDER_STATUS,
  13. ORDER_TYPE_OPTIONS,
  14. ORDER_STATUS_OPTIONS
  15. } from '@/constants/order'
  16. // 本地常量定义导入
  17. import {
  18. MaterialDetailDataSource
  19. } from '@/constants/order'
  20. import { ORDER_FORM_EVENTS, CUSTOMER_SELECT_EVENTS, ADDRESS_SELECT_EVENTS, MATERIAL_DETAIL_EVENTS } from './events'
  21. // 数字格式化工具导入
  22. import {
  23. formatAmount,
  24. formatFloatNumber,
  25. formatIntegerNumber,
  26. formatUnitPrice,
  27. formatTaxRate,
  28. preciseMultiply,
  29. preciseDivide,
  30. preciseRound,
  31. validateNumber,
  32. NUMBER_TYPES
  33. } from './number-format-utils'
  34. /**
  35. * 类型定义导入
  36. * @description 导入所有必要的TypeScript类型定义,确保类型安全
  37. */
  38. /**
  39. * @typedef {import('./types').MaterialDetailRecord} MaterialDetailRecord
  40. * @description 物料明细记录类型
  41. */
  42. /**
  43. * @typedef {import('./types').OrderFormModel} OrderFormModel
  44. * @description 订单表单数据模型类型
  45. */
  46. /**
  47. * @typedef {import('./types').MaterialUpdateEventData} MaterialUpdateEventData
  48. * @description 物料更新事件数据类型
  49. */
  50. /**
  51. * @typedef {import('./types').OrderFormRules} OrderFormRules
  52. * @description 订单表单验证规则类型
  53. */
  54. /**
  55. * @typedef {import('./types').OrderFormMethods} OrderFormMethods
  56. * @description 订单表单方法类型
  57. */
  58. /**
  59. * @typedef {import('smallwei__avue/form').AvueFormOption<OrderFormModel>} AvueFormOption
  60. * @typedef {import('smallwei__avue/form').AvueFormColumn<OrderFormModel>} AvueFormColumn
  61. * @typedef {import('smallwei__avue/form').AvueFormGroup<OrderFormModel>} AvueFormGroup
  62. * @typedef {import('smallwei__avue/crud').PageOption} PageOption
  63. */
  64. /**
  65. * @typedef {import('./types').MaterialDeleteEventData} MaterialDeleteEventData
  66. * @description 物料删除事件数据类型
  67. */
  68. /**
  69. * @typedef {import('./types').ApiResponse} ApiResponse
  70. * @description API响应数据类型
  71. */
  72. /**
  73. * @typedef {import('./types').ValidationRule} ValidationRule
  74. * @description 表单验证规则类型
  75. */
  76. /**
  77. * @typedef {import('@/api/types/order').SalesOrderCreateForm} SalesOrderCreateForm
  78. * @description 销售订单创建表单类型
  79. */
  80. /**
  81. * @typedef {import('@/api/types/order').SalesOrderItemCreateForm} SalesOrderItemCreateForm
  82. * @description 销售订单明细创建表单类型
  83. */
  84. /**
  85. * @typedef {import('@/constants/order').ORDER_TYPES} OrderTypeValue
  86. * @description 订单类型枚举值类型
  87. */
  88. /**
  89. * @typedef {import('@/constants/order').ORDER_STATUS} OrderStatusValue
  90. * @description 订单状态枚举值类型
  91. */
  92. /**
  93. * 订单表单混入组件
  94. * @description 提供订单表单的数据管理、验证规则和业务逻辑的混入组件
  95. * @mixin
  96. */
  97. export default {
  98. /**
  99. * 组件响应式数据
  100. * @description 定义组件的响应式数据状态
  101. * @returns {import('./types').OrderFormMixinData} 组件数据对象
  102. * @this {import('./types').OrderFormMixin}
  103. */
  104. data() {
  105. return {
  106. /**
  107. * 订单表单数据模型
  108. * @description 存储订单表单的所有字段数据
  109. * @type {OrderFormModel}
  110. */
  111. formData: this.createInitialFormData(),
  112. /**
  113. * 保存操作加载状态
  114. * @description 控制保存按钮的加载状态,防止重复提交
  115. * @type {boolean}
  116. */
  117. saveLoading: false,
  118. /**
  119. * 表单加载状态
  120. * @description 控制表单整体的加载状态,用于数据获取时的UI反馈
  121. * @type {boolean}
  122. */
  123. formLoading: false,
  124. /**
  125. * 物料明细列表
  126. * @description 存储当前订单的物料明细数据,包含数据来源和删除权限标识
  127. * @type {MaterialDetailRecord[]}
  128. */
  129. materialDetails: [],
  130. /**
  131. * 订单类型选项列表
  132. * @description 订单类型下拉选择器的选项数据
  133. * @type {typeof ORDER_TYPE_OPTIONS}
  134. */
  135. orderTypeOptions: ORDER_TYPE_OPTIONS,
  136. /**
  137. * 订单状态选项列表
  138. * @description 订单状态下拉选择器的选项数据
  139. * @type {typeof ORDER_STATUS_OPTIONS}
  140. */
  141. orderStatusOptions: ORDER_STATUS_OPTIONS,
  142. // 事件常量,用于模板中的动态事件绑定
  143. CUSTOMER_SELECT_EVENTS,
  144. ADDRESS_SELECT_EVENTS,
  145. MATERIAL_DETAIL_EVENTS
  146. }
  147. },
  148. /**
  149. * 计算属性
  150. * @description 组件的响应式计算属性
  151. */
  152. computed: {
  153. /**
  154. * 订单表单验证规则
  155. * @description 定义订单表单各字段的验证规则,支持必填、长度、格式等验证
  156. * @returns {OrderFormRules} 完整的表单验证规则对象
  157. * @this {import('./types').OrderFormMixin}
  158. */
  159. formRules() {
  160. return {
  161. orderCode: [
  162. {
  163. required: true,
  164. message: '请输入订单编码',
  165. trigger: 'blur'
  166. },
  167. {
  168. min: 3,
  169. max: 50,
  170. message: '订单编码长度在 3 到 50 个字符',
  171. trigger: 'blur'
  172. }
  173. ],
  174. orgCode: [
  175. {
  176. required: true,
  177. message: '请输入组织编码',
  178. trigger: 'blur'
  179. },
  180. {
  181. min: 2,
  182. max: 20,
  183. message: '组织编码长度在 2 到 20 个字符',
  184. trigger: 'blur'
  185. }
  186. ],
  187. orgName: [
  188. {
  189. required: true,
  190. message: '请输入组织名称',
  191. trigger: 'blur'
  192. },
  193. {
  194. min: 2,
  195. max: 100,
  196. message: '组织名称长度在 2 到 100 个字符',
  197. trigger: 'blur'
  198. }
  199. ],
  200. customerCode: [
  201. {
  202. required: true,
  203. message: '请输入客户编码',
  204. trigger: 'blur'
  205. },
  206. {
  207. min: 3,
  208. max: 50,
  209. message: '客户编码长度在 3 到 50 个字符',
  210. trigger: 'blur'
  211. }
  212. ],
  213. customerName: [
  214. {
  215. required: true,
  216. message: '请输入客户名称',
  217. trigger: 'blur'
  218. },
  219. {
  220. min: 2,
  221. max: 100,
  222. message: '客户名称长度在 2 到 100 个字符',
  223. trigger: 'blur'
  224. }
  225. ],
  226. orderType: [
  227. {
  228. required: true,
  229. message: '请选择订单类型',
  230. trigger: 'change'
  231. }
  232. ],
  233. totalAmount: [
  234. {
  235. required: true,
  236. message: '请输入订单总金额',
  237. trigger: 'blur'
  238. },
  239. {
  240. type: 'number',
  241. min: 0.01,
  242. message: '订单总金额必须大于0',
  243. trigger: 'blur'
  244. }
  245. ],
  246. totalQuantity: [
  247. {
  248. required: true,
  249. message: '请输入订单总数量',
  250. trigger: 'blur'
  251. },
  252. {
  253. type: 'number',
  254. min: 0.0001,
  255. message: '订单总数量必须大于0',
  256. trigger: 'blur'
  257. }
  258. ],
  259. receiverName: [
  260. {
  261. required: true,
  262. message: '请输入收货人姓名',
  263. trigger: 'blur'
  264. },
  265. {
  266. min: 2,
  267. max: 50,
  268. message: '收货人姓名长度在 2 到 50 个字符',
  269. trigger: 'blur'
  270. }
  271. ],
  272. receiverPhone: [
  273. {
  274. required: true,
  275. message: '请输入收货人电话',
  276. trigger: 'blur'
  277. },
  278. {
  279. /**
  280. * 手机号码格式验证器
  281. * @description 验证手机号码格式是否正确,支持1开头的11位数字
  282. * @param {Object} rule - 验证规则对象
  283. * @param {string} value - 待验证的值
  284. * @param {Function} callback - 验证回调函数
  285. * @returns {void}
  286. */
  287. validator: (rule, value, callback) => {
  288. if (!value || typeof value !== 'string') {
  289. callback()
  290. return
  291. }
  292. // 手机号码正则表达式:1开头,第二位为3-9,总共11位数字
  293. const phoneRegex = /^1[3-9]\d{9}$/
  294. if (!phoneRegex.test(value.trim())) {
  295. callback(new Error('请输入正确的手机号码格式(1开头的11位数字)'))
  296. } else {
  297. callback()
  298. }
  299. },
  300. trigger: 'blur'
  301. }
  302. ],
  303. receiverRegion: [
  304. {
  305. required: true,
  306. message: '请输入收货地区',
  307. trigger: 'blur'
  308. },
  309. {
  310. min: 2,
  311. max: 100,
  312. message: '收货地区长度在 2 到 100 个字符',
  313. trigger: 'blur'
  314. }
  315. ],
  316. receiverAddress: [
  317. {
  318. required: true,
  319. message: '请输入收货地址',
  320. trigger: 'blur'
  321. },
  322. {
  323. min: 5,
  324. max: 500,
  325. message: '收货地址长度在 5 到 500 个字符',
  326. trigger: 'blur'
  327. }
  328. ],
  329. remark: [
  330. {
  331. max: 1000,
  332. message: '备注信息不能超过 1000 个字符',
  333. trigger: 'blur'
  334. }
  335. ]
  336. }
  337. },
  338. /**
  339. * 表单标题
  340. * @description 根据编辑模式动态显示表单标题
  341. * @returns {string} 表单标题文本
  342. * @this {import('./types').OrderFormMixin}
  343. */
  344. formTitle() {
  345. return this.isEdit ? '编辑订单' : '新增订单'
  346. },
  347. /**
  348. * 物料明细表格配置
  349. * @description 获取物料明细表格的配置选项
  350. * @returns {Object} 表格配置对象
  351. * @this {import('./types').OrderFormMixin}
  352. */
  353. materialDetailTableOption() {
  354. return {
  355. border: true,
  356. stripe: true,
  357. menuAlign: 'center',
  358. align: 'center',
  359. addBtn: false,
  360. editBtn: false,
  361. delBtn: true,
  362. viewBtn: false,
  363. column: [
  364. {
  365. label: '物料编码',
  366. prop: 'itemCode',
  367. width: 120
  368. },
  369. {
  370. label: '物料名称',
  371. prop: 'itemName',
  372. width: 150
  373. },
  374. {
  375. label: '规格型号',
  376. prop: 'specs',
  377. width: 120
  378. },
  379. {
  380. label: '订单数量',
  381. prop: 'orderQuantity',
  382. width: 100
  383. },
  384. {
  385. label: '单价',
  386. prop: 'unitPrice',
  387. width: 100
  388. },
  389. {
  390. label: '总金额',
  391. prop: 'totalAmount',
  392. width: 120
  393. }
  394. ]
  395. }
  396. }
  397. },
  398. /**
  399. * 组件方法
  400. * @description 组件的业务逻辑方法集合
  401. */
  402. methods: {
  403. /**
  404. * 创建初始表单数据
  405. * @description 创建订单表单的初始数据结构
  406. * @returns {OrderFormModel} 初始化的表单数据对象
  407. * @private
  408. * @this {import('./types').OrderFormMixin}
  409. */
  410. createInitialFormData() {
  411. return {
  412. id: null,
  413. orderCode: '',
  414. orgCode: '',
  415. orgName: '',
  416. customerId: null,
  417. customerCode: '',
  418. customerName: '',
  419. orderType: ORDER_TYPES.NORMAL,
  420. totalAmount: null,
  421. totalQuantity: null,
  422. addressId: '',
  423. receiverName: '',
  424. receiverPhone: '',
  425. receiverRegion: '',
  426. receiverAddress: '',
  427. status: ORDER_STATUS.DRAFT,
  428. remark: ''
  429. }
  430. },
  431. /**
  432. * 初始化表单数据
  433. * @description 根据编辑模式初始化表单,编辑模式加载订单详情数据,新增模式重置表单为初始状态
  434. * @returns {Promise<void>}
  435. * @throws {Error} 当初始化过程中发生错误时抛出异常
  436. * @public
  437. * @this {import('./types').OrderFormMixin}
  438. */
  439. async initForm() {
  440. try {
  441. if (this.isEdit && this.orderId) {
  442. // 编辑模式:加载现有订单数据
  443. await this.loadOrderDetail(this.orderId)
  444. } else {
  445. // 新增模式:重置表单为初始状态
  446. await this.resetForm()
  447. }
  448. } catch (error) {
  449. console.error('初始化表单失败:', error)
  450. const errorMessage = error.message || '初始化表单失败,请刷新页面重试'
  451. this.$message.error(errorMessage)
  452. throw error
  453. }
  454. },
  455. /**
  456. * 重置表单数据
  457. * @description 将表单数据重置为初始状态,清除所有验证错误信息,并重置物料明细列表
  458. * @returns {Promise<void>}
  459. * @throws {Error} 当重置过程中发生严重错误时抛出异常
  460. * @public
  461. * @this {import('./types').OrderFormMixin}
  462. */
  463. async resetForm() {
  464. try {
  465. // 重置表单数据为初始状态
  466. this.formData = this.createInitialFormData()
  467. // 重置物料明细列表(如果存在)
  468. if (Array.isArray(this.materialDetails)) {
  469. this.materialDetails = []
  470. }
  471. // 重置保存状态
  472. this.saveLoading = false
  473. // 等待DOM更新后清除表单验证
  474. await this.$nextTick()
  475. // 清除表单验证状态
  476. if (this.$refs.orderForm && typeof this.$refs.orderForm.clearValidate === 'function') {
  477. this.$refs.orderForm.clearValidate()
  478. }
  479. } catch (error) {
  480. console.error('重置表单失败:', error)
  481. // 重置表单时发生严重错误,抛出异常
  482. throw new Error('重置表单失败,请刷新页面重试')
  483. }
  484. },
  485. /**
  486. * 加载订单详情数据
  487. * @description 根据订单ID从服务器获取订单详情并填充到表单中,同时并行加载物料明细数据以提高性能
  488. * @param {string|number} orderId - 订单唯一标识符
  489. * @returns {Promise<void>}
  490. * @throws {Error} 当订单ID无效、API调用失败或数据格式错误时抛出异常
  491. * @public
  492. * @this {import('./types').OrderFormMixin}
  493. */
  494. async loadOrderDetail(orderId) {
  495. // 参数验证
  496. if (!orderId || (typeof orderId !== 'string' && typeof orderId !== 'number')) {
  497. throw new Error('订单ID不能为空且必须是有效的字符串或数字')
  498. }
  499. try {
  500. // 并行加载订单详情和物料明细数据以提高性能
  501. const [orderResponse, materialResponse] = await Promise.all([
  502. getDetail(orderId),
  503. this.loadMaterialDetails(orderId)
  504. ])
  505. // 验证订单详情响应数据
  506. if (!orderResponse?.data?.success) {
  507. const errorMsg = orderResponse?.data?.msg || '获取订单详情失败'
  508. throw new Error(errorMsg)
  509. }
  510. if (!orderResponse.data.data) {
  511. throw new Error('订单数据不存在或已被删除')
  512. }
  513. const orderData = orderResponse.data.data
  514. // 安全地映射订单数据到表单,确保数据类型正确
  515. this.formData = this.mapOrderDataToForm(orderData)
  516. // 设置物料明细数据(确保是数组类型)
  517. this.materialDetails = Array.isArray(materialResponse) ? materialResponse : []
  518. console.log(`成功加载订单详情,订单编码: ${orderData.orderCode || orderId}`)
  519. } catch (error) {
  520. console.error('加载订单详情失败:', error)
  521. const errorMessage = error.message || '加载订单详情失败,请检查网络连接后重试'
  522. this.$message.error(errorMessage)
  523. throw error
  524. }
  525. },
  526. /**
  527. * 加载物料明细数据
  528. * @description 根据订单ID获取物料明细列表,并对数值字段进行格式化和验证,确保数据精确性和类型安全
  529. * @param {string|number} orderId - 订单唯一标识符
  530. * @returns {Promise<MaterialDetailRecord[]>} 格式化后的物料明细数组,数值字段已进行精度处理
  531. * @throws {Error} 当订单ID无效或API调用失败时抛出异常
  532. * @private
  533. * @this {import('./types').OrderFormMixin}
  534. */
  535. async loadMaterialDetails(orderId) {
  536. // 参数验证
  537. if (!orderId || (typeof orderId !== 'string' && typeof orderId !== 'number')) {
  538. console.error('loadMaterialDetails: 订单ID无效', orderId)
  539. return []
  540. }
  541. try {
  542. const response = await getOrderItemList(1, 1000, { orderId })
  543. // 验证响应数据结构
  544. if (!response?.data?.success) {
  545. const errorMsg = response?.data?.msg || '获取物料明细失败'
  546. throw new Error(errorMsg)
  547. }
  548. if (!response.data.data) {
  549. console.warn('物料明细数据为空')
  550. return []
  551. }
  552. const materialDetails = response.data.data.records
  553. // 确保返回的是数组类型
  554. if (!Array.isArray(materialDetails)) {
  555. console.warn('物料明细数据格式异常,返回空数组')
  556. return []
  557. }
  558. // 为远程加载的物料数据添加数据来源标识并格式化数字字段
  559. return materialDetails.map((material, index) => {
  560. try {
  561. // 验证和格式化数字字段,确保类型安全
  562. const orderQuantityValidation = validateNumber(material.orderQuantity)
  563. const unitPriceValidation = validateNumber(material.unitPrice)
  564. const taxRateValidation = validateNumber(material.taxRate)
  565. const taxAmountValidation = validateNumber(material.taxAmount)
  566. const totalAmountValidation = validateNumber(material.totalAmount)
  567. /** @type MaterialDetailRecord */
  568. const detailData = {
  569. ...material,
  570. dataSource: MaterialDetailDataSource.REMOTE,
  571. isDeletable: false, // 远程加载的数据不可删除
  572. // 格式化数字字段,确保精度和类型正确
  573. orderQuantity: orderQuantityValidation.isValid ? Math.round(orderQuantityValidation.value) : 0,
  574. unitPrice: unitPriceValidation.isValid ? preciseRound(unitPriceValidation.value, 2) : 0,
  575. taxRate: taxRateValidation.isValid ? preciseRound(taxRateValidation.value, 4) : 0,
  576. taxAmount: taxAmountValidation.isValid ? preciseRound(taxAmountValidation.value, 2) : 0,
  577. totalAmount: totalAmountValidation.isValid ? preciseRound(totalAmountValidation.value, 2) : 0,
  578. // 确保必要的字段存在
  579. itemCode: material.itemCode || '',
  580. itemName: material.itemName || '',
  581. specs: material.specs || '',
  582. }
  583. return detailData;
  584. } catch (itemError) {
  585. console.error(`格式化物料明细第${index + 1}项失败:`, itemError)
  586. // 返回默认的物料明细项,确保数据完整性
  587. return {
  588. ...material,
  589. dataSource: MaterialDetailDataSource.REMOTE,
  590. isDeletable: false,
  591. orderQuantity: 0,
  592. unitPrice: 0,
  593. taxRate: 0,
  594. taxAmount: 0,
  595. totalAmount: 0,
  596. itemCode: material.itemCode || '',
  597. itemName: material.itemName || '',
  598. specs: material.specs || material.specification || '',
  599. unit: material.unit || ''
  600. }
  601. }
  602. })
  603. } catch (error) {
  604. console.error('加载物料明细失败:', error)
  605. this.$message.warning('加载物料明细失败,请稍后重试')
  606. return []
  607. }
  608. },
  609. /**
  610. * 映射订单数据到表单格式
  611. * @description 将API返回的订单数据安全地映射为表单数据格式,并格式化数字字段
  612. * @param {import('@/api/types/order').OrderRecord} orderData - 从API获取的原始订单数据
  613. * @returns {OrderFormModel} 格式化后的表单数据
  614. * @private
  615. * @this {import('./types').OrderFormMixin}
  616. */
  617. mapOrderDataToForm(orderData) {
  618. // 验证和格式化数字字段
  619. const totalAmountValidation = validateNumber(orderData.totalAmount)
  620. const totalQuantityValidation = validateNumber(orderData.totalQuantity)
  621. const orderQuantityValidation = validateNumber(orderData.orderQuantity)
  622. return {
  623. id: orderData.id || null,
  624. orderCode: String(orderData.orderCode || ''),
  625. orgCode: String(orderData.orgCode || ''),
  626. orgName: String(orderData.orgName || ''),
  627. customerId: Number(orderData.customerId) || null,
  628. customerCode: String(orderData.customerCode || ''),
  629. customerName: String(orderData.customerName || ''),
  630. orderType: Number(orderData.orderType) || ORDER_TYPES.NORMAL,
  631. orderQuantity: orderQuantityValidation.isValid ? parseInt(orderQuantityValidation.value.toString()) : null,
  632. totalAmount: totalAmountValidation.isValid ? preciseRound(totalAmountValidation.value, 2) : null,
  633. totalQuantity: totalQuantityValidation.isValid ? preciseRound(totalQuantityValidation.value, 4) : null,
  634. addressId: String(orderData.addressId || ''),
  635. receiverName: String(orderData.receiverName || ''),
  636. receiverPhone: String(orderData.receiverPhone || ''),
  637. receiverRegion: String(orderData.receiverRegion || ''),
  638. receiverAddress: String(orderData.receiverAddress || ''),
  639. status: Number(orderData.status) || ORDER_STATUS.DRAFT,
  640. remark: String(orderData.remark || '')
  641. }
  642. },
  643. /**
  644. * 处理返回列表操作
  645. * @description 触发返回列表事件,通知父组件关闭表单
  646. * @returns {void}
  647. * @public
  648. * @emits back 返回列表事件
  649. * @this {import('./types').OrderFormMixin}
  650. */
  651. handleBack() {
  652. /**
  653. * 返回列表事件
  654. * @event back
  655. * @description 用户点击返回按钮时触发
  656. */
  657. this.$emit(ORDER_FORM_EVENTS.BACK)
  658. },
  659. /**
  660. * 处理表单保存操作
  661. * @description 验证表单数据并提交到服务器,支持新增和编辑模式
  662. * 编辑模式下先保存订单数据,再批量保存物料明细数据
  663. * @returns {Promise<void>}
  664. * @throws {Error} 当表单验证失败或API调用失败时抛出异常
  665. * @public
  666. * @emits save-success 保存成功事件
  667. * @this {import('./types').OrderFormMixin}
  668. */
  669. async handleSave() {
  670. if (this.saveLoading) {
  671. return // 防止重复提交
  672. }
  673. try {
  674. // 表单验证
  675. const isValid = await this.validateForm()
  676. if (!isValid) {
  677. return
  678. }
  679. this.saveLoading = true
  680. // 准备提交数据
  681. const submitData = this.prepareSubmitData()
  682. // 调用相应的API
  683. const response = await this.submitOrderData(submitData)
  684. // 编辑模式下,保存订单数据后再批量保存物料明细
  685. if (this.isEdit) {
  686. await this.saveMaterialDetails()
  687. }
  688. // 显示成功提示
  689. const successMessage = this.isEdit ? '订单更新成功' : '订单创建成功'
  690. this.$message.success(successMessage)
  691. /**
  692. * 保存成功事件
  693. * @event typeof ORDER_FORM_EVENTS.SAVE_SUCCESS
  694. * @param {Object} data - 保存后的订单数据
  695. * @description 订单保存成功后触发,携带最新的订单数据
  696. */
  697. this.$emit(ORDER_FORM_EVENTS.SAVE_SUCCESS, response.data.data)
  698. // 返回列表
  699. this.handleBack()
  700. } catch (error) {
  701. const errorMessage = this.isEdit ? '订单更新失败,请重试' : '订单创建失败,请重试'
  702. this.$message.error(errorMessage)
  703. throw error
  704. } finally {
  705. this.saveLoading = false
  706. }
  707. },
  708. /**
  709. * 提交订单数据到服务器
  710. * @description 根据编辑模式调用相应的API接口,新建状态下使用createSalesOrder包含物料明细
  711. * @param {OrderFormModel} submitData - 要提交的订单数据
  712. * @returns {Promise<import("@/api/types/order").SalesOrderCreateResponse>} API响应结果
  713. * @private
  714. * @this {import('./types').OrderFormMixin}
  715. */
  716. async submitOrderData(submitData) {
  717. if (this.isEdit) {
  718. return await update(submitData)
  719. } else {
  720. // 新建状态下使用createSalesOrder接口,包含物料明细数据
  721. const salesOrderData = this.prepareSalesOrderData(submitData)
  722. return await createSalesOrder(salesOrderData)
  723. }
  724. },
  725. /**
  726. * 准备销售订单创建数据
  727. * @description 将表单数据和物料明细数据组合为createSalesOrder接口所需的格式
  728. * @param {OrderFormModel} formData - 表单数据
  729. * @returns {SalesOrderCreateForm} 销售订单创建数据
  730. * @private
  731. * @this {import('./types').OrderFormMixin}
  732. */
  733. prepareSalesOrderData(formData) {
  734. // 转换物料明细数据为API所需格式
  735. const pcBladeOrderItemList = this.materialDetails.map(material => ({
  736. itemId: Number(material.itemId) || 0,
  737. itemCode: material.itemCode || '',
  738. itemName: material.itemName || '',
  739. specs: material.specs || material.specification || '',
  740. mainItemCategoryId: Number(material.mainItemCategoryId) || Number(material.mainCategoryId) || 0,
  741. mainItemCategoryName: material.mainItemCategoryName || material.mainCategoryName || '',
  742. warehouseId: Number(material.warehouseId) || 0,
  743. warehouseName: material.warehouseName || '',
  744. availableQuantity: Number(material.availableQuantity) || 0,
  745. orderQuantity: Number(material.orderQuantity) || 0,
  746. confirmQuantity: Number(material.confirmQuantity) || Number(material.orderQuantity) || 0,
  747. unitPrice: Number(material.unitPrice) || 0,
  748. taxRate: Number(material.taxRate) || 0,
  749. taxAmount: Number(material.taxAmount) || 0,
  750. totalAmount: Number(material.totalAmount) || 0,
  751. itemStatus: Number(material.itemStatus) || Number(material.status) || 0
  752. }))
  753. // 创建销售订单数据对象,不包含orderCode字段
  754. const salesOrderData = {
  755. ...formData,
  756. orgId: Number(formData.orgId) || 0,
  757. customerId: Number(formData.customerId) || 0,
  758. orderType: Number(formData.orderType) || 0,
  759. totalAmount: Number(formData.totalAmount) || 0,
  760. totalQuantity: Number(formData.totalQuantity) || 0,
  761. addressId: Number(formData.addressId) || 0,
  762. status: Number(formData.status) || 0,
  763. pcBladeOrderItemList
  764. }
  765. // 新增模式下,移除orderCode字段
  766. if (!this.isEdit && salesOrderData.orderCode) {
  767. delete salesOrderData.orderCode
  768. }
  769. return salesOrderData
  770. },
  771. /**
  772. * 验证表单数据
  773. * @description 使用AvueJS表单的验证功能验证所有字段
  774. * @returns {Promise<boolean>} 验证结果,true表示验证通过,false表示验证失败
  775. * @public
  776. * @this {import('./types').OrderFormMixin}
  777. */
  778. async validateForm() {
  779. if (!this.$refs.orderForm) {
  780. return false
  781. }
  782. try {
  783. // 使用更简洁的Promise包装器函数
  784. const isValid = await this.validateFormFields()
  785. if (!isValid) {
  786. this.$message.warning('请检查表单填写是否正确')
  787. }
  788. return isValid
  789. } catch (error) {
  790. this.$message.warning('请检查表单填写是否正确')
  791. return false
  792. }
  793. },
  794. /**
  795. * 验证表单字段
  796. * @description 验证AvueJS表单的所有字段,确保数据有效性
  797. * @returns {Promise<boolean>} 验证结果
  798. * @private
  799. * @this {import('./types').OrderFormMixin}
  800. */
  801. async validateFormFields() {
  802. return new Promise((resolve) => {
  803. this.$refs?.orderForm?.validate((valid) => {
  804. resolve(Boolean(valid))
  805. })
  806. })
  807. },
  808. /**
  809. * 准备提交数据
  810. * @description 复制表单数据并进行清理和格式化处理
  811. * @returns {OrderFormModel} 准备好的提交数据
  812. * @private
  813. * @this {import('./types').OrderFormMixin}
  814. */
  815. prepareSubmitData() {
  816. const submitData = { ...this.formData }
  817. // 清理和格式化数据
  818. return this.cleanAndFormatSubmitData(submitData)
  819. },
  820. /**
  821. * 清理和格式化提交数据
  822. * @description 移除空值字段并确保数据类型正确,使用精确的数字验证和格式化
  823. * @param {OrderFormModel} data - 原始表单数据
  824. * @returns {OrderFormModel} 清理后的数据对象
  825. * @private
  826. * @this {import('./types').OrderFormMixin}
  827. */
  828. cleanAndFormatSubmitData(data) {
  829. const cleanedData = {}
  830. Object.keys(data).forEach(key => {
  831. const value = data[key]
  832. // 新增模式下,移除orderCode字段
  833. if (!this.isEdit && key === 'orderCode') {
  834. return
  835. }
  836. // 跳过null、undefined和空字符串,但保留备注字段
  837. if (value === null || value === undefined || (value === '' && key !== 'remark')) {
  838. return
  839. }
  840. // 使用精确的数字验证和格式化
  841. if (key === 'totalAmount') {
  842. const validation = validateNumber(value)
  843. cleanedData[key] = validation.isValid ? preciseRound(validation.value, 2) : 0
  844. } else if (key === 'totalQuantity') {
  845. const validation = validateNumber(value)
  846. cleanedData[key] = validation.isValid ? Math.round(validation.value) : 0
  847. } else if (['orderType', 'status'].includes(key)) {
  848. cleanedData[key] = Number(value) || 0
  849. } else {
  850. cleanedData[key] = value
  851. }
  852. })
  853. return cleanedData
  854. },
  855. /**
  856. * 批量保存物料明细数据
  857. * @description 编辑模式下,只保存通过导入获取的物料数据,使用新增接口
  858. * @returns {Promise<void>}
  859. * @throws {Error} 当物料明细保存失败时抛出异常
  860. * @private
  861. * @this {import('./types').OrderFormMixin}
  862. */
  863. async saveMaterialDetails() {
  864. if (!this.materialDetails || this.materialDetails.length === 0) {
  865. return
  866. }
  867. // 过滤出通过导入获取的物料数据
  868. const importedMaterials = this.materialDetails.filter(material =>
  869. material.dataSource === MaterialDetailDataSource.IMPORTED
  870. )
  871. if (importedMaterials.length === 0) {
  872. return
  873. }
  874. const { add: addOrderItem } = await import('@/api/order/order-item')
  875. const savePromises = []
  876. const failedItems = []
  877. // 准备所有导入物料明细的保存请求
  878. for (let i = 0; i < importedMaterials.length; i++) {
  879. const material = importedMaterials[i]
  880. const materialData = this.prepareMaterialItemData(material)
  881. const savePromise = addOrderItem(materialData)
  882. .catch(error => {
  883. failedItems.push({ index: i + 1, itemCode: material.itemCode, error })
  884. return null
  885. })
  886. savePromises.push(savePromise)
  887. }
  888. // 等待所有保存操作完成
  889. await Promise.all(savePromises)
  890. // 检查是否有失败的项目
  891. if (failedItems.length > 0) {
  892. const errorMessage = `导入物料明细保存失败:${failedItems.map(item => `第${item.index}条(${item.itemCode})`).join('、')}`
  893. throw new Error(errorMessage)
  894. }
  895. },
  896. /**
  897. * 准备物料明细数据
  898. * @description 将物料明细数据转换为API接口所需的格式,用于新增物料明细
  899. * @param {MaterialDetailRecord} material - 物料明细数据
  900. * @returns {SalesOrderItemCreateForm} 格式化后的物料明细数据
  901. * @private
  902. * @this {import('./types').OrderFormMixin}
  903. */
  904. prepareMaterialItemData(material) {
  905. return {
  906. orderId: this.formData.id,
  907. orderCode: this.formData.orderCode || '',
  908. itemId: Number(material.itemId) || 0,
  909. itemCode: material.itemCode || '',
  910. itemName: material.itemName || '',
  911. specs: material.specs || material.specification || '',
  912. mainItemCategoryId: Number(material.mainItemCategoryId) || Number(material.mainCategoryId) || 0,
  913. mainItemCategoryName: material.mainItemCategoryName || material.mainCategoryName || '',
  914. warehouseId: Number(material.warehouseId) || 0,
  915. warehouseName: material.warehouseName || '',
  916. availableQuantity: Number(material.availableQuantity) || 0,
  917. orderQuantity: Number(material.orderQuantity) || 0,
  918. confirmQuantity: Number(material.confirmQuantity) || Number(material.orderQuantity) || 0,
  919. unitPrice: Number(material.unitPrice) || 0,
  920. taxRate: Number(material.taxRate) || 0,
  921. taxAmount: Number(material.taxAmount) || 0,
  922. totalAmount: Number(material.totalAmount) || 0,
  923. itemStatus: Number(material.itemStatus) || Number(material.status) || 0
  924. }
  925. },
  926. /**
  927. * 处理物料删除事件
  928. * @description 从物料明细列表中删除指定的物料记录,仅允许删除可删除的物料
  929. * @param {MaterialDeleteEventData} deleteData - 删除数据对象
  930. * @param {MaterialDetailRecord} deleteData.row - 要删除的物料记录
  931. * @param {number} deleteData.index - 记录在当前页的索引
  932. * @returns {void}
  933. * @public
  934. * @this {import('./types').OrderFormMixin}
  935. */
  936. handleMaterialDelete({ row, index }) {
  937. if (!row) {
  938. this.$message.warning('删除数据无效')
  939. return
  940. }
  941. // 检查物料是否可删除
  942. if (!row.isDeletable) {
  943. this.$message.warning('该物料不允许删除')
  944. return
  945. }
  946. try {
  947. // 从物料明细列表中移除该记录
  948. const materialIndex = this.materialDetails.findIndex(item =>
  949. item.itemCode === row.itemCode &&
  950. item.dataSource === row.dataSource
  951. )
  952. if (materialIndex !== -1) {
  953. this.materialDetails.splice(materialIndex, 1)
  954. this.$message.success(`物料 "${row.itemName}" 删除成功`)
  955. } else {
  956. this.$message.warning('未找到要删除的物料记录')
  957. }
  958. } catch (error) {
  959. this.$message.error('删除物料失败,请重试')
  960. console.error('删除物料失败:', error)
  961. }
  962. },
  963. /**
  964. * 处理物料导入事件
  965. * @description 将导入的物料数据添加到物料明细列表中,格式化数字字段并标记为可删除
  966. * @param {MaterialDetailRecord[]} importedMaterials - 导入的物料数据数组
  967. * @returns {void}
  968. * @public
  969. * @this {import('./types').OrderFormMixin}
  970. */
  971. handleMaterialImport(importedMaterials) {
  972. if (!Array.isArray(importedMaterials) || importedMaterials.length === 0) {
  973. this.$message.warning('没有有效的物料数据可导入')
  974. return
  975. }
  976. try {
  977. // 为导入的物料添加数据来源标识并格式化数字字段
  978. const formattedMaterials = importedMaterials.map(material => {
  979. const formatted = {
  980. ...material,
  981. dataSource: MaterialDetailDataSource.IMPORTED,
  982. isDeletable: true
  983. }
  984. // 格式化数字字段
  985. const quantityValidation = validateNumber(formatted.orderQuantity)
  986. const priceValidation = validateNumber(formatted.unitPrice)
  987. const rateValidation = validateNumber(formatted.taxRate)
  988. const amountValidation = validateNumber(formatted.totalAmount)
  989. const taxAmountValidation = validateNumber(formatted.taxAmount)
  990. formatted.orderQuantity = quantityValidation.isValid ? Math.round(quantityValidation.value) : 1
  991. formatted.unitPrice = priceValidation.isValid ? preciseRound(priceValidation.value, 2) : 0
  992. formatted.taxRate = rateValidation.isValid ? preciseRound(rateValidation.value, 4) : 0
  993. formatted.taxAmount = taxAmountValidation.isValid ? preciseRound(taxAmountValidation.value, 2) : 0
  994. formatted.totalAmount = amountValidation.isValid ? preciseRound(amountValidation.value, 2) : 0
  995. return formatted
  996. })
  997. // 添加到物料明细列表
  998. this.materialDetails.push(...formattedMaterials)
  999. this.$message.success(`成功导入 ${importedMaterials.length} 条物料明细`)
  1000. } catch (error) {
  1001. this.$message.error('导入物料失败,请重试')
  1002. console.error('导入物料失败:', error)
  1003. }
  1004. },
  1005. /**
  1006. * 处理表单提交事件
  1007. * @description AvueJS表单提交时的回调处理
  1008. * @param {OrderFormModel} formData - 表单数据
  1009. * @param {Function} done - 完成回调函数
  1010. * @this {import('./types').OrderFormMixin}
  1011. */
  1012. handleFormSubmit(formData, done) {
  1013. this.handleSave().finally(() => {
  1014. if (typeof done === 'function') {
  1015. done()
  1016. }
  1017. })
  1018. },
  1019. /**
  1020. * 处理表单重置事件
  1021. * @description AvueJS表单重置时的回调处理
  1022. * @this {import('./types').OrderFormMixin}
  1023. */
  1024. handleFormReset() {
  1025. this.resetForm()
  1026. },
  1027. /**
  1028. * 处理物料明细数据变化
  1029. * @description 当物料明细表格数据发生变化时的回调处理,自动重新计算订单总金额和总数量
  1030. * @param {MaterialDetailRecord[]} materialDetails - 更新后的物料明细列表
  1031. * @returns {void}
  1032. * @this {import('./types').OrderFormMixin}
  1033. */
  1034. handleMaterialChange(materialDetails) {
  1035. this.materialDetails = materialDetails
  1036. // 可以在这里添加其他业务逻辑,如计算订单总金额等
  1037. this.calculateOrderTotal()
  1038. },
  1039. /**
  1040. * 处理物料明细更新事件
  1041. * @description 当物料明细表格中的数据被编辑时的回调处理,自动重新计算订单总金额和总数量
  1042. * @param {MaterialUpdateEventData} updateData - 更新数据对象
  1043. * @returns {void}
  1044. * @this {import('./types').OrderFormMixin}
  1045. */
  1046. handleMaterialUpdate({ row, index }) {
  1047. // 如果有有效的索引,更新物料明细列表中对应的记录
  1048. if (index >= 0 && index < this.materialDetails.length) {
  1049. this.$set(this.materialDetails, index, { ...row })
  1050. }
  1051. // 无论索引是否有效,都重新计算订单总金额
  1052. this.calculateOrderTotal()
  1053. },
  1054. /**
  1055. * 计算订单总金额和总数量
  1056. * @description 根据物料明细计算订单总金额、总数量、总税额并更新表单数据
  1057. * @returns {void}
  1058. * @this {import('./types').OrderFormMixin}
  1059. */
  1060. calculateOrderTotal() {
  1061. // 计算订单总金额
  1062. const totalAmount = this.materialDetails.reduce((sum, item) => {
  1063. return sum + (Number(item.totalAmount) || 0)
  1064. }, 0)
  1065. // 计算订单总数量
  1066. const totalQuantity = this.materialDetails.reduce((sum, item) => {
  1067. return sum + (Number(item.orderQuantity) || 0)
  1068. }, 0)
  1069. // 计算总税额
  1070. const totalTaxAmount = this.materialDetails.reduce((sum, item) => {
  1071. return sum + (Number(item.taxAmount) || 0)
  1072. }, 0)
  1073. // 更新表单中的总金额、总数量和税额字段
  1074. if (this.formData) {
  1075. this.$set(this.formData, 'totalAmount', Math.round(totalAmount * 100) / 100)
  1076. this.$set(this.formData, 'totalQuantity', Math.round(totalQuantity))
  1077. this.$set(this.formData, 'totalTaxAmount', Math.round(totalTaxAmount * 100) / 100)
  1078. }
  1079. },
  1080. /**
  1081. * 处理客户选择事件
  1082. * @description 当客户选择组件选择客户时的回调处理,自动填充客户编码和客户名称,并清空地址相关字段
  1083. * @param {Object} customerData - 客户数据对象
  1084. * @param {string|number} customerData.customerId - 客户ID
  1085. * @param {string} customerData.customerCode - 客户编码
  1086. * @param {string} customerData.customerName - 客户名称
  1087. * @returns {void}
  1088. * @this {import('./types').OrderFormMixin}
  1089. */
  1090. handleCustomerSelected(customerData) {
  1091. if (this.formData) {
  1092. // 更新客户相关字段
  1093. this.$set(this.formData, 'customerId', customerData.customerId)
  1094. this.$set(this.formData, 'customerCode', customerData.customerCode)
  1095. this.$set(this.formData, 'customerName', customerData.customerName)
  1096. // 清空地址相关字段
  1097. this.$set(this.formData, 'addressId', '')
  1098. this.$set(this.formData, 'receiverName', '')
  1099. this.$set(this.formData, 'receiverPhone', '')
  1100. this.$set(this.formData, 'receiverRegion', '')
  1101. this.$set(this.formData, 'receiverAddress', '')
  1102. }
  1103. },
  1104. /**
  1105. * 处理地址选择事件
  1106. * @description 当地址选择组件选择地址时的回调处理,自动填充收货人相关信息
  1107. * @param {Object} addressData - 地址数据对象
  1108. * @param {string|number} addressData.addressId - 地址ID
  1109. * @param {string} addressData.receiverName - 收货人姓名
  1110. * @param {string} addressData.receiverPhone - 收货人电话
  1111. * @param {string} addressData.regionCode - 地区编码
  1112. * @param {string} addressData.regionName - 地区名称
  1113. * @param {string} addressData.detailAddress - 详细地址
  1114. * @param {string} addressData.postalCode - 邮政编码
  1115. * @returns {void}
  1116. * @this {import('./types').OrderFormMixin}
  1117. */
  1118. handleAddressSelected(addressData) {
  1119. if (this.formData) {
  1120. // 更新地址相关字段
  1121. this.$set(this.formData, 'addressId', addressData.addressId)
  1122. this.$set(this.formData, 'receiverName', addressData.receiverName || '')
  1123. this.$set(this.formData, 'receiverPhone', addressData.receiverPhone || '')
  1124. this.$set(this.formData, 'receiverRegion', addressData.regionName || '')
  1125. this.$set(this.formData, 'receiverAddress', addressData.detailAddress || '')
  1126. }
  1127. },
  1128. /**
  1129. * 处理地址回显
  1130. * @description 在编辑模式下,根据表单中的地址信息在地址选择组件中进行回显
  1131. * @returns {void}
  1132. * @this {import('./types').OrderFormMixin}
  1133. */
  1134. handleAddressEcho() {
  1135. // 查找地址选择组件的引用
  1136. const addressSelectRefs = this.$refs.orderForm?.$refs?.addressId
  1137. const addressSelectComponent = Array.isArray(addressSelectRefs) ? addressSelectRefs[0] : addressSelectRefs
  1138. if (addressSelectComponent && typeof addressSelectComponent.setEchoValue === 'function') {
  1139. // 构建地址信息对象用于匹配
  1140. const addressInfo = {
  1141. receiverName: this.formData.receiverName,
  1142. receiverPhone: this.formData.receiverPhone,
  1143. regionName: this.formData.receiverRegion,
  1144. detailAddress: this.formData.receiverAddress
  1145. }
  1146. // 调用地址选择组件的回显方法
  1147. addressSelectComponent.setEchoValue(addressInfo)
  1148. }
  1149. }
  1150. }
  1151. }