index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. <template>
  2. <div>
  3. <PlusPage
  4. ref="plusPageInstance"
  5. :columns="tableConfig"
  6. :request="getList"
  7. :is-card="true"
  8. :beforeSearchSubmit="handleBeforeSearch"
  9. :search="{
  10. labelWidth: 100,
  11. showNumber: 3
  12. }"
  13. :table="{
  14. actionBar: { buttons, type: 'link', width: 180 },
  15. adaptive: { offsetBottom: 50 },
  16. isSelection: true,
  17. rowKey: 'id',
  18. onSelectionChange: handleSelectionChange
  19. }"
  20. :pageInfoMap="{ page: 'pageNum', pageSize: 'pageSize' }"
  21. >
  22. <template #table-title>
  23. <el-row class="button-row">
  24. <el-button type="danger" @click="handleReliveOrder">
  25. 解除分配
  26. </el-button>
  27. <el-button type="primary" @click="handleReset"> 重新分配 </el-button>
  28. </el-row>
  29. </template>
  30. </PlusPage>
  31. <!-- 跟进弹窗编辑 -->
  32. <PlusDialogForm
  33. ref="dialogForm"
  34. v-model="form"
  35. v-model:visible="dialogVisible"
  36. :form="{
  37. columns,
  38. labelPosition: 'left',
  39. labelWidth: 100,
  40. rules
  41. }"
  42. :dialog="{ title: '跟进', width: 600, confirmLoading }"
  43. @confirm="handleSubmit"
  44. @close="handleClose"
  45. />
  46. <!-- 跟进状态弹窗 -->
  47. <plusDialog
  48. v-model="detailsVisible"
  49. :hasFooter="false"
  50. title="跟进记录"
  51. width="600"
  52. >
  53. <plus-descriptions
  54. :column="4"
  55. :data="detailForm"
  56. :columns="detailsColumns"
  57. :border="false"
  58. />
  59. <el-card>
  60. <el-timeline v-if="detailForm.list.length !== 0">
  61. <el-timeline-item
  62. v-for="item in detailForm.list"
  63. :key="item.id"
  64. :timestamp="item.createTime"
  65. >
  66. <el-card>
  67. <el-row>
  68. <el-col :span="8">
  69. <el-rate v-model="item.starLevel" disabled />
  70. </el-col>
  71. <el-col :span="16">
  72. <el-tag>{{ item.remark }}</el-tag>
  73. </el-col>
  74. </el-row>
  75. </el-card>
  76. </el-timeline-item>
  77. </el-timeline>
  78. <span v-else class="text-center flex justify-center">暂无数据</span>
  79. </el-card>
  80. </plusDialog>
  81. <!-- 重新分配弹窗 -->
  82. <PlusDialogForm
  83. ref="resetFormDialog"
  84. v-model="resetForm"
  85. v-model:visible="resetVisible"
  86. :form="{
  87. columns: resetColumns,
  88. labelWidth: 100,
  89. rules: resetRules
  90. }"
  91. title="重新分配"
  92. :dialog="{
  93. width: 400,
  94. confirmLoading
  95. }"
  96. @confirm="handleResetAccount"
  97. @close="handleClose"
  98. />
  99. </div>
  100. </template>
  101. <script lang="ts" setup>
  102. import {
  103. computed,
  104. onMounted,
  105. reactive,
  106. ref,
  107. resolveDirective,
  108. toRefs
  109. } from "vue";
  110. import type { FormRules } from "element-plus";
  111. import { ElMessage, ElMessageBox } from "element-plus";
  112. import {
  113. type FieldValues,
  114. type PlusColumn,
  115. PlusDescriptions,
  116. PlusDialog,
  117. PlusDialogForm,
  118. PlusPage,
  119. PlusPageInstance,
  120. useTable
  121. } from "plus-pro-components";
  122. import { useRouter } from "vue-router";
  123. import {
  124. getMerchantOrderInfoAllList,
  125. merchantFollowInfoAllocationAccount,
  126. merchantFollowInfoList,
  127. merchantFollowInfoOrderVisible,
  128. merchantFollowInfoReliveOrder,
  129. updateStartLevel
  130. } from "@/api/order";
  131. import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
  132. import { cloneDeep, isString } from "@pureadmin/utils";
  133. import { getChildAccount } from "@/api/childAccount";
  134. import { getDictionary } from "@/utils/dictionary";
  135. import { AesEncode } from "@/utils/crypto";
  136. defineOptions({
  137. name: "OrderLoan"
  138. });
  139. const router = useRouter();
  140. const plusPageInstance = ref<PlusPageInstance | null>(null);
  141. const getList = async (query: Record<string, any>) => {
  142. let res = await getMerchantOrderInfoAllList(query);
  143. return {
  144. data: res.data.list,
  145. total: res.data.total
  146. };
  147. };
  148. onMounted(() => {
  149. getChildAccountList();
  150. });
  151. // 搜索条件处理
  152. const handleBeforeSearch = (values: any) => {
  153. let params = cloneDeep(values);
  154. if (params.applyTime && params.applyTime.length === 2) {
  155. Reflect.set(params, "beginTime", values.applyTime[0]);
  156. Reflect.set(params, "endTime", values.applyTime[1]);
  157. } else {
  158. Reflect.deleteProperty(params, "beginTime");
  159. Reflect.deleteProperty(params, "endTime");
  160. }
  161. return params;
  162. };
  163. // 重新请求列表接口
  164. const refresh = () => {
  165. plusPageInstance.value?.getList();
  166. };
  167. const dialogTitle = computed(() => (state.isCreate ? "新增" : "编辑"));
  168. const multipleSelection = ref([]);
  169. const handleSelectionChange = (val: any[]) => {
  170. multipleSelection.value = val;
  171. };
  172. const handleReliveOrder = () => {
  173. if (multipleSelection.value.length === 0) {
  174. ElMessage.warning("请选择要解除分配的订单");
  175. return;
  176. }
  177. ElMessageBox.confirm("确认解除分配?", "提示").then(async () => {
  178. try {
  179. let res = await merchantFollowInfoReliveOrder({
  180. orderIds: multipleSelection.value.map((item: any) => item.id)
  181. });
  182. if (res.code === 0) {
  183. ElMessage.success("解除分配成功");
  184. refresh();
  185. }
  186. } catch (e) {
  187. ElMessage.error("删除失败");
  188. }
  189. });
  190. };
  191. const childAccountList = ref<any[]>([]);
  192. const getChildAccountList = async () => {
  193. let res = await getChildAccount();
  194. childAccountList.value = res.data;
  195. };
  196. // 表格数据
  197. const tableConfig: PlusColumn[] = [
  198. {
  199. label: "订单号",
  200. tableColumnProps: {
  201. showOverflowTooltip: true
  202. },
  203. width: 150,
  204. prop: "orderNo"
  205. },
  206. {
  207. label: "客户姓名",
  208. prop: "userName"
  209. },
  210. {
  211. label: "性别",
  212. prop: "userSex",
  213. width: 60,
  214. hideInSearch: true
  215. },
  216. {
  217. label: "客户星级",
  218. prop: "star",
  219. valueType: "rate",
  220. editable: true,
  221. width: 140,
  222. fieldProps: {
  223. disabled: true
  224. },
  225. hideInSearch: true
  226. },
  227. {
  228. label: "客户星级",
  229. prop: "star",
  230. valueType: "select",
  231. options: [
  232. { label: "1星", value: 1 },
  233. { label: "2星", value: 2 },
  234. { label: "3星", value: 3 },
  235. { label: "4星", value: 4 },
  236. { label: "5星", value: 5 }
  237. ],
  238. hideInTable: true
  239. },
  240. {
  241. label: "电话",
  242. prop: "maskPhone",
  243. width: 100,
  244. hideInSearch: true
  245. },
  246. {
  247. label: "电话",
  248. prop: "userMobile",
  249. hideInTable: true
  250. },
  251. {
  252. label: "微信",
  253. prop: "wxCode",
  254. tableColumnProps: {
  255. showOverflowTooltip: true
  256. },
  257. hideInSearch: true
  258. },
  259. {
  260. label: "优先联系方式",
  261. prop: "headContractType",
  262. hideInSearch: true
  263. },
  264. {
  265. label: "申请产品",
  266. prop: "productName",
  267. hideInSearch: true
  268. },
  269. {
  270. label: "需求资金",
  271. prop: "loanAmount",
  272. hideInSearch: true
  273. },
  274. {
  275. label: "贷款期限",
  276. prop: "loanTerm",
  277. hideInSearch: true,
  278. formatter: val => getDictionary("LoanLimit", val)
  279. },
  280. {
  281. label: "申请时间",
  282. prop: "applyTime",
  283. width: 170,
  284. valueType: "date-picker",
  285. fieldProps: {
  286. type: "datetimerange",
  287. rangeSeparator: "-",
  288. valueFormat: "YYYY-MM-DD HH:mm:ss"
  289. }
  290. },
  291. {
  292. label: "分配子账号",
  293. prop: "followerId",
  294. valueType: "select",
  295. options: computed(() => childAccountList.value),
  296. optionsMap: {
  297. label: "contractName",
  298. value: "id"
  299. },
  300. hideInTable: true
  301. },
  302. {
  303. label: "分配子账号",
  304. prop: "follower",
  305. hideInSearch: true
  306. },
  307. {
  308. label: "状态",
  309. prop: "orderStatus",
  310. valueType: "select",
  311. options: [
  312. { label: "待分配", value: 1 },
  313. { label: "已分配", value: 2 }
  314. ]
  315. },
  316. {
  317. label: "备注",
  318. prop: "remark",
  319. hideInSearch: true
  320. }
  321. ];
  322. /*--------------------表单--------------------*/
  323. // 表单实例
  324. const dialogForm = ref(null);
  325. interface State {
  326. dialogVisible: boolean;
  327. detailsVisible: boolean;
  328. resetVisible: boolean;
  329. confirmLoading: boolean;
  330. selectedIds: number[];
  331. isCreate: boolean;
  332. form: {};
  333. detailForm: {
  334. list: any;
  335. };
  336. resetForm: {};
  337. rules: FormRules;
  338. resetRules: FormRules;
  339. }
  340. const state = reactive<State>({
  341. dialogVisible: false,
  342. detailsVisible: false,
  343. resetVisible: false,
  344. confirmLoading: false,
  345. selectedIds: [],
  346. isCreate: false,
  347. form: {},
  348. detailForm: {
  349. list: []
  350. },
  351. resetForm: {},
  352. rules: {
  353. starLevel: [
  354. { required: true, message: "请选择客户星级", trigger: "change" }
  355. ]
  356. },
  357. resetRules: {
  358. accountId: [
  359. { required: true, message: "请选择分配子账号", trigger: "change" }
  360. ]
  361. }
  362. });
  363. const columns: PlusColumn[] = [
  364. {
  365. label: "客户星级",
  366. prop: "starLevel",
  367. valueType: "select",
  368. tooltip: {
  369. content:
  370. "<span>客户星级说明</span></br>" +
  371. "<span>0星:未接通</span></br>" +
  372. "<span>1星:接通但无办理意向</span></br>" +
  373. "<span>2星:有意向但无可贷点</span></br>" +
  374. "<span>3星:有可贷点但资质一般</span></br>" +
  375. "<span>4星:有可贷点且条件较好</span></br>" +
  376. "<span>5星:马上需要或条件优质</span></br>",
  377. rawContent: true
  378. },
  379. options: [
  380. { label: "1星", value: 1 },
  381. { label: "2星", value: 2 },
  382. { label: "3星", value: 3 },
  383. { label: "4星", value: 4 },
  384. { label: "5星", value: 5 }
  385. ]
  386. },
  387. {
  388. label: "备注",
  389. prop: "remark",
  390. valueType: "input",
  391. fieldProps: {
  392. type: "textarea",
  393. rows: 4,
  394. maxlength: 200,
  395. showWordLimit: true
  396. }
  397. }
  398. ];
  399. const detailsColumns: PlusColumn[] = [
  400. {
  401. label: "姓名:",
  402. prop: "userName"
  403. },
  404. {
  405. label: "需求资金:",
  406. prop: "loanAmount"
  407. },
  408. {
  409. label: "年龄:",
  410. prop: "age"
  411. },
  412. {
  413. label: "需求周期:",
  414. prop: "loanTerm"
  415. }
  416. ];
  417. const resetColumns: PlusColumn[] = [
  418. {
  419. label: "分配子账号",
  420. prop: "accountId",
  421. valueType: "select",
  422. options: computed(() => childAccountList.value),
  423. optionsMap: {
  424. label: "contractName",
  425. value: "id"
  426. }
  427. }
  428. ];
  429. const handleSubmit = async (values: FieldValues) => {
  430. confirmLoading.value = true;
  431. try {
  432. let params = form.value;
  433. console.log(params);
  434. let res = await updateStartLevel(params);
  435. if (res.code === 0) {
  436. ElMessage.success("修改成功");
  437. confirmLoading.value = false;
  438. dialogVisible.value = false;
  439. refresh();
  440. } else {
  441. ElMessage.error(res.msg);
  442. }
  443. } finally {
  444. confirmLoading.value = false;
  445. }
  446. };
  447. const handleResetAccount = async (values: FieldValues) => {
  448. confirmLoading.value = true;
  449. try {
  450. let params = resetForm.value;
  451. console.log(params);
  452. let res = await merchantFollowInfoAllocationAccount(params);
  453. if (res.code === 0) {
  454. ElMessage.success("重新分配成功");
  455. resetVisible.value = false;
  456. refresh();
  457. } else {
  458. ElMessage.error(res.msg);
  459. }
  460. } catch (e) {
  461. ElMessage.error("重新分配失败");
  462. } finally {
  463. confirmLoading.value = false;
  464. }
  465. };
  466. const handleClose = () => {
  467. // 重置表单校验状态
  468. console.log(dialogForm.value.formInstance);
  469. dialogForm.value.formInstance.resetFields();
  470. };
  471. const { buttons } = useTable();
  472. const perms = resolveDirective("perms");
  473. const handleReset = () => {
  474. if (multipleSelection.value.length === 0) {
  475. ElMessage.warning("请选择要重新分配的订单");
  476. return;
  477. }
  478. resetForm.value = {};
  479. resetForm.value.id = multipleSelection.value.map((item: any) => item.id);
  480. console.log(resetForm.value);
  481. resetVisible.value = true;
  482. };
  483. buttons.value = [
  484. {
  485. // 修改
  486. text: "详情",
  487. code: "edit",
  488. // props v0.1.16 版本新增函数类型
  489. props: {
  490. type: "primary"
  491. },
  492. onClick(val) {
  493. let visibleParams = {
  494. id: AesEncode(val.row.id)
  495. };
  496. merchantFollowInfoOrderVisible(visibleParams).then(res => {
  497. if (res.code === 0 && res.data.visible === true) {
  498. let params = {
  499. id: val.row.id,
  500. orderNo: val.row.orderNo
  501. };
  502. Object.keys(params).forEach(param => {
  503. if (!isString(params[param])) {
  504. params[param] = params[param].toString();
  505. }
  506. });
  507. // 保存信息到标签页
  508. useMultiTagsStoreHook().handleTags("push", {
  509. path: `/order/order-loan/detail`,
  510. name: "OrderLoanDetail",
  511. query: params,
  512. meta: {
  513. title: `订单详情`,
  514. // 如果使用的是非国际化精简版title可以像下面这么写
  515. // title: `No.${index} - 详情信息`,
  516. // 最大打开标签数
  517. dynamicLevel: 1
  518. }
  519. });
  520. // 路由跳转
  521. router.push({ name: "OrderLoanDetail", query: params });
  522. } else {
  523. ElMessage.error(res.data.msg);
  524. return;
  525. }
  526. });
  527. }
  528. },
  529. {
  530. // 跟进
  531. text: "跟进",
  532. props: {
  533. type: "primary"
  534. },
  535. onClick(val) {
  536. form.value = {};
  537. form.value.orderId = val.row.orderNo;
  538. dialogVisible.value = true;
  539. }
  540. },
  541. {
  542. // 修改
  543. text: "跟进记录",
  544. props: {
  545. type: "primary"
  546. },
  547. onClick(val: any) {
  548. console.log(val.row);
  549. detailForm.value = val.row;
  550. detailForm.value.list = [];
  551. merchantFollowInfoList({ orderNo: val.row.orderNo }).then(res => {
  552. detailForm.value.list = res.data;
  553. });
  554. detailsVisible.value = true;
  555. }
  556. }
  557. ];
  558. const {
  559. form,
  560. detailForm,
  561. resetForm,
  562. confirmLoading,
  563. rules,
  564. resetRules,
  565. dialogVisible,
  566. detailsVisible,
  567. resetVisible
  568. } = toRefs(state);
  569. </script>