src/views/salesManagement/opportunityManagement/index.vue
@@ -2,16 +2,59 @@
  <div class="app-container">
    <!-- 搜索区域 -->
    <div class="search_form">
      <el-form :model="searchForm" :inline="true" label-width="auto">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="客户名称">
          <el-input
            v-model="searchForm.customerName"
            placeholder="请输入客户名称"
            clearable
            prefix-icon="Search"
            style="width: 200px"
            style="width: 200px;"
            @change="handleQuery"
          />
        </el-form-item>
        <el-form-item label="城市">
          <el-input
              v-model="searchForm.city"
              placeholder="请输入城市名称"
              clearable
              prefix-icon="Search"
              style="width: 200px"
              @change="handleQuery"
          />
        </el-form-item>
        <el-form-item label="状态">
          <el-select
            v-model="searchForm.status"
            placeholder="请选择状态"
            clearable
            style="width: 160px"
            @change="handleQuery"
          >
            <el-option
              v-for="item in statusOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="录入人">
          <el-select
            v-model="searchForm.entryPerson"
            placeholder="请选择录入人"
            clearable
            filterable
            style="width: 200px"
            @change="handleQuery"
          >
            <el-option
              v-for="item in userList"
              :key="item.nickName"
              :label="item.nickName"
              :value="item.nickName"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="录入日期:">
          <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
@@ -20,12 +63,10 @@
        <el-form-item>
          <el-button type="primary" @click="handleQuery">搜索</el-button>
          <el-button @click="resetQuery">重置</el-button>
          <el-button type="primary" @click="handleAdd">新建</el-button>
         <el-button type="danger" plain @click="handleDelete">删除</el-button>
        </el-form-item>
      </el-form>
         <div class="actions">
         <el-button type="primary" @click="handleAdd">新建</el-button>
         <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
    </div>
    <!-- 表格区域 -->
    <div class="table_list">
@@ -38,7 +79,7 @@
        height="calc(100vh - 18.5em)"
        stripe
      >
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" type="selection" width="55" fixed="left"/>
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="状态" prop="status" width="120">
          <template #default="{ row }">
@@ -50,12 +91,13 @@
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="省份" prop="province" show-overflow-tooltip width="120" />
        <el-table-column label="省份" prop="province" show-overflow-tooltip />
        <el-table-column label="市" prop="city" show-overflow-tooltip/>
        <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">
        <el-table-column label="录入人" prop="entryPerson" show-overflow-tooltip width="120" />
        <el-table-column label="更新日期" prop="updateTime" width="120">
          <template #default="{ row }">
            {{ formatDate(row.updateTime) }}
          </template>
@@ -113,7 +155,7 @@
    <el-dialog
      v-model="dialogFormVisible"
      :title="operationType === 'add' ? '新建商机' : operationType === 'edit' ? '编辑商机' : operationType === 'addOperation' ? '添加商机' : '商机详情'"
      width="600px"
      width="1000px"
      @close="closeDialog"
    >
      <el-form
@@ -135,38 +177,128 @@
        </el-form-item>
        
        <el-form-item label="省份" prop="province">
          <el-select v-model="form.province" filterable placeholder="请选择省份" style="width: 100%" :disabled="operationType === 'detail' || operationType === 'addOperation'">
          <el-select v-model="form.province" filterable placeholder="请选择省份"
                               @change="getCityListChange"
                               style="width: 100%" :disabled="operationType === 'detail' || operationType === 'addOperation'">
            <el-option
              v-for="item in provinceOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="市" prop="city">
          <el-select v-model="form.city" filterable placeholder="请选择市"
                               style="width: 100%" :disabled="operationType === 'detail' || operationType === 'addOperation'">
            <el-option
              v-for="item in cityOptions"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        
        <el-form-item label="客户名称" prop="customerName">
          <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
              }}
            </el-option>
          </el-select>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="客户名称" prop="customerName">
              <el-select
                v-model="form.customerName"
                placeholder="请选择"
                clearable
                style="width: 100%"
                :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
                  }}
                </el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="商机来源" prop="businessSource">
              <el-input
                v-model="form.businessSource"
                placeholder="请输入商机来源"
                clearable
                :disabled="operationType === 'detail' || operationType === 'addOperation'"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="行业">
              <el-input v-model="form.industry" placeholder="请输入行业" clearable :disabled="operationType === 'detail' || operationType === 'addOperation'" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="主营产品">
              <el-input v-model="form.mainProducts" placeholder="请输入主营产品" clearable :disabled="operationType === 'detail' || operationType === 'addOperation'" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="主营业务收入">
              <el-input v-model="form.mainBusinessRevenue" placeholder="请输入主营业务收入" clearable :disabled="operationType === 'detail' || operationType === 'addOperation'" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="客户规模">
              <el-input v-model="form.customerScale" placeholder="请输入客户规模" clearable :disabled="operationType === 'detail' || operationType === 'addOperation'" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="信息化现状">
              <el-input v-model="form.informationState" placeholder="请输入信息化现状" clearable :disabled="operationType === 'detail' || operationType === 'addOperation'" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="合同金额" prop="contractAmount">
          <el-input
            v-model="form.contractAmount"
            placeholder="请输入合同金额"
            clearable
            :disabled="operationType === 'detail' || operationType === 'addOperation'"
          >
            <template #append>元</template>
          </el-input>
        </el-form-item>
        
        <el-form-item label="商机来源" prop="businessSource">
          <el-input v-model="form.businessSource" placeholder="请输入商机来源" :disabled="operationType === 'detail' || operationType === 'addOperation'" />
        </el-form-item>
        <el-form-item label="客户描述" prop="description" v-if="operationType !== 'detail'">
        <el-form-item label="改造内容" prop="description">
          <el-input
            v-model="form.description"
            type="textarea"
            :rows="3"
            placeholder="请输入客户描述"
            maxlength="500"
            :autosize="{ minRows: 4, maxRows: 10 }"
            :placeholder="renovationPlaceholder"
            show-word-limit
            :disabled="operationType === 'detail'"
          />
        </el-form-item>
        <el-form-item label="付款描述" prop="paymentDescription">
          <el-input
            v-model="form.paymentDescription"
            type="textarea"
            :autosize="{ minRows: 3, maxRows: 10 }"
            placeholder="是否垫资?企业是否开票?企业是否分补贴或额外出钱?"
            show-word-limit
            :disabled="operationType === 'detail'"
          />
        </el-form-item>
        
@@ -206,6 +338,25 @@
        </el-row>
      </el-form>
      
      <!-- 附件查看(仅在详情模式下显示) -->
      <div v-if="operationType === 'detail'" class="attachment-section">
        <el-divider content-position="left">附件材料</el-divider>
        <div v-if="form.businessCommonFiles && form.businessCommonFiles.length > 0">
          <el-table :data="form.businessCommonFiles" border stripe style="width: 100%">
            <el-table-column label="附件名称" prop="name" min-width="400" show-overflow-tooltip />
            <el-table-column fixed="right" label="操作" width="150" align="center">
              <template #default="scope">
                <el-button link type="primary" size="small" @click="downloadAttachment(scope.row)">下载</el-button>
                <el-button link type="primary" size="small" @click="previewAttachment(scope.row)">预览</el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
        <div v-else style="text-align: center; padding: 20px; color: #999;">
          暂无附件
        </div>
      </div>
      <!-- 变更记录时间线(仅在详情模式下显示) -->
      <div v-if="operationType === 'detail'" class="change-history-section">
        <el-divider content-position="left">变更记录</el-divider>
@@ -233,7 +384,7 @@
                  </el-tag>
                </div>
                <div class="description-change" v-if="record.description">
                  <span class="label">客户描述:</span>
                  <span class="label">改造内容:</span>
                  <span class="description-text">{{ record.description }}</span>
                </div>
              </div>
@@ -253,7 +404,9 @@
    </el-dialog>
    <!-- 附件列表对话框 -->
    <FileList ref="fileListRef" />
    <FileList ref="fileListRef" @refresh="handleFileListRefresh" />
    <!-- 文件预览组件 -->
    <filePreview ref="filePreviewRef" />
  </div>
</template>
@@ -265,16 +418,17 @@
import useUserStore from '@/store/modules/user'
import dayjs from 'dayjs'
import { getToken } from '@/utils/auth'
import {
  opportunityListPage,
  addOpportunity,
  updateOpportunity,
  delOpportunity,
  addDescription
import {
   opportunityListPage,
   addOpportunity,
   updateOpportunity,
   delOpportunity,
   addDescription, getProvinceList, getCityList
} from '@/api/salesManagement/opportunityManagement.js'
import { userListNoPage } from '@/api/system/user.js'
import {customerList, getSalesLedgerWithProducts} from '@/api/salesManagement/salesLedger.js'
import FileList from './fileList.vue'
import filePreview from '@/components/filePreview/index.vue'
const { proxy } = getCurrentInstance()
const userStore = useUserStore()
@@ -296,6 +450,9 @@
// 搜索表单
const searchForm = reactive({
  customerName: '',
  city: '',
  status: '',
  entryPerson: '',
  entryDate: [],
  entryDateStart: '',
  entryDateEnd: ''
@@ -309,12 +466,22 @@
  id: undefined,
  status: undefined,
  province: '',
   city: '',
  customerName: '',
  industry: '',
  informationState: '',
  mainBusinessRevenue: '',
  customerScale: '',
  mainProducts: '',
  businessSource: '',
  contractAmount: '',
  description: '',
  paymentDescription: '',
  entryPerson: userStore.nickName,
  entryDate: dayjs().format('YYYY-MM-DD')
})
const renovationPlaceholder = '1.标准化:\n2.定制化:\n3.外采:'
// 变更记录数据(模拟数据)
const changeHistory = ref([])
@@ -324,6 +491,8 @@
// FileList组件引用
const fileListRef = ref(null)
const currentAttachmentRow = ref(null)
const filePreviewRef = ref(null)
// 上传配置
const upload = reactive({
@@ -362,47 +531,14 @@
  { value: '新建', label: '新建' },
  { value: '项目跟踪', label: '项目跟踪' },
  { value: '合同签约', label: '合同签约' },
  { value: '备案申报', label: '备案申报' },
  { value: '项目交付', label: '项目交付' },
  { value: '项目验收', label: '项目验收' }
]
// 省份选项(示例)
const provinceOptions = [
  { 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 provinceOptions = ref([])
const cityOptions = ref([])
// 获取状态标签类型 
const getStatusTagType = (status) => { 
@@ -410,7 +546,8 @@
    '新建': 'info', 
    '项目跟踪': 'primary', 
    '合同签约': 'warning', 
    '项目交付': 'success',
    '备案申报': 'primary',
    '项目交付': 'success',
    '项目验收': 'success' 
  } 
  return typeMap[status] || 'info' 
@@ -422,7 +559,8 @@
    '新建': '新建', 
    '项目跟踪': '项目跟踪', 
    '合同签约': '合同签约', 
    '项目交付': '项目交付',
    '备案申报': '备案申报',
    '项目交付': '项目交付',
    '项目验收': '项目验收' 
  } 
  return textMap[status] || '未知' 
@@ -444,6 +582,9 @@
const resetQuery = () => {
  Object.assign(searchForm, {
    customerName: '',
    city: '',
    status: '',
    entryPerson: '',
    entryDate: [],
    entryDateStart: '',
    entryDateEnd: ''
@@ -516,8 +657,16 @@
  customerList().then((res) => {
    customerOption.value = res
  })
   getProvinceList().then(res => {
      provinceOptions.value = res.data
   })
  
  dialogFormVisible.value = true
}
const getCityListChange = (id) => {
   getCityList({provinceId: id}).then(res => {
      cityOptions.value = res.data
   })
}
// 添加操作
@@ -536,6 +685,7 @@
    // 保留原始商机ID,用于关联操作记录
    status: row.status,
    description: '', // 清空客户描述,允许重新填写
    paymentDescription: '', // 清空付款描述,允许重新填写
    entryPerson: userStore.nickName, // 设置录入人为当前账号
    entryDate: dayjs().format('YYYY-MM-DD') // 设置录入时间为当天
  })
@@ -596,8 +746,43 @@
    customerOption.value = res
  })
  
  // 加载省份列表
  await getProvinceList().then(res => {
    provinceOptions.value = res.data
  })
  // 如果后端返回的是name,需要转换为id
  let provinceId = row.province
  let cityId = row.city
  // 如果province是name字符串,查找对应的id
  if (row.province && typeof row.province === 'string' && !/^\d+$/.test(row.province)) {
    const provinceOption = provinceOptions.value.find(item => item.name === row.province)
    if (provinceOption) {
      provinceId = provinceOption.id
      // 加载对应的城市列表
      await getCityList({ provinceId: provinceId }).then(res => {
        cityOptions.value = res.data
        // 如果city是name字符串,查找对应的id
        if (row.city && typeof row.city === 'string' && !/^\d+$/.test(row.city)) {
          const cityOption = cityOptions.value.find(item => item.name === row.city)
          if (cityOption) {
            cityId = cityOption.id
          }
        }
      })
    }
  } else if (row.province) {
    // 如果province是id,直接加载城市列表
    await getCityList({ id: row.province }).then(res => {
      cityOptions.value = res.data
    })
  }
  // 使用当前账号和当天日期作为默认值
  Object.assign(form, row, {
    province: provinceId, // 使用转换后的id
    city: cityId, // 使用转换后的id
    entryPerson: userStore.nickName, // 设置录入人为当前账号
    entryDate: dayjs().format('YYYY-MM-DD') // 设置录入时间为当天
  })
@@ -619,6 +804,10 @@
        tempFileIds = fileList.value.map(item => item.tempId)
      }
      
      // 将省份和市的id转换为name
      const provinceName = form.province ? provinceOptions.value.find(item => item.id === form.province)?.name || form.province : ''
      const cityName = form.city ? cityOptions.value.find(item => item.id === form.city)?.name || form.city : ''
      let api
      let successMessage
      let submitData
@@ -628,6 +817,8 @@
        successMessage = '新建成功'
        submitData = {
          ...form,
          province: provinceName, // 传name而不是id
          city: cityName, // 传name而不是id
          tempFileIds: tempFileIds,
          type: 9  // 商机管理的类型标识
        }
@@ -638,6 +829,7 @@
        submitData = {
          status: form.status,
          description: form.description,
          paymentDescription: form.paymentDescription,
          entryPerson: form.entryPerson,
          entryDate: form.entryDate,
          tempFileIds: tempFileIds,
@@ -649,6 +841,8 @@
        successMessage = '修改成功'
        submitData = {
          ...form,
          province: provinceName, // 传name而不是id
          city: cityName, // 传name而不是id
          tempFileIds: tempFileIds,
          type: 9  // 商机管理的类型标识
        }
@@ -703,11 +897,19 @@
const resetForm = () => {
  Object.assign(form, {
    id: undefined,
    status: '新建',
    status: '',
    province: '',
    city: '',
    customerName: '',
    industry: '',
    informationState: '',
    mainBusinessRevenue: '',
    customerScale: '',
    mainProducts: '',
    businessSource: '',
    contractAmount: '',
    description: '',
    paymentDescription: '',
    entryPerson: userStore.nickName,
    entryDate: dayjs().format('YYYY-MM-DD')
  })
@@ -760,10 +962,45 @@
// 查看附件
function handleAttachment(row) {
   fileListRef.value.open(row.businessCommonFiles)
   currentAttachmentRow.value = row
   fileListRef.value.open(row.businessCommonFiles, row.id)
}
onMounted(() => {
// 下载附件(详情页面)
function downloadAttachment(row) {
   proxy.$download.name(row.url)
}
// 预览附件(详情页面)
function previewAttachment(row) {
   if (filePreviewRef.value) {
      filePreviewRef.value.open(row.url)
   } else {
      // 如果没有预览组件,直接打开链接
      window.open(row.url, '_blank')
   }
}
// 附件列表刷新
function handleFileListRefresh(rowId) {
   // 重新获取列表数据
   getList()
   // 等待列表数据更新后,找到对应的行并更新附件列表
   setTimeout(() => {
      if (currentAttachmentRow.value && tableData.value) {
         const updatedRow = tableData.value.find(item => item.id === currentAttachmentRow.value.id)
         if (updatedRow && updatedRow.businessCommonFiles) {
            currentAttachmentRow.value = updatedRow
            fileListRef.value.open(updatedRow.businessCommonFiles, updatedRow.id)
         }
      }
   }, 300)
}
onMounted(async () => {
  // 加载用户列表供搜索使用
  const userLists = await userListNoPage()
  userList.value = userLists.data
  getList()
})
</script>