leadIndex.js 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207
  1. // @ts-check
  2. import { getList, add, update, getDetail } from '@/api/order/lead'
  3. import { getList as getDetailList, add as addDetail, update as updateDetail, remove as removeDetail, getDetail as getDetailDetail } from '@/api/order/lead-detail'
  4. import { getCustomerList } from '@/api/common/index'
  5. import { mapGetters } from 'vuex'
  6. import {
  7. LEAD_PRIORITY,
  8. LEAD_STATUS,
  9. LEAD_PRIORITY_OPTIONS,
  10. LEAD_STATUS_OPTIONS,
  11. getLeadPriorityLabel,
  12. getLeadPriorityType,
  13. getLeadPriorityColor,
  14. getLeadStatusLabel,
  15. getLeadStatusType,
  16. getLeadStatusColor,
  17. isValidLeadPriority,
  18. isValidLeadStatus,
  19. isHighPriorityLead,
  20. isLowPriorityLead,
  21. isLeadEditable,
  22. isLeadConvertible,
  23. isLeadCompleted,
  24. isLeadActive,
  25. LEAD_SOURCE_OPTIONS
  26. } from '@/constants/lead'
  27. /**
  28. * @typedef {import('../types').LeadComponent} LeadComponent
  29. * @typedef {import('../types').LeadRecord} LeadRecord
  30. * @typedef {import('../types').LeadDetailRecord} LeadDetailRecord
  31. * @typedef {import('../types').LeadQueryParams} LeadQueryParams
  32. * @typedef {import('../types').CustomerOption} CustomerOption
  33. * @typedef {import('../types').PageInfo} PageInfo
  34. */
  35. /**
  36. * 销售线索查询参数类型定义
  37. * @typedef {Object} LeadQueryParams
  38. * @property {string} [leadCode] - 线索编码
  39. * @property {string} [customerCode] - 客户编码
  40. * @property {string} [customerName] - 客户名称
  41. * @property {string} [contactName] - 联系人姓名
  42. * @property {string} [contactPhone] - 联系人电话
  43. * @property {string} [title] - 线索标题
  44. * @property {number} [status] - 状态
  45. * @property {number} [priority] - 优先级
  46. * @property {string} [source] - 线索来源
  47. * @property {string} [groupName] - 分组名称
  48. * @property {string[]} [endTime] - 截止时间范围
  49. */
  50. /**
  51. * 销售线索详细信息记录类型定义
  52. * @typedef {Object} LeadDetailRecord
  53. * @property {string} id - 详细信息ID
  54. * @property {string} leadId - 线索ID
  55. * @property {string} detailText - 详细信息内容
  56. * @property {string} createTime - 创建时间
  57. * @property {string} updateTime - 更新时间
  58. */
  59. /**
  60. * @type {LeadComponent & Vue}
  61. */
  62. export default {
  63. computed: {
  64. ...mapGetters(['permission', 'userInfo']),
  65. /**
  66. * 权限列表
  67. * @returns {Object} 权限配置
  68. */
  69. permissionList() {
  70. return {
  71. // addBtn: this.vaildData(this.permission.order_lead_add, false),
  72. // viewBtn: this.vaildData(this.permission.order_lead_view, false),
  73. // delBtn: this.vaildData(this.permission.order_lead_delete, false),
  74. // editBtn: this.vaildData(this.permission.order_lead_edit, false)
  75. addBtn: true,
  76. viewBtn: true,
  77. delBtn: false,
  78. editBtn: true,
  79. }
  80. },
  81. /**
  82. * 选中数据的ID列表
  83. * @returns {string[]} ID列表
  84. */
  85. ids() {
  86. return this.selectionList.map(ele => ele.id)
  87. },
  88. /**
  89. * 详细信息选中数据的ID列表
  90. * @returns {string[]} ID列表
  91. */
  92. detailIds() {
  93. return this.detailSelectionList.map(ele => ele.id)
  94. }
  95. },
  96. data() {
  97. return {
  98. /**
  99. * 详细信息表格配置
  100. */
  101. detailOption: {
  102. height: 'auto',
  103. calcHeight: 30,
  104. tip: false,
  105. searchShow: false,
  106. border: true,
  107. index: true,
  108. viewBtn: true,
  109. selection: true,
  110. dialogClickModal: false,
  111. column: [
  112. {
  113. label: '详细信息内容',
  114. prop: 'detailText',
  115. type: 'textarea',
  116. span: 24,
  117. minRows: 4,
  118. maxRows: 8,
  119. rules: [{
  120. required: true,
  121. message: '请输入详细信息内容',
  122. trigger: ['blur', 'change']
  123. }, {
  124. type: 'string',
  125. min: 10,
  126. message: '详细信息内容至少10个字符',
  127. trigger: ['blur', 'change']
  128. }],
  129. overHidden: true,
  130. width: 400
  131. },
  132. {
  133. label: '创建时间',
  134. prop: 'createTime',
  135. type: 'datetime',
  136. format: 'yyyy-MM-dd HH:mm:ss',
  137. valueFormat: 'yyyy-MM-dd HH:mm:ss',
  138. addDisplay: false,
  139. editDisplay: false,
  140. viewDisplay: true,
  141. width: 180
  142. },
  143. {
  144. label: '更新时间',
  145. prop: 'updateTime',
  146. type: 'datetime',
  147. format: 'yyyy-MM-dd HH:mm:ss',
  148. valueFormat: 'yyyy-MM-dd HH:mm:ss',
  149. addDisplay: false,
  150. editDisplay: false,
  151. viewDisplay: true,
  152. width: 180
  153. }
  154. ]
  155. },
  156. /**
  157. * 详细信息权限列表
  158. */
  159. detailPermissionList: {
  160. addBtn: true,
  161. viewBtn: true,
  162. delBtn: false,
  163. editBtn: true,
  164. },
  165. /**
  166. * 详细信息分页信息
  167. * @type {{total: number, currentPage: number, pageSize: number}}
  168. */
  169. detailPage: {
  170. total: 0,
  171. currentPage: 1,
  172. pageSize: 10
  173. },
  174. /**
  175. * 详细信息加载状态
  176. * @type {boolean}
  177. */
  178. detailLoading: false,
  179. /**
  180. * 详细信息弹窗显示状态
  181. * @type {boolean}
  182. */
  183. detailDialogVisible: false,
  184. /**
  185. * 当前选中的线索
  186. * @type {LeadRecord|null}
  187. */
  188. currentLead: null,
  189. /**
  190. * 详细信息表格数据
  191. * @type {LeadDetailRecord[]}
  192. */
  193. detailData: [],
  194. /**
  195. * 详细信息表单数据
  196. * @type {LeadDetailRecord}
  197. */
  198. detailForm: {},
  199. /**
  200. * 详细信息选中的数据列表
  201. * @type {LeadDetailRecord[]}
  202. */
  203. detailSelectionList: [],
  204. /**
  205. * 表格数据
  206. * @type {LeadRecord[]}
  207. */
  208. data: [],
  209. /**
  210. * 查询参数
  211. * @type {LeadQueryParams}
  212. */
  213. query: {},
  214. /**
  215. * 加载状态
  216. * @type {boolean}
  217. */
  218. loading: true,
  219. /**
  220. * 表单数据
  221. * @type {LeadRecord}
  222. */
  223. form: {},
  224. /**
  225. * 选中的数据列表
  226. * @type {LeadRecord[]}
  227. */
  228. selectionList: [],
  229. /**
  230. * 分页信息
  231. * @type {{total: number, currentPage: number, pageSize: number}}
  232. */
  233. page: {
  234. total: 0,
  235. currentPage: 1,
  236. pageSize: 10
  237. },
  238. /**
  239. * 客户选项列表
  240. * @type {{label: string, value: string, customerCode: string, customerName: string, customerId: string}[]}
  241. */
  242. customerOptions: [],
  243. /**
  244. * 客户选项加载状态
  245. * @type {boolean}
  246. */
  247. customerLoading: false,
  248. /**
  249. * 客户搜索关键词
  250. * @type {string}
  251. */
  252. customerSearchKeyword: '',
  253. /**
  254. * 客户搜索定时器
  255. * @type {number|null}
  256. */
  257. customerSearchTimer: null,
  258. /**
  259. * 表格配置
  260. */
  261. option: {
  262. height: 'auto',
  263. calcHeight: 30,
  264. tip: false,
  265. searchShow: true,
  266. searchMenuSpan: 6,
  267. border: true,
  268. index: true,
  269. viewBtn: true,
  270. selection: true,
  271. dialogClickModal: false,
  272. column: [
  273. {
  274. label: '线索编码',
  275. prop: 'leadCode',
  276. rules: [{
  277. required: true,
  278. message: '请输入线索编码',
  279. trigger: 'blur'
  280. }],
  281. search: true
  282. },
  283. {
  284. label: '客户',
  285. prop: 'customerId',
  286. type: 'select',
  287. search: false,
  288. width: 200,
  289. filterable: true,
  290. remote: false,
  291. reserveKeyword: true,
  292. placeholder: '请选择客户',
  293. dicData: [],
  294. props: {
  295. label: 'label',
  296. value: 'value'
  297. },
  298. rules: [{
  299. required: true,
  300. message: '请选择客户',
  301. trigger: 'change'
  302. }],
  303. /**
  304. * 客户选择变更事件
  305. * @param {Object} param - 事件参数
  306. * @param {string} param.value - 选中的客户ID
  307. * @param {Object} param.column - 列配置
  308. * @returns {void}
  309. */
  310. change: ({ value, column }) => {
  311. if (value) {
  312. const selectedCustomer = this.customerOptions.find(option => option.value === value)
  313. if (selectedCustomer) {
  314. // 自动填充客户信息
  315. this.form.customerId = selectedCustomer.customerId
  316. this.form.customerCode = selectedCustomer.customerCode
  317. this.form.customerName = selectedCustomer.customerName
  318. } else {
  319. this.clearCustomerData()
  320. }
  321. } else {
  322. this.clearCustomerData()
  323. }
  324. },
  325. /**
  326. * 远程搜索方法
  327. * @param {string} query - 搜索关键词
  328. * @returns {void}
  329. */
  330. remoteMethod: (query) => {
  331. this.searchCustomers(query)
  332. }
  333. },
  334. {
  335. label: '客户编码',
  336. prop: 'customerCode',
  337. search: true,
  338. addDisplay: false,
  339. editDisplay: false,
  340. width: 120
  341. },
  342. {
  343. label: '客户名称',
  344. prop: 'customerName',
  345. search: true,
  346. addDisplay: false,
  347. editDisplay: false,
  348. width: 150
  349. },
  350. {
  351. label: '联系人',
  352. prop: 'contactName',
  353. rules: [{
  354. required: true,
  355. message: '请输入联系人姓名',
  356. trigger: 'blur'
  357. }],
  358. search: true
  359. },
  360. {
  361. label: '联系电话',
  362. prop: 'contactPhone',
  363. rules: [{
  364. required: true,
  365. message: '请输入联系电话',
  366. trigger: 'blur'
  367. }, {
  368. pattern: /^1[3-9]\d{9}$/,
  369. message: '请输入正确的手机号码',
  370. trigger: 'blur'
  371. }],
  372. search: true
  373. },
  374. {
  375. label: '线索标题',
  376. prop: 'title',
  377. rules: [{
  378. required: true,
  379. message: '请输入线索标题',
  380. trigger: 'blur'
  381. }],
  382. search: true,
  383. overHidden: true
  384. },
  385. {
  386. label: '截止时间',
  387. prop: 'endTime',
  388. type: 'datetime',
  389. format: 'yyyy-MM-dd HH:mm:ss',
  390. valueFormat: 'yyyy-MM-dd HH:mm:ss',
  391. rules: [{
  392. required: true,
  393. message: '请选择截止时间',
  394. trigger: 'blur'
  395. }],
  396. search: true,
  397. searchRange: true,
  398. slot: true
  399. },
  400. {
  401. label: '优先级',
  402. prop: 'priority',
  403. type: 'select',
  404. dicData: LEAD_PRIORITY_OPTIONS,
  405. rules: [{
  406. required: true,
  407. message: '请选择优先级',
  408. trigger: 'blur'
  409. }],
  410. search: true,
  411. slot: true
  412. },
  413. {
  414. label: '线索来源',
  415. prop: 'source',
  416. type: 'select',
  417. filterable: true,
  418. allowCreate: true,
  419. clearable: true,
  420. placeholder: '请选择或输入线索来源',
  421. dicData: LEAD_SOURCE_OPTIONS,
  422. rules: [{
  423. required: true,
  424. message: '请选择或输入线索来源',
  425. trigger: 'blur'
  426. }],
  427. search: true
  428. },
  429. {
  430. label: '分组名称',
  431. prop: 'groupName',
  432. rules: [{
  433. required: true,
  434. message: '请输入分组名称',
  435. trigger: 'blur'
  436. }],
  437. search: true
  438. },
  439. {
  440. label: '状态',
  441. prop: 'status',
  442. type: 'select',
  443. dicData: LEAD_STATUS_OPTIONS,
  444. rules: [{
  445. required: true,
  446. message: '请选择状态',
  447. trigger: 'blur'
  448. }],
  449. search: true,
  450. slot: true
  451. },
  452. {
  453. label: '关闭原因',
  454. prop: 'closeReason',
  455. type: 'textarea',
  456. span: 24,
  457. hide: true,
  458. viewDisplay: true
  459. },
  460. {
  461. label: '创建时间',
  462. prop: 'createTime',
  463. type: 'datetime',
  464. format: 'yyyy-MM-dd HH:mm:ss',
  465. valueFormat: 'yyyy-MM-dd HH:mm:ss',
  466. addDisplay: false,
  467. editDisplay: false,
  468. viewDisplay: true,
  469. width: 180
  470. },
  471. {
  472. label: '更新时间',
  473. prop: 'updateTime',
  474. type: 'datetime',
  475. format: 'yyyy-MM-dd HH:mm:ss',
  476. valueFormat: 'yyyy-MM-dd HH:mm:ss',
  477. addDisplay: false,
  478. editDisplay: false,
  479. viewDisplay: true,
  480. width: 180
  481. }
  482. ]
  483. }
  484. }
  485. },
  486. methods: {
  487. /**
  488. * 加载客户选项列表
  489. * @param {string} [keyword=''] - 搜索关键词
  490. * @returns {Promise<void>}
  491. * @this {LeadComponent & Vue}
  492. */
  493. async loadCustomerOptions(keyword = '') {
  494. try {
  495. this.customerLoading = true
  496. const params = {}
  497. // 如果有搜索关键词,添加到查询参数中
  498. if (keyword.trim()) {
  499. params.Customer_CODE = keyword
  500. params.Customer_NAME = keyword
  501. }
  502. const response = await getCustomerList(1, 50, params)
  503. if (response.data && response.data.success) {
  504. const customers = response.data.data.records || []
  505. this.customerOptions = customers.map(customer => ({
  506. value: customer.Customer_ID,
  507. label: `${customer.Customer_CODE} - ${customer.Customer_NAME}`,
  508. customerCode: customer.Customer_CODE,
  509. customerName: customer.Customer_NAME,
  510. customerId: customer.Customer_ID
  511. }))
  512. // 更新表格配置中的选项数据
  513. this.updateCustomerOptionsInColumn()
  514. } else {
  515. this.customerOptions = []
  516. this.$message.warning('获取客户列表失败')
  517. }
  518. } catch (error) {
  519. this.customerOptions = []
  520. console.error('加载客户选项失败:', error)
  521. this.$message.error('加载客户选项失败,请稍后重试')
  522. } finally {
  523. this.customerLoading = false
  524. }
  525. },
  526. /**
  527. * 搜索客户(防抖处理)
  528. * @param {string} query - 搜索关键词
  529. * @returns {void}
  530. * @this {LeadComponent & Vue}
  531. */
  532. searchCustomers(query) {
  533. // 清除之前的定时器
  534. if (this.customerSearchTimer) {
  535. clearTimeout(this.customerSearchTimer)
  536. }
  537. // 设置新的定时器,300ms后执行搜索
  538. this.customerSearchTimer = setTimeout(() => {
  539. this.loadCustomerOptions(query)
  540. }, 300)
  541. },
  542. /**
  543. * 更新表格配置中的客户选项数据
  544. * @returns {void}
  545. * @this {LeadComponent & Vue}
  546. */
  547. updateCustomerOptionsInColumn() {
  548. const customerColumn = this.option.column.find(col => col.prop === 'customerId')
  549. if (customerColumn) {
  550. customerColumn.dicData = this.customerOptions
  551. }
  552. },
  553. /**
  554. * 清除客户相关数据
  555. * @returns {void}
  556. */
  557. clearCustomerData() {
  558. this.form.customerName = ''
  559. this.form.customerCode = ''
  560. this.form.customerId = ''
  561. },
  562. /**
  563. * 新增前的回调
  564. * @param {Function} done - 完成回调
  565. * @param {string} type - 操作类型
  566. * @this {LeadComponent & Vue}
  567. */
  568. async beforeOpen(done, type) {
  569. // 确保客户选项已加载
  570. if (this.customerOptions.length === 0) {
  571. await this.loadCustomerOptions()
  572. }
  573. if (['edit', 'view'].includes(type)) {
  574. try {
  575. const res = await getDetail(this.form.id)
  576. this.form = res.data.data
  577. // 处理客户数据回显
  578. if (this.form.customerId) {
  579. // 检查当前客户是否在选项列表中
  580. const existingCustomer = this.customerOptions.find(option =>
  581. option.value === this.form.customerId ||
  582. option.customerId === this.form.customerId.toString()
  583. )
  584. // 如果客户不在选项列表中,添加当前客户到选项列表
  585. if (!existingCustomer && this.form.customerCode && this.form.customerName) {
  586. const currentCustomerOption = {
  587. value: this.form.customerId,
  588. label: `${this.form.customerCode} - ${this.form.customerName}`,
  589. customerCode: this.form.customerCode,
  590. customerName: this.form.customerName,
  591. customerId: this.form.customerId.toString()
  592. }
  593. // 将当前客户添加到选项列表开头
  594. // this.customerOptions.unshift(currentCustomerOption)
  595. // 更新表格配置中的选项数据
  596. await this.updateCustomerOptionsInColumn()
  597. }
  598. // 选中当前客户
  599. this.form.customerId = existingCustomer ? existingCustomer.value : this.form.customerId
  600. this.form.customerCode = existingCustomer ? existingCustomer.customerCode : this.form.customerCode
  601. this.form.customerName = existingCustomer ? existingCustomer.customerName : this.form.customerName
  602. }
  603. } catch (error) {
  604. console.error('获取详情失败:', error)
  605. }
  606. } else if (type === 'add') {
  607. // 新增时清除客户相关数据
  608. this.clearCustomerData()
  609. }
  610. done()
  611. },
  612. /**
  613. * 获取状态文本
  614. * @param {number} status - 状态值
  615. * @returns {string} 状态文本
  616. */
  617. getStatusText(status) {
  618. return getLeadStatusLabel(status)
  619. },
  620. /**
  621. * 获取状态标签类型
  622. * @param {number} status - 状态值
  623. * @returns {string} 标签类型
  624. */
  625. getStatusType(status) {
  626. return getLeadStatusType(status)
  627. },
  628. /**
  629. * 获取优先级文本
  630. * @param {number} priority - 优先级值
  631. * @returns {string} 优先级文本
  632. */
  633. getPriorityText(priority) {
  634. return getLeadPriorityLabel(priority)
  635. },
  636. /**
  637. * 获取优先级标签类型
  638. * @param {number} priority - 优先级值
  639. * @returns {string} 标签类型
  640. */
  641. getPriorityType(priority) {
  642. return getLeadPriorityType(priority)
  643. },
  644. /**
  645. * 判断线索是否可以编辑
  646. * @param {number} status - 状态值
  647. * @returns {boolean} 是否可以编辑
  648. */
  649. canEditLead(status) {
  650. return isLeadEditable(status)
  651. },
  652. /**
  653. * 判断线索是否可以转化
  654. * @param {number} status - 状态值
  655. * @returns {boolean} 是否可以转化
  656. */
  657. canConvertLead(status) {
  658. return isLeadConvertible(status)
  659. },
  660. /**
  661. * 判断是否为高优先级线索
  662. * @param {number} priority - 优先级值
  663. * @returns {boolean} 是否为高优先级
  664. */
  665. isHighPriority(priority) {
  666. return isHighPriorityLead(priority)
  667. },
  668. /**
  669. * 判断线索是否处于活跃状态
  670. * @param {number} status - 状态值
  671. * @returns {boolean} 是否处于活跃状态
  672. */
  673. isActiveLead(status) {
  674. return isLeadActive(status)
  675. },
  676. /**
  677. * 判断是否逾期
  678. * @param {string} endTime - 截止时间
  679. * @param {number} status - 状态
  680. * @returns {boolean} 是否逾期
  681. */
  682. isOverdue(endTime, status) {
  683. if (status === 3) return false // 已关闭的不显示逾期
  684. const now = new Date()
  685. const end = new Date(endTime)
  686. return end < now
  687. },
  688. /**
  689. * 批量删除
  690. * @returns {Promise<void>}
  691. * @this {LeadComponent & Vue}
  692. */
  693. async handleDelete() {
  694. if (this.selectionList.length === 0) {
  695. this.$message.warning('请选择至少一条数据')
  696. return
  697. }
  698. try {
  699. await this.$confirm('确定将选择数据删除?', {
  700. confirmButtonText: '确定',
  701. cancelButtonText: '取消',
  702. type: 'warning'
  703. })
  704. // await remove(this.ids)
  705. await this.onLoad(this.page)
  706. this.$message({
  707. type: 'success',
  708. message: '操作成功!'
  709. })
  710. this.$refs.crud.toggleSelection()
  711. } catch (error) {
  712. console.error('批量删除失败:', error)
  713. }
  714. },
  715. /**
  716. * 更新操作
  717. * @param {LeadRecord} row - 行数据
  718. * @param {number} index - 行索引
  719. * @param {Function} done - 完成回调
  720. * @param {Function} loading - 加载回调
  721. * @returns {Promise<void>}
  722. * @this {LeadComponent & Vue}
  723. */
  724. async rowUpdate(row, index, done, loading) {
  725. try {
  726. await update(row)
  727. await this.onLoad(this.page)
  728. this.$message({
  729. type: 'success',
  730. message: '操作成功!'
  731. })
  732. done()
  733. } catch (error) {
  734. console.error('更新失败:', error)
  735. loading()
  736. }
  737. },
  738. /**
  739. * 新增操作
  740. * @param {LeadRecord} row - 行数据
  741. * @param {Function} done - 完成回调
  742. * @param {Function} loading - 加载回调
  743. * @returns {Promise<void>}
  744. * @this {LeadComponent & Vue}
  745. */
  746. async rowSave(row, done, loading) {
  747. try {
  748. await add(row)
  749. await this.onLoad(this.page)
  750. this.$message({
  751. type: 'success',
  752. message: '操作成功!'
  753. })
  754. done()
  755. } catch (error) {
  756. console.error('新增失败:', error)
  757. loading()
  758. }
  759. },
  760. /**
  761. * 获取数据
  762. * @param {Object} page - 分页信息
  763. * @param {LeadQueryParams} [params] - 查询参数
  764. * @returns {Promise<void>}
  765. * @this {LeadComponent & Vue}
  766. */
  767. async onLoad(page, params = {}) {
  768. this.loading = true
  769. try {
  770. // 处理时间范围查询
  771. const queryParams = { ...params }
  772. if (queryParams.endTime && Array.isArray(queryParams.endTime)) {
  773. queryParams.endTimeStart = queryParams.endTime[0]
  774. queryParams.endTimeEnd = queryParams.endTime[1]
  775. delete queryParams.endTime
  776. }
  777. const res = await getList(page.currentPage, page.pageSize, queryParams)
  778. const data = res.data.data
  779. this.data = data.records || []
  780. this.page.total = data.total || 0
  781. } catch (error) {
  782. console.error('获取数据失败:', error)
  783. this.$message.error('获取数据失败')
  784. } finally {
  785. this.loading = false
  786. }
  787. },
  788. /**
  789. * 搜索变更
  790. * @param {LeadQueryParams} params - 查询参数
  791. * @param {Function} done - 完成回调
  792. * @this {LeadComponent & Vue}
  793. */
  794. searchChange(params, done) {
  795. this.query = params
  796. this.onLoad(this.page, params)
  797. done()
  798. },
  799. /**
  800. * 搜索重置
  801. * @param {Function} done - 完成回调
  802. * @this {LeadComponent & Vue}
  803. */
  804. searchReset(done) {
  805. this.query = {}
  806. this.onLoad(this.page)
  807. done()
  808. },
  809. /**
  810. * 选择变更
  811. * @param {LeadRecord[]} list - 选中的数据
  812. * @this {LeadComponent & Vue}
  813. */
  814. selectionChange(list) {
  815. this.selectionList = list
  816. },
  817. /**
  818. * 清空选择
  819. * @this {LeadComponent & Vue}
  820. */
  821. selectionClear() {
  822. this.selectionList = []
  823. },
  824. /**
  825. * 当前页变更
  826. * @param {number} currentPage - 当前页
  827. * @this {LeadComponent & Vue}
  828. */
  829. currentChange(currentPage) {
  830. this.page.currentPage = currentPage
  831. },
  832. /**
  833. * 页大小变更
  834. * @param {number} pageSize - 页大小
  835. * @this {LeadComponent & Vue}
  836. */
  837. sizeChange(pageSize) {
  838. this.page.pageSize = pageSize
  839. },
  840. /**
  841. * 刷新变更
  842. * @this {LeadComponent & Vue}
  843. */
  844. refreshChange() {
  845. this.onLoad(this.page, this.query)
  846. },
  847. /**
  848. * 查看线索详细信息
  849. * @param {LeadRecord} row - 行数据
  850. * @returns {Promise<void>}
  851. * @this {LeadComponent & Vue}
  852. */
  853. async handleViewDetail(row) {
  854. this.currentLead = row
  855. this.detailDialogVisible = true
  856. this.detailPage.currentPage = 1
  857. await this.detailOnLoad(this.detailPage)
  858. },
  859. /**
  860. * 查看详情
  861. * @param {LeadRecord} row - 行数据
  862. * @returns {void}
  863. * @this {LeadComponent & Vue}
  864. */
  865. handleView(row) {
  866. this.form = { ...row }
  867. this.$refs.crud.rowView(row)
  868. },
  869. /**
  870. * 编辑
  871. * @param {LeadRecord} row - 行数据
  872. * @returns {void}
  873. * @this {LeadComponent & Vue}
  874. */
  875. handleEdit(row) {
  876. this.form = { ...row }
  877. this.$refs.crud.rowEdit(row)
  878. },
  879. /**
  880. * 删除单行
  881. * @param {LeadRecord} row - 行数据
  882. * @returns {Promise<void>}
  883. * @this {LeadComponent & Vue}
  884. */
  885. async handleRowDelete(row) {
  886. try {
  887. await this.$confirm(`确定删除线索 "${row.title}" 吗?`, '提示', {
  888. confirmButtonText: '确定',
  889. cancelButtonText: '取消',
  890. type: 'warning'
  891. })
  892. // const res = await remove(row.id)
  893. // if (res.data && res.data.success) {
  894. // this.$message.success('删除成功')
  895. // this.onLoad(this.page)
  896. // } else {
  897. // this.$message.error(res.data ? res.data.msg : '删除失败')
  898. // }
  899. } catch (error) {
  900. if (error !== 'cancel') {
  901. console.error('删除失败:', error)
  902. this.$message.error('删除失败,请稍后重试')
  903. }
  904. }
  905. },
  906. // ========== 详细信息相关方法 ==========
  907. /**
  908. * 详细信息数据加载
  909. * @param {Object} page - 分页参数
  910. * @param {Object} params - 查询参数
  911. * @returns {Promise<void>}
  912. * @this {LeadComponent & Vue}
  913. */
  914. async detailOnLoad(page, params = {}) {
  915. if (!this.currentLead) return
  916. this.detailLoading = true
  917. try {
  918. const queryParams = {
  919. ...params,
  920. leadId: this.currentLead.id
  921. }
  922. const res = await getDetailList(page.currentPage, page.pageSize, queryParams)
  923. if (res.data && res.data.success) {
  924. const data = res.data.data
  925. this.detailData = data.records || []
  926. this.detailPage.total = data.total || 0
  927. } else {
  928. this.detailData = []
  929. this.detailPage.total = 0
  930. this.$message.error(res.data ? res.data.msg : '获取详细信息失败')
  931. }
  932. } catch (error) {
  933. this.detailData = []
  934. this.detailPage.total = 0
  935. console.error('获取详细信息失败:', error)
  936. this.$message.error('获取详细信息失败,请稍后重试')
  937. } finally {
  938. this.detailLoading = false
  939. }
  940. },
  941. /**
  942. * 详细信息新增前的回调
  943. * @param {Function} done - 完成回调
  944. * @param {string} type - 操作类型
  945. * @returns {Promise<void>}
  946. * @this {LeadComponent & Vue}
  947. */
  948. async detailBeforeOpen(done, type) {
  949. if (['edit', 'view'].includes(type)) {
  950. try {
  951. const res = await getDetailDetail(this.detailForm.id)
  952. if (res.data && res.data.success) {
  953. this.detailForm = res.data.data
  954. } else {
  955. this.$message.error('获取详情失败')
  956. return
  957. }
  958. } catch (error) {
  959. console.error('获取详情失败:', error)
  960. this.$message.error('获取详情失败')
  961. return
  962. }
  963. } else if (type === 'add') {
  964. // 新增时设置线索ID
  965. this.detailForm.leadId = this.currentLead.id
  966. }
  967. done()
  968. },
  969. /**
  970. * 详细信息新增
  971. * @param {LeadDetailRecord} row - 表单数据
  972. * @param {Function} done - 完成回调
  973. * @param {Function} loading - 加载状态回调
  974. * @returns {Promise<void>}
  975. * @this {LeadComponent & Vue}
  976. */
  977. async detailRowSave(row, done, loading) {
  978. try {
  979. loading()
  980. const params = {
  981. leadId: this.currentLead.id,
  982. detailText: row.detailText
  983. }
  984. const res = await addDetail(params)
  985. if (res.data && res.data.success) {
  986. this.$message.success('添加成功')
  987. this.detailOnLoad(this.detailPage)
  988. done()
  989. } else {
  990. this.$message.error(res.data ? res.data.msg : '添加失败')
  991. loading()
  992. }
  993. } catch (error) {
  994. console.error('添加失败:', error)
  995. this.$message.error('添加失败,请稍后重试')
  996. loading()
  997. }
  998. },
  999. /**
  1000. * 详细信息修改
  1001. * @param {LeadDetailRecord} row - 表单数据
  1002. * @param {number} index - 行索引
  1003. * @param {Function} done - 完成回调
  1004. * @param {Function} loading - 加载状态回调
  1005. * @returns {Promise<void>}
  1006. * @this {LeadComponent & Vue}
  1007. */
  1008. async detailRowUpdate(row, index, done, loading) {
  1009. try {
  1010. loading()
  1011. const params = {
  1012. id: row.id,
  1013. leadId: this.currentLead.id,
  1014. detailText: row.detailText
  1015. }
  1016. const res = await updateDetail(params)
  1017. if (res.data && res.data.success) {
  1018. this.$message.success('修改成功')
  1019. this.detailOnLoad(this.detailPage)
  1020. done()
  1021. } else {
  1022. this.$message.error(res.data ? res.data.msg : '修改失败')
  1023. loading()
  1024. }
  1025. } catch (error) {
  1026. console.error('修改失败:', error)
  1027. this.$message.error('修改失败,请稍后重试')
  1028. loading()
  1029. }
  1030. },
  1031. /**
  1032. * 详细信息删除
  1033. * @param {LeadDetailRecord} row - 行数据
  1034. * @param {number} index - 行索引
  1035. * @returns {Promise<void>}
  1036. * @this {LeadComponent & Vue}
  1037. */
  1038. async detailRowDel(row, index) {
  1039. try {
  1040. const res = await removeDetail(row.id)
  1041. if (res.data && res.data.success) {
  1042. this.$message.success('删除成功')
  1043. this.detailOnLoad(this.detailPage)
  1044. } else {
  1045. this.$message.error(res.data ? res.data.msg : '删除失败')
  1046. }
  1047. } catch (error) {
  1048. console.error('删除失败:', error)
  1049. this.$message.error('删除失败,请稍后重试')
  1050. }
  1051. },
  1052. /**
  1053. * 详细信息选择变更
  1054. * @param {LeadDetailRecord[]} list - 选中的数据列表
  1055. * @returns {void}
  1056. * @this {LeadComponent & Vue}
  1057. */
  1058. detailSelectionChange(list) {
  1059. this.detailSelectionList = list
  1060. },
  1061. /**
  1062. * 详细信息当前页变更
  1063. * @param {number} currentPage - 当前页码
  1064. * @returns {void}
  1065. * @this {LeadComponent & Vue}
  1066. */
  1067. detailCurrentChange(currentPage) {
  1068. this.detailPage.currentPage = currentPage
  1069. },
  1070. /**
  1071. * 详细信息页大小变更
  1072. * @param {number} pageSize - 页大小
  1073. * @returns {void}
  1074. * @this {LeadComponent & Vue}
  1075. */
  1076. detailSizeChange(pageSize) {
  1077. this.detailPage.pageSize = pageSize
  1078. },
  1079. /**
  1080. * 详细信息刷新变更
  1081. * @returns {void}
  1082. * @this {LeadComponent & Vue}
  1083. */
  1084. detailRefreshChange() {
  1085. this.detailOnLoad(this.detailPage)
  1086. },
  1087. /**
  1088. * 批量删除详细信息
  1089. * @returns {Promise<void>}
  1090. * @this {LeadComponent & Vue}
  1091. */
  1092. async handleDetailDelete() {
  1093. if (this.detailSelectionList.length === 0) {
  1094. this.$message.warning('请选择要删除的数据')
  1095. return
  1096. }
  1097. try {
  1098. await this.$confirm('确定删除选中的详细信息吗?', '提示', {
  1099. confirmButtonText: '确定',
  1100. cancelButtonText: '取消',
  1101. type: 'warning'
  1102. })
  1103. const res = await removeDetail(this.detailIds)
  1104. if (res.data && res.data.success) {
  1105. this.$message.success('删除成功')
  1106. this.detailOnLoad(this.detailPage)
  1107. this.$refs.detailCrud.toggleSelection()
  1108. } else {
  1109. this.$message.error(res.data ? res.data.msg : '删除失败')
  1110. }
  1111. } catch (error) {
  1112. if (error !== 'cancel') {
  1113. console.error('删除失败:', error)
  1114. this.$message.error('删除失败,请稍后重试')
  1115. }
  1116. }
  1117. }
  1118. },
  1119. mounted() {
  1120. // 初始化加载客户选项
  1121. this.loadCustomerOptions()
  1122. },
  1123. beforeDestroy() {
  1124. // 清理定时器
  1125. if (this.customerSearchTimer) {
  1126. clearTimeout(this.customerSearchTimer)
  1127. }
  1128. }
  1129. }