forecastIndex.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. import { getForecastList, addForecast, updateForecast } from '@/api/forecast'
  2. import { getCustomerList } from '@/api/common'
  3. import { getItemList } from '@/api/common'
  4. import {
  5. APPROVAL_STATUS,
  6. APPROVAL_STATUS_CONFIG,
  7. APPROVAL_STATUS_OPTIONS,
  8. getApprovalStatusLabel,
  9. getApprovalStatusType,
  10. canEdit,
  11. FORECAST_FORM_RULES,
  12. DEFAULT_FORECAST_FORM
  13. } from '@/constants/forecast'
  14. import { mapGetters } from 'vuex'
  15. import ForecastFormAvue from '@/components/forecast-form/forecast-form-avue.vue'
  16. /**
  17. * 经销商销售预测申报页面业务逻辑混入
  18. * @description 提供预测申报的增删改查、表单验证、远程搜索等功能
  19. */
  20. export default {
  21. /**
  22. * 组件注册
  23. */
  24. components: {
  25. ForecastFormAvue
  26. },
  27. data() {
  28. return {
  29. /** @type {Object} 表单数据 */
  30. form: { ...DEFAULT_FORECAST_FORM },
  31. /** @type {Object} 查询参数 */
  32. query: {},
  33. /** @type {boolean} 表格加载状态 */
  34. loading: true,
  35. /** @type {boolean} 表单提交状态 */
  36. submitting: false,
  37. /** @type {boolean} 保存按钮加载状态 */
  38. saveLoading: false,
  39. /** @type {boolean} 添加/编辑弹窗显示状态(保留兼容性) */
  40. dialogVisible: false,
  41. /** @type {boolean} 是否为编辑模式(保留兼容性) */
  42. isEdit: false,
  43. /** @type {boolean} 表单页面显示状态 */
  44. editVisible: false,
  45. /** @type {string} 表单模式:'add' | 'edit' | 'view' */
  46. editMode: 'add',
  47. /** @type {string} 表单标题 */
  48. editTitle: '',
  49. /** @type {string|number|null} 当前编辑的预测记录ID */
  50. currentForecastId: null,
  51. /** @type {Object} 分页配置 */
  52. page: {
  53. pageSize: 10,
  54. currentPage: 1,
  55. total: 0
  56. },
  57. /** @type {Array<Object>} 表格数据 */
  58. data: [],
  59. /** @type {Array<Object>} 客户选项 */
  60. customerOptions: [],
  61. /** @type {boolean} 客户选项加载状态 */
  62. customerLoading: false,
  63. /** @type {Array<Object>} 物料选项 */
  64. itemOptions: [],
  65. /** @type {boolean} 物料选项加载状态 */
  66. itemLoading: false,
  67. /** @type {Object} avue表格配置 */
  68. option: {
  69. height: 'auto',
  70. calcHeight: 30,
  71. tip: false,
  72. searchShow: true,
  73. searchMenuSpan: 6,
  74. border: true,
  75. index: true,
  76. viewBtn: false,
  77. selection: false,
  78. addBtn: true,
  79. editBtn: false, // 禁用内置编辑按钮
  80. delBtn: false,
  81. excelBtn: false,
  82. columnBtn: false,
  83. refreshBtn: true,
  84. dialogClickModal: false,
  85. addBtnText: '新增预测申报',
  86. editBtnText: '编辑',
  87. // 添加这个配置来完全隐藏操作列
  88. menu: false,
  89. column: [
  90. {
  91. label: '预测编码',
  92. prop: 'forecastCode',
  93. search: true,
  94. searchSpan: 6,
  95. width: 150,
  96. overHidden: true
  97. },
  98. {
  99. label: '年份',
  100. prop: 'year',
  101. type: 'year',
  102. valueFormat: 'yyyy', // 添加这行,确保提交的数据是YYYY格式
  103. search: true,
  104. searchSpan: 6,
  105. width: 100
  106. },
  107. {
  108. label: '月份',
  109. prop: 'month',
  110. type: 'select',
  111. dicData: [
  112. { label: '1月', value: 1 },
  113. { label: '2月', value: 2 },
  114. { label: '3月', value: 3 },
  115. { label: '4月', value: 4 },
  116. { label: '5月', value: 5 },
  117. { label: '6月', value: 6 },
  118. { label: '7月', value: 7 },
  119. { label: '8月', value: 8 },
  120. { label: '9月', value: 9 },
  121. { label: '10月', value: 10 },
  122. { label: '11月', value: 11 },
  123. { label: '12月', value: 12 }
  124. ],
  125. search: true,
  126. searchSpan: 6,
  127. width: 100
  128. },
  129. {
  130. label: '客户名称',
  131. prop: 'customerName',
  132. search: true,
  133. searchSpan: 6,
  134. width: 180,
  135. overHidden: true
  136. },
  137. {
  138. label: '客户编码',
  139. prop: 'customerCode',
  140. search: false,
  141. width: 150,
  142. overHidden: true
  143. },
  144. {
  145. label: '品牌名称',
  146. prop: 'brandName',
  147. search: false,
  148. width: 120,
  149. overHidden: true
  150. },
  151. {
  152. label: '物料名称',
  153. prop: 'itemName',
  154. search: false,
  155. width: 200,
  156. overHidden: true
  157. },
  158. {
  159. label: '物料编码',
  160. prop: 'itemCode',
  161. search: false,
  162. width: 150,
  163. overHidden: true
  164. },
  165. {
  166. label: '规格',
  167. prop: 'specs',
  168. search: false,
  169. width: 150,
  170. overHidden: true
  171. },
  172. {
  173. label: '预测数量',
  174. prop: 'forecastQuantity',
  175. type: 'number',
  176. precision: 4,
  177. search: false,
  178. width: 120,
  179. align: 'right'
  180. },
  181. {
  182. label: '当前库存',
  183. prop: 'currentInventory',
  184. type: 'number',
  185. precision: 4,
  186. search: false,
  187. width: 120,
  188. align: 'right'
  189. },
  190. {
  191. label: '审批状态',
  192. prop: 'approvalStatus',
  193. type: 'select',
  194. dicData: APPROVAL_STATUS_OPTIONS,
  195. slot: true,
  196. search: true,
  197. searchSpan: 6,
  198. width: 120
  199. },
  200. {
  201. label: '审批人',
  202. prop: 'approvedName',
  203. search: false,
  204. width: 120,
  205. overHidden: true
  206. },
  207. {
  208. label: '审批时间',
  209. prop: 'approvedTime',
  210. type: 'datetime',
  211. format: 'yyyy-MM-dd HH:mm:ss',
  212. search: false,
  213. width: 160
  214. },
  215. {
  216. label: '创建时间',
  217. prop: 'createTime',
  218. type: 'datetime',
  219. format: 'yyyy-MM-dd HH:mm:ss',
  220. search: false,
  221. width: 160
  222. },
  223. // 添加自定义编辑按钮列
  224. {
  225. label: '操作',
  226. prop: 'editBtn',
  227. slot: true,
  228. width: 120,
  229. fixed: 'right'
  230. }
  231. ]
  232. },
  233. /** @type {typeof FORECAST_FORM_RULES} 表单验证规则 */
  234. formRules: FORECAST_FORM_RULES
  235. }
  236. },
  237. computed: {
  238. ...mapGetters(['permission']),
  239. /**
  240. * 权限配置
  241. * @returns {Object} 权限配置对象
  242. */
  243. permissionList() {
  244. return {
  245. // addBtn: this.vaildData(this.permission.forecast_add, false),
  246. // viewBtn: this.vaildData(this.permission.forecast_view, false),
  247. // delBtn: false, // 不提供删除功能
  248. // editBtn: this.vaildData(this.permission.forecast_edit, false)
  249. addBtn: false,
  250. viewBtn: false,
  251. delBtn: false, // 不提供删除功能
  252. editBtn: false
  253. }
  254. },
  255. /**
  256. * 弹窗标题
  257. * @returns {string} 弹窗标题文本
  258. */
  259. dialogTitle() {
  260. return this.isEdit ? '编辑预测申报' : '新增预测申报'
  261. }
  262. },
  263. created() {
  264. this.loadCustomerOptions()
  265. this.loadItemOptions()
  266. },
  267. methods: {
  268. /**
  269. * 加载客户选项(初始化时加载部分数据)
  270. * @returns {Promise<void>}
  271. */
  272. async loadCustomerOptions() {
  273. try {
  274. this.customerLoading = true
  275. const res = await getCustomerList(1, 20)
  276. if (res.data && res.data.success) {
  277. this.customerOptions = res.data.data.records.map(item => ({
  278. value: item.Customer_ID.toString(),
  279. label: item.Customer_NAME,
  280. customerCode: item.Customer_CODE,
  281. customerName: item.Customer_NAME
  282. }))
  283. }
  284. } catch (error) {
  285. console.error('加载客户选项失败:', error)
  286. this.$message.error('加载客户选项失败')
  287. } finally {
  288. this.customerLoading = false
  289. }
  290. },
  291. /**
  292. * 远程搜索客户
  293. * @param {string} query - 搜索关键词
  294. * @returns {Promise<void>}
  295. */
  296. async remoteSearchCustomer(query) {
  297. if (!query) {
  298. this.loadCustomerOptions()
  299. return
  300. }
  301. try {
  302. this.customerLoading = true
  303. const res = await getCustomerList(1, 50, {
  304. Customer_NAME: query
  305. })
  306. if (res.data && res.data.success) {
  307. this.customerOptions = res.data.data.records.map(item => ({
  308. value: item.Customer_ID.toString(),
  309. label: item.Customer_NAME,
  310. customerCode: item.Customer_CODE,
  311. customerName: item.Customer_NAME
  312. }))
  313. }
  314. } catch (error) {
  315. console.error('搜索客户失败:', error)
  316. } finally {
  317. this.customerLoading = false
  318. }
  319. },
  320. /**
  321. * 客户选择变化处理
  322. * @param {number} customerId - 客户ID
  323. * @returns {void}
  324. */
  325. handleCustomerChange(customerId) {
  326. const customer = this.customerOptions.find(item => item.value === customerId)
  327. if (customer) {
  328. this.form.customerId = customer.value.toString()
  329. this.form.customerCode = customer.customerCode
  330. this.form.customerName = customer.customerName
  331. }
  332. },
  333. /**
  334. * 加载物料选项(初始化时加载部分数据)
  335. * @returns {Promise<void>}
  336. */
  337. async loadItemOptions() {
  338. try {
  339. this.itemLoading = true
  340. const res = await getItemList(1, 20)
  341. if (res.data && res.data.success) {
  342. this.itemOptions = res.data.data.records.map(item => ({
  343. value: item.Item_ID,
  344. label: item.Item_Name,
  345. itemId: item.Item_ID,
  346. itemCode: item.Item_Code,
  347. itemName: item.Item_Name,
  348. specs: item.Item_PECS || ''
  349. }))
  350. }
  351. } catch (error) {
  352. console.error('加载物料选项失败:', error)
  353. this.$message.error('加载物料选项失败')
  354. } finally {
  355. this.itemLoading = false
  356. }
  357. },
  358. /**
  359. * 远程搜索物料
  360. * @param {string} query - 搜索关键词
  361. * @returns {Promise<void>}
  362. */
  363. async remoteSearchItem(query) {
  364. if (!query) {
  365. this.loadItemOptions()
  366. return
  367. }
  368. try {
  369. this.itemLoading = true
  370. const res = await getItemList(1, 50, {
  371. itemName: query
  372. })
  373. if (res.data && res.data.success) {
  374. this.itemOptions = res.data.data.records.map(item => ({
  375. value: item.Item_ID,
  376. label: item.Item_Name,
  377. itemId: item.Item_ID,
  378. itemCode: item.Item_Code,
  379. itemName: item.Item_Name,
  380. specs: item.Item_PECS || ''
  381. }))
  382. }
  383. } catch (error) {
  384. console.error('搜索物料失败:', error)
  385. } finally {
  386. this.itemLoading = false
  387. }
  388. },
  389. /**
  390. * 物料选择变化处理
  391. * @param {number} itemId - 物料ID
  392. * @returns {void}
  393. */
  394. handleItemChange(itemId) {
  395. const item = this.itemOptions.find(option => option.value === itemId)
  396. if (item) {
  397. this.form.itemId = item.value
  398. this.form.itemCode = item.itemCode
  399. this.form.itemName = item.itemName
  400. this.form.specs = item.specs
  401. }
  402. },
  403. /**
  404. * 获取审批状态标签
  405. * @param {number} status - 审批状态
  406. * @returns {string} 状态标签
  407. */
  408. getApprovalStatusLabel,
  409. /**
  410. * 获取审批状态类型
  411. * @param {number} status - 审批状态
  412. * @returns {string} 状态类型
  413. */
  414. getApprovalStatusType,
  415. /**
  416. * 检查是否可以编辑
  417. * @param {Object} row - 行数据
  418. * @returns {boolean} 是否可以编辑
  419. */
  420. canEditRow(row) {
  421. return canEdit(row.approvalStatus)
  422. },
  423. /**
  424. * 搜索重置
  425. * @returns {void}
  426. */
  427. searchReset() {
  428. this.query = {}
  429. this.onLoad(this.page)
  430. },
  431. /**
  432. * 搜索条件变化
  433. * @param {Object} params - 搜索参数
  434. * @param {Function} done - 完成回调
  435. * @returns {void}
  436. */
  437. searchChange(params, done) {
  438. this.query = params
  439. this.page.currentPage = 1
  440. this.onLoad(this.page, params)
  441. done()
  442. },
  443. /**
  444. * 页码变化
  445. * @param {number} currentPage - 当前页码
  446. * @returns {void}
  447. */
  448. currentChange(currentPage) {
  449. this.page.currentPage = currentPage
  450. },
  451. /**
  452. * 页大小变化
  453. * @param {number} pageSize - 页大小
  454. * @returns {void}
  455. */
  456. sizeChange(pageSize) {
  457. this.page.pageSize = pageSize
  458. },
  459. /**
  460. * 刷新数据
  461. * @returns {void}
  462. */
  463. refreshChange() {
  464. this.onLoad(this.page, this.query)
  465. },
  466. /**
  467. * 加载数据
  468. * @param {Object} page - 分页参数
  469. * @param {Object} params - 查询参数
  470. * @returns {Promise<void>}
  471. */
  472. async onLoad(page, params = {}) {
  473. this.loading = true
  474. try {
  475. const res = await getForecastList(page.currentPage, page.pageSize, {
  476. ...params,
  477. ...this.query
  478. })
  479. if (res.data && res.data.success) {
  480. const data = res.data.data
  481. this.page.total = data.total
  482. this.data = data.records
  483. } else {
  484. this.$message.error(res.data?.msg || '加载数据失败')
  485. this.data = []
  486. this.page.total = 0
  487. }
  488. } catch (error) {
  489. console.error('加载数据失败:', error)
  490. this.$message.error('加载数据失败,请稍后重试')
  491. this.data = []
  492. this.page.total = 0
  493. } finally {
  494. this.loading = false
  495. }
  496. },
  497. /**
  498. * 新增按钮点击
  499. * @returns {void}
  500. */
  501. rowAdd() {
  502. this.isEdit = false
  503. this.currentForecastId = null
  504. this.dialogVisible = true
  505. },
  506. /**
  507. * 新增预测申报(新版本)
  508. * @description 显示新增预测申报表单页面
  509. */
  510. handleAdd() {
  511. console.log('新增预测申报')
  512. this.editMode = 'add'
  513. this.editTitle = '新增预测申报'
  514. this.currentForecastId = null
  515. this.editVisible = true
  516. // 重置表单数据
  517. this.form = { ...DEFAULT_FORECAST_FORM }
  518. // 生成预测编码
  519. this.generateForecastCode()
  520. // 保持兼容性
  521. this.isEdit = false
  522. this.dialogVisible = true
  523. },
  524. /**
  525. * 编辑按钮点击
  526. * @param {Object} row - 行数据
  527. * @param {number} index - 行索引
  528. * @returns {void}
  529. */
  530. rowEdit(row, index) {
  531. if (!this.canEditRow(row)) {
  532. this.$message.warning('已审批或已拒绝的记录不能修改')
  533. return
  534. }
  535. this.isEdit = true
  536. this.currentForecastId = row.id
  537. this.dialogVisible = true
  538. },
  539. /**
  540. * 编辑按钮点击(新版本)
  541. * @param {Object} row - 行数据
  542. * @param {number} index - 行索引
  543. * @description 显示编辑预测申报表单页面
  544. */
  545. handleEdit(row, index) {
  546. if (!this.canEditRow(row)) {
  547. this.$message.warning('已审批或已拒绝的记录不能修改')
  548. return
  549. }
  550. console.log('编辑预测申报', row, index)
  551. this.editMode = 'edit'
  552. this.editTitle = '编辑预测申报'
  553. this.currentForecastId = row.id
  554. this.editVisible = true
  555. // 设置表单数据
  556. this.form = { ...row }
  557. // 保持兼容性
  558. this.isEdit = true
  559. this.dialogVisible = true
  560. },
  561. /**
  562. * 返回列表
  563. * @description 关闭表单页面,返回列表页面
  564. */
  565. handleBackToList() {
  566. this.editVisible = false
  567. this.editMode = 'add'
  568. this.editTitle = ''
  569. this.currentForecastId = null
  570. this.form = { ...DEFAULT_FORECAST_FORM }
  571. // 保持兼容性
  572. this.dialogVisible = false
  573. this.isEdit = false
  574. },
  575. /**
  576. * 处理保存按钮点击
  577. * @returns {void}
  578. */
  579. handleSave() {
  580. if (this.$refs.forecastForm) {
  581. this.saveLoading = true
  582. this.$refs.forecastForm.handleSave()
  583. }
  584. },
  585. /**
  586. * 处理表单提交成功
  587. * @param {Object} data - 提交成功的数据
  588. * @returns {void}
  589. */
  590. handleFormSubmit(data) {
  591. console.log('表单提交成功', data)
  592. this.saveLoading = false
  593. // 关闭表单页面,返回列表
  594. this.handleBackToList()
  595. // 刷新列表数据
  596. this.refreshChange()
  597. // 显示成功消息
  598. this.$message.success(this.editMode === 'add' ? '新增成功' : '修改成功')
  599. },
  600. /**
  601. * 处理表单关闭
  602. * @returns {void}
  603. */
  604. handleFormClose() {
  605. console.log('表单关闭')
  606. this.saveLoading = false
  607. // 返回列表页面
  608. this.handleBackToList()
  609. },
  610. /**
  611. * 表单提交(兼容旧版本)
  612. * @returns {Promise<void>}
  613. */
  614. async rowSave() {
  615. // 此方法保留用于兼容 avue-crud 的事件绑定
  616. // 实际表单提交逻辑已移至 forecast-form 组件内部
  617. console.warn('rowSave 方法已废弃,请使用组件化表单')
  618. },
  619. /**
  620. * 取消表单(兼容旧版本)
  621. * @returns {void}
  622. */
  623. rowCancel() {
  624. // 此方法保留用于兼容 avue-crud 的事件绑定
  625. this.dialogVisible = false
  626. },
  627. /**
  628. * 生成预测编码
  629. * @returns {void}
  630. */
  631. generateForecastCode() {
  632. const now = new Date()
  633. const year = now.getFullYear()
  634. const month = String(now.getMonth() + 1).padStart(2, '0')
  635. const timestamp = now.getTime().toString().slice(-6)
  636. this.form.forecastCode = `FC-${year}-${month}-${timestamp}`
  637. }
  638. }
  639. }