gaoluyang
2025-12-12 e8eda23ee6fe0273619d826ba768ce975a0d8ccf
src/views/salesManagement/opportunityManagement/index.vue
@@ -51,16 +51,16 @@
          </template>
        </el-table-column>
        <el-table-column label="省份" prop="province" show-overflow-tooltip width="120" />
        <el-table-column label="客户名称" prop="customerName" show-overflow-tooltip width="230" />
        <el-table-column label="商机来源" prop="businessSource" show-overflow-tooltip width="150" />
        <el-table-column label="客户描述" prop="description" show-overflow-tooltip min-width="200" />
        <el-table-column label="录入人" prop="entryPerson" show-overflow-tooltip width="120" />
        <el-table-column label="更新日期" prop="updateTime" width="120">
        <el-table-column label="客户名称" prop="customerName" show-overflow-tooltip />
        <el-table-column label="商机来源" prop="businessSource" show-overflow-tooltip />
        <!-- <el-table-column label="客户描述" prop="description" show-overflow-tooltip min-width="200" /> -->
        <el-table-column label="录入人" prop="entryPerson" show-overflow-tooltip />
        <el-table-column label="更新日期" prop="updateTime">
          <template #default="{ row }">
            {{ formatDate(row.updateTime) }}
          </template>
        </el-table-column>
        <el-table-column label="操作" fixed="right" width="130" align="center">
        <el-table-column label="操作" fixed="right" width="220" align="center">
          <template #default="{ row }">
            <el-button
              link
@@ -74,9 +74,25 @@
              link
              type="primary"
              size="small"
              @click="handleAddOperation(row)"
            >
              添加描述
            </el-button>
            <el-button
              link
              type="primary"
              size="small"
              @click="handleDetail(row)"
            >
              详情
            </el-button>
            <el-button
              link
              type="primary"
              size="small"
              @click="handleAttachment(row)"
            >
              附件
            </el-button>
          </template>
        </el-table-column>
@@ -96,7 +112,7 @@
    <!-- 新增/编辑对话框 -->
    <el-dialog
      v-model="dialogFormVisible"
      :title="operationType === 'add' ? '新建商机' : operationType === 'edit' ? '编辑商机' : '商机详情'"
      :title="operationType === 'add' ? '新建商机' : operationType === 'edit' ? '编辑商机' : operationType === 'addOperation' ? '添加商机' : '商机详情'"
      width="600px"
      @close="closeDialog"
    >
@@ -119,7 +135,7 @@
        </el-form-item>
        
        <el-form-item label="省份" prop="province">
          <el-select v-model="form.province" filterable placeholder="请选择省份" style="width: 100%" :disabled="operationType === 'detail'">
          <el-select v-model="form.province" filterable placeholder="请选择省份" style="width: 100%" :disabled="operationType === 'detail' || operationType === 'addOperation'">
            <el-option
              v-for="item in provinceOptions"
              :key="item.value"
@@ -130,7 +146,7 @@
        </el-form-item>
        
        <el-form-item label="客户名称" prop="customerName">
          <el-select v-model="form.customerName" placeholder="请选择" clearable :disabled="operationType === 'detail'">
          <el-select v-model="form.customerName" placeholder="请选择" clearable :disabled="operationType === 'detail' || operationType === 'addOperation'">
            <el-option v-for="item in customerOption" :key="item.customerName" :label="item.customerName" :value="item.customerName">
              {{
                item.customerName + "——" + item.taxpayerIdentificationNumber
@@ -140,10 +156,10 @@
        </el-form-item>
        
        <el-form-item label="商机来源" prop="businessSource">
          <el-input v-model="form.businessSource" placeholder="请输入商机来源" :disabled="operationType === 'detail'" />
          <el-input v-model="form.businessSource" placeholder="请输入商机来源" :disabled="operationType === 'detail' || operationType === 'addOperation'" />
        </el-form-item>
        
        <el-form-item label="客户描述" prop="description">
        <el-form-item label="客户描述" prop="description" v-if="operationType !== 'detail'">
          <el-input
            v-model="form.description"
            type="textarea"
@@ -151,26 +167,80 @@
            placeholder="请输入客户描述"
            maxlength="500"
            show-word-limit
            :disabled="operationType === 'detail'"
          />
        </el-form-item>
        
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="录入人" prop="entryPerson">
              <el-select v-model="form.entryPerson" placeholder="请选择" clearable @change="changs" :disabled="operationType === 'detail'">
              <el-select v-model="form.entryPerson" placeholder="请选择" clearable @change="changs" :disabled="operationType === 'detail' || operationType === 'addOperation'">
                <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="录入日期" prop="entryDateStart">
              <el-date-picker style="width: 100%" v-model="form.entryDateStart" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
                type="date" placeholder="请选择" clearable :disabled="operationType === 'detail'" />
          <el-col :span="12">
             <el-form-item label="录入日期" prop="entryDate">
               <el-date-picker style="width: 100%" v-model="form.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD"
                 type="date" placeholder="请选择" clearable :disabled="operationType === 'detail' || operationType === 'addOperation'" />
             </el-form-item>
           </el-col>
        </el-row>
        <!-- 附件上传(非详情模式下显示) -->
        <el-row :gutter="30" v-if="operationType !== 'detail'">
          <el-col :span="24">
            <el-form-item label="附件材料:">
              <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload
                :headers="upload.headers" :data="upload.data" :before-upload="handleBeforeUpload" :on-error="handleUploadError"
                :on-success="handleUploadSuccess" :on-remove="handleRemove">
                <el-button type="primary">上传</el-button>
                <template #tip>
                  <div class="el-upload__tip">
                    文件格式支持
                    doc,docx,xls,xlsx,ppt,pptx,pdf,txt,xml,jpg,jpeg,png,gif,bmp,rar,zip,7z
                  </div>
                </template>
              </el-upload>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <!-- 变更记录时间线(仅在详情模式下显示) -->
      <div v-if="operationType === 'detail'" class="change-history-section">
        <el-divider content-position="left">变更记录</el-divider>
        <el-timeline>
          <el-timeline-item
            v-for="record in changeHistory"
            :key="record.id"
            :timestamp="record.timestamp"
            :type="record.type === 'current' ? 'primary' : record.type === 'update' ? 'success' : 'info'"
            :hollow="record.type === 'current'"
            placement="top"
          >
            <el-card shadow="hover" class="timeline-card">
              <template #header>
                <div class="card-header">
                  <span class="action-type">{{ record.action }}</span>
                  <span class="operator">操作人:{{ record.operator }}</span>
                </div>
              </template>
              <div class="change-content">
                <div class="status-change" v-if="record.status">
                  <span class="label">状态:</span>
                  <el-tag :type="record.type === 'current' ? 'primary' : 'info'" size="small">
                    {{ getStatusLabel(record.status) }}
                  </el-tag>
                </div>
                <div class="description-change" v-if="record.description">
                  <span class="label">客户描述:</span>
                  <span class="description-text">{{ record.description }}</span>
                </div>
              </div>
            </el-card>
          </el-timeline-item>
        </el-timeline>
      </div>
      
      <template #footer>
        <div class="dialog-footer">
@@ -181,6 +251,9 @@
        </div>
      </template>
    </el-dialog>
    <!-- 附件列表对话框 -->
    <FileList ref="fileListRef" />
  </div>
</template>
@@ -191,14 +264,17 @@
import pagination from '@/components/PIMTable/Pagination.vue'
import useUserStore from '@/store/modules/user'
import dayjs from 'dayjs'
import { getToken } from '@/utils/auth'
import { 
  opportunityListPage, 
  addOpportunity, 
  updateOpportunity, 
  delOpportunity
  delOpportunity,
  addDescription
} from '@/api/salesManagement/opportunityManagement.js'
import { userListNoPage } from '@/api/system/user.js'
import { customerList } from '@/api/salesManagement/salesLedger.js'
import {customerList, getSalesLedgerWithProducts} from '@/api/salesManagement/salesLedger.js'
import FileList from './fileList.vue'
const { proxy } = getCurrentInstance()
const userStore = useUserStore()
@@ -231,14 +307,39 @@
const formRef = ref()
const form = reactive({
  id: undefined,
  status: 'new',
  status: undefined,
  province: '',
  customerName: '',
  businessSource: '',
  description: '',
  entryPerson: userStore.nickName,
  entryDateStart: dayjs().format('YYYY-MM-DD')
  entryDate: dayjs().format('YYYY-MM-DD')
})
// 变更记录数据(模拟数据)
const changeHistory = ref([])
// 文件列表
const fileList = ref([])
// FileList组件引用
const fileListRef = ref(null)
// 上传配置
const upload = reactive({
  // 上传的地址
  url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
  // 设置上传的请求头部
  headers: { Authorization: "Bearer " + getToken() },
  // 上传参数
  data: { type: 9 }
})
// 获取状态标签
const getStatusLabel = (statusValue) => {
  const status = statusOptions.find(item => item.value === statusValue)
  return status ? status.label : statusValue
}
// 表单验证规则
const rules = reactive({
@@ -251,80 +352,80 @@
  entryPerson: [
    { required: true, message: '请选择录入人', trigger: 'change' }
  ],
  entryDateStart: [
  entryDate: [
    { required: true, message: '请选择录入日期', trigger: 'change' }
  ]
})
// 状态选项
const statusOptions = [
  { value: 'new', label: '新建' },
  { value: 'tracking', label: '项目跟踪' },
  { value: 'contract', label: '合同签约' },
  { value: 'delivery', label: '项目交付' },
  { value: 'acceptance', label: '项目验收' }
  { value: '新建', label: '新建' },
  { value: '项目跟踪', label: '项目跟踪' },
  { value: '合同签约', label: '合同签约' },
  { value: '项目交付', label: '项目交付' },
  { value: '项目验收', label: '项目验收' }
]
// 省份选项(示例)
const provinceOptions = [
  { value: 'beijing', label: '北京市' },
  { value: 'tianjin', label: '天津市' },
  { value: 'hebei', label: '河北省' },
  { value: 'shanxi', label: '山西省' },
  { value: 'neimenggu', label: '内蒙古自治区' },
  { value: 'liaoning', label: '辽宁省' },
  { value: 'jilin', label: '吉林省' },
  { value: 'heilongjiang', label: '黑龙江省' },
  { value: 'shanghai', label: '上海市' },
  { value: 'jiangsu', label: '江苏省' },
  { value: 'zhejiang', label: '浙江省' },
  { value: 'anhui', label: '安徽省' },
  { value: 'fujian', label: '福建省' },
  { value: 'jiangxi', label: '江西省' },
  { value: 'shandong', label: '山东省' },
  { value: 'henan', label: '河南省' },
  { value: 'hubei', label: '湖北省' },
  { value: 'hunan', label: '湖南省' },
  { value: 'guangdong', label: '广东省' },
  { value: 'guangxi', label: '广西壮族自治区' },
  { value: 'hainan', label: '海南省' },
  { value: 'chongqing', label: '重庆市' },
  { value: 'sichuan', label: '四川省' },
  { value: 'guizhou', label: '贵州省' },
  { value: 'yunnan', label: '云南省' },
  { value: 'xizang', label: '西藏自治区' },
  { value: 'shaanxi', label: '陕西省' },
  { value: 'gansu', label: '甘肃省' },
  { value: 'qinghai', label: '青海省' },
  { value: 'ningxia', label: '宁夏回族自治区' },
  { value: 'xinjiang', label: '新疆维吾尔自治区' },
  { value: 'taiwan', label: '台湾省' },
  { value: 'xianggang', label: '香港特别行政区' },
  { value: 'aomen', label: '澳门特别行政区' }
  { value: '北京市', label: '北京市' },
  { value: '天津市', label: '天津市' },
  { value: '河北省', label: '河北省' },
  { value: '山西省', label: '山西省' },
  { value: '内蒙古自治区', label: '内蒙古自治区' },
  { value: '辽宁省', label: '辽宁省' },
  { value: '吉林省', label: '吉林省' },
  { value: '黑龙江省', label: '黑龙江省' },
  { value: '上海市', label: '上海市' },
  { value: '江苏省', label: '江苏省' },
  { value: '浙江省', label: '浙江省' },
  { value: '安徽省', label: '安徽省' },
  { value: '福建省', label: '福建省' },
  { value: '江西省', label: '江西省' },
  { value: '山东省', label: '山东省' },
  { value: '河南省', label: '河南省' },
  { value: '湖北省', label: '湖北省' },
  { value: '湖南省', label: '湖南省' },
  { value: '广东省', label: '广东省' },
  { value: '广西壮族自治区', label: '广西壮族自治区' },
  { value: '海南省', label: '海南省' },
  { value: '重庆市', label: '重庆市' },
  { value: '四川省', label: '四川省' },
  { value: '贵州省', label: '贵州省' },
  { value: '云南省', label: '云南省' },
  { value: '西藏自治区', label: '西藏自治区' },
  { value: '陕西省', label: '陕西省' },
  { value: '甘肃省', label: '甘肃省' },
  { value: '青海省', label: '青海省' },
  { value: '宁夏回族自治区', label: '宁夏回族自治区' },
  { value: '新疆维吾尔自治区', label: '新疆维吾尔自治区' },
  { value: '台湾省', label: '台湾省' },
  { value: '香港特别行政区', label: '香港特别行政区' },
  { value: '澳门特别行政区', label: '澳门特别行政区' }
]
// 获取状态标签类型
const getStatusTagType = (status) => {
  const typeMap = {
    'new': 'info',
    'tracking': 'primary',
    'contract': 'warning',
    'delivery': 'success',
    'acceptance': 'success'
  }
  return typeMap[status] || 'info'
}
// 获取状态标签类型
const getStatusTagType = (status) => {
  const typeMap = {
    '新建': 'info',
    '项目跟踪': 'primary',
    '合同签约': 'warning',
    '项目交付': 'success',
    '项目验收': 'success'
  }
  return typeMap[status] || 'info'
}
// 获取状态文本
const getStatusText = (status) => {
  const textMap = {
    'new': '新建',
    'tracking': '项目跟踪',
    'contract': '合同签约',
    'delivery': '项目交付',
    'acceptance': '项目验收'
  }
  return textMap[status] || '未知'
// 获取状态文本
const getStatusText = (status) => {
  const textMap = {
    '新建': '新建',
    '项目跟踪': '项目跟踪',
    '合同签约': '合同签约',
    '项目交付': '项目交付',
    '项目验收': '项目验收'
  }
  return textMap[status] || '未知'
}
// 格式化日期
@@ -419,6 +520,28 @@
  dialogFormVisible.value = true
}
// 添加操作
const handleAddOperation = async (row) => {
  operationType.value = 'addOperation'
  // 加载用户列表和客户列表
  let userLists = await userListNoPage()
  userList.value = userLists.data
  customerList().then((res) => {
    customerOption.value = res
  })
  // 使用当前行数据作为基础,但只能修改状态和客户描述
  Object.assign(form, row, {
    // 保留原始商机ID,用于关联操作记录
    status: row.status,
    description: '', // 清空客户描述,允许重新填写
    entryPerson: userStore.nickName, // 设置录入人为当前账号
    entryDate: dayjs().format('YYYY-MM-DD') // 设置录入时间为当天
  })
  dialogFormVisible.value = true
}
// 查看详情
const handleDetail = async (row) => {
  operationType.value = 'detail'
@@ -434,7 +557,32 @@
  Object.assign(form, row, {
    entryDateStart: row.updateTime || row.entryDateStart
  })
  // 生成模拟变更记录
  generateChangeHistory(row)
  dialogFormVisible.value = true
}
// 生成变更记录
const generateChangeHistory = (row) => {
  // 使用businessDescription数组数据生成变更记录
  const history = []
  if (row.businessDescription && Array.isArray(row.businessDescription)) {
    row.businessDescription.forEach((item, index) => {
      history.push({
        id: item.id || index,
        timestamp: item.entryDate || item.updateTime || item.createTime,
        operator: item.entryPerson || '系统',
        status: item.status,
        description: item.description,
        type: index === 0 ? 'current' : 'info',
        action: index === 0 ? '当前状态' : '历史记录'
      })
    })
  }
  changeHistory.value = history
}
// 编辑商机
@@ -448,9 +596,10 @@
    customerOption.value = res
  })
  
  // 使用updateTime作为录入时间反显
  // 使用当前账号和当天日期作为默认值
  Object.assign(form, row, {
    entryDateStart: row.updateTime || row.entryDateStart
    entryPerson: userStore.nickName, // 设置录入人为当前账号
    entryDate: dayjs().format('YYYY-MM-DD') // 设置录入时间为当天
  })
  dialogFormVisible.value = true
}
@@ -464,11 +613,50 @@
const submitForm = () => {
  formRef.value.validate(valid => {
    if (valid) {
      const api = operationType.value === 'add' ? addOpportunity : updateOpportunity
      // 收集附件文件的临时ID
      let tempFileIds = []
      if (fileList.value !== null && fileList.value.length > 0) {
        tempFileIds = fileList.value.map(item => item.tempId)
      }
      
      api(form).then(res => {
      let api
      let successMessage
      let submitData
      if (operationType.value === 'add') {
        api = addOpportunity
        successMessage = '新建成功'
        submitData = {
          ...form,
          tempFileIds: tempFileIds,
          type: 9  // 商机管理的类型标识
        }
      } else if (operationType.value === 'addOperation') {
        api = addDescription
        successMessage = '添加操作成功'
        // 添加操作时传递状态、描述、录入人、录入日期、附件和商机ID
        submitData = {
          status: form.status,
          description: form.description,
          entryPerson: form.entryPerson,
          entryDate: form.entryDate,
          tempFileIds: tempFileIds,
          type: 9,  // 商机管理的类型标识
          businessOpportunityId: form.id  // 传递商机ID
        }
      } else {
        api = updateOpportunity
        successMessage = '修改成功'
        submitData = {
          ...form,
          tempFileIds: tempFileIds,
          type: 9  // 商机管理的类型标识
        }
      }
      api(submitData).then(res => {
        if (res.code === 200) {
          proxy.$modal.msgSuccess(operationType.value === 'add' ? '新建成功' : '修改成功')
          proxy.$modal.msgSuccess(successMessage)
          closeDialog()
          getList()
        } else {
@@ -515,13 +703,13 @@
const resetForm = () => {
  Object.assign(form, {
    id: undefined,
    status: 'new',
    status: '新建',
    province: '',
    customerName: '',
    businessSource: '',
    description: '',
    entryPerson: userStore.nickName,
    entryDateStart: dayjs().format('YYYY-MM-DD')
    entryDate: dayjs().format('YYYY-MM-DD')
  })
  
  if (formRef.value) {
@@ -533,6 +721,46 @@
const closeDialog = () => {
  dialogFormVisible.value = false
  resetForm()
}
// 上传前校检
function handleBeforeUpload(file) {
  // 校检文件大小
  // if (file.size > 1024 * 1024 * 10) {
  //   proxy.$modal.msgError("上传文件大小不能超过10MB!");
  //   return false;
  // }
  proxy.$modal.loading("正在上传文件,请稍候...");
  return true;
}
// 上传失败
function handleUploadError(err) {
  proxy.$modal.msgError("上传文件失败");
  proxy.$modal.closeLoading();
}
// 上传成功回调
function handleUploadSuccess(res, file, uploadFiles) {
  proxy.$modal.closeLoading();
  if (res.code === 200) {
    file.tempId = res.data.tempId;
    proxy.$modal.msgSuccess("上传成功");
  } else {
    proxy.$modal.msgError(res.msg);
    proxy.$refs.fileUpload.handleRemove(file);
  }
}
// 移除文件
function handleRemove(file) {
  // 这里可以添加删除文件的逻辑
  proxy.$modal.msgSuccess("删除成功");
}
// 查看附件
function handleAttachment(row) {
   fileListRef.value.open(row.businessCommonFiles)
}
onMounted(() => {
@@ -571,4 +799,85 @@
    }
  }
}
/* 变更记录时间线样式 */
.change-history-section {
  margin-top: 20px;
  .el-divider {
    margin: 20px 0;
  }
  .timeline-card {
    margin: 8px 0;
    border-radius: 8px;
    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 8px 0;
      .action-type {
        font-weight: 600;
        color: #333;
      }
      .operator {
        font-size: 12px;
        color: #666;
      }
    }
    .change-content {
      .status-change, .description-change {
        margin-bottom: 8px;
        .label {
          font-weight: 500;
          color: #666;
          margin-right: 8px;
        }
        .description-text {
          color: #333;
          line-height: 1.5;
        }
      }
    }
  }
  /* 时间线样式优化 */
  :deep(.el-timeline) {
    padding-left: 0;
    .el-timeline-item {
      .el-timeline-item__node {
        background-color: #409eff;
        &.el-timeline-item__node--primary {
          background-color: #409eff;
        }
        &.el-timeline-item__node--success {
          background-color: #67c23a;
        }
        &.el-timeline-item__node--info {
          background-color: #909399;
        }
        &.el-timeline-item__node--hollow {
          background-color: transparent;
          border-color: #409eff;
        }
      }
      .el-timeline-item__timestamp {
        color: #666;
        font-size: 12px;
      }
    }
  }
}
</style>