forecastIndex.js 15 KB

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