index.vue 13 KB

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