index.vue 15 KB

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