order-form-mixin.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. import { add, update, getDetail } from '@/api/order/order'
  2. import { getList as getOrderItemList } from '@/api/order/order-item'
  3. import {
  4. ORDER_TYPES,
  5. ORDER_STATUS,
  6. ORDER_TYPE_OPTIONS,
  7. ORDER_STATUS_OPTIONS
  8. } from '@/constants/order'
  9. /**
  10. * @typedef {import('./types').OrderFormModel} OrderFormModel
  11. * @typedef {import('./types').ValidationRule} ValidationRule
  12. * @typedef {import('./types').OrderFormRules} OrderFormRules
  13. * @typedef {import('./types').OrderFormData} OrderFormMixinData
  14. */
  15. /**
  16. * @typedef {Object} ApiResponse
  17. * @property {number} code - 响应状态码
  18. * @property {string} message - 响应消息
  19. * @property {*} data - 响应数据
  20. */
  21. /**
  22. * @typedef {import('./types').OrderTypeOption} OrderTypeOption
  23. * @typedef {import('./types').OrderStatusOption} OrderStatusOption
  24. */
  25. /**
  26. * 订单表单混入组件
  27. * @description 提供订单表单的数据管理、验证规则和业务逻辑的混入组件
  28. * @mixin
  29. */
  30. export default {
  31. /**
  32. * 组件响应式数据
  33. * @description 定义组件的响应式数据状态
  34. * @returns {OrderFormMixinData} 组件数据对象
  35. */
  36. data() {
  37. return {
  38. /**
  39. * 订单表单数据模型
  40. * @description 存储订单表单的所有字段数据
  41. * @type {OrderFormModel}
  42. */
  43. formData: this.createInitialFormData(),
  44. /**
  45. * 保存操作加载状态
  46. * @description 控制保存按钮的加载状态,防止重复提交
  47. * @type {boolean}
  48. */
  49. saveLoading: false,
  50. /**
  51. * 订单类型选项列表
  52. * @description 订单类型下拉选择器的选项数据
  53. * @type {OrderTypeOption[]}
  54. */
  55. orderTypeOptions: Object.freeze([...ORDER_TYPE_OPTIONS]),
  56. /**
  57. * 订单状态选项列表
  58. * @description 订单状态下拉选择器的选项数据
  59. * @type {OrderStatusOption[]}
  60. */
  61. orderStatusOptions: Object.freeze([...ORDER_STATUS_OPTIONS])
  62. }
  63. },
  64. /**
  65. * 计算属性
  66. * @description 组件的响应式计算属性
  67. */
  68. computed: {
  69. /**
  70. * 订单表单验证规则
  71. * @description 定义订单表单各字段的验证规则,支持必填、长度、格式等验证
  72. * @returns {OrderFormRules} 完整的表单验证规则对象
  73. */
  74. formRules() {
  75. return {
  76. orderCode: [
  77. {
  78. required: true,
  79. message: '请输入订单编码',
  80. trigger: 'blur'
  81. },
  82. {
  83. min: 3,
  84. max: 50,
  85. message: '订单编码长度在 3 到 50 个字符',
  86. trigger: 'blur'
  87. }
  88. ],
  89. orgCode: [
  90. {
  91. required: true,
  92. message: '请输入组织编码',
  93. trigger: 'blur'
  94. },
  95. {
  96. min: 2,
  97. max: 20,
  98. message: '组织编码长度在 2 到 20 个字符',
  99. trigger: 'blur'
  100. }
  101. ],
  102. orgName: [
  103. {
  104. required: true,
  105. message: '请输入组织名称',
  106. trigger: 'blur'
  107. },
  108. {
  109. min: 2,
  110. max: 100,
  111. message: '组织名称长度在 2 到 100 个字符',
  112. trigger: 'blur'
  113. }
  114. ],
  115. customerCode: [
  116. {
  117. required: true,
  118. message: '请输入客户编码',
  119. trigger: 'blur'
  120. },
  121. {
  122. min: 3,
  123. max: 50,
  124. message: '客户编码长度在 3 到 50 个字符',
  125. trigger: 'blur'
  126. }
  127. ],
  128. customerName: [
  129. {
  130. required: true,
  131. message: '请输入客户名称',
  132. trigger: 'blur'
  133. },
  134. {
  135. min: 2,
  136. max: 100,
  137. message: '客户名称长度在 2 到 100 个字符',
  138. trigger: 'blur'
  139. }
  140. ],
  141. orderType: [
  142. {
  143. required: true,
  144. message: '请选择订单类型',
  145. trigger: 'change'
  146. }
  147. ],
  148. totalAmount: [
  149. {
  150. required: true,
  151. message: '请输入订单总金额',
  152. trigger: 'blur'
  153. },
  154. {
  155. type: 'number',
  156. min: 0.01,
  157. message: '订单总金额必须大于0',
  158. trigger: 'blur'
  159. }
  160. ],
  161. totalQuantity: [
  162. {
  163. required: true,
  164. message: '请输入订单总数量',
  165. trigger: 'blur'
  166. },
  167. {
  168. type: 'number',
  169. min: 0.0001,
  170. message: '订单总数量必须大于0',
  171. trigger: 'blur'
  172. }
  173. ],
  174. receiverName: [
  175. {
  176. required: true,
  177. message: '请输入收货人姓名',
  178. trigger: 'blur'
  179. },
  180. {
  181. min: 2,
  182. max: 50,
  183. message: '收货人姓名长度在 2 到 50 个字符',
  184. trigger: 'blur'
  185. }
  186. ],
  187. receiverPhone: [
  188. {
  189. required: true,
  190. message: '请输入收货人电话',
  191. trigger: 'blur'
  192. },
  193. {
  194. pattern: /^1[3-9]\d{9}$/,
  195. message: '请输入正确的手机号码',
  196. trigger: 'blur'
  197. }
  198. ],
  199. receiverAddress: [
  200. {
  201. required: true,
  202. message: '请输入收货地址',
  203. trigger: 'blur'
  204. },
  205. {
  206. min: 5,
  207. max: 500,
  208. message: '收货地址长度在 5 到 500 个字符',
  209. trigger: 'blur'
  210. }
  211. ],
  212. remark: [
  213. {
  214. max: 1000,
  215. message: '备注信息不能超过 1000 个字符',
  216. trigger: 'blur'
  217. }
  218. ]
  219. }
  220. }
  221. },
  222. /**
  223. * 组件方法
  224. * @description 组件的业务逻辑方法集合
  225. */
  226. methods: {
  227. /**
  228. * 创建初始表单数据
  229. * @description 创建订单表单的初始数据结构
  230. * @returns {OrderFormModel} 初始化的表单数据对象
  231. * @private
  232. */
  233. createInitialFormData() {
  234. return {
  235. id: null,
  236. orderCode: '',
  237. orgCode: '',
  238. orgName: '',
  239. customerCode: '',
  240. customerName: '',
  241. orderType: ORDER_TYPES.NORMAL,
  242. totalAmount: null,
  243. totalQuantity: null,
  244. receiverName: '',
  245. receiverPhone: '',
  246. receiverAddress: '',
  247. status: ORDER_STATUS.DRAFT,
  248. remark: ''
  249. }
  250. },
  251. /**
  252. * 初始化表单数据
  253. * @description 根据编辑模式初始化表单,编辑模式加载数据,新增模式重置表单
  254. * @returns {Promise<void>}
  255. * @public
  256. */
  257. async initForm() {
  258. try {
  259. if (this.isEdit && this.orderId) {
  260. await this.loadOrderDetail(this.orderId)
  261. } else {
  262. this.resetForm()
  263. }
  264. } catch (error) {
  265. this.$message.error('初始化表单失败,请刷新页面重试')
  266. }
  267. },
  268. /**
  269. * 重置表单数据
  270. * @description 将表单数据重置为初始状态,并清除所有验证错误信息
  271. * @returns {Promise<void>}
  272. * @public
  273. */
  274. async resetForm() {
  275. try {
  276. // 重置表单数据为初始状态
  277. this.formData = this.createInitialFormData()
  278. // 等待DOM更新后清除表单验证
  279. await this.$nextTick()
  280. if (this.$refs.orderForm && typeof this.$refs.orderForm.clearValidate === 'function') {
  281. this.$refs.orderForm.clearValidate()
  282. }
  283. } catch (error) {
  284. // 重置表单时发生错误,静默处理
  285. }
  286. },
  287. /**
  288. * 加载订单详情数据
  289. * @description 根据订单ID从服务器获取订单详情并填充到表单中,同时加载物料明细数据
  290. * @param {string|number} orderId - 订单唯一标识
  291. * @returns {Promise<void>}
  292. * @throws {Error} 当API调用失败或数据格式错误时抛出异常
  293. * @public
  294. */
  295. async loadOrderDetail(orderId) {
  296. if (!orderId) {
  297. throw new Error('订单ID不能为空')
  298. }
  299. try {
  300. // 并行加载订单详情和物料明细数据
  301. const [orderResponse, materialResponse] = await Promise.all([
  302. getDetail(orderId),
  303. this.loadMaterialDetails(orderId)
  304. ])
  305. if (!orderResponse || !orderResponse.data || !orderResponse.data.data) {
  306. throw new Error('服务器返回数据格式错误')
  307. }
  308. const orderData = orderResponse.data.data
  309. // 安全地映射订单数据到表单,确保数据类型正确
  310. this.formData = this.mapOrderDataToForm(orderData)
  311. // 设置物料明细数据
  312. this.materialDetails = materialResponse
  313. } catch (error) {
  314. const errorMessage = error.message || '加载订单详情失败'
  315. this.$message.error(`${errorMessage},请重试`)
  316. throw error
  317. }
  318. },
  319. /**
  320. * 加载订单物料明细数据
  321. * @description 根据订单ID从服务器获取物料明细列表,用于编辑模式下的数据回显
  322. * @param {string|number} orderId - 订单唯一标识符
  323. * @returns {Promise<OrderItemRecord[]>} 物料明细记录列表,失败时返回空数组
  324. * @private
  325. */
  326. async loadMaterialDetails(orderId) {
  327. try {
  328. const response = await getOrderItemList(1, 1000, { orderId })
  329. if (!response || !response.data || !response.data.data) {
  330. throw new Error('物料明细数据格式错误')
  331. }
  332. return response.data.data.records || []
  333. } catch (error) {
  334. this.$message.warning('加载物料明细失败,请稍后重试')
  335. return []
  336. }
  337. },
  338. /**
  339. * 映射订单数据到表单格式
  340. * @description 将API返回的订单数据安全地映射为表单数据格式
  341. * @param {Object} orderData - 从API获取的原始订单数据
  342. * @returns {OrderFormModel} 格式化后的表单数据
  343. * @private
  344. */
  345. mapOrderDataToForm(orderData) {
  346. return {
  347. id: orderData.id || null,
  348. orderCode: String(orderData.orderCode || ''),
  349. orgCode: String(orderData.orgCode || ''),
  350. orgName: String(orderData.orgName || ''),
  351. customerCode: String(orderData.customerCode || ''),
  352. customerName: String(orderData.customerName || ''),
  353. orderType: Number(orderData.orderType) || ORDER_TYPES.NORMAL,
  354. totalAmount: orderData.totalAmount ? Number(orderData.totalAmount) : null,
  355. totalQuantity: orderData.totalQuantity ? Number(orderData.totalQuantity) : null,
  356. receiverName: String(orderData.receiverName || ''),
  357. receiverPhone: String(orderData.receiverPhone || ''),
  358. receiverAddress: String(orderData.receiverAddress || ''),
  359. status: Number(orderData.status) || ORDER_STATUS.DRAFT,
  360. remark: String(orderData.remark || '')
  361. }
  362. },
  363. /**
  364. * 处理返回列表操作
  365. * @description 触发返回列表事件,通知父组件关闭表单
  366. * @returns {void}
  367. * @public
  368. * @emits back 返回列表事件
  369. */
  370. handleBack() {
  371. /**
  372. * 返回列表事件
  373. * @event back
  374. * @description 用户点击返回按钮时触发
  375. */
  376. this.$emit('back')
  377. },
  378. /**
  379. * 处理表单保存操作
  380. * @description 验证表单数据并提交到服务器,支持新增和编辑模式
  381. * @returns {Promise<void>}
  382. * @throws {Error} 当表单验证失败或API调用失败时抛出异常
  383. * @public
  384. * @emits save-success 保存成功事件
  385. */
  386. async handleSave() {
  387. if (this.saveLoading) {
  388. return // 防止重复提交
  389. }
  390. try {
  391. // 表单验证
  392. const isValid = await this.validateForm()
  393. if (!isValid) {
  394. return
  395. }
  396. this.saveLoading = true
  397. // 准备提交数据
  398. const submitData = this.prepareSubmitData()
  399. // 调用相应的API
  400. const response = await this.submitOrderData(submitData)
  401. // 显示成功提示
  402. const successMessage = this.isEdit ? '订单更新成功' : '订单创建成功'
  403. this.$message.success(successMessage)
  404. /**
  405. * 保存成功事件
  406. * @event save-success
  407. * @param {Object} data - 保存后的订单数据
  408. * @description 订单保存成功后触发,携带最新的订单数据
  409. */
  410. this.$emit('save-success', response.data.data)
  411. // 返回列表
  412. this.handleBack()
  413. } catch (error) {
  414. const errorMessage = this.isEdit ? '订单更新失败,请重试' : '订单创建失败,请重试'
  415. this.$message.error(errorMessage)
  416. throw error
  417. } finally {
  418. this.saveLoading = false
  419. }
  420. },
  421. /**
  422. * 提交订单数据到服务器
  423. * @description 根据编辑模式调用相应的API接口
  424. * @param {Object} submitData - 要提交的订单数据
  425. * @returns {Promise<ApiResponse>} API响应结果
  426. * @private
  427. */
  428. async submitOrderData(submitData) {
  429. if (this.isEdit) {
  430. return await update(submitData)
  431. } else {
  432. return await add(submitData)
  433. }
  434. },
  435. /**
  436. * 验证表单数据
  437. * @description 使用AvueJS表单的验证功能验证所有字段
  438. * @returns {Promise<boolean>} 验证结果,true表示验证通过,false表示验证失败
  439. * @public
  440. */
  441. async validateForm() {
  442. if (!this.$refs.orderForm) {
  443. return false
  444. }
  445. try {
  446. // 使用更简洁的Promise包装器函数
  447. const isValid = await this.validateFormFields()
  448. if (!isValid) {
  449. this.$message.warning('请检查表单填写是否正确')
  450. }
  451. return isValid
  452. } catch (error) {
  453. this.$message.warning('请检查表单填写是否正确')
  454. return false
  455. }
  456. },
  457. /**
  458. * 验证表单字段
  459. * @description 封装表单验证逻辑,提供更清晰的异步处理
  460. * @returns {Promise<boolean>} 验证结果
  461. * @private
  462. */
  463. validateFormFields() {
  464. return new Promise((resolve) => {
  465. this.$refs.orderForm.validate((valid) => {
  466. resolve(Boolean(valid))
  467. })
  468. })
  469. },
  470. /**
  471. * 准备提交数据
  472. * @description 处理表单数据,移除空值字段并确保数据类型正确
  473. * @returns {Object} 格式化后的提交数据对象
  474. * @public
  475. */
  476. prepareSubmitData() {
  477. const submitData = { ...this.formData }
  478. // 清理和格式化数据
  479. return this.cleanAndFormatSubmitData(submitData)
  480. },
  481. /**
  482. * 清理和格式化提交数据
  483. * @description 移除空值字段并确保数据类型正确
  484. * @param {OrderFormModel} data - 原始表单数据
  485. * @returns {Object} 清理后的数据对象
  486. * @private
  487. */
  488. cleanAndFormatSubmitData(data) {
  489. const cleanedData = {}
  490. Object.keys(data).forEach(key => {
  491. const value = data[key]
  492. // 跳过null、undefined和空字符串,但保留备注字段
  493. if (value === null || value === undefined || (value === '' && key !== 'remark')) {
  494. return
  495. }
  496. // 确保数字类型字段的正确性
  497. if (['totalAmount', 'totalQuantity', 'orderType', 'status'].includes(key)) {
  498. cleanedData[key] = Number(value) || 0
  499. } else {
  500. cleanedData[key] = value
  501. }
  502. })
  503. return cleanedData
  504. }
  505. }
  506. }