// @ts-check import { getList, add, update, getDetail } from '@/api/order/lead' import { getList as getDetailList, add as addDetail, update as updateDetail, remove as removeDetail, getDetail as getDetailDetail } from '@/api/order/lead-detail' import { getCustomerList } from '@/api/common/index' import { mapGetters } from 'vuex' import { LEAD_PRIORITY, LEAD_STATUS, LEAD_PRIORITY_OPTIONS, LEAD_STATUS_OPTIONS, getLeadPriorityLabel, getLeadPriorityType, getLeadPriorityColor, getLeadStatusLabel, getLeadStatusType, getLeadStatusColor, isValidLeadPriority, isValidLeadStatus, isHighPriorityLead, isLowPriorityLead, isLeadEditable, isLeadConvertible, isLeadCompleted, isLeadActive, LEAD_SOURCE_OPTIONS } from '@/constants/lead' /** * @typedef {import('../types').LeadComponent} LeadComponent * @typedef {import('../types').LeadRecord} LeadRecord * @typedef {import('../types').LeadDetailRecord} LeadDetailRecord * @typedef {import('../types').LeadQueryParams} LeadQueryParams * @typedef {import('../types').CustomerOption} CustomerOption * @typedef {import('../types').PageInfo} PageInfo */ /** * 销售线索查询参数类型定义 * @typedef {Object} LeadQueryParams * @property {string} [leadCode] - 线索编码 * @property {string} [customerCode] - 客户编码 * @property {string} [customerName] - 客户名称 * @property {string} [contactName] - 联系人姓名 * @property {string} [contactPhone] - 联系人电话 * @property {string} [title] - 线索标题 * @property {number} [status] - 状态 * @property {number} [priority] - 优先级 * @property {string} [source] - 线索来源 * @property {string} [groupName] - 分组名称 * @property {string[]} [endTime] - 截止时间范围 */ /** * 销售线索详细信息记录类型定义 * @typedef {Object} LeadDetailRecord * @property {string} id - 详细信息ID * @property {string} leadId - 线索ID * @property {string} detailText - 详细信息内容 * @property {string} createTime - 创建时间 * @property {string} updateTime - 更新时间 */ /** * @type {LeadComponent & Vue} */ export default { computed: { ...mapGetters(['permission', 'userInfo']), /** * 权限列表 * @returns {Object} 权限配置 */ permissionList() { return { // addBtn: this.vaildData(this.permission.order_lead_add, false), // viewBtn: this.vaildData(this.permission.order_lead_view, false), // delBtn: this.vaildData(this.permission.order_lead_delete, false), // editBtn: this.vaildData(this.permission.order_lead_edit, false) addBtn: true, viewBtn: true, delBtn: false, editBtn: true, } }, /** * 选中数据的ID列表 * @returns {string[]} ID列表 */ ids() { return this.selectionList.map(ele => ele.id) }, /** * 详细信息选中数据的ID列表 * @returns {string[]} ID列表 */ detailIds() { return this.detailSelectionList.map(ele => ele.id) } }, data() { return { /** * 详细信息表格配置 */ detailOption: { height: 'auto', calcHeight: 30, tip: false, searchShow: false, border: true, index: true, viewBtn: true, selection: true, dialogClickModal: false, column: [ { label: '详细信息内容', prop: 'detailText', type: 'textarea', span: 24, minRows: 4, maxRows: 8, rules: [{ required: true, message: '请输入详细信息内容', trigger: ['blur', 'change'] }, { type: 'string', min: 10, message: '详细信息内容至少10个字符', trigger: ['blur', 'change'] }], overHidden: true, width: 400 }, { label: '创建时间', prop: 'createTime', type: 'datetime', format: 'yyyy-MM-dd HH:mm:ss', valueFormat: 'yyyy-MM-dd HH:mm:ss', addDisplay: false, editDisplay: false, viewDisplay: true, width: 180 }, { label: '更新时间', prop: 'updateTime', type: 'datetime', format: 'yyyy-MM-dd HH:mm:ss', valueFormat: 'yyyy-MM-dd HH:mm:ss', addDisplay: false, editDisplay: false, viewDisplay: true, width: 180 } ] }, /** * 详细信息权限列表 */ detailPermissionList: { addBtn: true, viewBtn: true, delBtn: false, editBtn: true, }, /** * 详细信息分页信息 * @type {{total: number, currentPage: number, pageSize: number}} */ detailPage: { total: 0, currentPage: 1, pageSize: 10 }, /** * 详细信息加载状态 * @type {boolean} */ detailLoading: false, /** * 详细信息弹窗显示状态 * @type {boolean} */ detailDialogVisible: false, /** * 当前选中的线索 * @type {LeadRecord|null} */ currentLead: null, /** * 详细信息表格数据 * @type {LeadDetailRecord[]} */ detailData: [], /** * 详细信息表单数据 * @type {LeadDetailRecord} */ detailForm: {}, /** * 详细信息选中的数据列表 * @type {LeadDetailRecord[]} */ detailSelectionList: [], /** * 表格数据 * @type {LeadRecord[]} */ data: [], /** * 查询参数 * @type {LeadQueryParams} */ query: {}, /** * 加载状态 * @type {boolean} */ loading: true, /** * 表单数据 * @type {LeadRecord} */ form: {}, /** * 选中的数据列表 * @type {LeadRecord[]} */ selectionList: [], /** * 分页信息 * @type {{total: number, currentPage: number, pageSize: number}} */ page: { total: 0, currentPage: 1, pageSize: 10 }, /** * 客户选项列表 * @type {{label: string, value: string, customerCode: string, customerName: string, customerId: string}[]} */ customerOptions: [], /** * 客户选项加载状态 * @type {boolean} */ customerLoading: false, /** * 客户搜索关键词 * @type {string} */ customerSearchKeyword: '', /** * 客户搜索定时器 * @type {number|null} */ customerSearchTimer: null, /** * 表格配置 */ option: { height: 'auto', calcHeight: 30, tip: false, searchShow: true, searchMenuSpan: 6, border: true, index: true, viewBtn: true, selection: true, dialogClickModal: false, column: [ { label: '线索编码', prop: 'leadCode', rules: [{ required: true, message: '请输入线索编码', trigger: 'blur' }], search: true }, { label: '客户', prop: 'customerId', type: 'select', search: false, width: 200, filterable: true, remote: false, reserveKeyword: true, placeholder: '请选择客户', dicData: [], props: { label: 'label', value: 'value' }, rules: [{ required: true, message: '请选择客户', trigger: 'change' }], /** * 客户选择变更事件 * @param {Object} param - 事件参数 * @param {string} param.value - 选中的客户ID * @param {Object} param.column - 列配置 * @returns {void} */ change: ({ value, column }) => { if (value) { const selectedCustomer = this.customerOptions.find(option => option.value === value) if (selectedCustomer) { // 自动填充客户信息 this.form.customerId = selectedCustomer.customerId this.form.customerCode = selectedCustomer.customerCode this.form.customerName = selectedCustomer.customerName } else { this.clearCustomerData() } } else { this.clearCustomerData() } }, /** * 远程搜索方法 * @param {string} query - 搜索关键词 * @returns {void} */ remoteMethod: (query) => { this.searchCustomers(query) } }, { label: '客户编码', prop: 'customerCode', search: true, addDisplay: false, editDisplay: false, width: 120 }, { label: '客户名称', prop: 'customerName', search: true, addDisplay: false, editDisplay: false, width: 150 }, { label: '联系人', prop: 'contactName', rules: [{ required: true, message: '请输入联系人姓名', trigger: 'blur' }], search: true }, { label: '联系电话', prop: 'contactPhone', rules: [{ required: true, message: '请输入联系电话', trigger: 'blur' }, { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }], search: true }, { label: '线索标题', prop: 'title', rules: [{ required: true, message: '请输入线索标题', trigger: 'blur' }], search: true, overHidden: true }, { label: '截止时间', prop: 'endTime', type: 'datetime', format: 'yyyy-MM-dd HH:mm:ss', valueFormat: 'yyyy-MM-dd HH:mm:ss', rules: [{ required: true, message: '请选择截止时间', trigger: 'blur' }], search: true, searchRange: true, slot: true }, { label: '优先级', prop: 'priority', type: 'select', dicData: LEAD_PRIORITY_OPTIONS, rules: [{ required: true, message: '请选择优先级', trigger: 'blur' }], search: true, slot: true }, { label: '线索来源', prop: 'source', type: 'select', filterable: true, allowCreate: true, clearable: true, placeholder: '请选择或输入线索来源', dicData: LEAD_SOURCE_OPTIONS, rules: [{ required: true, message: '请选择或输入线索来源', trigger: 'blur' }], search: true }, { label: '分组名称', prop: 'groupName', rules: [{ required: true, message: '请输入分组名称', trigger: 'blur' }], search: true }, { label: '状态', prop: 'status', type: 'select', dicData: LEAD_STATUS_OPTIONS, rules: [{ required: true, message: '请选择状态', trigger: 'blur' }], search: true, slot: true }, { label: '关闭原因', prop: 'closeReason', type: 'textarea', span: 24, hide: true, viewDisplay: true }, { label: '创建时间', prop: 'createTime', type: 'datetime', format: 'yyyy-MM-dd HH:mm:ss', valueFormat: 'yyyy-MM-dd HH:mm:ss', addDisplay: false, editDisplay: false, viewDisplay: true, width: 180 }, { label: '更新时间', prop: 'updateTime', type: 'datetime', format: 'yyyy-MM-dd HH:mm:ss', valueFormat: 'yyyy-MM-dd HH:mm:ss', addDisplay: false, editDisplay: false, viewDisplay: true, width: 180 } ] } } }, methods: { /** * 加载客户选项列表 * @param {string} [keyword=''] - 搜索关键词 * @returns {Promise} * @this {LeadComponent & Vue} */ async loadCustomerOptions(keyword = '') { try { this.customerLoading = true const params = {} // 如果有搜索关键词,添加到查询参数中 if (keyword.trim()) { params.Customer_CODE = keyword params.Customer_NAME = keyword } const response = await getCustomerList(1, 50, params) if (response.data && response.data.success) { const customers = response.data.data.records || [] this.customerOptions = customers.map(customer => ({ value: customer.Customer_ID, label: `${customer.Customer_CODE} - ${customer.Customer_NAME}`, customerCode: customer.Customer_CODE, customerName: customer.Customer_NAME, customerId: customer.Customer_ID })) // 更新表格配置中的选项数据 this.updateCustomerOptionsInColumn() } else { this.customerOptions = [] this.$message.warning('获取客户列表失败') } } catch (error) { this.customerOptions = [] console.error('加载客户选项失败:', error) this.$message.error('加载客户选项失败,请稍后重试') } finally { this.customerLoading = false } }, /** * 搜索客户(防抖处理) * @param {string} query - 搜索关键词 * @returns {void} * @this {LeadComponent & Vue} */ searchCustomers(query) { // 清除之前的定时器 if (this.customerSearchTimer) { clearTimeout(this.customerSearchTimer) } // 设置新的定时器,300ms后执行搜索 this.customerSearchTimer = setTimeout(() => { this.loadCustomerOptions(query) }, 300) }, /** * 更新表格配置中的客户选项数据 * @returns {void} * @this {LeadComponent & Vue} */ updateCustomerOptionsInColumn() { const customerColumn = this.option.column.find(col => col.prop === 'customerId') if (customerColumn) { customerColumn.dicData = this.customerOptions } }, /** * 清除客户相关数据 * @returns {void} */ clearCustomerData() { this.form.customerName = '' this.form.customerCode = '' this.form.customerId = '' }, /** * 新增前的回调 * @param {Function} done - 完成回调 * @param {string} type - 操作类型 * @this {LeadComponent & Vue} */ async beforeOpen(done, type) { // 确保客户选项已加载 if (this.customerOptions.length === 0) { await this.loadCustomerOptions() } if (['edit', 'view'].includes(type)) { try { const res = await getDetail(this.form.id) this.form = res.data.data // 处理客户数据回显 if (this.form.customerId) { // 检查当前客户是否在选项列表中 const existingCustomer = this.customerOptions.find(option => option.value === this.form.customerId || option.customerId === this.form.customerId.toString() ) // 如果客户不在选项列表中,添加当前客户到选项列表 if (!existingCustomer && this.form.customerCode && this.form.customerName) { const currentCustomerOption = { value: this.form.customerId, label: `${this.form.customerCode} - ${this.form.customerName}`, customerCode: this.form.customerCode, customerName: this.form.customerName, customerId: this.form.customerId.toString() } // 将当前客户添加到选项列表开头 // this.customerOptions.unshift(currentCustomerOption) // 更新表格配置中的选项数据 await this.updateCustomerOptionsInColumn() } // 选中当前客户 this.form.customerId = existingCustomer ? existingCustomer.value : this.form.customerId this.form.customerCode = existingCustomer ? existingCustomer.customerCode : this.form.customerCode this.form.customerName = existingCustomer ? existingCustomer.customerName : this.form.customerName } } catch (error) { console.error('获取详情失败:', error) } } else if (type === 'add') { // 新增时清除客户相关数据 this.clearCustomerData() } done() }, /** * 获取状态文本 * @param {number} status - 状态值 * @returns {string} 状态文本 */ getStatusText(status) { return getLeadStatusLabel(status) }, /** * 获取状态标签类型 * @param {number} status - 状态值 * @returns {string} 标签类型 */ getStatusType(status) { return getLeadStatusType(status) }, /** * 获取优先级文本 * @param {number} priority - 优先级值 * @returns {string} 优先级文本 */ getPriorityText(priority) { return getLeadPriorityLabel(priority) }, /** * 获取优先级标签类型 * @param {number} priority - 优先级值 * @returns {string} 标签类型 */ getPriorityType(priority) { return getLeadPriorityType(priority) }, /** * 判断线索是否可以编辑 * @param {number} status - 状态值 * @returns {boolean} 是否可以编辑 */ canEditLead(status) { return isLeadEditable(status) }, /** * 判断线索是否可以转化 * @param {number} status - 状态值 * @returns {boolean} 是否可以转化 */ canConvertLead(status) { return isLeadConvertible(status) }, /** * 判断是否为高优先级线索 * @param {number} priority - 优先级值 * @returns {boolean} 是否为高优先级 */ isHighPriority(priority) { return isHighPriorityLead(priority) }, /** * 判断线索是否处于活跃状态 * @param {number} status - 状态值 * @returns {boolean} 是否处于活跃状态 */ isActiveLead(status) { return isLeadActive(status) }, /** * 判断是否逾期 * @param {string} endTime - 截止时间 * @param {number} status - 状态 * @returns {boolean} 是否逾期 */ isOverdue(endTime, status) { if (status === 3) return false // 已关闭的不显示逾期 const now = new Date() const end = new Date(endTime) return end < now }, /** * 批量删除 * @returns {Promise} * @this {LeadComponent & Vue} */ async handleDelete() { if (this.selectionList.length === 0) { this.$message.warning('请选择至少一条数据') return } try { await this.$confirm('确定将选择数据删除?', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }) // await remove(this.ids) await this.onLoad(this.page) this.$message({ type: 'success', message: '操作成功!' }) this.$refs.crud.toggleSelection() } catch (error) { console.error('批量删除失败:', error) } }, /** * 更新操作 * @param {LeadRecord} row - 行数据 * @param {number} index - 行索引 * @param {Function} done - 完成回调 * @param {Function} loading - 加载回调 * @returns {Promise} * @this {LeadComponent & Vue} */ async rowUpdate(row, index, done, loading) { try { await update(row) await this.onLoad(this.page) this.$message({ type: 'success', message: '操作成功!' }) done() } catch (error) { console.error('更新失败:', error) loading() } }, /** * 新增操作 * @param {LeadRecord} row - 行数据 * @param {Function} done - 完成回调 * @param {Function} loading - 加载回调 * @returns {Promise} * @this {LeadComponent & Vue} */ async rowSave(row, done, loading) { try { await add(row) await this.onLoad(this.page) this.$message({ type: 'success', message: '操作成功!' }) done() } catch (error) { console.error('新增失败:', error) loading() } }, /** * 获取数据 * @param {Object} page - 分页信息 * @param {LeadQueryParams} [params] - 查询参数 * @returns {Promise} * @this {LeadComponent & Vue} */ async onLoad(page, params = {}) { this.loading = true try { // 处理时间范围查询 const queryParams = { ...params } if (queryParams.endTime && Array.isArray(queryParams.endTime)) { queryParams.endTimeStart = queryParams.endTime[0] queryParams.endTimeEnd = queryParams.endTime[1] delete queryParams.endTime } const res = await getList(page.currentPage, page.pageSize, queryParams) const data = res.data.data this.data = data.records || [] this.page.total = data.total || 0 } catch (error) { console.error('获取数据失败:', error) this.$message.error('获取数据失败') } finally { this.loading = false } }, /** * 搜索变更 * @param {LeadQueryParams} params - 查询参数 * @param {Function} done - 完成回调 * @this {LeadComponent & Vue} */ searchChange(params, done) { this.query = params this.onLoad(this.page, params) done() }, /** * 搜索重置 * @param {Function} done - 完成回调 * @this {LeadComponent & Vue} */ searchReset(done) { this.query = {} this.onLoad(this.page) done() }, /** * 选择变更 * @param {LeadRecord[]} list - 选中的数据 * @this {LeadComponent & Vue} */ selectionChange(list) { this.selectionList = list }, /** * 清空选择 * @this {LeadComponent & Vue} */ selectionClear() { this.selectionList = [] }, /** * 当前页变更 * @param {number} currentPage - 当前页 * @this {LeadComponent & Vue} */ currentChange(currentPage) { this.page.currentPage = currentPage }, /** * 页大小变更 * @param {number} pageSize - 页大小 * @this {LeadComponent & Vue} */ sizeChange(pageSize) { this.page.pageSize = pageSize }, /** * 刷新变更 * @this {LeadComponent & Vue} */ refreshChange() { this.onLoad(this.page, this.query) }, /** * 查看线索详细信息 * @param {LeadRecord} row - 行数据 * @returns {Promise} * @this {LeadComponent & Vue} */ async handleViewDetail(row) { this.currentLead = row this.detailDialogVisible = true this.detailPage.currentPage = 1 await this.detailOnLoad(this.detailPage) }, /** * 查看详情 * @param {LeadRecord} row - 行数据 * @returns {void} * @this {LeadComponent & Vue} */ handleView(row) { this.form = { ...row } this.$refs.crud.rowView(row) }, /** * 编辑 * @param {LeadRecord} row - 行数据 * @returns {void} * @this {LeadComponent & Vue} */ handleEdit(row) { this.form = { ...row } this.$refs.crud.rowEdit(row) }, /** * 删除单行 * @param {LeadRecord} row - 行数据 * @returns {Promise} * @this {LeadComponent & Vue} */ async handleRowDelete(row) { try { await this.$confirm(`确定删除线索 "${row.title}" 吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }) // const res = await remove(row.id) // if (res.data && res.data.success) { // this.$message.success('删除成功') // this.onLoad(this.page) // } else { // this.$message.error(res.data ? res.data.msg : '删除失败') // } } catch (error) { if (error !== 'cancel') { console.error('删除失败:', error) this.$message.error('删除失败,请稍后重试') } } }, // ========== 详细信息相关方法 ========== /** * 详细信息数据加载 * @param {Object} page - 分页参数 * @param {Object} params - 查询参数 * @returns {Promise} * @this {LeadComponent & Vue} */ async detailOnLoad(page, params = {}) { if (!this.currentLead) return this.detailLoading = true try { const queryParams = { ...params, leadId: this.currentLead.id } const res = await getDetailList(page.currentPage, page.pageSize, queryParams) if (res.data && res.data.success) { const data = res.data.data this.detailData = data.records || [] this.detailPage.total = data.total || 0 } else { this.detailData = [] this.detailPage.total = 0 this.$message.error(res.data ? res.data.msg : '获取详细信息失败') } } catch (error) { this.detailData = [] this.detailPage.total = 0 console.error('获取详细信息失败:', error) this.$message.error('获取详细信息失败,请稍后重试') } finally { this.detailLoading = false } }, /** * 详细信息新增前的回调 * @param {Function} done - 完成回调 * @param {string} type - 操作类型 * @returns {Promise} * @this {LeadComponent & Vue} */ async detailBeforeOpen(done, type) { if (['edit', 'view'].includes(type)) { try { const res = await getDetailDetail(this.detailForm.id) if (res.data && res.data.success) { this.detailForm = res.data.data } else { this.$message.error('获取详情失败') return } } catch (error) { console.error('获取详情失败:', error) this.$message.error('获取详情失败') return } } else if (type === 'add') { // 新增时设置线索ID this.detailForm.leadId = this.currentLead.id } done() }, /** * 详细信息新增 * @param {LeadDetailRecord} row - 表单数据 * @param {Function} done - 完成回调 * @param {Function} loading - 加载状态回调 * @returns {Promise} * @this {LeadComponent & Vue} */ async detailRowSave(row, done, loading) { try { loading() const params = { leadId: this.currentLead.id, detailText: row.detailText } const res = await addDetail(params) if (res.data && res.data.success) { this.$message.success('添加成功') this.detailOnLoad(this.detailPage) done() } else { this.$message.error(res.data ? res.data.msg : '添加失败') loading() } } catch (error) { console.error('添加失败:', error) this.$message.error('添加失败,请稍后重试') loading() } }, /** * 详细信息修改 * @param {LeadDetailRecord} row - 表单数据 * @param {number} index - 行索引 * @param {Function} done - 完成回调 * @param {Function} loading - 加载状态回调 * @returns {Promise} * @this {LeadComponent & Vue} */ async detailRowUpdate(row, index, done, loading) { try { loading() const params = { id: row.id, leadId: this.currentLead.id, detailText: row.detailText } const res = await updateDetail(params) if (res.data && res.data.success) { this.$message.success('修改成功') this.detailOnLoad(this.detailPage) done() } else { this.$message.error(res.data ? res.data.msg : '修改失败') loading() } } catch (error) { console.error('修改失败:', error) this.$message.error('修改失败,请稍后重试') loading() } }, /** * 详细信息删除 * @param {LeadDetailRecord} row - 行数据 * @param {number} index - 行索引 * @returns {Promise} * @this {LeadComponent & Vue} */ async detailRowDel(row, index) { try { const res = await removeDetail(row.id) if (res.data && res.data.success) { this.$message.success('删除成功') this.detailOnLoad(this.detailPage) } else { this.$message.error(res.data ? res.data.msg : '删除失败') } } catch (error) { console.error('删除失败:', error) this.$message.error('删除失败,请稍后重试') } }, /** * 详细信息选择变更 * @param {LeadDetailRecord[]} list - 选中的数据列表 * @returns {void} * @this {LeadComponent & Vue} */ detailSelectionChange(list) { this.detailSelectionList = list }, /** * 详细信息当前页变更 * @param {number} currentPage - 当前页码 * @returns {void} * @this {LeadComponent & Vue} */ detailCurrentChange(currentPage) { this.detailPage.currentPage = currentPage }, /** * 详细信息页大小变更 * @param {number} pageSize - 页大小 * @returns {void} * @this {LeadComponent & Vue} */ detailSizeChange(pageSize) { this.detailPage.pageSize = pageSize }, /** * 详细信息刷新变更 * @returns {void} * @this {LeadComponent & Vue} */ detailRefreshChange() { this.detailOnLoad(this.detailPage) }, /** * 批量删除详细信息 * @returns {Promise} * @this {LeadComponent & Vue} */ async handleDetailDelete() { if (this.detailSelectionList.length === 0) { this.$message.warning('请选择要删除的数据') return } try { await this.$confirm('确定删除选中的详细信息吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }) const res = await removeDetail(this.detailIds) if (res.data && res.data.success) { this.$message.success('删除成功') this.detailOnLoad(this.detailPage) this.$refs.detailCrud.toggleSelection() } else { this.$message.error(res.data ? res.data.msg : '删除失败') } } catch (error) { if (error !== 'cancel') { console.error('删除失败:', error) this.$message.error('删除失败,请稍后重试') } } } }, mounted() { // 初始化加载客户选项 this.loadCustomerOptions() }, beforeDestroy() { // 清理定时器 if (this.customerSearchTimer) { clearTimeout(this.customerSearchTimer) } } }