index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  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 scroll-bar">
  6. <el-form :model="searchForm">
  7. <el-form-item label="任务名称:" label-width="7em" prop="taskName">
  8. <el-input v-model="searchForm.taskName" placeholder="请输入" />
  9. </el-form-item>
  10. <el-form-item label="优先级:" label-width="7em" prop="priority">
  11. <el-select v-model="searchForm.priority" placeholder="请选择">
  12. <el-option v-for="item in PRIORITY" :key="item.value" :value="item.value" :label="item.label" />
  13. </el-select>
  14. </el-form-item>
  15. <el-form-item label="客户名称:" label-width="7em" prop="customName">
  16. <el-input v-model="searchForm.customName" placeholder="请输入" />
  17. </el-form-item>
  18. <el-form-item label="联系人号码:" label-width="7em" prop="contactsName">
  19. <el-input v-model="searchForm.contactsName" placeholder="请输入" />
  20. </el-form-item>
  21. <el-form-item label="执行人:" label-width="7em" prop="executorName">
  22. <el-input v-model="searchForm.executorName" placeholder="请输入" />
  23. </el-form-item>
  24. <el-form-item label="商机名称:" label-width="7em" prop="businessName">
  25. <el-input v-model="searchForm.businessName" placeholder="请输入" />
  26. </el-form-item>
  27. <el-form-item label="销售订单:" label-width="7em" prop="orderName">
  28. <el-input v-model="searchForm.orderName" placeholder="请输入" />
  29. </el-form-item>
  30. <el-form-item label="线索名称:" label-width="7em" prop="clueName">
  31. <el-input v-model="searchForm.clueName" placeholder="请输入" />
  32. </el-form-item>
  33. <el-form-item label="任务状态:" label-width="7em" prop="status">
  34. <el-select v-model="searchForm.status" placeholder="请选择">
  35. <el-option v-for="item in STATUS" :key="item.value" :value="item.value" :label="item.label" />
  36. </el-select>
  37. </el-form-item>
  38. <el-form-item label="开始时间:" label-width="7em" prop="startDate">
  39. <el-date-picker v-model="searchForm.startDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" />
  40. </el-form-item>
  41. <el-form-item label="截止时间:" label-width="7em" prop="endDate">
  42. <el-date-picker v-model="searchForm.endDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" />
  43. </el-form-item>
  44. </el-form>
  45. </div>
  46. <div class="w-full flex p-3 shadow-[0_-3px_5px_0px_rgba(0,0,0,0.2)]">
  47. <el-button size="large" class="w-full" @click="reset()">重置</el-Button>
  48. <el-button type="primary" size="large" class="w-full" @click="search()">搜索</el-Button>
  49. </div>
  50. </div>
  51. </div>
  52. <div class="flex-1 p-5 overflow-auto">
  53. <div class="bg-white w-full h-full p-3 shadow-md rounded-md flex flex-col">
  54. <div class="ml-auto p-3">
  55. <el-button type="primary" v-permission="['tasksAdd']" @click="createTasks()">创建任务</el-Button>
  56. <el-button type="primary" :disabled="len == 0" :loading="btnLoading" @click="deleteTasks()">批量删除</el-Button>
  57. <el-button type="primary" v-permission="['tasksImport']" @click="openImportModal()">导入</el-Button>
  58. <!-- <el-button type="primary" :loading="btnLoading" @click="exportTasks()">导出</el-Button> -->
  59. <el-button type="primary" v-permission="['tasksExport']" :loading="btnLoading" @click="newExportTasks()">导出</el-Button>
  60. </div>
  61. <div class="flex-1 overflow-y-auto">
  62. <el-table :data="tableData" :show-overflow-tooltip="tableShowOverflowTooltip" style="width: 100%;height: 100%;" ref="tableRef" v-loading="loading">
  63. <el-table-column type="selection" width="55" />
  64. <el-table-column prop="taskName" label="任务名称" header-align="center" align="center" show-overflow-tooltip
  65. width="200" />
  66. <el-table-column prop="priority" label="优先级" width="90" :sortable="true" header-align="center"
  67. align="center">
  68. <template #default="scope">
  69. {{ PRIORITY.find(item => item.value == scope.row.priority)?.label }}
  70. </template>
  71. </el-table-column>
  72. <el-table-column prop="status" label="状态" width="100" header-align="center" align="center">
  73. <template #default="scope">
  74. <el-text :type="STATUS[scope.row.status]?.type">
  75. {{ STATUS[scope.row.status]?.label }}
  76. </el-text>
  77. </template>
  78. </el-table-column>
  79. <el-table-column prop="executorNames" label="执行人" width="120" header-align="center" align="center" />
  80. <el-table-column prop="startDate" label="开始时间" width="200" :sortable="true" header-align="center"
  81. align="center" value-format="YYYY-MM-DD" />
  82. <el-table-column prop="endDate" label="截止时间" width="200" :sortable="true" header-align="center"
  83. align="center" value-format="YYYY-MM-DD" />
  84. <el-table-column prop="customName" label="客户名称" header-align="center" align="center" width="120">
  85. <template #default="scope">
  86. <el-link :underline="false" type="primary" @click="goDetail(scope.row, 'customer', 'customId')">
  87. {{ scope.row.customName }}
  88. </el-link>
  89. </template>
  90. </el-table-column>
  91. <el-table-column prop="businessName" label="商机名称" header-align="center" align="center" width="200">
  92. <template #default="scope">
  93. <el-link :underline="false" type="primary"
  94. @click="goDetail(scope.row, 'business', 'businessOpportunityId')">
  95. {{ scope.row.businessName }}
  96. </el-link>
  97. </template>
  98. </el-table-column>
  99. <el-table-column prop="orderName" label="销售订单" header-align="center" align="center" width="200">
  100. <template #default="scope">
  101. <el-link :underline="false" type="primary" @click="goDetail(scope.row, 'order', 'orderId')">
  102. {{ scope.row.orderName }}
  103. </el-link>
  104. </template>
  105. </el-table-column>
  106. <el-table-column prop="clueName" label="线索名称" header-align="center" align="center" width="200">
  107. <template #default="scope">
  108. <el-link :underline="false" type="primary" @click="goDetail(scope.row, 'thread', 'clueId')">
  109. {{ scope.row.clueName }}
  110. </el-link>
  111. </template>
  112. </el-table-column>
  113. <el-table-column prop="contactsName" label="联系人名称" header-align="center" align="center" width="120">
  114. <template #default="scope">
  115. <el-link :underline="false" type="primary" @click="goDetail(scope.row, 'contacts', 'contactsId')">
  116. {{ scope.row.contactsName }}
  117. </el-link>
  118. </template>
  119. </el-table-column>
  120. <el-table-column prop="contactsTel" label="联系人号码" header-align="center" align="center" width="140" />
  121. <el-table-column fixed="right" label="操作" header-align="center" align="center" width="160" v-permission="['tasksEdit']">
  122. <template #default="scope">
  123. <el-button link type="primary" @click.prevent="editRow(scope.row)">
  124. 编辑
  125. </el-button>
  126. <el-button link type="primary" v-if="scope.row.status == '2'"
  127. @click.prevent="restart(scope.row)">
  128. 重启
  129. </el-button>
  130. <el-button link type="primary" v-else @click.prevent="finishRow(scope.row)">
  131. 完成
  132. </el-button>
  133. <el-button link type="danger" @click.prevent="deleteRow(scope.row)">
  134. 删除
  135. </el-button>
  136. </template>
  137. </el-table-column>
  138. </el-table>
  139. </div>
  140. <div class="ml-auto">
  141. <el-pagination layout="total, prev, pager, next, sizes" :total="totalCount"
  142. :current-page="searchForm.pageIndex" @size-change="sizeChage" @current-change="currentChange" />
  143. </div>
  144. </div>
  145. </div>
  146. <TaskModal :visible="taskModalVisible" :title="isEdit ? '编辑任务' : '新建任务'" :save-loading="taskLoading"
  147. :edit-form="taskForm" :show-log="isEdit" @close="closeTaskModal" @submit="submitForm" />
  148. <ImportModal :visible="importVisible" :save-loading="importLoading" @close="closeImportModal"
  149. @submit="importExcel" />
  150. <ExportModal :visible="exportVisible" :save-loading="exportLoading" @close="closeExportModal"
  151. @submit="exportExcel" />
  152. </div>
  153. </template>
  154. <script lang="ts" setup>
  155. import { computed, inject, onBeforeMount, onMounted, ref, } from 'vue';
  156. import { useRouter } from 'vue-router';
  157. import { useStore } from '@/store';
  158. import { MOD, PRIORITY, STATUS, defaultSearchForm, PAGE_LIST, ADD_TASK, DELETE_TASKS, UPDATE_TASK, UPDATE_TASK_STATUS, IMPORT_DATA, EXPORT_DATA, EXPORT_DATA_BY_TASK_ID } from './api';
  159. import { ElTable, dayjs } from 'element-plus';
  160. import TaskModal from '@/components/TaskModal/index.vue';
  161. import ImportModal from './ImportModal.vue';
  162. import ExportModal from './ExportModal.vue';
  163. import { post, uploadFile } from '@/utils/request';
  164. import { getFromValue, confirmAction, downloadFile } from '@/utils/tools';
  165. import { tableShowOverflowTooltip } from '@/utils/globalVariables'
  166. import { pushMap } from './type';
  167. const router = useRouter()
  168. const { getFunctionList } = useStore()
  169. const globalPopup = inject<GlobalPopup>('globalPopup')
  170. const pagePermission = ref<any[]>();
  171. const taskModalVisible = ref(false);
  172. const taskForm = ref<any>();
  173. const isEdit = ref(false);
  174. const len = computed(() => {
  175. return tableRef.value?.getSelectionRows().length
  176. })
  177. const taskLoading = ref<saveLoadingType>("1");
  178. function closeTaskModal() {
  179. taskModalVisible.value = false;
  180. taskForm.value = null;
  181. }
  182. function submitForm(data: any, isClose: boolean) {
  183. const { executorId, startDate, endDate, repeatEndDate } = data;
  184. let params = {
  185. ...data,
  186. startDate: startDate && dayjs(startDate).format('YYYY-MM-DD 00:00:00'),
  187. endDate: endDate && dayjs(endDate).format('YYYY-MM-DD 23:59:59'),
  188. repeatEndDate: repeatEndDate && dayjs(repeatEndDate).format('YYYY-MM-DD 23:59:59')
  189. }
  190. if (executorId) {
  191. params = {
  192. ...params,
  193. executorId: executorId.join(','),
  194. taskLogs: []
  195. }
  196. }
  197. taskLoading.value = "2";
  198. let url = isEdit.value ? UPDATE_TASK : ADD_TASK
  199. let msg = isEdit.value ? "修改成功" : "新建成功"
  200. post(url, getFromValue(params)).then(() => {
  201. taskLoading.value = "3";
  202. taskModalVisible.value = isClose;
  203. globalPopup?.showSuccess(msg)
  204. search();
  205. }).catch(err => {
  206. taskLoading.value = "4"
  207. globalPopup?.showError(err.msg)
  208. })
  209. }
  210. const searchForm = ref<any>();
  211. const tableRef = ref<InstanceType<typeof ElTable>>();
  212. const loading = ref<boolean>(false);
  213. const totalCount = ref<number>(0);
  214. const tableData = ref<any[]>([])
  215. function newExportTasks() {
  216. btnLoading.value = true
  217. const { startDate, endDate } = searchForm.value;
  218. let params = {
  219. ...searchForm.value,
  220. startDate: startDate && dayjs(startDate).format('YYYY-MM-DD 00:00:00'),
  221. endDate: endDate && dayjs(endDate).format('YYYY-MM-DD 23:59:59')
  222. }
  223. post(EXPORT_DATA_BY_TASK_ID, {...getFromValue(params)}).then((res) => {
  224. globalPopup?.showSuccess("导出成功")
  225. downloadFile(res.data, "任务列表.xlsx");
  226. }).finally(() => {
  227. btnLoading.value = false
  228. })
  229. }
  230. function search() {
  231. loading.value = true;
  232. const { startDate, endDate } = searchForm.value;
  233. let params = {
  234. ...searchForm.value,
  235. startDate: startDate && dayjs(startDate).format('YYYY-MM-DD 00:00:00'),
  236. endDate: endDate && dayjs(endDate).format('YYYY-MM-DD 23:59:59')
  237. }
  238. post(PAGE_LIST, getFromValue(params)).then(({ data }) => {
  239. loading.value = false;
  240. const { total, record } = data;
  241. totalCount.value = total;
  242. tableData.value = record.map((item: any) => ({
  243. ...item,
  244. executorNames: item.taskExecutors?.join(',') ?? ''
  245. }));
  246. }).catch(err => {
  247. globalPopup?.showError(err);
  248. loading.value = false;
  249. })
  250. }
  251. function reset() {
  252. searchForm.value = { ...defaultSearchForm };
  253. }
  254. function sizeChage(currentSize: number): void {
  255. searchForm.value = {
  256. ...searchForm.value,
  257. pageSize: currentSize,
  258. pageIndex: 1
  259. }
  260. search()
  261. }
  262. function currentChange(currentPage: number): void {
  263. searchForm.value = {
  264. ...searchForm.value,
  265. pageIndex: currentPage
  266. }
  267. search()
  268. }
  269. function createTasks() {
  270. taskModalVisible.value = true;
  271. taskForm.value = null;
  272. isEdit.value = false;
  273. }
  274. function deleteTasks() {
  275. confirmAction("确定删除所选内容吗?").then(() => {
  276. const taskIds = tableRef.value?.getSelectionRows()?.map((item: any) => item.id).join(",")
  277. post(DELETE_TASKS, {
  278. taskIds
  279. }).then(() => {
  280. search();
  281. globalPopup?.showSuccess("删除成功")
  282. }).catch(err => {
  283. globalPopup?.showError(err)
  284. })
  285. });
  286. }
  287. const importVisible = ref(false);
  288. const importLoading = ref<saveLoadingType>("1");
  289. function openImportModal() {
  290. importVisible.value = true;
  291. }
  292. function closeImportModal() {
  293. importVisible.value = false;
  294. }
  295. function importExcel(data: any) {
  296. const formData = new FormData();
  297. formData.append("multipartFile", data);
  298. importLoading.value = "2";
  299. uploadFile(IMPORT_DATA, formData).then(_res => {
  300. globalPopup?.showSuccess("导入成功")
  301. importLoading.value = "3";
  302. search();
  303. }).catch(err => {
  304. globalPopup?.showError(err)
  305. importLoading.value = "4";
  306. })
  307. }
  308. const exportVisible = ref(false);
  309. const exportLoading = ref<saveLoadingType>("1");
  310. const btnLoading = ref(false);
  311. function exportTasks() {
  312. const data: any[] = tableRef.value?.getSelectionRows()
  313. if (data.length === 0) {
  314. // TODO
  315. exportVisible.value = true;
  316. return
  317. }
  318. btnLoading.value = true;
  319. const taskIds = data.map((v: any) => v.id).join(",");
  320. post(EXPORT_DATA_BY_TASK_ID, {
  321. taskIds
  322. }).then(({ data }) => {
  323. downloadFile(data, "任务列表.xlsx");
  324. btnLoading.value = false;
  325. }).catch(err => {
  326. btnLoading.value = false;
  327. globalPopup?.showError(err)
  328. })
  329. }
  330. function closeExportModal() {
  331. exportVisible.value = false;
  332. }
  333. function exportExcel(data: any) {
  334. exportLoading.value = "2";
  335. post(EXPORT_DATA, getFromValue(data)).then(({ data }) => {
  336. downloadFile(data, "任务列表.xlsx");
  337. exportLoading.value = "3";
  338. exportVisible.value = false;
  339. }).catch(err => {
  340. globalPopup?.showError(err)
  341. })
  342. setTimeout(() => {
  343. }, 2000)
  344. }
  345. function editRow(row: any) {
  346. isEdit.value = true;
  347. taskModalVisible.value = true;
  348. let value = {
  349. ...row
  350. }
  351. if (value.executorId) {
  352. value.executorId = value.executorId.split(",")
  353. }
  354. taskForm.value = value;
  355. }
  356. function finishRow(item: any) {
  357. post(UPDATE_TASK_STATUS, {
  358. id: item.id,
  359. status: 2
  360. }).then(() => {
  361. search()
  362. globalPopup?.showSuccess("操作成功")
  363. }).catch(err => {
  364. globalPopup?.showError(err)
  365. })
  366. }
  367. function restart(item: any) {
  368. post(UPDATE_TASK_STATUS, {
  369. id: item.id,
  370. status: 0
  371. }).then(() => {
  372. search()
  373. globalPopup?.showSuccess("操作成功")
  374. }).catch(err => {
  375. globalPopup?.showError(err)
  376. })
  377. }
  378. function deleteRow(item: any) {
  379. confirmAction("确定删除吗?").then(() => {
  380. post(DELETE_TASKS, {
  381. taskIds: item.id
  382. }).then(() => {
  383. search();
  384. globalPopup?.showSuccess("删除成功")
  385. }).catch(err => {
  386. console.log("err", err);
  387. globalPopup?.showError(err)
  388. })
  389. })
  390. }
  391. function goDetail(item: any, path: keyof pushMap, typeId: pushMap[keyof pushMap]) {
  392. router.push({
  393. path: `/${path}/detail`,
  394. query: {
  395. id: item[typeId]
  396. }
  397. })
  398. }
  399. onBeforeMount(() => {
  400. pagePermission.value = getFunctionList(MOD);
  401. searchForm.value = { ...defaultSearchForm };
  402. })
  403. onMounted(() => {
  404. search()
  405. })
  406. </script>
  407. <style lang="scss" scoped></style>