order-form.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. <template>
  2. <!-- 订单表单容器 - 参照示例HTML的基础容器结构 -->
  3. <div class="order-form-container basic-container">
  4. <!-- 表单头部导航 -->
  5. <div class="order-form-header">
  6. <div class="header-left">
  7. <el-button
  8. type="text"
  9. icon="el-icon-arrow-left"
  10. size="small"
  11. class="back-btn"
  12. @click="handleBack"
  13. >
  14. 返回列表
  15. </el-button>
  16. <span class="form-title">{{ formTitle }}</span>
  17. </div>
  18. <div class="header-right">
  19. <el-button
  20. type="text"
  21. icon="el-icon-upload2"
  22. size="small"
  23. v-if="isEdit && canSubmitToU9(formData) && (orderId || formData.id)"
  24. @click="handleSubmitToU9"
  25. style="color: #409eff"
  26. >
  27. 提交
  28. </el-button>
  29. <!-- 顶部保存按钮 -->
  30. <el-button
  31. type="primary"
  32. icon="el-icon-check"
  33. size="small"
  34. :loading="saveLoading"
  35. @click="handleSave"
  36. >
  37. 保存
  38. </el-button>
  39. </div>
  40. </div>
  41. <!-- 表单内容区域 - 参照示例HTML的avue-form结构 -->
  42. <div class="order-form-content">
  43. <avue-form
  44. ref="orderForm"
  45. v-model="formData"
  46. :option="formOption"
  47. class="order-form"
  48. @submit="handleFormSubmit"
  49. @reset-change="handleFormReset"
  50. >
  51. <!-- 自定义客户选择组件 -->
  52. <template #customerId="{ value, column }">
  53. <customer-select
  54. v-model="formData.customerId"
  55. :placeholder="column.placeholder"
  56. :disabled="column.disabled"
  57. @customer-selected="handleCustomerSelected"
  58. />
  59. </template>
  60. <!-- 自定义地址选择组件 -->
  61. <template #addressId="{ value, column }">
  62. <address-select
  63. v-model="formData.addressId"
  64. :customer-code="formData.customerCode"
  65. :placeholder="column.placeholder"
  66. :disabled="column.disabled"
  67. @address-selected="handleAddressSelected"
  68. />
  69. </template>
  70. </avue-form>
  71. <!-- 物料明细区域 -->
  72. <div class="material-detail-section">
  73. <material-detail-table
  74. :order-id="orderId"
  75. :edit-mode="true"
  76. :material-details="materialDetails"
  77. @refresh="handleMaterialChange"
  78. @material-import="handleMaterialImport"
  79. @material-delete="handleMaterialDelete"
  80. @material-update="handleMaterialUpdate"
  81. />
  82. </div>
  83. </div>
  84. </div>
  85. </template>
  86. <script>
  87. // @ts-check
  88. import orderFormMixin from './order-form-mixin'
  89. import MaterialDetailTable from './material-detail-table.vue'
  90. import CustomerSelect from './customer-select.vue'
  91. import AddressSelect from './address-select.vue'
  92. /**
  93. * 订单表单组件类型定义
  94. * @typedef {import('./types').OrderFormModel} OrderFormModel
  95. * @typedef {import('./types').MaterialDetailRecord} MaterialDetailRecord
  96. * @typedef {import('./types').MaterialUpdateEventData} MaterialUpdateEventData
  97. * @typedef {import('./types').MaterialDeleteEventData} MaterialDeleteEventData
  98. * @typedef {import('./types').CustomerSelectData} CustomerSelectData
  99. * @typedef {import('./types').AddressSelectData} AddressSelectData
  100. * @typedef {import('./types').OrderFormComponent} OrderFormComponent
  101. * @typedef {import('smallwei__avue/form').AvueFormOption<OrderFormModel>} AvueFormOption
  102. * @typedef {import('smallwei__avue/form').AvueFormColumn<OrderFormModel>} AvueFormColumn
  103. * @typedef {import('smallwei__avue/form').AvueFormGroup<OrderFormModel>} AvueFormGroup
  104. */
  105. /**
  106. * 订单表单组件
  107. * @description 基于AvueJS的订单表单组件,支持新增和编辑订单功能,包含物料明细管理和自动计算功能
  108. * @this {import('./types').OrderFormComponent}
  109. */
  110. export default {
  111. name: 'OrderForm',
  112. mixins: [orderFormMixin],
  113. /**
  114. * 组件注册
  115. */
  116. components: {
  117. MaterialDetailTable,
  118. CustomerSelect,
  119. AddressSelect
  120. },
  121. /**
  122. * 组件属性定义
  123. * @description 定义组件接收的外部属性及其类型约束
  124. */
  125. props: {
  126. /**
  127. * 表单可见性控制
  128. * @description 控制订单表单的显示和隐藏状态
  129. * @type {boolean}
  130. * @default false
  131. */
  132. visible: {
  133. type: Boolean,
  134. default: false,
  135. validator: (value) => typeof value === 'boolean'
  136. },
  137. /**
  138. * 编辑模式标识
  139. * @description 标识当前表单是新增模式还是编辑模式
  140. * @type {boolean}
  141. * @default false
  142. */
  143. isEdit: {
  144. type: Boolean,
  145. default: false,
  146. validator: (value) => typeof value === 'boolean'
  147. },
  148. /**
  149. * 订单唯一标识
  150. * @description 编辑模式下用于标识要编辑的订单记录
  151. * @type {string|number|null}
  152. * @default null
  153. */
  154. orderId: {
  155. type: [String, Number],
  156. default: null,
  157. validator: (value) => {
  158. return value === null ||
  159. typeof value === 'string' ||
  160. typeof value === 'number'
  161. }
  162. }
  163. },
  164. /**
  165. * 属性监听器
  166. * @description 监听组件属性变化并执行相应的响应逻辑
  167. */
  168. watch: {
  169. /**
  170. * 监听表单可见性变化
  171. * @description 当表单从隐藏变为可见时,初始化表单数据
  172. * @param {boolean} newVal - 新的可见性状态
  173. * @param {boolean} oldVal - 旧的可见性状态
  174. */
  175. visible: {
  176. handler(newVal, oldVal) {
  177. if (newVal && !oldVal) {
  178. this.initForm()
  179. }
  180. },
  181. immediate: true
  182. },
  183. /**
  184. * 监听订单ID变化
  185. * @description 当订单ID变化且处于编辑模式时,加载订单详情数据
  186. * @param {string|number|null} newVal - 新的订单ID
  187. * @param {string|number|null} oldVal - 旧的订单ID
  188. */
  189. orderId: {
  190. handler(newVal, oldVal) {
  191. if (newVal && this.isEdit && newVal !== oldVal) {
  192. this.loadOrderDetail(newVal)
  193. }
  194. },
  195. immediate: true
  196. },
  197. /**
  198. * 监听表单数据变化,用于地址回显
  199. * @description 当表单数据中的客户编码和地址相关字段都有值时,触发地址选择组件的回显
  200. */
  201. 'formData.customerCode': {
  202. handler(newVal) {
  203. if (newVal && this.isEdit && this.formData.addressId) {
  204. // 延迟执行,确保地址选择组件已经加载完成
  205. this.$nextTick(() => {
  206. this.handleAddressEcho()
  207. })
  208. }
  209. }
  210. }
  211. }
  212. }
  213. </script>
  214. <style scooped>
  215. /* 全局覆盖外层el-card的body padding - 使用更高优先级 */
  216. .order-form-container .el-card .el-card__body {
  217. padding: 0 !important;
  218. }
  219. /* 针对可能的嵌套情况 */
  220. .el-card .el-card__body {
  221. padding: 0 !important;
  222. }
  223. </style>
  224. <style scoped>
  225. .order-form-container {
  226. /* padding: 20px; */
  227. background-color: #f5f5f5;
  228. min-height: 100vh;
  229. /* max-width: 1200px; */
  230. margin: 0 auto;
  231. }
  232. .order-form-header {
  233. display: flex;
  234. justify-content: space-between;
  235. align-items: center;
  236. padding: 16px 20px;
  237. background-color: #ffffff;
  238. border-radius: 8px 8px 0 0;
  239. border-bottom: 1px solid #ebeef5;
  240. margin-bottom: 0;
  241. }
  242. .header-left {
  243. display: flex;
  244. align-items: center;
  245. gap: 12px;
  246. }
  247. .back-btn {
  248. color: #409eff;
  249. font-size: 14px;
  250. padding: 0;
  251. }
  252. .back-btn:hover {
  253. color: #66b1ff;
  254. }
  255. .form-title {
  256. font-size: 18px;
  257. font-weight: 600;
  258. color: #303133;
  259. margin: 0;
  260. }
  261. .header-right {
  262. display: flex;
  263. gap: 8px;
  264. }
  265. .order-form-content {
  266. background-color: #ffffff;
  267. padding: 10px;
  268. border-radius: 0 0 8px 8px;
  269. /* box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); */
  270. }
  271. .order-form {
  272. max-width: 100%;
  273. }
  274. .material-detail-section {
  275. background-color: #ffffff;
  276. margin-top: 16px;
  277. border-radius: 8px;
  278. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  279. }
  280. /* 响应式设计 */
  281. @media (max-width: 768px) {
  282. .order-form-container {
  283. padding: 10px;
  284. }
  285. .order-form-header {
  286. flex-direction: column;
  287. gap: 12px;
  288. align-items: flex-start;
  289. padding: 12px 16px;
  290. }
  291. .header-right {
  292. width: 100%;
  293. justify-content: flex-end;
  294. }
  295. .form-title {
  296. font-size: 16px;
  297. }
  298. .order-form-content {
  299. padding: 16px;
  300. }
  301. }
  302. </style>