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">
@@ -37,6 +78,8 @@
        :row-key="(row) => row.id"
        height="calc(100vh - 18.5em)"
        stripe
        show-summary
        :summary-method="contractAmountSummaryMethod"
      >
        <el-table-column align="center" type="selection" width="55" fixed="left"/>
        <el-table-column align="center" label="序号" type="index" width="60" />
@@ -53,7 +96,9 @@
        <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="industry" show-overflow-tooltip />
        <el-table-column label="商机来源" prop="businessSource" show-overflow-tooltip />
        <el-table-column label="签约金额" prop="contractAmount" 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">
@@ -61,7 +106,7 @@
            {{ formatDate(row.updateTime) }}
          </template>
        </el-table-column>
        <el-table-column label="操作" fixed="right" width="220" align="center">
        <el-table-column label="操作" fixed="right" width="240" align="center">
          <template #default="{ row }">
            <el-button
              link
@@ -77,7 +122,7 @@
              size="small"
              @click="handleAddOperation(row)"
            >
              添加描述
              添加拜访记录
            </el-button>
            <el-button
              link
@@ -159,27 +204,116 @@
          </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="请输入客户描述"
            :autosize="{ minRows: 4, maxRows: 10 }"
            placeholder="请输入拜访记录"
            show-word-limit
            :disabled="operationType === 'detail'"
          />
        </el-form-item>
        <el-form-item label="改造内容" prop="renContent">
          <el-input
            v-model="form.renContent"
            type="textarea"
            :autosize="{ minRows: 3, maxRows: 8 }"
            :placeholder="renovationPlaceholder"
            show-word-limit
            :disabled="operationType === 'detail' || operationType === 'addOperation'"
          />
        </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' || operationType === 'addOperation'"
          />
        </el-form-item>
        
@@ -204,9 +338,10 @@
          <el-col :span="24">
            <el-form-item label="附件材料:">
              <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload
                :disabled="operationType === 'addOperation'"
                :headers="upload.headers" :data="upload.data" :before-upload="handleBeforeUpload" :on-error="handleUploadError"
                :on-success="handleUploadSuccess" :on-remove="handleRemove">
                <el-button type="primary">上传</el-button>
                <el-button type="primary" :disabled="operationType === 'addOperation'">上传</el-button>
                <template #tip>
                  <div class="el-upload__tip">
                    文件格式支持
@@ -218,6 +353,25 @@
          </el-col>
        </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">
@@ -246,7 +400,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>
@@ -266,7 +420,9 @@
    </el-dialog>
    <!-- 附件列表对话框 -->
    <FileList ref="fileListRef" />
    <FileList ref="fileListRef" @refresh="handleFileListRefresh" />
    <!-- 文件预览组件 -->
    <filePreview ref="filePreviewRef" />
  </div>
</template>
@@ -288,6 +444,7 @@
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()
@@ -298,6 +455,25 @@
const tableLoading = ref(false)
const userList = ref([])
const customerOption = ref([])
const DEFAULT_USER_QUERY = { postCode: 'Market_Sales' }
let userListPromise = null
const loadUserList = async (query = DEFAULT_USER_QUERY) => {
  if (userListPromise) return userListPromise
  userListPromise = (async () => {
    try {
      const res = await userListNoPage(query)
      userList.value = res?.data || []
      return userList.value
    } catch (err) {
      console.error('获取用户列表失败:', err)
      userList.value = []
      userListPromise = null
      throw err
    }
  })()
  return userListPromise
}
// 分页配置
const page = reactive({
@@ -309,6 +485,9 @@
// 搜索表单
const searchForm = reactive({
  customerName: '',
  city: '',
  status: '',
  entryPerson: '',
  entryDate: [],
  entryDateStart: '',
  entryDateEnd: ''
@@ -324,11 +503,21 @@
  province: '',
   city: '',
  customerName: '',
  industry: '',
  informationState: '',
  mainBusinessRevenue: '',
  customerScale: '',
  mainProducts: '',
  businessSource: '',
  contractAmount: '',
  description: '',
  renContent: '',
  paymentDescription: '',
  entryPerson: userStore.nickName,
  entryDate: dayjs().format('YYYY-MM-DD')
})
const renovationPlaceholder = '1.标准化:\n2.定制化:\n3.外采:'
// 变更记录数据(模拟数据)
const changeHistory = ref([])
@@ -338,6 +527,8 @@
// FileList组件引用
const fileListRef = ref(null)
const currentAttachmentRow = ref(null)
const filePreviewRef = ref(null)
// 上传配置
const upload = reactive({
@@ -375,9 +566,13 @@
const statusOptions = [
  { value: '新建', label: '新建' },
  { value: '项目跟踪', label: '项目跟踪' },
  { value: '放弃', label: '放弃' },
  { value: '合同签约', label: '合同签约' },
  { value: '备案申报', label: '备案申报' },
  { value: '项目交付', label: '项目交付' },
  { value: '项目验收', label: '项目验收' }
  { value: '项目验收', label: '项目验收' },
  { value: '项目回款', label: '项目回款' },
  { value: '回补贴', label: '回补贴' }
]
// 省份选项
@@ -389,9 +584,13 @@
  const typeMap = { 
    '新建': 'info', 
    '项目跟踪': 'primary', 
    '放弃': 'danger',
    '合同签约': 'warning', 
    '项目交付': 'success',
    '项目验收': 'success'
    '备案申报': 'primary',
    '项目交付': 'success',
    '项目验收': 'success',
    '项目回款': 'success',
    '回补贴': 'success'
  } 
  return typeMap[status] || 'info' 
@@ -401,9 +600,13 @@
  const textMap = { 
    '新建': '新建', 
    '项目跟踪': '项目跟踪', 
    '放弃': '放弃',
    '合同签约': '合同签约', 
    '项目交付': '项目交付',
    '项目验收': '项目验收'
    '备案申报': '备案申报',
    '项目交付': '项目交付',
    '项目验收': '项目验收',
    '项目回款': '项目回款',
    '回补贴': '回补贴'
  } 
  return textMap[status] || '未知' 
}
@@ -424,6 +627,9 @@
const resetQuery = () => {
  Object.assign(searchForm, {
    customerName: '',
    city: '',
    status: '',
    entryPerson: '',
    entryDate: [],
    entryDateStart: '',
    entryDateEnd: ''
@@ -473,6 +679,11 @@
  })
}
// 签约金额合计(复用全局 summarizeTable)
const contractAmountSummaryMethod = (param) => {
  return proxy.summarizeTable(param, ["contractAmount"]);
}
// 分页变化
const paginationChange = (pagination) => {
  page.current = pagination.page
@@ -491,8 +702,7 @@
  resetForm()
  
  // 加载用户列表和客户列表
  let userLists = await userListNoPage()
  userList.value = userLists.data
  await loadUserList()
  customerList().then((res) => {
    customerOption.value = res
  })
@@ -513,17 +723,16 @@
  operationType.value = 'addOperation'
  
  // 加载用户列表和客户列表
  let userLists = await userListNoPage()
  userList.value = userLists.data
  await loadUserList()
  customerList().then((res) => {
    customerOption.value = res
  })
  
  // 使用当前行数据作为基础,但只能修改状态和客户描述
  // 使用当前行数据作为基础,但只能修改状态和拜访记录;付款描述、改造内容等保留反显
  Object.assign(form, row, {
    // 保留原始商机ID,用于关联操作记录
    status: row.status,
    description: '', // 清空客户描述,允许重新填写
    description: '', // 清空拜访记录,允许重新填写
    entryPerson: userStore.nickName, // 设置录入人为当前账号
    entryDate: dayjs().format('YYYY-MM-DD') // 设置录入时间为当天
  })
@@ -535,8 +744,7 @@
  operationType.value = 'detail'
  
  // 加载用户列表和客户列表
  let userLists = await userListNoPage()
  userList.value = userLists.data
  await loadUserList()
  customerList().then((res) => {
    customerOption.value = res
  })
@@ -578,8 +786,7 @@
  operationType.value = 'edit'
  
  // 加载用户列表和客户列表
  let userLists = await userListNoPage()
  userList.value = userLists.data
  await loadUserList()
  customerList().then((res) => {
    customerOption.value = res
  })
@@ -667,6 +874,7 @@
        submitData = {
          status: form.status,
          description: form.description,
          paymentDescription: form.paymentDescription,
          entryPerson: form.entryPerson,
          entryDate: form.entryDate,
          tempFileIds: tempFileIds,
@@ -734,11 +942,20 @@
const resetForm = () => {
  Object.assign(form, {
    id: undefined,
    status: '新建',
    status: '',
    province: '',
    city: '',
    customerName: '',
    industry: '',
    informationState: '',
    mainBusinessRevenue: '',
    customerScale: '',
    mainProducts: '',
    businessSource: '',
    contractAmount: '',
    description: '',
    renContent: '',
    paymentDescription: '',
    entryPerson: userStore.nickName,
    entryDate: dayjs().format('YYYY-MM-DD')
  })
@@ -791,10 +1008,44 @@
// 查看附件
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 () => {
  // 加载用户列表供搜索使用
  await loadUserList()
  getList()
})
</script>