index.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. <template>
  2. <div class="h-full flex">
  3. <div class="p-5 w-80 pr-0">
  4. <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col">
  5. <div class="flex-1 p-3 overflow-y-auto">
  6. <el-form :model="businessOpportunityForm" label-width="70px" style="max-width: 600px">
  7. <el-form-item label="商机名称">
  8. <el-input v-model="businessOpportunityForm.name" clearable placeholder="请输入"></el-input>
  9. </el-form-item>
  10. <el-form-item label="商机阶段">
  11. <el-select v-model="businessOpportunityForm.stageId" placeholder="请选择">
  12. <el-option v-for="item in fixedData.BusinessStage" :key="item.id" :label="item.name" :value="item.id" />
  13. </el-select>
  14. </el-form-item>
  15. <el-form-item label="客户名称">
  16. <el-input v-model="businessOpportunityForm.customerName" clearable placeholder="请输入"></el-input>
  17. </el-form-item>
  18. <el-form-item label="联系人">
  19. <el-input v-model="businessOpportunityForm.contactPerson" clearable placeholder="请输入"></el-input>
  20. </el-form-item>
  21. <el-form-item label="产品">
  22. <el-select v-model="businessOpportunityForm.product" placeholder="请选择">
  23. <el-option v-for="item in fixedData.Personnel" :key="item.id" :label="item.name" :value="item.id" />
  24. </el-select>
  25. </el-form-item>
  26. <el-form-item label="负责人">
  27. <el-select v-model="businessOpportunityForm.inchargerId" placeholder="请选择">
  28. <el-option v-for="item in fixedData.Personnel" :key="item.id" :label="item.name" :value="item.id" />
  29. </el-select>
  30. </el-form-item>
  31. <el-form-item label="创建时间">
  32. <el-date-picker v-model="businessOpportunityForm.startTime" type="date" placeholder="请选择"
  33. :clearable="false" format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
  34. </el-form-item>
  35. <el-form-item label="">
  36. <el-date-picker v-model="businessOpportunityForm.endTime" type="date" placeholder="请选择" :clearable="false"
  37. format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
  38. </el-form-item>
  39. </el-form>
  40. </div>
  41. <div class="w-full flex p-3 shadow-[0_-3px_5px_0px_rgba(0,0,0,0.2)]">
  42. <El-button class="w-full" @click="resetForm()">重置</El-Button>
  43. <El-button type="primary" class="w-full" @click="getBusinessTableList()">搜索</El-Button>
  44. </div>
  45. </div>
  46. </div>
  47. <div class="flex-1 p-5 overflow-auto">
  48. <div class="bg-white w-full h-full p-3 shadow-md rounded-md flex flex-col">
  49. <div class="flex justify-end pb-3">
  50. <el-button v-permission="['businessAddAnEdit']" type="primary"
  51. @click="editNewBusiness(false)">新建商机</el-button>
  52. <el-button type="primary" @click="showVisible('batchTransferVisible')"
  53. :disabled="batchTableData.length <= 0">批量转移</el-button>
  54. <el-button type="primary" @click="batchDeteleItem()" :disabled="batchTableData.length <= 0">批量删除</el-button>
  55. <el-button type="primary" @click="showVisible('stageSetVisible')">阶段设置</el-button>
  56. <el-button type="primary" @click="showVisible('deteleBusinessVisible')">回收站</el-button>
  57. <el-button v-permission="['businessImport']" type="primary"
  58. @click="showVisible('importVisible')">导入</el-button>
  59. <el-button v-permission="['businessExport']" type="primary" @click="exportBusinessTableList()"
  60. :loading="allLoading.exoprtLoading">导出</el-button>
  61. </div>
  62. <div class="flex-1 w-full overflow-hidden">
  63. <el-table ref="businessTableRef" :data="businessTable" border v-loading="allLoading.businessTableLading"
  64. @selection-change="changeBatch" style="width: 100%;height: 100%;">
  65. <el-table-column type="selection" width="55" />
  66. <el-table-column v-for="(item, index) in tableColumn" :prop="item.prop" :label="item.label" :key="index"
  67. :width="item.width">
  68. <template #default="scope">
  69. <el-button link type="primary" size="large" @click="dealWithTableColumn(scope.row, item.eventName)"
  70. v-if="item.eventName">{{ scope.row[item.prop] }}</el-button>
  71. <template v-else>{{ scope.row[item.prop] }}</template>
  72. </template>
  73. </el-table-column>
  74. <el-table-column label="操作" fixed="right" width="200">
  75. <template #default="scope">
  76. <el-button link type="primary" size="large" @click="editNewBusiness(scope.row)"
  77. v-permission="['businessAddAnEdit']">编辑</el-button>
  78. <el-button link type="primary" size="large" @click="newTask(scope.row)"
  79. v-permission="['tasksAdd']">新建任务</el-button>
  80. <el-button link type="danger" size="large" @click="businessDeteleItem(scope.row.id, scope.row.name)"
  81. v-permission="['businessAddAnEdit']">删除</el-button>
  82. </template>
  83. </el-table-column>
  84. </el-table>
  85. </div>
  86. <div class="flex justify-end pt-3">
  87. <el-pagination layout="total, prev, pager, next, sizes" :page-size="businessOpportunityForm.pageFrom"
  88. @size-change="handleSizeChange" @current-change="handleCurrentChange" :total="businessTotalTable"
  89. :hide-on-single-page="true" />
  90. </div>
  91. </div>
  92. </div>
  93. <!-- 弹窗 -->
  94. <el-dialog v-model="allVisible.newBusinessisible" width="1000" :show-close="false" top="10vh"
  95. :before-close="handleClose">
  96. <template #header="{ close, titleId, titleClass }">
  97. <div class="flex justify-between items-center border-b pb-3 dialog-header">
  98. <h4 :id="titleId">{{ allText.newBusinessisibleText }}</h4>
  99. <div>
  100. <el-button type="primary" :loading="allLoading.newBusinessSaveLading"
  101. :disabled="allLoading.businessSaveLading" @click="editBusiness(true)">保存并新建</el-button>
  102. <el-button type="primary" @click="editBusiness(false)" :loading="allLoading.businessSaveLading"
  103. :disabled="allLoading.newBusinessSaveLading">保存</el-button>
  104. <el-button @click="closeVisible('newBusinessisible')">取消</el-button>
  105. </div>
  106. </div>
  107. </template>
  108. <div class="h-[60vh] overflow-y-auto scroll-bar pt-3" v-loading="allLoading.generateFormLading">
  109. <GenerateForm ref="businessTemplateRef" :data="businessTemplate" :value="businessTemplateValue"
  110. :key="businessTemplateKey" />
  111. <div>相关产品</div>
  112. <RelatedProducts ref="relatedProductsRef" :productTableList="productTableList"
  113. :productTableListValue="productTableListValue" />
  114. </div>
  115. </el-dialog>
  116. <!-- 批量转移 -->
  117. <el-dialog v-model="allVisible.batchTransferVisible" width="600" :show-close="false" top="10vh">
  118. <template #header="{ close, titleId, titleClass }">
  119. <div class="flex justify-between items-center border-b pb-3 dialog-header">
  120. <h4 :id="titleId">{{ allText.transferText }}</h4>
  121. <div>
  122. <el-button type="primary" v-loading="allLoading.transferLoading" @click="transferBusiness()">转移</el-button>
  123. <el-button @click="allVisible.batchTransferVisible = false">取消</el-button>
  124. </div>
  125. </div>
  126. </template>
  127. <div class="scroll-bar m-6">
  128. <div class="flex mb-4">
  129. <div class="w-20 flex items-center justify-end pr-4">转移至:</div>
  130. <el-select v-model="transferPersonnel" placeholder="请选择" class="flex1">
  131. <el-option v-for="item in fixedData.Personnel" :key="item.id" :label="item.name" :value="item.id" />
  132. </el-select>
  133. </div>
  134. <div class="pl-3 text-[#e94a4a]">转移后,将看不到此商机</div>
  135. </div>
  136. </el-dialog>
  137. <!-- 导入 -->
  138. <el-dialog v-model="allVisible.importVisible" width="680" :show-close="false" top="10vh">
  139. <template #header="{ close, titleId, titleClass }">
  140. <div class="flex justify-between items-center border-b pb-3 dialog-header">
  141. <h4 :id="titleId">导入产品</h4>
  142. <div class="flex">
  143. <el-upload class="upload-demo mr-3" :limit="1" :show-file-list="false" accept=".xlsx"
  144. :http-request="importBusiness">
  145. <el-button type="primary" :loading="allLoading.importLoading">导入</el-button>
  146. </el-upload>
  147. <el-button @click="allVisible.importVisible = false">取消</el-button>
  148. </div>
  149. </div>
  150. </template>
  151. <div class="p-8">
  152. <div class="ml-4 mr-4">
  153. <div class="flex items-center">1、点击下载 <el-link type="primary"
  154. @click="downloadTemplate(MODURL, '商机导入模板.xlsx')">商机导入模板.xlsx</el-link></div>
  155. <div class="mt-4">2、填写excel文件、商机名称、商机金额、商机阶段必填</div>
  156. </div>
  157. </div>
  158. </el-dialog>
  159. <!-- 新建任务 -->
  160. <TaskModal :visible="allVisible.taskModalVisible" :edit-form="taskModalForm" :save-loading="taskLoading"
  161. @close="closeVisible('taskModalVisible')" @submit="submitForm" :title="'新建任务'"
  162. :disabled-list="['taskType', 'businessOpportunityId']" />
  163. <!-- 回收站 -->
  164. <DeteleBusiness :visibles="allVisible.deteleBusinessVisible" @closeVisible="closeVisible" />
  165. <!-- 阶段设置 -->
  166. <StageSetting :visibles="allVisible.stageSetVisible" @closeVisible="closeVisible" />
  167. </div>
  168. </template>
  169. <script lang="ts" setup>
  170. import { ref, reactive, onMounted, inject } from "vue";
  171. import type { ElTable, FormInstance, FormRules, UploadRequestOptions } from 'element-plus'
  172. import { useRouter, useRoute } from "vue-router";
  173. import { GETSYSFILED, MOD, GETPERSONNEL, GETGENERATEFOEM, GETBUSINESSLIST, UPDATEINSET, BUSINESSDETELE, BATCHTRANSFER, MODURL, tableColumn, BUSIESS_GETSATE, URL_IMPOERBUSINESS, BUSIESS_INFO, URL_EXPORTBUSINESS } from './api'
  174. import { GETTABLELIST } from '@/pages/product/api'
  175. import { post, get, uploadFile } from "@/utils/request";
  176. import { getAllListByCode, getFromValue, resetFromValue, getFirstDayOfMonth, createTaskFromType, formatDate, confirmAction, downloadTemplate, downloadFile } from '@/utils/tools'
  177. import { createTask } from '@/components/TaskModal/taskFunction'
  178. import { formatDateTime } from '@/utils/times'
  179. import { GenerateForm } from '@zmjs/form-design';
  180. import RelatedProducts from '@/components/relatedProducts/relatedProducts.vue'
  181. import TaskModal from '@/components/TaskModal/index.vue'
  182. import DeteleBusiness from './component/deteleTables.vue'
  183. import StageSetting from './component/stageSetting.vue'
  184. const route = useRoute()
  185. const router = useRouter()
  186. const globalPopup = inject<GlobalPopup>('globalPopup')
  187. const businessTableRef = ref<InstanceType<typeof ElTable>>() // 商机table dom
  188. const businessTotalTable = ref(0)
  189. const businessTemplateRef = ref<typeof GenerateForm>() // 自定义表单dom
  190. const relatedProductsRef = ref<typeof RelatedProducts>()
  191. const businessTemplateValue = ref({})
  192. const businessTemplateKey = ref(1)
  193. const businessTemplate = ref({
  194. config: {},
  195. list: []
  196. }) // 自定义表单数据
  197. const businessTable = ref([])
  198. const allLoading = reactive({
  199. businessTableLading: false,
  200. businessSaveLading: false,
  201. newBusinessSaveLading: false,
  202. transferLoading: false,
  203. importLoading: false,
  204. exoprtLoading: false,
  205. generateFormLading: false
  206. })
  207. const allVisible = reactive({
  208. newBusinessisible: false,
  209. recycleVisible: false,
  210. taskModalVisible: false,
  211. batchTransferVisible: false,
  212. deteleBusinessVisible: false,
  213. stageSetVisible: false,
  214. importVisible: false
  215. })
  216. const allText = reactive({
  217. newBusinessisibleText: '新建商机',
  218. transferText: '转移商机'
  219. }) // 所有文本
  220. const taskModalForm = ref({}) // 任务弹窗表单
  221. const taskLoading = ref<saveLoadingType>("1");
  222. const batchTableData = ref([]) // 批量数据
  223. const transferPersonnel = ref('') // 转移人
  224. const businessOpportunityForm = reactive<businessOpportunityFormType>({
  225. name: '',
  226. stageId: '',
  227. customerName: '',
  228. contactPerson: '',
  229. product: '',
  230. inchargerId: '',
  231. startTime: getFirstDayOfMonth(new Date()),
  232. endTime: formatDate(new Date()),
  233. pageIndex: 1,
  234. pageFrom: 10
  235. })
  236. const fixedData = reactive({
  237. BusinessStage: [] as fixedDataInterface[],
  238. Personnel: [] as personnelInterface[]
  239. })
  240. const productTableList = ref([])
  241. const productTableListValue = ref([])
  242. function editBusiness(visibles: boolean) {
  243. businessTemplateRef.value?.getData().then((res: any) => {
  244. let productTableListData = relatedProductsRef?.value?.returnData() || []
  245. productTableListData.forEach((item: any) => {
  246. delete item.id
  247. })
  248. let newForm = {
  249. ...res,
  250. expectedTransactionDate: res.expectedTransactionDate ? formatDateTime(new Date(res.expectedTransactionDate)) : '',
  251. businessItemProductList: productTableListData ? JSON.stringify(productTableListData) : []
  252. }
  253. allLoading.businessSaveLading = true
  254. post(UPDATEINSET, { ...businessTemplateValue.value, ...newForm }).then((_res) => {
  255. allVisible.newBusinessisible = visibles
  256. globalPopup?.showSuccess('保存成功')
  257. getBusinessTableList()
  258. }).finally(() => {
  259. allLoading.businessSaveLading = false
  260. })
  261. }).catch((_err: any) => {
  262. console.log(_err)
  263. globalPopup?.showError('请填写完整')
  264. })
  265. }
  266. function editNewBusiness(item: any) {
  267. console.log(item, '选择数据')
  268. showVisible('newBusinessisible')
  269. allLoading.generateFormLading = true
  270. if (item) {
  271. editProduct(item)
  272. businessTemplateValue.value = item
  273. allText.newBusinessisibleText = '编辑商机'
  274. }
  275. if (!item) {
  276. businessTemplateValue.value = {}
  277. productTableListValue.value = []
  278. allText.newBusinessisibleText = '新建商机'
  279. }
  280. setTimeout(() => {
  281. businessTemplateRef.value && businessTemplateRef.value.reset()
  282. businessTemplateKey.value++
  283. allLoading.generateFormLading = false
  284. }, 500)
  285. }
  286. function newTask(item: any) {
  287. const { id } = item
  288. taskModalForm.value = { ...createTaskFromType(1), businessOpportunityId: id, }
  289. showVisible('taskModalVisible')
  290. }
  291. function submitForm(submitData: any, isClose: boolean) { // 任务提交
  292. taskLoading.value = '2'
  293. createTask(submitData, isClose).then((res) => {
  294. const { saveLoading, isClose } = res
  295. taskLoading.value = saveLoading
  296. allVisible.taskModalVisible = isClose
  297. globalPopup?.showSuccess('新增成功')
  298. }).catch((err) => {
  299. const { saveLoading, isClose, message } = err
  300. taskLoading.value = saveLoading
  301. allVisible.taskModalVisible = isClose
  302. globalPopup?.showError(message)
  303. })
  304. }
  305. function transferBusiness() {
  306. const ids = batchTableData.value.map((item: any) => item.id).join(',')
  307. allLoading.transferLoading = true
  308. post(BATCHTRANSFER, { ids, inchargerId: transferPersonnel.value }).then(() => {
  309. transferPersonnel.value = ''
  310. globalPopup?.showSuccess('转移成功')
  311. closeVisible('batchTransferVisible')
  312. getBusinessTableList()
  313. }).finally(() => {
  314. allLoading.transferLoading = false
  315. })
  316. }
  317. function batchDeteleItem() {
  318. const value = batchTableData.value.map((item: any) => item.id).join(',')
  319. const label = batchTableData.value.map((item: any) => item.name).join(',')
  320. businessDeteleItem(value, label, true)
  321. }
  322. function businessDeteleItem(value: string | number, label: string, batch: boolean = false) {
  323. confirmAction(`确定${batch ? '批量' : ''}删除【${label}】商机吗?`).then(() => {
  324. post(BUSINESSDETELE, { ids: value }).then(res => {
  325. if (res.code != 'ok') {
  326. globalPopup?.showError(res.msg)
  327. return
  328. }
  329. globalPopup?.showSuccess('删除成功')
  330. changeBatch(false)
  331. getBusinessTableList()
  332. }).catch((err) => {
  333. globalPopup?.showError(err.message)
  334. })
  335. })
  336. }
  337. async function importBusiness(param: UploadRequestOptions) {
  338. allLoading.importLoading = true
  339. const formData = new FormData();
  340. formData.append('multipartFile', param.file)
  341. const res = await uploadFile(URL_IMPOERBUSINESS, formData).finally(() => {
  342. allLoading.importLoading = false
  343. })
  344. if (res.code == 'ok') {
  345. globalPopup?.showSuccess('导入成功' || '')
  346. getBusinessTableList()
  347. return
  348. }
  349. globalPopup?.showError(res.msg || '')
  350. }
  351. function exportBusinessTableList() {
  352. allLoading.exoprtLoading = true
  353. let valueForm = getFromValue(businessOpportunityForm)
  354. post(URL_EXPORTBUSINESS, { ...valueForm }).then((res) => {
  355. downloadFile(res.data, '商机表导出.xlsx')
  356. }).finally(() => {
  357. allLoading.exoprtLoading = false
  358. })
  359. }
  360. function changeBatch(flag: boolean = true) {
  361. if (flag) {
  362. batchTableData.value = businessTableRef.value && businessTableRef.value.getSelectionRows()
  363. } else {
  364. batchTableData.value = []
  365. businessTableRef.value && businessTableRef.value.clearSelection()
  366. }
  367. }
  368. function editProduct(row: any) {
  369. post(BUSIESS_INFO, { id: row.id }).then(({ data }) => {
  370. const list = (data.businessItemProducts || []).map((item: any) => {
  371. const { id, productName, productId, productCode, unit, unitName, typeName, type, price, inventory, orderProductDetail, num, discount, sealPrice, totalPrice, quantity } = item
  372. return {
  373. id, productId: productId, productName, productCode, unit, unitName, typeName, type, price, inventory,
  374. num, discount, sealPrice, totalPrice, quantity
  375. }
  376. })
  377. productTableListValue.value = list
  378. })
  379. }
  380. function handleSizeChange(val: number) {
  381. businessOpportunityForm.pageIndex = 1
  382. businessOpportunityForm.pageFrom = val
  383. getBusinessTableList()
  384. }
  385. function handleCurrentChange(val: number) {
  386. businessOpportunityForm.pageIndex = val
  387. getBusinessTableList()
  388. }
  389. function showVisible(type: keyof typeof allVisible) { // 显示弹窗
  390. allVisible[type] = true
  391. }
  392. function closeVisible(type: keyof typeof allVisible) {
  393. allVisible[type] = false
  394. }
  395. function handleClose(done: () => void) {
  396. done()
  397. }
  398. function getBusinessTableList() {
  399. const formValue = getFromValue(businessOpportunityForm)
  400. allLoading.businessTableLading = true
  401. post(GETBUSINESSLIST, { ...formValue }).then((res) => {
  402. const { data, total } = res.data
  403. businessTable.value = data.map((item: any) => {
  404. return {
  405. ...item,
  406. expectedTransactionDate: formatDate(new Date(item.expectedTransactionDate))
  407. }
  408. })
  409. businessTotalTable.value = total
  410. }).finally(() => {
  411. allLoading.businessTableLading = false
  412. })
  413. }
  414. function resetForm() {
  415. let reset = {
  416. startTime: getFirstDayOfMonth(new Date()),
  417. endTime: formatDate(new Date()),
  418. pageIndex: 1,
  419. pageFrom: 10
  420. }
  421. let newBusinessOpportunityForm = resetFromValue(businessOpportunityForm, { ...reset })
  422. Object.assign(businessOpportunityForm, newBusinessOpportunityForm)
  423. getBusinessTableList()
  424. }
  425. async function getSystemField() {
  426. // const systemField = getAllListByCode(['商机阶段'])
  427. // for (let i in systemField) {
  428. // const { data } = await get(`${GETSYSFILED}?code=${systemField[i]}`)
  429. // for (let key of Object.keys(fixedData)) {
  430. // if (systemField[i] == key) {
  431. // Object.assign(fixedData, { [key]: data })
  432. // }
  433. // }
  434. // }
  435. const row = await post(BUSIESS_GETSATE, {})
  436. fixedData.BusinessStage = (row.data || []).map((item: any) => {
  437. const { name, id, seq } = item
  438. return { name, id, seq }
  439. }).sort(function (a: any, b: any) { return a.seq - b.seq; });
  440. const { data } = await post(GETPERSONNEL, {})
  441. fixedData.Personnel = data.map((item: any) => {
  442. const { id, name, phone, jobNumber } = item
  443. return {
  444. id, name, phone, jobNumber
  445. }
  446. })
  447. const res = await get(GETGENERATEFOEM)
  448. businessTemplate.value = JSON.parse(res.data[0].config)
  449. }
  450. function toBusinessTableDetail(row: any) {
  451. router.push({
  452. path: `${MOD}/detail`,
  453. query: { id: row.id }
  454. })
  455. }
  456. function dealWithTableColumn(row: any, eventName: string) {
  457. if (eventName == 'toClueTableDetail') {
  458. toBusinessTableDetail(row)
  459. }
  460. }
  461. function getProductTableList() {
  462. post(GETTABLELIST, { pageIndex: -1, pageSize: -1 }).then((res) => {
  463. if (res.code == 'ok') {
  464. const { record, total } = res.data
  465. productTableList.value = record.map((item: any) => {
  466. const { id, productName, productCode, unit, unitName, typeName, type, price, inventory } = item
  467. return {
  468. id,
  469. productId: id,
  470. productName,
  471. productCode,
  472. unit,
  473. unitName,
  474. price,
  475. type,
  476. typeName,
  477. inventory,
  478. quantity: '',
  479. discount: '',
  480. totalPrice: ''
  481. }
  482. })
  483. }
  484. })
  485. }
  486. onMounted(() => {
  487. getSystemField()
  488. getProductTableList()
  489. getBusinessTableList()
  490. })
  491. </script>
  492. <style lang="scss" scoped>
  493. .dialog-header {
  494. h4 {
  495. font-size: 18px;
  496. line-height: 24px;
  497. }
  498. }
  499. </style>