add.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. <template>
  2. <el-drawer
  3. v-model="visible"
  4. title="新增销售线索"
  5. size="80%"
  6. class="leads-drawer"
  7. :with-header="false"
  8. >
  9. <div class="drawer-content">
  10. <el-form :model="form" :rules="rules" ref="formRef" label-width="110px" label-position="left">
  11. <div class="drawer-header">
  12. <div class="header-title">
  13. <span class="main-title">销售线索</span>
  14. <span class="sub-title">(3个月以上出结果或金额不明确的项目信息,计入:销售线索)</span>
  15. </div>
  16. <el-button link icon="Close" @click="visible = false" class="close-btn"></el-button>
  17. </div>
  18. <!-- 基本信息 -->
  19. <div class="form-section">
  20. <div class="section-title"><span>基本信息</span></div>
  21. <el-row :gutter="30">
  22. <el-col :span="12">
  23. <el-form-item label="归属公司" prop="companyNo">
  24. <el-select v-model="form.companyNo" placeholder="请选择" filterable style="width: 100%" clearable>
  25. <el-option
  26. v-for="item in companyOptions"
  27. :key="item.companyCode"
  28. :label="item.companyName"
  29. :value="item.companyCode"
  30. />
  31. </el-select>
  32. </el-form-item>
  33. </el-col>
  34. <el-col :span="12">
  35. <el-form-item label="客户名称" prop="customerNo">
  36. <el-select
  37. v-model="form.customerNo"
  38. placeholder="请输入关键字搜索客户"
  39. filterable
  40. style="width: 100%"
  41. @change="handleCustomerChange"
  42. >
  43. <el-option
  44. v-for="item in localCustomerOptions"
  45. :key="item.customerNo"
  46. :label="item.customerName"
  47. :value="item.customerNo"
  48. />
  49. </el-select>
  50. </el-form-item>
  51. </el-col>
  52. </el-row>
  53. <el-row :gutter="30">
  54. <el-col :span="12">
  55. <el-form-item label="项目负责人" prop="leader">
  56. <el-select v-model="form.leader" placeholder="请选择" filterable style="width: 100%" @change="handleLeaderChange">
  57. <el-option
  58. v-for="item in userOptions"
  59. :key="item.staffId"
  60. :label="item.staffName"
  61. :value="item.staffId"
  62. />
  63. </el-select>
  64. </el-form-item>
  65. </el-col>
  66. <el-col :span="12">
  67. <el-form-item label="项目名称" prop="projectName">
  68. <el-input v-model="form.projectName" placeholder="请输入" />
  69. </el-form-item>
  70. </el-col>
  71. </el-row>
  72. <el-row :gutter="30">
  73. <el-col :span="12">
  74. <el-form-item label="金额(万)" prop="projectBudget">
  75. <el-input-number
  76. v-model="form.projectBudget"
  77. :precision="2"
  78. :step="1"
  79. :min="0"
  80. placeholder="请输入金额"
  81. controls-position="right"
  82. style="width: 100%"
  83. />
  84. </el-form-item>
  85. </el-col>
  86. <el-col :span="12">
  87. <el-form-item label="赢单率(%)" prop="winRate">
  88. <el-input v-model="form.winRate" placeholder="请输入" type="number">
  89. <template #append>%</template>
  90. </el-input>
  91. </el-form-item>
  92. </el-col>
  93. </el-row>
  94. <el-row :gutter="30">
  95. <el-col :span="12">
  96. <el-form-item label="立项时间" prop="approvalDate">
  97. <el-date-picker v-model="form.approvalDate" type="date" placeholder="请选择" value-format="YYYY-MM-DD" style="width: 100%" />
  98. </el-form-item>
  99. </el-col>
  100. <el-col :span="12">
  101. <el-form-item label="成单时间" prop="expectedCompletionTime">
  102. <el-date-picker v-model="form.expectedCompletionTime" type="date" placeholder="请选择" value-format="YYYY-MM-DD" style="width: 100%" />
  103. </el-form-item>
  104. </el-col>
  105. </el-row>
  106. <el-row :gutter="30">
  107. <el-col :span="12">
  108. <el-form-item label="营销活动" prop="activityNo">
  109. <el-select v-model="form.activityNo" placeholder="请选择" style="width: 100%" clearable filterable>
  110. <el-option v-for="item in marketingActivityOptions" :key="item.value" :label="item.label" :value="item.value" />
  111. </el-select>
  112. </el-form-item>
  113. </el-col>
  114. </el-row>
  115. </div>
  116. <!-- 项目信息 -->
  117. <div class="form-section">
  118. <div class="section-title"><span>项目信息</span></div>
  119. <el-row :gutter="30">
  120. <el-col :span="12">
  121. <el-form-item label="项目级别" prop="projectLevel">
  122. <el-select v-model="form.projectLevel" placeholder="请选择" style="width: 100%" clearable filterable>
  123. <el-option v-for="item in projectLevelOptions" :key="item.value" :label="item.label" :value="item.value" />
  124. </el-select>
  125. </el-form-item>
  126. </el-col>
  127. <el-col :span="12">
  128. <el-form-item label="项目区域" prop="projectArea">
  129. <el-input v-model="form.projectArea" placeholder="请输入" />
  130. </el-form-item>
  131. </el-col>
  132. </el-row>
  133. <el-row :gutter="30">
  134. <el-col :span="12">
  135. <el-form-item label="采购方式" prop="procurementMethod">
  136. <el-select v-model="form.procurementMethod" placeholder="请选择" style="width: 100%" clearable filterable>
  137. <el-option v-for="item in procurementMethodOptions" :key="item.value" :label="item.label" :value="item.value" />
  138. </el-select>
  139. </el-form-item>
  140. </el-col>
  141. <el-col :span="12">
  142. <el-form-item label="信息来源" prop="infoSource">
  143. <el-select v-model="form.infoSource" placeholder="请选择" style="width: 100%" clearable filterable>
  144. <el-option v-for="item in infoSourceOptions" :key="item.value" :label="item.label" :value="item.value" />
  145. </el-select>
  146. </el-form-item>
  147. </el-col>
  148. </el-row>
  149. <el-row :gutter="30">
  150. <el-col :span="24">
  151. <el-form-item label="采购内容" prop="purchaseContent">
  152. <el-input v-model="form.purchaseContent" type="textarea" :rows="3" maxlength="200" show-word-limit placeholder="请输入采购内容" />
  153. </el-form-item>
  154. </el-col>
  155. </el-row>
  156. <el-row :gutter="30">
  157. <el-col :span="24">
  158. <el-form-item label="项目描述" prop="projectDescription">
  159. <el-input v-model="form.projectDescription" type="textarea" :rows="3" maxlength="200" show-word-limit placeholder="请输入项目描述" />
  160. </el-form-item>
  161. </el-col>
  162. </el-row>
  163. <el-row :gutter="30">
  164. <el-col :span="24">
  165. <el-form-item label="竞争对手" prop="competitor">
  166. <el-input v-model="form.competitor" type="textarea" :rows="3" maxlength="200" show-word-limit placeholder="请输入竞争对手" />
  167. </el-form-item>
  168. </el-col>
  169. </el-row>
  170. </div>
  171. <!-- 附件 -->
  172. <div class="form-section">
  173. <div class="section-title attachment-header">
  174. <span>附件</span>
  175. <el-upload
  176. :action="uploadFileUrl"
  177. :headers="headers"
  178. :on-success="handleUploadSuccess"
  179. :show-file-list="false"
  180. multiple
  181. class="upload-btn"
  182. >
  183. <el-button link type="primary" icon="Upload">上传附件</el-button>
  184. </el-upload>
  185. </div>
  186. <el-table :data="fileList" border class="at-table">
  187. <el-table-column label="文件名称" prop="name" show-overflow-tooltip />
  188. <el-table-column label="文件类型" prop="type" width="120" align="center" />
  189. <el-table-column label="上传时间" prop="uploadTime" width="180" align="center" />
  190. <el-table-column label="操作" width="100" align="center">
  191. <template #default="scope">
  192. <el-button link type="danger" @click="handleDeleteFile(scope.$index)">删除</el-button>
  193. </template>
  194. </el-table-column>
  195. </el-table>
  196. </div>
  197. </el-form>
  198. </div>
  199. <div class="drawer-footer">
  200. <el-button type="primary" @click="submitForm" :loading="submitting">保存</el-button>
  201. <el-button @click="visible = false">取消</el-button>
  202. </div>
  203. </el-drawer>
  204. </template>
  205. <script setup>
  206. import { ref, reactive, watch, getCurrentInstance } from 'vue';
  207. import { addLeads } from '@/api/saleManage/leads/index';
  208. import { listCustomerInfo } from "@/api/customer/customerInfo/index";
  209. import { globalHeaders } from '@/utils/request';
  210. import { Close, Upload } from '@element-plus/icons-vue';
  211. const props = defineProps({
  212. modelValue: Boolean,
  213. companyOptions: Array,
  214. customerOptions: Array,
  215. userOptions: Array,
  216. projectLevelOptions: Array,
  217. procurementMethodOptions: Array,
  218. infoSourceOptions: Array,
  219. marketingActivityOptions: Array,
  220. industryOptions: Array
  221. });
  222. const emit = defineEmits(['update:modelValue', 'success']);
  223. const proxy = getCurrentInstance().proxy;
  224. const visible = ref(false);
  225. const submitting = ref(false);
  226. const formRef = ref(null);
  227. const fileList = ref([]);
  228. const uploadFileUrl = import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
  229. const headers = globalHeaders();
  230. const customerLoading = ref(false);
  231. const localCustomerOptions = ref([]);
  232. const form = reactive({
  233. izClue: 1,
  234. status: '1',
  235. fileNo: ''
  236. });
  237. const rules = reactive({
  238. companyNo: [{ required: true, message: '归属公司不能为空', trigger: 'change' }],
  239. customerNo: [{ required: true, message: '客户名称不能为空', trigger: 'change' }],
  240. leader: [{ required: true, message: '项目负责人不能为空', trigger: 'change' }],
  241. projectName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
  242. projectBudget: [{ required: true, message: '金额不能为空', trigger: 'blur' }],
  243. winRate: [{ required: true, message: '赢单率不能为空', trigger: 'blur' }],
  244. expectedCompletionTime: [{ required: true, message: '成单时间不能为空', trigger: 'change' }],
  245. projectLevel: [{ required: true, message: '项目级别不能为空', trigger: 'change' }],
  246. projectArea: [{ required: true, message: '项目区域不能为空', trigger: 'blur' }],
  247. procurementMethod: [{ required: true, message: '采购方式不能为空', trigger: 'change' }],
  248. infoSource: [{ required: true, message: '信息来源不能为空', trigger: 'change' }],
  249. purchaseContent: [{ required: true, message: '采购内容不能为空', trigger: 'blur' }],
  250. projectDescription: [{ required: true, message: '项目描述不能为空', trigger: 'blur' }],
  251. });
  252. const remoteLoadCustomers = (query) => {
  253. customerLoading.value = true;
  254. listCustomerInfo({ pageNum: 1, pageSize: 500, isHighSeas: 'all' }).then(res => {
  255. const list = res.rows || [];
  256. localCustomerOptions.value = list.map(i => ({ ...i, customerNo: String(i.customerNo) }));
  257. }).finally(() => {
  258. customerLoading.value = false;
  259. });
  260. };
  261. watch(() => props.modelValue, (val) => {
  262. visible.value = val;
  263. if (val) {
  264. reset();
  265. remoteLoadCustomers(''); // 默认加载前20条
  266. }
  267. });
  268. watch(() => visible.value, (val) => { emit('update:modelValue', val); });
  269. const reset = () => {
  270. Object.assign(form, {
  271. id: undefined,
  272. izClue: 1,
  273. companyNo: undefined,
  274. customerNo: undefined,
  275. customerName: undefined,
  276. leader: undefined,
  277. leaderName: undefined,
  278. projectName: undefined,
  279. projectBudget: undefined,
  280. winRate: undefined,
  281. approvalDate: undefined,
  282. expectedCompletionTime: undefined,
  283. projectLevel: undefined,
  284. projectArea: undefined,
  285. procurementMethod: undefined,
  286. infoSource: undefined,
  287. purchaseContent: undefined,
  288. projectDescription: undefined,
  289. competitor: undefined,
  290. status: '1',
  291. fileNo: '',
  292. activityNo: undefined,
  293. profession: undefined
  294. });
  295. fileList.value = [];
  296. if (formRef.value) formRef.value.resetFields();
  297. };
  298. const handleCustomerChange = (no) => {
  299. const customer = localCustomerOptions.value.find(i => String(i.customerNo) === String(no));
  300. if (customer) {
  301. form.customerName = customer.customerName;
  302. form.deptNo = String(customer.belongDeptId || '');
  303. // 行业跟随客户变化 (对应客户信息的 industry_category_id)
  304. if (customer.industryCategoryId) {
  305. form.profession = String(customer.industryCategoryId);
  306. }
  307. }
  308. };
  309. const handleLeaderChange = (id) => {
  310. const staff = props.userOptions.find(i => i.staffId === id);
  311. if (staff) {
  312. form.leaderName = staff.staffName;
  313. }
  314. };
  315. const handleProductSupportChange = (id) => {
  316. const staff = props.userOptions.find(i => i.staffId === id);
  317. if (staff) {
  318. form.productSupportName = staff.staffName;
  319. }
  320. };
  321. const handleUploadSuccess = (res) => {
  322. if (res.code === 200) {
  323. fileList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId, type: res.data.fileName.split('.').pop().toUpperCase(), uploadTime: proxy.parseTime(new Date()) });
  324. form.fileNo = fileList.value.map(i => i.ossId).join(',');
  325. }
  326. };
  327. const handleDeleteFile = (idx) => {
  328. fileList.value.splice(idx, 1);
  329. form.fileNo = fileList.value.map(i => i.ossId).join(',');
  330. };
  331. const submitForm = () => {
  332. formRef.value.validate(valid => {
  333. if (valid) {
  334. submitting.value = true;
  335. addLeads(form).then(() => {
  336. proxy.$modal.msgSuccess("新增成功");
  337. visible.value = false;
  338. emit('success');
  339. }).finally(() => { submitting.value = false; });
  340. }
  341. });
  342. };
  343. </script>
  344. <style scoped lang="scss">
  345. .drawer-content {
  346. padding: 15px 25px;
  347. :deep(.el-form-item__label) {
  348. font-weight: normal;
  349. }
  350. }
  351. .drawer-header {
  352. display: flex;
  353. justify-content: space-between;
  354. align-items: flex-start;
  355. margin-bottom: 20px;
  356. .header-title {
  357. .main-title {
  358. font-size: 18px;
  359. font-weight: normal;
  360. color: #333;
  361. }
  362. .sub-title {
  363. font-size: 12px;
  364. color: #999;
  365. margin-left: 10px;
  366. }
  367. }
  368. .close-btn {
  369. font-size: 18px;
  370. color: #999;
  371. }
  372. }
  373. .form-section {
  374. margin-bottom: 25px;
  375. .section-title {
  376. font-size: 14px;
  377. font-weight: normal;
  378. color: #409eff;
  379. margin-bottom: 15px;
  380. // 移除蓝条和左边距
  381. }
  382. }
  383. .attachment-header {
  384. display: flex;
  385. justify-content: space-between;
  386. align-items: center;
  387. }
  388. .drawer-footer {
  389. position: absolute;
  390. bottom: 0;
  391. left: 0;
  392. right: 0;
  393. padding: 15px 25px;
  394. border-top: 1px solid #f0f0f0;
  395. background: #fff;
  396. text-align: right;
  397. .el-button {
  398. font-weight: normal;
  399. }
  400. }
  401. .at-table {
  402. :deep(th.el-table__cell) {
  403. font-weight: normal;
  404. color: #333;
  405. }
  406. }
  407. </style>