Bläddra i källkod

feat(销售线索): 新增销售线索管理模块

yz 1 månad sedan
förälder
incheckning
15de6701fa
3 ändrade filer med 793 tillägg och 0 borttagningar
  1. 138 0
      src/api/order/lead.js
  2. 23 0
      src/router/views/index.js
  3. 632 0
      src/views/order/lead/index.vue

+ 138 - 0
src/api/order/lead.js

@@ -0,0 +1,138 @@
+import request from '@/router/axios'
+
+/**
+ * 销售线索记录类型定义
+ * @typedef {Object} LeadRecord
+ * @property {string} id - 线索ID
+ * @property {string} createUser - 创建用户ID
+ * @property {string} createDept - 创建部门ID
+ * @property {string} createTime - 创建时间
+ * @property {string} updateUser - 更新用户ID
+ * @property {string} updateTime - 更新时间
+ * @property {number} status - 状态 1-新建 2-跟进中 3-已关闭
+ * @property {number} isDeleted - 是否删除 0-未删除 1-已删除
+ * @property {string} leadCode - 线索编码
+ * @property {number} customerId - 客户ID
+ * @property {string} customerCode - 客户编码
+ * @property {string} customerName - 客户名称
+ * @property {string} contactName - 联系人姓名
+ * @property {string} contactPhone - 联系人电话
+ * @property {string} title - 线索标题
+ * @property {string} endTime - 截止时间
+ * @property {number} priority - 优先级 1-高 2-中 3-低
+ * @property {string} source - 线索来源
+ * @property {string} groupName - 分组名称
+ * @property {string|null} closeReason - 关闭原因
+ */
+
+/**
+ * 销售线索查询参数类型定义
+ * @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] - 截止时间范围
+ * @property {string} [endTimeStart] - 截止时间开始
+ * @property {string} [endTimeEnd] - 截止时间结束
+ */
+
+/**
+ * 销售线索新增参数类型定义
+ * @typedef {Object} LeadAddParams
+ * @property {string} leadCode - 线索编码
+ * @property {number} customerId - 客户ID
+ * @property {string} customerCode - 客户编码
+ * @property {string} customerName - 客户名称
+ * @property {string} contactName - 联系人姓名
+ * @property {string} contactPhone - 联系人电话
+ * @property {string} title - 线索标题
+ * @property {string} endTime - 截止时间
+ * @property {number} priority - 优先级
+ * @property {string} source - 线索来源
+ * @property {string} groupName - 分组名称
+ * @property {number} status - 状态
+ * @property {string|null} closeReason - 关闭原因
+ */
+
+/**
+ * 销售线索更新参数类型定义
+ * @typedef {Object} LeadUpdateParams
+ * @property {string} id - 线索ID
+ * @property {string} leadCode - 线索编码
+ * @property {number} customerId - 客户ID
+ * @property {string} customerCode - 客户编码
+ * @property {string} customerName - 客户名称
+ * @property {string} contactName - 联系人姓名
+ * @property {string} contactPhone - 联系人电话
+ * @property {string} title - 线索标题
+ * @property {string} endTime - 截止时间
+ * @property {number} priority - 优先级
+ * @property {string} source - 线索来源
+ * @property {string} groupName - 分组名称
+ * @property {number} status - 状态
+ * @property {string|null} closeReason - 关闭原因
+ */
+
+/**
+ * 获取销售线索列表
+ * @param {number} current - 当前页码
+ * @param {number} size - 每页大小
+ * @param {LeadQueryParams} [params] - 查询参数
+ * @returns {Promise<{data: {data: {records: LeadRecord[], total: number, size: number, current: number, pages: number}}, code: number, success: boolean, msg: string}>}
+ */
+export const getList = (current, size, params) => {
+  return request({
+    url: '/api/blade-factory/api/factory/lead',
+    method: 'get',
+    params: {
+      ...params,
+      current: current,
+      size
+    }
+  })
+}
+
+/**
+ * 获取销售线索详情
+ * @param {string} id - 线索ID
+ * @returns {Promise<{data: {data: LeadRecord}, code: number, success: boolean, msg: string}>}
+ */
+export const getDetail = (id) => {
+  return request({
+    url: `/api/blade-factory/api/factory/lead/${id}`,
+    method: 'get'
+  })
+}
+
+/**
+ * 新增销售线索
+ * @param {LeadAddParams} row - 线索数据
+ * @returns {Promise<{data: {data: boolean}, code: number, success: boolean, msg: string}>}
+ */
+export const add = (row) => {
+  return request({
+    url: '/api/blade-factory/api/factory/lead-detail',
+    method: 'post',
+    data: row
+  })
+}
+
+/**
+ * 更新销售线索
+ * @param {LeadUpdateParams} row - 线索数据
+ * @returns {Promise<{data: {data: boolean}, code: number, success: boolean, msg: string}>}
+ */
+export const update = (row) => {
+  return request({
+    url: '/api/blade-factory/api/factory/lead',
+    method: 'put',
+    data: row
+  })
+}

+ 23 - 0
src/router/views/index.js

@@ -103,6 +103,29 @@ export default [
         ]
     },
     {
+        path: "/lead",
+        component: Layout,
+        redirect: "/lead/index",
+        meta: {
+            icon: "el-icon-user",
+            title: "销售线索管理",
+            keepAlive: true
+        },
+        children: [
+            {
+                path: "index",
+                name: "销售线索管理",
+                component: () => import("@/views/order/lead/index"),
+                meta: {
+                    keepAlive: true,
+                    isAuth: true,
+                    title: "销售线索管理",
+                    icon: "el-icon-user"
+                }
+            }
+        ]
+    },
+    {
         path: "/claim",
         component: Layout,
         redirect: "/claim/index",

+ 632 - 0
src/views/order/lead/index.vue

@@ -0,0 +1,632 @@
+<template>
+  <basic-container>
+    <avue-crud
+      :option="option"
+      :data="data"
+      ref="crud"
+      v-model="form"
+      :page.sync="page"
+      :permission="permissionList"
+      :before-open="beforeOpen"
+      :table-loading="loading"
+      @row-del="rowDel"
+      @row-update="rowUpdate"
+      @row-save="rowSave"
+      @search-change="searchChange"
+      @search-reset="searchReset"
+      @selection-change="selectionChange"
+      @current-change="currentChange"
+      @size-change="sizeChange"
+      @refresh-change="refreshChange"
+      @on-load="onLoad"
+    >
+      <template slot="menuLeft">
+        <el-button
+          type="danger"
+          size="small"
+          plain
+          icon="el-icon-delete"
+          v-if="permission.order_lead_delete"
+          @click="handleDelete"
+        >
+          删除
+        </el-button>
+      </template>
+
+      <template slot-scope="{row}" slot="status">
+        <el-tag :type="getStatusType(row.status)">
+          {{ getStatusText(row.status) }}
+        </el-tag>
+      </template>
+
+      <template slot-scope="{row}" slot="priority">
+        <el-tag :type="getPriorityType(row.priority)">
+          {{ getPriorityText(row.priority) }}
+        </el-tag>
+      </template>
+
+      <template slot-scope="{row}" slot="endTime">
+        <span :class="{ 'text-danger': isOverdue(row.endTime, row.status) }">
+          {{ row.endTime }}
+        </span>
+      </template>
+    </avue-crud>
+  </basic-container>
+</template>
+
+<script>
+import { getList, add, update, remove, getDetail } from '@/api/order/lead'
+import { getCustomerList } from '@/api/common/index'
+import { mapGetters } from 'vuex'
+
+/**
+ * 销售线索记录类型定义
+ * @typedef {Object} LeadRecord
+ * @property {string} id - 线索ID
+ * @property {string} createUser - 创建用户ID
+ * @property {string} createDept - 创建部门ID
+ * @property {string} createTime - 创建时间
+ * @property {string} updateUser - 更新用户ID
+ * @property {string} updateTime - 更新时间
+ * @property {number} status - 状态 1-新建 2-跟进中 3-已关闭
+ * @property {number} isDeleted - 是否删除
+ * @property {string} leadCode - 线索编码
+ * @property {number} customerId - 客户ID
+ * @property {string} customerCode - 客户编码
+ * @property {string} customerName - 客户名称
+ * @property {string} contactName - 联系人姓名
+ * @property {string} contactPhone - 联系人电话
+ * @property {string} title - 线索标题
+ * @property {string} endTime - 截止时间
+ * @property {number} priority - 优先级
+ * @property {string} source - 线索来源
+ * @property {string} groupName - 分组名称
+ * @property {string|null} closeReason - 关闭原因
+ */
+
+/**
+ * 销售线索查询参数类型定义
+ * @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] - 截止时间范围
+ */
+
+export default {
+  name: 'Lead',
+  data() {
+    return {
+      /**
+       * 表格数据
+       * @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}[]}
+       */
+      customerOptions: [],
+      /**
+       * 表格配置
+       */
+      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: 'customerCode',
+            rules: [{
+              required: true,
+              message: '请输入客户编码',
+              trigger: 'blur'
+            }],
+            search: true
+          },
+          {
+            label: '客户名称',
+            prop: 'customerName',
+            rules: [{
+              required: true,
+              message: '请输入客户名称',
+              trigger: 'blur'
+            }],
+            search: true
+          },
+          {
+            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: [
+              { label: '高', value: 1 },
+              { label: '中', value: 2 },
+              { label: '低', value: 3 }
+            ],
+            rules: [{
+              required: true,
+              message: '请选择优先级',
+              trigger: 'blur'
+            }],
+            search: true,
+            slot: true
+          },
+          {
+            label: '线索来源',
+            prop: 'source',
+            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: [
+              { label: '新建', value: 1 },
+              { label: '跟进中', value: 2 },
+              { label: '已关闭', value: 3 }
+            ],
+            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
+          },
+          {
+            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
+          }
+        ]
+      },
+      /**
+       * 权限列表
+       */
+      permissionList: {
+        // 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,
+      }
+    }
+  },
+  computed: {
+    ...mapGetters(['permission']),
+    ids() {
+      const ids = []
+      this.selectionList.forEach(ele => {
+        ids.push(ele.id)
+      })
+      return ids.join(',')
+    }
+  },
+  methods: {
+    /**
+     * 获取状态文本
+     * @param {number} status - 状态值
+     * @returns {string} 状态文本
+     */
+    getStatusText(status) {
+      const statusMap = {
+        1: '新建',
+        2: '跟进中',
+        3: '已关闭'
+      }
+      return statusMap[status] || '未知状态'
+    },
+
+    /**
+     * 获取状态标签类型
+     * @param {number} status - 状态值
+     * @returns {string} 标签类型
+     */
+    getStatusType(status) {
+      const typeMap = {
+        1: 'info',
+        2: 'warning',
+        3: 'success'
+      }
+      return typeMap[status] || 'info'
+    },
+
+    /**
+     * 获取优先级文本
+     * @param {number} priority - 优先级值
+     * @returns {string} 优先级文本
+     */
+    getPriorityText(priority) {
+      const priorityMap = {
+        1: '高',
+        2: '中',
+        3: '低'
+      }
+      return priorityMap[priority] || '未知优先级'
+    },
+
+    /**
+     * 获取优先级标签类型
+     * @param {number} priority - 优先级值
+     * @returns {string} 标签类型
+     */
+    getPriorityType(priority) {
+      const typeMap = {
+        1: 'danger',
+        2: 'warning',
+        3: 'success'
+      }
+      return typeMap[priority] || 'info'
+    },
+
+    /**
+     * 判断是否逾期
+     * @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<void>}
+     */
+    async rowDel(row) {
+      try {
+        await this.$confirm('确定将选择数据删除?', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        await remove(row.id)
+        await this.onLoad(this.page)
+        this.$message({
+          type: 'success',
+          message: '操作成功!'
+        })
+      } catch (error) {
+        console.error('删除失败:', error)
+      }
+    },
+
+    /**
+     * 批量删除
+     * @returns {Promise<void>}
+     */
+    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<void>}
+     */
+    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<void>}
+     */
+    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 {Function} done - 完成回调
+     * @param {string} type - 操作类型
+     */
+    beforeOpen(done, type) {
+      if (['edit', 'view'].includes(type)) {
+        // 编辑和查看时获取详情
+        getDetail(this.form.id).then(res => {
+          this.form = res.data.data
+        })
+      }
+      done()
+    },
+
+    /**
+     * 获取数据
+     * @param {Object} page - 分页信息
+     * @param {LeadQueryParams} [params] - 查询参数
+     * @returns {Promise<void>}
+     */
+    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 - 完成回调
+     */
+    searchChange(params, done) {
+      this.query = params
+      this.onLoad(this.page, params)
+      done()
+    },
+
+    /**
+     * 搜索重置
+     * @param {Function} done - 完成回调
+     */
+    searchReset(done) {
+      this.query = {}
+      this.onLoad(this.page)
+      done()
+    },
+
+    /**
+     * 选择变更
+     * @param {LeadRecord[]} list - 选中的数据
+     */
+    selectionChange(list) {
+      this.selectionList = list
+    },
+
+    /**
+     * 清空选择
+     */
+    selectionClear() {
+      this.selectionList = []
+    },
+
+    /**
+     * 当前页变更
+     * @param {number} currentPage - 当前页
+     */
+    currentChange(currentPage) {
+      this.page.currentPage = currentPage
+    },
+
+    /**
+     * 页大小变更
+     * @param {number} pageSize - 页大小
+     */
+    sizeChange(pageSize) {
+      this.page.pageSize = pageSize
+    },
+
+    /**
+     * 刷新变更
+     */
+    refreshChange() {
+      this.onLoad(this.page, this.query)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.text-danger {
+  color: #f56c6c;
+  font-weight: bold;
+}
+
+// 表格中的优先级和状态样式
+::v-deep .el-table {
+  .el-tag {
+    margin: 0;
+  }
+}
+</style>