order-form-mixin.js 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649
  1. /**
  2. * @fileoverview 订单表单混入组件
  3. * @description 提供订单表单的数据管理、验证规则和业务逻辑的混入组件,支持新增和编辑模式
  4. */
  5. // API接口导入
  6. import { add, update as updateOrderHeader, getDetail } from '@/api/order/order'
  7. import { getList as getOrderItemList } from '@/api/order/order-item'
  8. import { createSalesOrder, updateOrder, addOrderItem, updateOrderItem } from '@/api/order/sales-order'
  9. import { getCustomerInfo } from '@/api/common/index'
  10. import { getList as getAddressList } from '@/api/order/address'
  11. import { submitOrderToU9 } from '@/api/order/sales-order'
  12. // 常量和枚举导入
  13. import {
  14. ORDER_TYPES,
  15. ORDER_STATUS,
  16. ORDER_TYPE_OPTIONS,
  17. ORDER_STATUS_OPTIONS,
  18. ORDER_ITEM_STATUS
  19. } from '@/constants/order'
  20. // 本地常量定义导入
  21. import {
  22. MaterialDetailDataSource
  23. } from '@/constants/order'
  24. import { ORDER_FORM_EVENTS, CUSTOMER_SELECT_EVENTS, ADDRESS_SELECT_EVENTS, MATERIAL_DETAIL_EVENTS } from './events'
  25. import { getFormOption } from './form-option'
  26. // 数字格式化工具导入
  27. import {
  28. formatAmount,
  29. formatFloatNumber,
  30. formatIntegerNumber,
  31. formatUnitPrice,
  32. formatTaxRate,
  33. preciseMultiply,
  34. preciseDivide,
  35. preciseRound,
  36. validateNumber,
  37. NUMBER_TYPES
  38. } from './number-format-utils'
  39. /**
  40. * 类型定义导入
  41. * @description 导入所有必要的TypeScript类型定义,确保类型安全
  42. */
  43. /**
  44. * @typedef {import('./types').MaterialDetailRecord} MaterialDetailRecord
  45. * @description 物料明细记录类型
  46. */
  47. /**
  48. * @typedef {import('./types').OrderFormModel} OrderFormModel
  49. * @description 订单表单数据模型类型
  50. */
  51. /**
  52. * @typedef {import('./types').MaterialUpdateEventData} MaterialUpdateEventData
  53. * @description 物料更新事件数据类型
  54. */
  55. /**
  56. * @typedef {import('./types').OrderFormRules} OrderFormRules
  57. * @description 订单表单验证规则类型
  58. */
  59. /**
  60. * @typedef {import('./types').OrderFormMethods} OrderFormMethods
  61. * @description 订单表单方法类型
  62. */
  63. /**
  64. * @typedef {import('smallwei__avue/form').AvueFormOption} AvueFormOption
  65. * @typedef {import('smallwei__avue/form').AvueFormColumn} AvueFormColumn
  66. * @typedef {import('smallwei__avue/form').AvueFormGroup} AvueFormGroup
  67. * @typedef {import('smallwei__avue/crud').PageOption} PageOption
  68. */
  69. /**
  70. * @typedef {import('./types').MaterialDeleteEventData} MaterialDeleteEventData
  71. * @description 物料删除事件数据类型
  72. */
  73. /**
  74. * @typedef {import('./types').ApiResponse} ApiResponse
  75. * @description API响应数据类型
  76. */
  77. /**
  78. * @typedef {import('./types').ValidationRule} ValidationRule
  79. * @description 表单验证规则类型
  80. */
  81. /**
  82. * @typedef {import('@/api/types/order').SalesOrderCreateForm} SalesOrderCreateForm
  83. * @description 销售订单创建表单类型
  84. */
  85. /**
  86. * @typedef {import('@/api/types/order').SalesOrderItemCreateForm} SalesOrderItemCreateForm
  87. * @description 销售订单明细创建表单类型
  88. */
  89. /**
  90. * @typedef {import('@/constants/order').ORDER_TYPES} OrderTypeValue
  91. * @description 订单类型枚举值类型
  92. */
  93. /**
  94. * @typedef {import('@/constants/order').ORDER_STATUS} OrderStatusValue
  95. * @description 订单状态枚举值类型
  96. */
  97. /**
  98. * 订单表单混入组件
  99. * @description 提供订单表单的数据管理、验证规则和业务逻辑的混入组件
  100. * @mixin
  101. */
  102. export default {
  103. /**
  104. * 组件响应式数据
  105. * @description 定义组件的响应式数据状态
  106. * @returns {import('./types').OrderFormMixinData} 组件数据对象
  107. * @this {import('./types').OrderFormMixinComponent}
  108. */
  109. data() {
  110. return {
  111. /**
  112. * 订单表单数据模型
  113. * @description 存储订单表单的所有字段数据
  114. * @type {OrderFormModel}
  115. */
  116. formData: this.createInitialFormData(),
  117. /**
  118. * 保存操作加载状态
  119. * @description 控制保存按钮的加载状态,防止重复提交
  120. * @type {boolean}
  121. */
  122. saveLoading: false,
  123. /**
  124. * 表单加载状态
  125. * @description 控制表单整体的加载状态,用于数据获取时的UI反馈
  126. * @type {boolean}
  127. */
  128. formLoading: false,
  129. /**
  130. * 物料明细列表
  131. * @description 存储当前订单的物料明细数据,包含数据来源和删除权限标识
  132. * @type {MaterialDetailRecord[]}
  133. */
  134. materialDetails: [],
  135. /**
  136. * 远程明细原始快照映射
  137. * @description 记录从服务器加载的远程物料明细的原始值,用于草稿状态下的变更对比
  138. * @type {Record<string, {orderQuantity:number, confirmQuantity:number, unitPrice:number, taxRate:number}>}
  139. */
  140. originalRemoteDetailsById: {},
  141. /**
  142. * 订单类型选项列表
  143. * @description 订单类型下拉选择器的选项数据
  144. * @type {typeof ORDER_TYPE_OPTIONS}
  145. */
  146. orderTypeOptions: ORDER_TYPE_OPTIONS,
  147. /**
  148. * 订单状态选项列表
  149. * @description 订单状态下拉选择器的选项数据
  150. * @type {typeof ORDER_STATUS_OPTIONS}
  151. */
  152. orderStatusOptions: ORDER_STATUS_OPTIONS,
  153. // 事件常量,用于模板中的动态事件绑定
  154. CUSTOMER_SELECT_EVENTS,
  155. ADDRESS_SELECT_EVENTS,
  156. MATERIAL_DETAIL_EVENTS
  157. }
  158. },
  159. /**
  160. * 计算属性
  161. * @description 组件的响应式计算属性
  162. */
  163. computed: {
  164. /**
  165. * 订单表单验证规则
  166. * @description 定义订单表单各字段的验证规则,支持必填、长度、格式等验证
  167. * @returns {OrderFormRules} 完整的表单验证规则对象
  168. * @this {import('./types').OrderFormMixinComponent}
  169. */
  170. formRules() {
  171. return {
  172. orderCode: [
  173. {
  174. required: true,
  175. message: '请输入订单编码',
  176. trigger: 'blur'
  177. },
  178. {
  179. min: 3,
  180. max: 50,
  181. message: '订单编码长度在 3 到 50 个字符',
  182. trigger: 'blur'
  183. }
  184. ],
  185. orgCode: [
  186. {
  187. required: true,
  188. message: '请输入组织编码',
  189. trigger: 'blur'
  190. },
  191. {
  192. min: 2,
  193. max: 20,
  194. message: '组织编码长度在 2 到 20 个字符',
  195. trigger: 'blur'
  196. }
  197. ],
  198. orgName: [
  199. {
  200. required: true,
  201. message: '请输入组织名称',
  202. trigger: 'blur'
  203. },
  204. {
  205. min: 2,
  206. max: 100,
  207. message: '组织名称长度在 2 到 100 个字符',
  208. trigger: 'blur'
  209. }
  210. ],
  211. customerCode: [
  212. {
  213. required: true,
  214. message: '请输入客户编码',
  215. trigger: 'blur'
  216. },
  217. {
  218. min: 3,
  219. max: 50,
  220. message: '客户编码长度在 3 到 50 个字符',
  221. trigger: 'blur'
  222. }
  223. ],
  224. customerName: [
  225. {
  226. required: true,
  227. message: '请输入客户名称',
  228. trigger: 'blur'
  229. },
  230. {
  231. min: 2,
  232. max: 100,
  233. message: '客户名称长度在 2 到 100 个字符',
  234. trigger: 'blur'
  235. }
  236. ],
  237. orderType: [
  238. {
  239. required: true,
  240. message: '请选择订单类型',
  241. trigger: 'change'
  242. }
  243. ],
  244. totalAmount: [
  245. {
  246. required: true,
  247. message: '请输入订单总金额',
  248. trigger: 'blur'
  249. },
  250. {
  251. type: 'number',
  252. min: 0.01,
  253. message: '订单总金额必须大于0',
  254. trigger: 'blur'
  255. }
  256. ],
  257. totalQuantity: [
  258. {
  259. required: true,
  260. message: '请输入订单总数量',
  261. trigger: 'blur'
  262. },
  263. {
  264. type: 'number',
  265. min: 0.0001,
  266. message: '订单总数量必须大于0',
  267. trigger: 'blur'
  268. }
  269. ],
  270. receiverName: [
  271. {
  272. required: true,
  273. message: '请输入收货人姓名',
  274. trigger: 'blur'
  275. },
  276. {
  277. min: 2,
  278. max: 50,
  279. message: '收货人姓名长度在 2 到 50 个字符',
  280. trigger: 'blur'
  281. }
  282. ],
  283. receiverPhone: [
  284. {
  285. required: true,
  286. message: '请输入收货人电话',
  287. trigger: 'blur'
  288. },
  289. {
  290. /**
  291. * 手机号码格式验证器
  292. * @description 验证手机号码格式是否正确,支持1开头的11位数字
  293. * @param {Object} rule - 验证规则对象
  294. * @param {string} value - 待验证的值
  295. * @param {Function} callback - 验证回调函数
  296. * @returns {void}
  297. */
  298. validator: (rule, value, callback) => {
  299. if (!value || typeof value !== 'string') {
  300. callback()
  301. return
  302. }
  303. // 手机号码正则表达式:1开头,第二位为3-9,总共11位数字
  304. const phoneRegex = /^1[3-9]\d{9}$/
  305. if (!phoneRegex.test(value.trim())) {
  306. callback(new Error('请输入正确的手机号码格式(1开头的11位数字)'))
  307. } else {
  308. callback()
  309. }
  310. },
  311. trigger: 'blur'
  312. }
  313. ],
  314. receiverRegion: [
  315. {
  316. required: true,
  317. message: '请输入收货地区',
  318. trigger: 'blur'
  319. },
  320. {
  321. min: 2,
  322. max: 100,
  323. message: '收货地区长度在 2 到 100 个字符',
  324. trigger: 'blur'
  325. }
  326. ],
  327. receiverAddress: [
  328. {
  329. required: true,
  330. message: '请输入收货地址',
  331. trigger: 'blur'
  332. },
  333. {
  334. min: 5,
  335. max: 500,
  336. message: '收货地址长度在 5 到 500 个字符',
  337. trigger: 'blur'
  338. }
  339. ],
  340. remark: [
  341. {
  342. max: 1000,
  343. message: '备注信息不能超过 1000 个字符',
  344. trigger: 'blur'
  345. }
  346. ]
  347. }
  348. },
  349. /**
  350. * 表单标题
  351. * @description 根据编辑模式动态显示表单标题
  352. * @returns {string} 表单标题文本
  353. * @this {import('./types').OrderFormMixin}
  354. */
  355. formTitle() {
  356. return this.isEdit ? '编辑订单' : '新增订单'
  357. },
  358. /**
  359. * 表单配置选项
  360. * @description 根据编辑模式动态获取表单配置
  361. * @returns {AvueFormOption} 表单配置对象
  362. * @this {import('./types').OrderFormMixin}
  363. */
  364. formOption() {
  365. return getFormOption(this.isEdit)
  366. },
  367. /**
  368. * 物料明细表格配置
  369. * @description 获取物料明细表格的配置选项
  370. * @returns {Object} 表格配置对象
  371. * @this {import('./types').OrderFormMixin}
  372. */
  373. materialDetailTableOption() {
  374. return {
  375. border: true,
  376. stripe: true,
  377. menuAlign: 'center',
  378. align: 'center',
  379. addBtn: false,
  380. editBtn: false,
  381. delBtn: true,
  382. viewBtn: false,
  383. column: [
  384. {
  385. label: '物料编码',
  386. prop: 'itemCode',
  387. width: 120
  388. },
  389. {
  390. label: '物料名称',
  391. prop: 'itemName',
  392. width: 150
  393. },
  394. {
  395. label: '规格型号',
  396. prop: 'specs',
  397. width: 120
  398. },
  399. {
  400. label: '订单数量',
  401. prop: 'orderQuantity',
  402. width: 100
  403. },
  404. {
  405. label: '单价',
  406. prop: 'unitPrice',
  407. width: 100
  408. },
  409. {
  410. label: '总金额',
  411. prop: 'totalAmount',
  412. width: 120
  413. }
  414. ]
  415. }
  416. },
  417. /**
  418. * 是否为草稿状态
  419. * @description 根据订单状态判断是否为草稿
  420. * @returns {boolean}
  421. */
  422. isDraft() {
  423. const status = Number(this.formData && this.formData.status)
  424. return status === ORDER_STATUS.DRAFT
  425. }
  426. },
  427. /**
  428. * 组件方法
  429. * @description 组件的业务逻辑方法集合
  430. */
  431. methods: {
  432. /**
  433. * 创建初始表单数据
  434. * @description 创建订单表单的初始数据结构
  435. * @returns {OrderFormModel} 初始化的表单数据对象
  436. * @private
  437. * @this {import('./types').OrderFormMixinComponent}
  438. */
  439. createInitialFormData() {
  440. return {
  441. id: undefined,
  442. orderCode: '',
  443. orgId: undefined,
  444. orgCode: '',
  445. orgName: '',
  446. customerId: null,
  447. customerCode: '',
  448. customerName: '',
  449. orderType: ORDER_TYPES.NORMAL,
  450. orderQuantity: 0,
  451. totalAmount: null,
  452. totalQuantity: null,
  453. addressId: null,
  454. receiverName: '',
  455. receiverPhone: '',
  456. receiverRegion: '',
  457. receiverAddress: '',
  458. status: ORDER_STATUS.DRAFT,
  459. remark: ''
  460. }
  461. },
  462. /**
  463. * 初始化表单数据
  464. * @description 根据编辑模式初始化表单,编辑模式加载订单详情数据,新增模式重置表单为初始状态
  465. * @returns {Promise<void>}
  466. * @throws {Error} 当初始化过程中发生错误时抛出异常
  467. * @public
  468. * @this {import('./types').OrderFormMixinComponent}
  469. */
  470. async initForm() {
  471. try {
  472. if (this.isEdit && this.orderId) {
  473. // 编辑模式:加载现有订单数据
  474. await this.loadOrderDetail(this.orderId)
  475. } else {
  476. // 新增模式:重置表单为初始状态
  477. await this.resetForm()
  478. }
  479. } catch (error) {
  480. console.error('初始化表单失败:', error)
  481. const errorMessage = error.message || '初始化表单失败,请刷新页面重试'
  482. this.$message.error(errorMessage)
  483. throw error
  484. }
  485. },
  486. /**
  487. * 重置表单数据
  488. * @description 将表单数据重置为初始状态,清除所有验证错误信息,并重置物料明细列表
  489. * @returns {Promise<void>}
  490. * @throws {Error} 当重置过程中发生严重错误时抛出异常
  491. * @public
  492. * @this {import('./types').OrderFormMixinComponent}
  493. */
  494. async resetForm() {
  495. try {
  496. // 重置表单数据为初始状态
  497. this.formData = this.createInitialFormData()
  498. // 重置物料明细列表(如果存在)
  499. if (Array.isArray(this.materialDetails)) {
  500. this.materialDetails = []
  501. }
  502. // 重置保存状态
  503. this.saveLoading = false
  504. // 在新增模式下,调用getCustomerInfo接口获取客户信息
  505. if (!this.isEdit) {
  506. await this.loadCustomerInfo()
  507. }
  508. // 等待DOM更新后清除表单验证
  509. await this.$nextTick()
  510. // 清除表单验证状态
  511. if (this.$refs.orderForm && typeof this.$refs.orderForm.clearValidate === 'function') {
  512. this.$refs.orderForm.clearValidate()
  513. }
  514. } catch (error) {
  515. console.error('重置表单失败:', error)
  516. // 重置表单时发生严重错误,抛出异常
  517. throw new Error('重置表单失败,请刷新页面重试')
  518. }
  519. },
  520. /**
  521. * 加载客户信息
  522. * @description 在新增模式下调用getCustomerInfo接口获取客户信息并填充表单
  523. * @returns {Promise<void>}
  524. * @throws {Error} 当API调用失败时抛出异常
  525. * @private
  526. * @this {import('./types').OrderFormMixinComponent}
  527. */
  528. async loadCustomerInfo() {
  529. try {
  530. this.formLoading = true
  531. const response = await getCustomerInfo()
  532. if (response?.data?.success && response.data.data) {
  533. const customerData = response.data.data
  534. // 填充客户相关字段到表单(根据实际API返回的字段名称)
  535. this.$set(this.formData, 'orgId', customerData.ORG_ID)
  536. this.$set(this.formData, 'orgCode', customerData.ORG_CODE || '')
  537. this.$set(this.formData, 'orgName', customerData.ORG_NAME || '')
  538. this.$set(this.formData, 'customerId', customerData.Customer_ID)
  539. this.$set(this.formData, 'customerCode', customerData.Customer_CODE || '')
  540. this.$set(this.formData, 'customerName', customerData.Customer_NAME || '')
  541. // 根据Customer_CODE加载地址信息
  542. if (customerData.Customer_CODE) {
  543. await this.loadCustomerAddresses(customerData.Customer_CODE)
  544. }
  545. } else {
  546. const errorMsg = response?.data?.msg || '获取客户信息失败'
  547. console.warn('获取客户信息失败:', errorMsg)
  548. this.$message.warning('获取客户信息失败,请手动填写相关信息')
  549. }
  550. } catch (error) {
  551. console.error('加载客户信息API调用失败:', error)
  552. this.$message.error('网络错误,获取客户信息失败')
  553. throw error
  554. } finally {
  555. this.formLoading = false
  556. }
  557. },
  558. /**
  559. * 加载客户地址信息
  560. * @description 根据客户ID获取地址列表,如果只有一个地址则直接选中,如果多个则选中默认地址
  561. * @param {string} customerCode - 客户ID
  562. * @returns {Promise<void>}
  563. * @throws {Error} 当API调用失败时抛出异常
  564. * @private
  565. * @this {import('./types').OrderFormMixinComponent}
  566. */
  567. async loadCustomerAddresses(customerCode) {
  568. try {
  569. // 使用getList方法,传递分页参数和查询条件
  570. const response = await getAddressList(1, 100, { customerCode })
  571. if (response?.data?.success && response.data.data?.records) {
  572. const addresses = response.data.data.records
  573. if (addresses.length === 1) {
  574. // 只有一个地址,直接选中
  575. this.selectAddress(addresses[0])
  576. } else if (addresses.length > 1) {
  577. // 多个地址,选中默认地址
  578. const defaultAddress = addresses.find(addr => addr.isDefault === 1)
  579. if (defaultAddress) {
  580. this.selectAddress(defaultAddress)
  581. }
  582. }
  583. } else {
  584. console.warn('获取客户地址失败:', response?.data?.msg || '未知错误')
  585. }
  586. } catch (error) {
  587. console.error('加载客户地址API调用失败:', error)
  588. // 地址加载失败不影响主流程,只记录错误
  589. }
  590. },
  591. /**
  592. * 选择地址
  593. * @description 将选中的地址信息填充到表单中
  594. * @param {import('@/api/types/address').CustomerAddressRecord} address - 地址对象
  595. * @returns {void}
  596. * @private
  597. * @this {import('./types').OrderFormMixinComponent}
  598. */
  599. selectAddress(address) {
  600. if (address && this.formData) {
  601. this.$set(this.formData, 'addressId', address.id.toString())
  602. this.$set(this.formData, 'receiverName', address.receiverName || '')
  603. this.$set(this.formData, 'receiverPhone', address.receiverPhone || '')
  604. this.$set(this.formData, 'receiverRegion', address.regionName || '')
  605. this.$set(this.formData, 'receiverAddress', address.detailAddress || '')
  606. }
  607. },
  608. /**
  609. * 加载订单详情数据
  610. * @description 根据订单ID从服务器获取订单详情并填充到表单中,同时并行加载物料明细数据以提高性能
  611. * @param {string|number} orderId - 订单唯一标识符
  612. * @returns {Promise<void>}
  613. * @throws {Error} 当订单ID无效、API调用失败或数据格式错误时抛出异常
  614. * @public
  615. * @this {import('./types').OrderFormMixinComponent}
  616. */
  617. async loadOrderDetail(orderId) {
  618. // 参数验证
  619. if (!orderId || (typeof orderId !== 'string' && typeof orderId !== 'number')) {
  620. throw new Error('订单ID不能为空且必须是有效的字符串或数字')
  621. }
  622. try {
  623. // 并行加载订单详情和物料明细数据以提高性能
  624. const [orderResponse, materialResponse] = await Promise.all([
  625. getDetail(String(orderId)),
  626. this.loadMaterialDetails(String(orderId))
  627. ])
  628. // 验证订单详情响应数据
  629. if (!orderResponse?.data?.success) {
  630. const errorMsg = (orderResponse?.data && (orderResponse.data.message || orderResponse.data.msg)) || '获取订单详情失败'
  631. throw new Error(errorMsg)
  632. }
  633. if (!orderResponse.data.data) {
  634. throw new Error('订单数据不存在或已被删除')
  635. }
  636. const orderData = orderResponse.data.data
  637. // 安全地映射订单数据到表单,确保数据类型正确
  638. this.formData = this.mapOrderDataToForm(orderData)
  639. // 设置物料明细数据(确保是数组类型)
  640. this.materialDetails = Array.isArray(materialResponse) ? materialResponse : []
  641. // 新增:建立远程明细原始快照,用于后续对比并持久化更新
  642. this.originalRemoteDetailsById = this.buildOriginalRemoteSnapshot(this.materialDetails)
  643. console.log(`成功加载订单详情,订单编码: ${orderData.orderCode || orderId}`)
  644. } catch (error) {
  645. console.error('加载订单详情失败:', error)
  646. const errorMessage = error.message || '加载订单详情失败,请检查网络连接后重试'
  647. this.$message.error(errorMessage)
  648. throw error
  649. }
  650. },
  651. /**
  652. * 加载物料明细数据
  653. * @description 根据订单ID获取物料明细列表,并对数值字段进行格式化和验证,确保数据精确性和类型安全
  654. * @param {string|number} orderId - 订单唯一标识符
  655. * @returns {Promise<MaterialDetailRecord[]>} 格式化后的物料明细数组,数值字段已进行精度处理
  656. * @throws {Error} 当订单ID无效或API调用失败时抛出异常
  657. * @private
  658. * @this {import('./types').OrderFormMixinComponent}
  659. */
  660. async loadMaterialDetails(orderId) {
  661. // 参数验证
  662. if (!orderId || (typeof orderId !== 'string' && typeof orderId !== 'number')) {
  663. console.error('loadMaterialDetails: 订单ID无效', orderId)
  664. return []
  665. }
  666. try {
  667. const response = await getOrderItemList(1, 1000, { orderId })
  668. // 验证响应数据结构
  669. if (!response?.data?.success) {
  670. const errorMsg = (response?.data && (response.data.message || response.data.msg)) || '获取物料明细失败'
  671. throw new Error(errorMsg)
  672. }
  673. if (!response.data.data) {
  674. console.warn('物料明细数据为空')
  675. return []
  676. }
  677. const materialDetails = response.data.data.records
  678. // 确保返回的是数组类型
  679. if (!Array.isArray(materialDetails)) {
  680. console.warn('物料明细数据格式异常,返回空数组')
  681. return []
  682. }
  683. // 为远程加载的物料数据添加数据来源标识并格式化数字字段
  684. return materialDetails.map((material, index) => {
  685. try {
  686. // 验证和格式化数字字段,确保类型安全
  687. const orderQuantityValidation = validateNumber(material.orderQuantity)
  688. const unitPriceValidation = validateNumber(material.unitPrice)
  689. const taxRateValidation = validateNumber(material.taxRate)
  690. const taxAmountValidation = validateNumber(material.taxAmount)
  691. const totalAmountValidation = validateNumber(material.totalAmount)
  692. const availableQuantityValidation = validateNumber(material.availableQuantity)
  693. const confirmQuantityValidation = validateNumber(material.confirmQuantity)
  694. const detailData = {
  695. ...material,
  696. id: String(material.id || ''),
  697. itemId: String(material.itemId || ''),
  698. warehouseId: String(material.warehouseId || ''),
  699. mainCategoryId: String(material.mainItemCategoryId) || '',
  700. mainItemCategoryId: material.mainItemCategoryId ? String(material.mainItemCategoryId) : undefined,
  701. mainItemCategoryName: material.mainItemCategoryName || '',
  702. mainCategoryName: material.mainItemCategoryName || '',
  703. createTime: material.createTime || new Date().toISOString(),
  704. updateTime: material.updateTime || new Date().toISOString(),
  705. dataSource: MaterialDetailDataSource.REMOTE,
  706. isDeletable: false, // 远程加载的数据不可删除
  707. // 格式化数字字段,确保精度和类型正确
  708. orderQuantity: orderQuantityValidation.isValid ? Math.round(orderQuantityValidation.value) : 0,
  709. unitPrice: unitPriceValidation.isValid ? preciseRound(unitPriceValidation.value, 2) : 0,
  710. taxRate: taxRateValidation.isValid ? preciseRound(taxRateValidation.value, 4) : 0,
  711. taxAmount: taxAmountValidation.isValid ? preciseRound(taxAmountValidation.value, 2) : 0,
  712. totalAmount: totalAmountValidation.isValid ? preciseRound(totalAmountValidation.value, 2) : 0,
  713. availableQuantity: availableQuantityValidation.isValid ? preciseRound(availableQuantityValidation.value, 0) : 0,
  714. confirmQuantity: confirmQuantityValidation.isValid ? preciseRound(confirmQuantityValidation.value, 0) : 0,
  715. // 确保必要的字段存在
  716. itemCode: material.itemCode || '',
  717. itemName: material.itemName || '',
  718. specs: material.specs || ''
  719. }
  720. return detailData;
  721. } catch (itemError) {
  722. console.error(`格式化物料明细第${index + 1}项失败:`, itemError)
  723. // 返回默认的物料明细项,确保数据完整性
  724. return {
  725. ...material,
  726. id: String(material.id || ''),
  727. mainItemCategoryId: String(material.mainItemCategoryId || ''),
  728. mainItemCategoryName: material.mainItemCategoryName || '',
  729. mainCategoryName: material.mainItemCategoryName || '',
  730. itemId: String(material.itemId || ''),
  731. warehouseId: String(material.warehouseId || ''),
  732. createTime: material.createTime || new Date().toISOString(),
  733. updateTime: material.updateTime || new Date().toISOString(),
  734. dataSource: MaterialDetailDataSource.REMOTE,
  735. isDeletable: false,
  736. availableQuantity: 0,
  737. confirmQuantity: 0,
  738. orderQuantity: 0,
  739. unitPrice: 0,
  740. taxRate: 0,
  741. taxAmount: 0,
  742. totalAmount: 0,
  743. itemCode: material.itemCode || '',
  744. itemName: material.itemName || '',
  745. specs: material.specs || ''
  746. }
  747. }
  748. })
  749. } catch (error) {
  750. console.error('加载物料明细失败:', error)
  751. this.$message.warning('加载物料明细失败,请稍后重试')
  752. return []
  753. }
  754. },
  755. // 构建远程明细原始快照
  756. buildOriginalRemoteSnapshot(materials) {
  757. const map = {}
  758. try {
  759. (materials || [])
  760. .filter(m => m && m.dataSource === MaterialDetailDataSource.REMOTE)
  761. .forEach(m => {
  762. const key = String(m.id || m.itemId || m.itemCode || '')
  763. if (!key) return
  764. map[key] = {
  765. orderQuantity: Math.round(Number(m.orderQuantity) || 0),
  766. confirmQuantity: Math.round(Number(m.confirmQuantity) || 0),
  767. unitPrice: preciseRound(Number(m.unitPrice) || 0, 2),
  768. taxRate: preciseRound(Number(m.taxRate) || 0, 4)
  769. }
  770. })
  771. } catch (e) {
  772. // eslint-disable-next-line no-console
  773. console.warn('构建原始快照失败:', e)
  774. }
  775. return map
  776. },
  777. // 判断远程明细是否发生变化(与原始快照对比)
  778. hasRemoteMaterialChanged(currentRow) {
  779. if (!currentRow) return false
  780. const key = String(currentRow.id || currentRow.itemId || currentRow.itemCode || '')
  781. if (!key) return false
  782. const original = this.originalRemoteDetailsById && this.originalRemoteDetailsById[key]
  783. if (!original) return false
  784. const curr = {
  785. orderQuantity: Math.round(Number(currentRow.orderQuantity) || 0),
  786. confirmQuantity: Math.round(Number(currentRow.confirmQuantity) || 0),
  787. unitPrice: preciseRound(Number(currentRow.unitPrice) || 0, 2),
  788. taxRate: preciseRound(Number(currentRow.taxRate) || 0, 4)
  789. }
  790. return (
  791. curr.orderQuantity !== original.orderQuantity ||
  792. curr.confirmQuantity !== original.confirmQuantity ||
  793. curr.unitPrice !== original.unitPrice ||
  794. curr.taxRate !== original.taxRate
  795. )
  796. },
  797. /**
  798. * 映射订单数据到表单格式
  799. * @description 将API返回的订单数据安全地映射为表单数据格式,并格式化数字字段
  800. * @param {import('@/api/types/order').OrderRecord} orderData - 从API获取的原始订单数据
  801. * @returns {OrderFormModel} 格式化后的表单数据
  802. * @private
  803. * @this {import('./types').OrderFormMixinComponent}
  804. */
  805. mapOrderDataToForm(orderData) {
  806. // 验证和格式化数字字段
  807. const totalAmountValidation = validateNumber(orderData.totalAmount)
  808. const totalQuantityValidation = validateNumber(orderData.totalQuantity)
  809. const orderQuantityValidation = validateNumber(orderData.orderQuantity)
  810. return {
  811. id: orderData.id ? String(orderData.id) : undefined,
  812. orderCode: String(orderData.orderCode || ''),
  813. orgId: orderData.orgId ? String(orderData.orgId) : undefined,
  814. orgCode: String(orderData.orgCode || ''),
  815. orgName: String(orderData.orgName || ''),
  816. customerId: orderData.customerId ? String(orderData.customerId) : null,
  817. customerCode: String(orderData.customerCode || ''),
  818. customerName: String(orderData.customerName || ''),
  819. orderType: Number(orderData.orderType) || ORDER_TYPES.NORMAL,
  820. orderQuantity: orderQuantityValidation.isValid ? parseInt(orderQuantityValidation.value.toString()) : 0,
  821. totalAmount: totalAmountValidation.isValid ? preciseRound(totalAmountValidation.value, 2) : null,
  822. totalQuantity: totalQuantityValidation.isValid ? preciseRound(totalQuantityValidation.value, 4) : null,
  823. addressId: orderData.addressId ? String(orderData.addressId) : '',
  824. receiverName: String(orderData.receiverName || ''),
  825. receiverPhone: String(orderData.receiverPhone || ''),
  826. receiverRegion: String(orderData.receiverRegion || ''),
  827. receiverAddress: String(orderData.receiverAddress || ''),
  828. status: Number(orderData.status) || ORDER_STATUS.DRAFT,
  829. remark: String(orderData.remark || '')
  830. }
  831. },
  832. /**
  833. * 处理返回列表操作
  834. * @description 触发返回列表事件,通知父组件关闭表单
  835. * @returns {void}
  836. * @public
  837. * @emits back 返回列表事件
  838. * @this {import('./types').OrderFormMixinComponent}
  839. */
  840. handleBack() {
  841. /**
  842. * 返回列表事件
  843. * @event back
  844. * @description 用户点击返回按钮时触发
  845. */
  846. this.$emit(ORDER_FORM_EVENTS.BACK)
  847. },
  848. /**
  849. * 处理表单保存操作
  850. * @description 验证表单数据并提交到服务器,支持新增和编辑模式
  851. * 编辑模式下使用updateOrder方法一次性保存订单和物料明细数据
  852. * @returns {Promise<void>}
  853. * @throws {Error} 当表单验证失败或API调用失败时抛出异常
  854. * @public
  855. * @emits save-success 保存成功事件
  856. * @this {import('./types').OrderFormMixinComponent}
  857. */
  858. async handleSave() {
  859. if (this.saveLoading) {
  860. return // 防止重复提交
  861. }
  862. try {
  863. // 表单验证
  864. const isValid = await this.validateForm()
  865. if (!isValid) {
  866. return
  867. }
  868. // 库存校验:订单数量不得超过可用数量(可用数量=storeInventory 映射而来)
  869. const exceededItems = (this.materialDetails || []).filter(it => Number(it.orderQuantity || 0) > Number(it.availableQuantity || 0))
  870. if (exceededItems.length > 0) {
  871. const detailText = exceededItems
  872. .slice(0, 5)
  873. .map(it => `${it.itemName || it.itemCode || '物料'}:订单数量 ${Number(it.orderQuantity || 0)} > 可用数量 ${Number(it.availableQuantity || 0)}`)
  874. .join('\n')
  875. await this.$alert(
  876. `库存不足,以下物料订单数量超过可用数量:\n${detailText}${exceededItems.length > 5 ? '\n...' : ''}`,
  877. '库存不足',
  878. { customClass: 'order-stock-alert' }
  879. )
  880. return
  881. }
  882. this.saveLoading = true
  883. // 准备提交数据
  884. const submitData = this.prepareSubmitData()
  885. // 调用相应的API
  886. const response = await this.submitOrderData(submitData)
  887. // 显示成功提示
  888. const successMessage = this.isEdit ? '订单更新成功' : '订单创建成功'
  889. this.$message.success(successMessage)
  890. /**
  891. * 保存成功事件
  892. * @event typeof ORDER_FORM_EVENTS.SAVE_SUCCESS
  893. * @param {Object} data - 保存后的订单数据
  894. * @description 订单保存成功后触发,携带最新的订单数据
  895. */
  896. this.$emit(ORDER_FORM_EVENTS.SAVE_SUCCESS, response.data.data)
  897. // 保持在当前页:编辑模式不返回列表,仅在新增模式返回列表
  898. if (!this.isEdit) {
  899. // 新增模式:保存成功后返回列表
  900. this.handleBack()
  901. } else {
  902. // 编辑模式:留在当前页,方便继续编辑
  903. }
  904. } catch (error) {
  905. const errorMessage = this.isEdit ? '订单更新失败,请重试' : '订单创建失败,请重试'
  906. this.$message.error(errorMessage)
  907. throw error
  908. } finally {
  909. this.saveLoading = false
  910. }
  911. },
  912. /**
  913. * 判断订单是否可以提交到U9
  914. * @description 仅草稿(0)和未提交(1)状态允许提交
  915. * @param {import('./types').OrderFormModel | any} row - 订单数据对象
  916. * @returns {boolean}
  917. */
  918. canSubmitToU9(row) {
  919. if (!row) return false
  920. const status = Number(row.status)
  921. return status === ORDER_STATUS.DRAFT || status === ORDER_STATUS.SUBMITTED
  922. },
  923. /**
  924. * 提交订单到U9系统(表单页)
  925. * @description 弹出确认框,提交当前表单的订单ID到U9,成功后刷新订单详情以同步状态
  926. * @returns {Promise<void>}
  927. * @this {import('./types').OrderFormMixinComponent}
  928. */
  929. async handleSubmitToU9() {
  930. // 仅在编辑模式且存在ID时允许提交
  931. const orderId = this.formData && (this.formData.id || this.orderId)
  932. if (!this.isEdit || !orderId) return
  933. /** @type {any} */
  934. let loadingInstance = null
  935. try {
  936. await this.$confirm('确认要提交该订单到U9系统吗?', '提示', {
  937. confirmButtonText: '确定',
  938. cancelButtonText: '取消',
  939. type: 'warning'
  940. })
  941. loadingInstance = this.$loading({
  942. lock: true,
  943. text: '正在提交到U9系统...',
  944. spinner: 'el-icon-loading',
  945. background: 'rgba(0, 0, 0, 0.7)'
  946. })
  947. const response = await submitOrderToU9({ id: String(orderId) })
  948. if (response && response.data && response.data.success) {
  949. this.$message.success('订单提交成功')
  950. // 提交成功后,重新加载详情以更新状态
  951. if (orderId) {
  952. await this.loadOrderDetail(orderId)
  953. }
  954. } else {
  955. this.$message.error((response && response.data && response.data.msg) || '提交失败')
  956. }
  957. } catch (error) {
  958. if (error !== 'cancel') {
  959. // eslint-disable-next-line no-console
  960. console.error('提交订单到U9失败:', error)
  961. this.$message.error('提交失败,请稍后重试')
  962. }
  963. } finally {
  964. if (loadingInstance) loadingInstance.close()
  965. }
  966. },
  967. /**
  968. * 提交订单数据到服务器
  969. * @description 根据编辑模式调用相应的API接口,编辑模式下使用updateOrder包含物料明细,新建状态下使用createSalesOrder包含物料明细
  970. * @param {OrderFormModel} submitData - 要提交的订单数据
  971. * @returns {Promise<import("@/api/types/order").SalesOrderCreateResponse>} API响应结果
  972. * @private
  973. * @this {import('./types').OrderFormMixinComponent}
  974. */
  975. async submitOrderData(submitData) {
  976. if (this.isEdit) {
  977. // 编辑状态下:先仅更新订单基础信息(不包含物料明细),再单独处理导入新增与远程变更
  978. // 第一步:更新订单头
  979. const headerData = this.prepareSubmitData()
  980. const headerResponse = await updateOrderHeader(headerData)
  981. const headerSuccess = headerResponse && headerResponse.data && headerResponse.data.success
  982. if (!headerSuccess) {
  983. throw new Error((headerResponse && headerResponse.data && headerResponse.data.msg) || '订单基础信息更新失败')
  984. }
  985. // 第二步:仅添加通过导入方式新增的物料(不含从订单获取的物料)
  986. const importedMaterials = (this.materialDetails || []).filter(m => m.dataSource === MaterialDetailDataSource.IMPORTED)
  987. if (importedMaterials.length > 0) {
  988. const orderId = (this.formData && this.formData.id) || this.orderId
  989. const orderCode = (this.formData && this.formData.orderCode) || ''
  990. const payloads = importedMaterials.map(material => ({
  991. orderId: String(orderId),
  992. orderCode,
  993. itemId: String(material.itemId || ''),
  994. itemCode: material.itemCode || '',
  995. itemName: material.itemName || '',
  996. specs: material.specs || '',
  997. mainItemCategoryId: String(material.mainItemCategoryId || ''),
  998. mainItemCategoryName: material.mainItemCategoryName || '',
  999. warehouseId: String(material.warehouseId || ''),
  1000. warehouseName: material.warehouseName || '',
  1001. availableQuantity: Number(material.availableQuantity) || 0,
  1002. orderQuantity: Number(material.orderQuantity) || 0,
  1003. confirmQuantity: Number(material.confirmQuantity) || Number(material.orderQuantity) || 0,
  1004. unitPrice: Number(material.unitPrice) || 0,
  1005. taxRate: Number(material.taxRate) || 0,
  1006. taxAmount: Number(material.taxAmount) || 0,
  1007. totalAmount: Number(material.totalAmount) || 0,
  1008. itemStatus: material.itemStatus || ORDER_ITEM_STATUS.UNCONFIRMED
  1009. }))
  1010. const results = await Promise.all(payloads.map(p => addOrderItem(p)))
  1011. const allOk = results.every(r => r && r.data && r.data.success)
  1012. if (!allOk) {
  1013. throw new Error('部分物料添加失败')
  1014. }
  1015. }
  1016. // 第三步:草稿状态下,找出远程明细的变更并调用 updateOrderItem 持久化
  1017. if (this.isDraft) {
  1018. const orderId = (this.formData && this.formData.id) || this.orderId
  1019. const remoteChanged = (this.materialDetails || [])
  1020. .filter(m => m.dataSource === MaterialDetailDataSource.REMOTE)
  1021. .filter(m => this.hasRemoteMaterialChanged(m))
  1022. if (remoteChanged.length > 0) {
  1023. const updatePayloads = remoteChanged.map(material => ({
  1024. id: String(material.id || ''),
  1025. orderId: String(orderId || ''),
  1026. orderCode: (this.formData && this.formData.orderCode) || '',
  1027. itemId: String(material.itemId || ''),
  1028. itemCode: material.itemCode || '',
  1029. itemName: material.itemName || '',
  1030. specs: material.specs || '',
  1031. mainItemCategoryId: String(material.mainItemCategoryId || ''),
  1032. mainItemCategoryName: material.mainItemCategoryName || '',
  1033. warehouseId: String(material.warehouseId || ''),
  1034. warehouseName: material.warehouseName || '',
  1035. availableQuantity: Number(material.availableQuantity) || 0,
  1036. orderQuantity: Number(material.orderQuantity) || 0,
  1037. confirmQuantity: Number(material.confirmQuantity) || Number(material.orderQuantity) || 0,
  1038. unitPrice: Number(material.unitPrice) || 0,
  1039. taxRate: Number(material.taxRate) || 0,
  1040. taxAmount: Number(material.taxAmount) || 0,
  1041. totalAmount: Number(material.totalAmount) || 0,
  1042. itemStatus: material.itemStatus || ORDER_ITEM_STATUS.UNCONFIRMED
  1043. }))
  1044. const updateResults = await Promise.all(updatePayloads.map(p => updateOrderItem(p)))
  1045. const updatesOk = updateResults.every(r => r && r.data && r.data.success)
  1046. if (!updatesOk) {
  1047. throw new Error('部分物料更新失败')
  1048. }
  1049. // 更新成功后,刷新原始快照,避免后续重复提交
  1050. this.originalRemoteDetailsById = this.buildOriginalRemoteSnapshot(this.materialDetails)
  1051. }
  1052. }
  1053. // 返回头部更新的响应
  1054. return headerResponse
  1055. } else {
  1056. // 新建状态下使用createSalesOrder接口,包含物料明细数据
  1057. const salesOrderData = this.prepareSalesOrderData(submitData)
  1058. return await createSalesOrder(salesOrderData)
  1059. }
  1060. },
  1061. /**
  1062. * 准备销售订单创建数据
  1063. * @description 将表单数据和物料明细数据组合为createSalesOrder接口所需的格式
  1064. * @param {OrderFormModel} formData - 表单数据
  1065. * @returns {SalesOrderCreateForm} 销售订单创建数据
  1066. * @private
  1067. * @this {import('./types').OrderFormMixinComponent}
  1068. */
  1069. prepareSalesOrderData(formData) {
  1070. // 转换物料明细数据为API所需格式
  1071. const pcBladeOrderItemList = this.materialDetails.map(material => ({
  1072. itemId: String(material.itemId || ''),
  1073. itemCode: material.itemCode || '',
  1074. itemName: material.itemName || '',
  1075. specs: material.specs || '',
  1076. mainItemCategoryId: String(material.mainItemCategoryId || ''),
  1077. mainItemCategoryName: material.mainItemCategoryName || '',
  1078. warehouseId: String(material.warehouseId || ''),
  1079. warehouseName: material.warehouseName || '',
  1080. availableQuantity: Number(material.availableQuantity) || 0,
  1081. orderQuantity: Number(material.orderQuantity) || 0,
  1082. confirmQuantity: Number(material.confirmQuantity) || Number(material.orderQuantity) || 0,
  1083. unitPrice: Number(material.unitPrice) || 0,
  1084. taxRate: Number(material.taxRate) || 0,
  1085. taxAmount: Number(material.taxAmount) || 0,
  1086. totalAmount: Number(material.totalAmount) || 0,
  1087. itemStatus: material.itemStatus || ORDER_ITEM_STATUS.UNCONFIRMED,
  1088. }))
  1089. // 创建销售订单数据对象
  1090. const salesOrderData = {
  1091. ...formData,
  1092. id: formData.id ? String(formData.id) : undefined,
  1093. orgId: (formData.orgId !== null && formData.orgId !== undefined && formData.orgId !== '') ? String(formData.orgId) : '',
  1094. customerId: (formData.customerId !== null && formData.customerId !== undefined && formData.customerId !== '') ? String(formData.customerId) : '',
  1095. orderType: Number(formData.orderType) || 0,
  1096. totalAmount: Number(formData.totalAmount) || 0,
  1097. totalQuantity: Number(formData.totalQuantity) || 0,
  1098. addressId: formData.addressId ? String(formData.addressId) : '',
  1099. status: formData.status,
  1100. pcBladeOrderItemList
  1101. }
  1102. // 新增模式下,移除orderCode和id字段
  1103. if (!this.isEdit) {
  1104. const { orderCode, id, ...dataWithoutOrderCodeAndId } = salesOrderData
  1105. return dataWithoutOrderCodeAndId
  1106. }
  1107. return salesOrderData
  1108. },
  1109. /**
  1110. * 准备销售订单更新数据
  1111. * @description 将表单数据和物料明细数据组合为updateOrder接口所需的格式
  1112. * @param {OrderFormModel} formData - 表单数据
  1113. * @returns {import('@/api/types/order').SalesOrderUpdateForm} 销售订单更新数据
  1114. * @private
  1115. * @this {import('./types').OrderFormMixinComponent}
  1116. */
  1117. prepareSalesOrderUpdateData(formData) {
  1118. // 转换所有物料明细数据为API所需格式
  1119. const pcBladeOrderItemList = this.materialDetails.map(material => ({
  1120. id: material.id ? material.id : undefined, // 保持为字符串类型
  1121. orderId: formData.id || '', // 保持为字符串类型,避免大整数精度丢失
  1122. itemId: String(material.itemId || ''),
  1123. itemCode: material.itemCode || '',
  1124. itemName: material.itemName || '',
  1125. specs: material.specs || '',
  1126. mainItemCategoryId: String(material.mainItemCategoryId || ''),
  1127. mainItemCategoryName: material.mainItemCategoryName || '',
  1128. warehouseId: String(material.warehouseId || ''),
  1129. warehouseName: material.warehouseName || '',
  1130. availableQuantity: Number(material.availableQuantity) || 0,
  1131. orderQuantity: Number(material.orderQuantity) || 0,
  1132. confirmQuantity: Number(material.confirmQuantity) || Number(material.orderQuantity) || 0,
  1133. unitPrice: Number(material.unitPrice) || 0,
  1134. taxRate: Number(material.taxRate) || 0,
  1135. taxAmount: Number(material.taxAmount) || 0,
  1136. totalAmount: Number(material.totalAmount) || 0,
  1137. itemStatus: material.itemStatus || ORDER_ITEM_STATUS.UNCONFIRMED,
  1138. }))
  1139. // 创建销售订单更新数据对象
  1140. const salesOrderUpdateData = {
  1141. id: formData.id || '', // 保持为字符串类型,避免大整数精度丢失
  1142. orderCode: formData.orderCode || '',
  1143. orgId: (formData.orgId !== null && formData.orgId !== undefined && formData.orgId !== '') ? String(formData.orgId) : '',
  1144. orgCode: formData.orgCode || '',
  1145. orgName: formData.orgName || '',
  1146. customerId: (formData.customerId !== null && formData.customerId !== undefined && formData.customerId !== '') ? String(formData.customerId) : '',
  1147. customerCode: formData.customerCode || '',
  1148. customerName: formData.customerName || '',
  1149. orderType: Number(formData.orderType) || 0,
  1150. orderQuantity: Number(formData.orderQuantity) || 0,
  1151. totalAmount: Number(formData.totalAmount) || 0,
  1152. totalQuantity: Number(formData.totalQuantity) || 0,
  1153. addressId: formData.addressId ? String(formData.addressId) : '',
  1154. receiverName: formData.receiverName || '',
  1155. receiverPhone: formData.receiverPhone || '',
  1156. receiverRegion: formData.receiverRegion || '',
  1157. receiverAddress: formData.receiverAddress || '',
  1158. status: formData.status,
  1159. remark: formData.remark || '',
  1160. pcBladeOrderItemList
  1161. }
  1162. return salesOrderUpdateData
  1163. },
  1164. /**
  1165. * 验证表单数据
  1166. * @description 使用AvueJS表单的验证功能验证所有字段
  1167. * @returns {Promise<boolean>} 验证结果,true表示验证通过,false表示验证失败
  1168. * @public
  1169. * @this {import('./types').OrderFormMixinComponent}
  1170. */
  1171. async validateForm() {
  1172. if (!this.$refs.orderForm) {
  1173. return false
  1174. }
  1175. try {
  1176. // 使用更简洁的Promise包装器函数
  1177. const isValid = await this.validateFormFields()
  1178. if (!isValid) {
  1179. this.$message.warning('请检查表单填写是否正确')
  1180. }
  1181. return isValid
  1182. } catch (error) {
  1183. this.$message.warning('请检查表单填写是否正确')
  1184. return false
  1185. }
  1186. },
  1187. /**
  1188. * 验证表单字段
  1189. * @description 验证AvueJS表单的所有字段,确保数据有效性
  1190. * @returns {Promise<boolean>} 验证结果
  1191. * @private
  1192. * @this {import('./types').OrderFormMixinComponent}
  1193. */
  1194. async validateFormFields() {
  1195. return new Promise((resolve) => {
  1196. this.$refs?.orderForm?.validate(/** @param {boolean} valid */ (valid) => {
  1197. resolve(Boolean(valid))
  1198. })
  1199. })
  1200. },
  1201. /**
  1202. * 准备提交数据
  1203. * @description 复制表单数据并进行清理和格式化处理
  1204. * @returns {OrderFormModel} 准备好的提交数据
  1205. * @private
  1206. * @this {import('./types').OrderFormMixinComponent}
  1207. */
  1208. prepareSubmitData() {
  1209. const submitData = { ...this.formData }
  1210. // 清理和格式化数据
  1211. return this.cleanAndFormatSubmitData(submitData)
  1212. },
  1213. /**
  1214. * 清理和格式化提交数据
  1215. * @description 移除空值字段并确保数据类型正确,使用精确的数字验证和格式化
  1216. * @param {OrderFormModel} data - 原始表单数据
  1217. * @returns {OrderFormModel} 清理后的数据对象
  1218. * @private
  1219. * @this {import('./types').OrderFormMixinComponent}
  1220. */
  1221. cleanAndFormatSubmitData(data) {
  1222. /** @type {Record<string, any>} */
  1223. const cleanedData = {}
  1224. Object.keys(data).forEach(key => {
  1225. const value = /** @type {Record<string, any>} */(data)[key]
  1226. // 新增模式下,移除orderCode和id字段
  1227. if (!this.isEdit && (key === 'orderCode' || key === 'id')) {
  1228. return
  1229. }
  1230. // 跳过null、undefined和空字符串,但保留备注字段
  1231. if (value === null || value === undefined || (value === '' && key !== 'remark')) {
  1232. return
  1233. }
  1234. // 使用精确的数字验证和格式化
  1235. if (key === 'totalAmount') {
  1236. const validation = validateNumber(value)
  1237. cleanedData[key] = validation.isValid ? preciseRound(validation.value, 2) : 0
  1238. } else if (key === 'totalQuantity') {
  1239. const validation = validateNumber(value)
  1240. cleanedData[key] = validation.isValid ? Math.round(validation.value) : 0
  1241. } else if (['orderType', 'status'].includes(key)) {
  1242. cleanedData[key] = Number(value) || 0
  1243. } else if (key === 'id' && this.isEdit) {
  1244. // 编辑模式下保持id字段为字符串类型,避免大整数精度丢失
  1245. cleanedData[key] = String(value)
  1246. } else if (['orgId', 'customerId', 'addressId'].includes(key)) {
  1247. // 统一将所有ID类型字段以字符串传输,避免精度丢失
  1248. cleanedData[key] = String(value)
  1249. } else {
  1250. cleanedData[key] = value
  1251. }
  1252. })
  1253. return /** @type {any} */ (cleanedData)
  1254. },
  1255. /**
  1256. * 处理物料删除事件
  1257. * @description 从物料明细列表中删除指定的物料记录,仅允许删除可删除的物料
  1258. * @param {import('./types').MaterialDetailRecord} row - 要删除的物料记录
  1259. * @returns {void}
  1260. * @public
  1261. * @this {import('./types').OrderFormMixinComponent}
  1262. */
  1263. handleMaterialDelete(/** @type {{ row: import('./types').MaterialDetailRecord, index: number }} */ { row, index }) {
  1264. if (!row) {
  1265. this.$message.warning('删除数据无效')
  1266. return
  1267. }
  1268. // 检查物料是否可删除
  1269. if (!row.isDeletable) {
  1270. this.$message.warning('该物料不允许删除')
  1271. return
  1272. }
  1273. try {
  1274. // 从物料明细列表中移除该记录
  1275. const materialIndex = this.materialDetails.findIndex(item =>
  1276. item.itemCode === row.itemCode &&
  1277. item.dataSource === row.dataSource
  1278. )
  1279. if (materialIndex !== -1) {
  1280. this.materialDetails.splice(materialIndex, 1)
  1281. this.$message.success(`物料 "${row.itemName}" 删除成功`)
  1282. } else {
  1283. this.$message.warning('未找到要删除的物料记录')
  1284. }
  1285. } catch (error) {
  1286. this.$message.error('删除物料失败,请重试')
  1287. console.error('删除物料失败:', error)
  1288. }
  1289. },
  1290. /**
  1291. * 处理物料导入事件
  1292. * @description 将导入的物料数据添加到物料明细列表中,格式化数字字段并标记为可删除
  1293. * @param {MaterialDetailRecord[]} importedMaterials - 导入的物料数据数组
  1294. * @returns {void}
  1295. * @public
  1296. * @this {import('./types').OrderFormMixinComponent}
  1297. */
  1298. handleMaterialImport(importedMaterials) {
  1299. if (!Array.isArray(importedMaterials) || importedMaterials.length === 0) {
  1300. this.$message.warning('没有有效的物料数据可导入')
  1301. return
  1302. }
  1303. try {
  1304. // 为导入的物料添加数据来源标识并格式化数字字段
  1305. const formattedMaterials = importedMaterials.map(material => {
  1306. const formatted = {
  1307. ...material,
  1308. dataSource: MaterialDetailDataSource.IMPORTED,
  1309. isDeletable: true
  1310. }
  1311. // 格式化数字字段
  1312. const quantityValidation = validateNumber(formatted.orderQuantity)
  1313. const priceValidation = validateNumber(formatted.unitPrice)
  1314. const rateValidation = validateNumber(formatted.taxRate)
  1315. const amountValidation = validateNumber(formatted.totalAmount)
  1316. const taxAmountValidation = validateNumber(formatted.taxAmount)
  1317. formatted.orderQuantity = quantityValidation.isValid ? Math.round(quantityValidation.value) : 1
  1318. formatted.unitPrice = priceValidation.isValid ? preciseRound(priceValidation.value, 2) : 0
  1319. formatted.taxRate = rateValidation.isValid && rateValidation.value > 0 ? preciseRound(rateValidation.value, 4) : 13
  1320. // Calculate total amount
  1321. const totalAmount = preciseMultiply(formatted.orderQuantity, formatted.unitPrice)
  1322. formatted.totalAmount = preciseRound(totalAmount, 2)
  1323. // Calculate tax amount
  1324. const taxAmount = preciseDivide(preciseMultiply(totalAmount, formatted.taxRate), 100)
  1325. formatted.taxAmount = preciseRound(taxAmount, 2)
  1326. return formatted
  1327. })
  1328. // 添加到物料明细列表
  1329. this.materialDetails.push(...formattedMaterials)
  1330. this.$message.success(`成功导入 ${importedMaterials.length} 条物料明细`)
  1331. } catch (error) {
  1332. this.$message.error('导入物料失败,请重试')
  1333. console.error('导入物料失败:', error)
  1334. }
  1335. },
  1336. /**
  1337. * 处理表单提交事件
  1338. * @description AvueJS表单提交时的回调处理
  1339. * @param {OrderFormModel} formData - 表单数据
  1340. * @param {Function} done - 完成回调函数
  1341. * @this {import('./types').OrderFormMixinComponent}
  1342. */
  1343. handleFormSubmit(/** @type {import('./types').OrderFormModel} */ formData, /** @type {Function} */ done) {
  1344. this.handleSave().finally(() => {
  1345. if (typeof done === 'function') {
  1346. done()
  1347. }
  1348. })
  1349. },
  1350. /**
  1351. * 处理表单重置事件
  1352. * @description AvueJS表单重置时的回调处理
  1353. * @this {import('./types').OrderFormMixinComponent}
  1354. */
  1355. handleFormReset() {
  1356. this.resetForm()
  1357. },
  1358. /**
  1359. * 处理物料明细数据变化
  1360. * @description 当物料明细表格数据发生变化时的回调处理,自动重新计算订单总金额和总数量
  1361. * @param {MaterialDetailRecord[]} materialDetails - 更新后的物料明细列表
  1362. * @returns {void}
  1363. * @this {import('./types').OrderFormMixinComponent}
  1364. */
  1365. handleMaterialChange(materialDetails) {
  1366. if (Array.isArray(materialDetails)) {
  1367. this.materialDetails = materialDetails
  1368. }
  1369. this.calculateOrderTotal()
  1370. },
  1371. /**
  1372. * 处理物料明细更新事件
  1373. * @description 当物料明细表格中的数据被编辑时的回调处理,自动重新计算订单总金额和总数量
  1374. * @param {import('./types').MaterialUpdateEventData} eventData - 更新事件数据对象
  1375. * @returns {void}
  1376. * @this {import('./types').OrderFormMixinComponent}
  1377. */
  1378. handleMaterialUpdate(eventData) {
  1379. if (!eventData || typeof eventData !== 'object') {
  1380. console.warn('Invalid material update event data:', eventData)
  1381. return
  1382. }
  1383. const { index, row } = eventData
  1384. if (typeof index !== 'number' || index < 0) {
  1385. console.warn('Invalid material update index:', index)
  1386. return
  1387. }
  1388. if (!row || typeof row !== 'object') {
  1389. console.warn('Invalid material update data:', row)
  1390. return
  1391. }
  1392. // 验证索引是否在有效范围内
  1393. if (index >= this.materialDetails.length) {
  1394. console.warn('Material update index out of range:', index, 'length:', this.materialDetails.length)
  1395. return
  1396. }
  1397. // 更新物料明细数据
  1398. this.$set(this.materialDetails, index, {
  1399. ...this.materialDetails[index],
  1400. ...row,
  1401. updateTime: new Date().toISOString()
  1402. })
  1403. // 重新计算订单总计
  1404. this.calculateOrderTotal()
  1405. },
  1406. /**
  1407. * 计算订单总金额和总数量
  1408. * @description 根据物料明细计算订单总金额、总数量、总税额并更新表单数据
  1409. * @returns {void}
  1410. * @this {import('./types').OrderFormMixinComponent}
  1411. */
  1412. calculateOrderTotal() {
  1413. // 计算订单总金额
  1414. const totalAmount = this.materialDetails.reduce((sum, item) => {
  1415. return sum + (Number(item.totalAmount) || 0)
  1416. }, 0)
  1417. // 计算订单总数量
  1418. const totalQuantity = this.materialDetails.reduce((sum, item) => {
  1419. return sum + (Number(item.orderQuantity) || 0)
  1420. }, 0)
  1421. // 计算总税额
  1422. const totalTaxAmount = this.materialDetails.reduce((sum, item) => {
  1423. return sum + (Number(item.taxAmount) || 0)
  1424. }, 0)
  1425. // 更新表单中的总金额、总数量和税额字段
  1426. if (this.formData) {
  1427. this.$set(this.formData, 'totalAmount', Math.round(totalAmount * 100) / 100)
  1428. this.$set(this.formData, 'totalQuantity', Math.round(totalQuantity))
  1429. this.$set(this.formData, 'totalTaxAmount', Math.round(totalTaxAmount * 100) / 100)
  1430. }
  1431. },
  1432. /**
  1433. * 处理客户选择事件
  1434. * @description 当客户选择组件选择客户时的回调处理,自动填充客户编码和客户名称,并清空地址相关字段
  1435. * @param {import('./types').CustomerSelectData} customerData - 客户数据对象
  1436. * @returns {void}
  1437. * @this {import('./types').OrderFormMixinComponent}
  1438. */
  1439. handleCustomerSelected(/** @type {import('./types').CustomerSelectData} */ customerData) {
  1440. if (this.formData) {
  1441. // 更新客户相关字段
  1442. this.$set(this.formData, 'customerId', customerData.customerId != null ? String(customerData.customerId) : null)
  1443. this.$set(this.formData, 'customerCode', customerData.customerCode)
  1444. this.$set(this.formData, 'customerName', customerData.customerName)
  1445. // 清空地址相关字段
  1446. this.$set(this.formData, 'addressId', '')
  1447. this.$set(this.formData, 'receiverName', '')
  1448. this.$set(this.formData, 'receiverPhone', '')
  1449. this.$set(this.formData, 'receiverRegion', '')
  1450. this.$set(this.formData, 'receiverAddress', '')
  1451. }
  1452. },
  1453. /**
  1454. * 处理地址选择事件
  1455. * @description 当地址选择组件选择地址时的回调处理,自动填充收货人相关信息
  1456. * @param {import('./types').AddressSelectData} addressData - 地址数据对象
  1457. * @returns {void}
  1458. * @this {import('./types').OrderFormMixinComponent}
  1459. */
  1460. handleAddressSelected(/** @type {import('./types').AddressSelectData} */ addressData) {
  1461. if (this.formData) {
  1462. // 更新地址相关字段
  1463. this.$set(this.formData, 'addressId', addressData.addressId != null ? String(addressData.addressId) : '')
  1464. this.$set(this.formData, 'receiverName', addressData.receiverName || '')
  1465. this.$set(this.formData, 'receiverPhone', addressData.receiverPhone || '')
  1466. this.$set(this.formData, 'receiverRegion', addressData.regionName || '')
  1467. this.$set(this.formData, 'receiverAddress', addressData.detailAddress || '')
  1468. }
  1469. },
  1470. /**
  1471. * 处理地址回显
  1472. * @description 在编辑模式下,根据表单中的地址信息在地址选择组件中进行回显
  1473. * @returns {void}
  1474. * @this {import('./types').OrderFormMixinComponent}
  1475. */
  1476. handleAddressEcho() {
  1477. // 查找地址选择组件的引用
  1478. const addressSelectRefs = this.$refs.orderForm?.$refs?.addressId
  1479. const addressSelectComponent = Array.isArray(addressSelectRefs) ? addressSelectRefs[0] : addressSelectRefs
  1480. if (addressSelectComponent && typeof addressSelectComponent.setEchoValue === 'function') {
  1481. // 构建地址信息对象用于匹配
  1482. const addressInfo = {
  1483. receiverName: this.formData.receiverName,
  1484. receiverPhone: this.formData.receiverPhone,
  1485. regionName: this.formData.receiverRegion,
  1486. detailAddress: this.formData.receiverAddress
  1487. }
  1488. // 调用地址选择组件的回显方法
  1489. addressSelectComponent.setEchoValue(addressInfo)
  1490. }
  1491. }
  1492. }
  1493. }