order.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. <template>
  2. <cl-crud ref="Crud" class="orderList">
  3. <cl-row>
  4. <cl-filter-group :items="items" :data="filterParam" :reset-btn="true" />
  5. <cl-export-btn :columns="Table?.columns" />
  6. </cl-row>
  7. <cl-row>
  8. <el-checkbox v-model="showSummary" label="统计数据" size="small" />
  9. <el-tag v-if="showSummary" class="ml-2" effect="light">成功总金额: {{ summary.num1 }}</el-tag>
  10. <el-tag v-if="showSummary" class="ml-2" effect="light">成功订单手续费: {{ summary.num2 }}</el-tag>
  11. <el-tag v-if="showSummary && checkPerm(service.dj.order.permission.update)" class="ml-2"
  12. effect="light">通道手续费: {{ summary.num5.toFixed(2) }}</el-tag>
  13. <el-tag v-if="showSummary" class="ml-2" effect="light">成功订单数: {{ summary.num3 }}</el-tag>
  14. <el-tag v-if="showSummary" class="ml-2" effect="light">成功率: {{ summary.num4.toFixed(2) }}%</el-tag>
  15. </cl-row>
  16. <cl-row>
  17. <!-- 数据表格 -->
  18. <cl-table ref="Table" :contextMenu="[]">
  19. <template #column-detail="{ scope }">
  20. <div style="padding: 10px;">
  21. <el-descriptions border :column="4" class="desc">
  22. <el-descriptions-item :span="2" label="付款时间">
  23. {{ scope.row.date }}
  24. </el-descriptions-item>
  25. <el-descriptions-item :span="1" label="用户ID">
  26. {{ scope.row.userId }}
  27. </el-descriptions-item>
  28. <el-descriptions-item :span="1" label="用户IP">
  29. {{ scope.row.userIp }}
  30. </el-descriptions-item>
  31. <el-descriptions-item :span="4" label="通知地址">
  32. {{ scope.row.notifyUrl }}
  33. </el-descriptions-item>
  34. <el-descriptions-item :span="4" label="同步跳转地址">
  35. {{ scope.row.returnUrl }}
  36. </el-descriptions-item>
  37. <el-descriptions-item :span="4" label="支付地址">
  38. {{ scope.row.payUrl }}
  39. </el-descriptions-item>
  40. <el-descriptions-item :span="4" label="备注/失败描述">
  41. {{ scope.row.remark }}
  42. </el-descriptions-item>
  43. </el-descriptions>
  44. </div>
  45. </template>
  46. </cl-table>
  47. </cl-row>
  48. <cl-row>
  49. <cl-flex1 />
  50. <!-- 分页控件 -->
  51. <cl-pagination />
  52. </cl-row>
  53. <!-- 新增、编辑 -->
  54. <cl-upsert ref="Upsert" />
  55. <cl-form ref="Form" />
  56. </cl-crud>
  57. </template>
  58. <script lang="ts" name="pay-order" setup>
  59. import { useCrud, useTable, useUpsert, useForm } from "@cool-vue/crud";
  60. import { ref } from "vue";
  61. import { useCool } from "/@/cool";
  62. import dayjs from "dayjs";
  63. import { ElMessage } from "element-plus";
  64. import { checkPerm } from "/$/base";
  65. const Form = useForm();
  66. const { service } = useCool();
  67. const showSummary = ref(false);
  68. const filterParam = ref({
  69. createTime: [
  70. dayjs().format("YYYY-MM-DD ") + "00:00:00",
  71. dayjs().format("YYYY-MM-DD ") + "23:59:59"
  72. ]
  73. });
  74. function openRefund(data: any) {
  75. Form.value?.open({
  76. title: "发起退款",
  77. width: "800px",
  78. form: {
  79. orderNo: data.orderNo,
  80. reson: data.reson,
  81. amount: data.amount
  82. },
  83. items: [
  84. {
  85. prop: "orderNo",
  86. label: "订单号",
  87. component: {
  88. name: "el-input",
  89. props: {
  90. disabled: true
  91. }
  92. },
  93. required: true
  94. },
  95. {
  96. label: "金额",
  97. prop: "amount",
  98. required: true,
  99. component: {
  100. name: "el-input-number",
  101. props: {
  102. style: { width: "200px" },
  103. precision: 2,
  104. min: 0,
  105. disabled: true
  106. }
  107. }
  108. },
  109. {
  110. prop: "reson",
  111. label: "退款原因",
  112. component: { name: "el-input", props: { type: "textarea", rows: 4 } },
  113. }
  114. ],
  115. on: {
  116. async submit(data, { done, close }) {
  117. try {
  118. await service.dj.order.toRefund(data);
  119. ElMessage.success("订单发起退款成功,请到退款订单页面查询结果");
  120. close()
  121. } catch (e) {
  122. ElMessage.error(e.message);
  123. close()
  124. }
  125. }
  126. }
  127. });
  128. }
  129. const summary = ref({
  130. num1: 0,
  131. num2: 0,
  132. num3: 0,
  133. num4: 0,
  134. num5: 0
  135. });
  136. const items = ref<ClForm.Item[]>([
  137. {
  138. label: "订单号",
  139. prop: "orderNo",
  140. component: {
  141. name: "el-input"
  142. }
  143. },
  144. {
  145. label: "商户订单号",
  146. prop: "outOrderNo",
  147. component: {
  148. name: "el-input"
  149. }
  150. },
  151. {
  152. label: "交易号",
  153. prop: "traceNo",
  154. component: {
  155. name: "el-input"
  156. }
  157. },
  158. {
  159. label: "商户号",
  160. prop: "mchId",
  161. component: {
  162. name: "el-input"
  163. },
  164. hidden: !checkPerm(service.dj.order.permission.mch)
  165. },
  166. {
  167. label: "支付方式",
  168. prop: "payType",
  169. component: {
  170. name: "el-input"
  171. }
  172. },
  173. {
  174. label: "通道编码",
  175. prop: "code",
  176. component: {
  177. name: "el-input"
  178. },
  179. hidden: !checkPerm(service.dj.order.permission.code)
  180. },
  181. {
  182. label: "支付状态",
  183. prop: "status",
  184. component: {
  185. name: "el-select",
  186. props: {
  187. clearable: true
  188. },
  189. options: [
  190. {
  191. label: "待支付",
  192. value: 0
  193. },
  194. {
  195. label: "支付成功",
  196. value: 1
  197. },
  198. {
  199. label: "下单失败",
  200. value: 2
  201. },
  202. {
  203. label: "订单取消",
  204. value: 3
  205. },
  206. ]
  207. }
  208. },
  209. {
  210. label: "通知状态",
  211. prop: "notifyStatus",
  212. component: {
  213. name: "el-select",
  214. props: {
  215. clearable: true
  216. },
  217. options: [
  218. {
  219. label: "未通知",
  220. value: 0
  221. },
  222. {
  223. label: "已通知",
  224. value: 1
  225. },
  226. {
  227. label: "通知失败",
  228. value: 2
  229. }
  230. ]
  231. }
  232. },
  233. {
  234. label: "日期范围",
  235. prop: "createTime",
  236. component: {
  237. name: "el-date-picker",
  238. props: {
  239. style: { width: "360px" },
  240. type: "datetimerange",
  241. unlinkPanels: true,
  242. valueFormat: "YYYY-MM-DD HH:mm:ss",
  243. shortcuts: [
  244. {
  245. text: '今天',
  246. value: () => {
  247. return [dayjs().startOf('day'), dayjs().endOf('day')]
  248. }
  249. },
  250. {
  251. text: '昨天',
  252. value: () => {
  253. return [dayjs().subtract(1, 'day').startOf('day'), dayjs().subtract(1, 'day').endOf('day')]
  254. }
  255. },
  256. {
  257. text: '近三天',
  258. value: () => {
  259. return [dayjs().subtract(2, 'day').startOf('day'), dayjs().endOf('day')]
  260. }
  261. },
  262. {
  263. text: '近七天',
  264. value: () => {
  265. return [dayjs().subtract(6, 'day').startOf('day'), dayjs().endOf('day')]
  266. }
  267. },
  268. {
  269. text: '近一个月',
  270. value: () => {
  271. return [dayjs().startOf('month'), dayjs().endOf('month')]
  272. }
  273. },
  274. ],
  275. onChange() { }
  276. }
  277. }
  278. }
  279. ]);
  280. // cl-upsert
  281. const Upsert = useUpsert({
  282. dialog: {
  283. title: "标记成功"
  284. },
  285. async onInfo(data, { done, next }) {
  286. const newData = await next(data);
  287. newData.status = 1;
  288. done(newData);
  289. },
  290. items: [
  291. {
  292. label: "订单号",
  293. prop: "orderNo",
  294. required: true,
  295. component: {
  296. name: "el-input",
  297. props: {
  298. disabled: true
  299. }
  300. }
  301. },
  302. {
  303. prop: "status",
  304. label: "订单状态",
  305. component: {
  306. name: "el-select",
  307. props: {
  308. disabled: true
  309. },
  310. options: [
  311. {
  312. label: "待支付",
  313. value: 0
  314. },
  315. {
  316. label: "支付成功",
  317. value: 1
  318. },
  319. {
  320. label: "下单失败",
  321. value: 2
  322. },
  323. {
  324. label: "订单取消",
  325. value: 3
  326. },
  327. ]
  328. },
  329. required: true
  330. },
  331. {
  332. prop: "traceNo",
  333. label: "交易号",
  334. component: {
  335. name: "el-input"
  336. },
  337. required: true
  338. },
  339. {
  340. prop: "date",
  341. label: "付款时间",
  342. component: {
  343. name: "el-date-picker",
  344. props: { type: "datetime", valueFormat: "YYYY-MM-DD HH:mm:ss" }
  345. },
  346. required: true
  347. },
  348. {
  349. prop: "remark",
  350. label: "备注",
  351. component: { name: "el-input", props: { type: "textarea", rows: 4 } }
  352. }
  353. ]
  354. });
  355. // cl-table
  356. const Table = useTable({
  357. autoHeight: false,
  358. columns: [
  359. () => {
  360. return {
  361. label: "#",
  362. type: "expand",
  363. prop: "detail"
  364. };
  365. },
  366. { prop: "orderNo", label: "订单号", showOverflowTooltip: true, width: 120 },
  367. { prop: "outOrderNo", label: "商户订单号", showOverflowTooltip: true, width: 120 },
  368. { prop: "traceNo", label: "交易号", showOverflowTooltip: true, width: 120 },
  369. {
  370. prop: "mchId", label: "商户号", hidden: !checkPerm(service.dj.order.permission.mch), width: 90
  371. },
  372. {
  373. prop: "amount",
  374. label: "金额",
  375. width: 90,
  376. formatter(row) {
  377. return (+row.amount).toFixed(2);
  378. }
  379. },
  380. {
  381. prop: "charge",
  382. label: "手续费",
  383. width: 90,
  384. formatter(row) {
  385. return (+row.charge).toFixed(2);
  386. }
  387. },
  388. {
  389. prop: "currency",
  390. label: "货币"
  391. },
  392. // { prop: "date", label: "付款时间" },
  393. {
  394. prop: "status",
  395. label: "订单状态",
  396. dict: [
  397. { label: "待支付", value: 0, color: "#909399" },
  398. { label: "支付成功", value: 1, color: "#67C23A" },
  399. { label: "下单失败", value: 2, color: "#F56C6C" },
  400. { label: "订单取消", value: 3, color: "#F56C6C" }
  401. ], width: 90
  402. },
  403. {
  404. prop: "notifyStatus",
  405. label: "通知状态",
  406. dict: [
  407. { label: "未通知", value: 0, color: "#909399" },
  408. { label: "已通知", value: 1, color: "#67C23A" },
  409. { label: "通知失败", value: 2, color: "#F56C6C" }
  410. ], width: 90
  411. },
  412. { prop: "payType", label: "支付方式", showOverflowTooltip: true, width: 90 },
  413. {
  414. prop: "code", label: "通道编码", hidden: !checkPerm(service.dj.order.permission.code), width: 120
  415. },
  416. { prop: "createTime", label: "创建时间", width: 155 },
  417. // { prop: "remark", label: "备注", showOverflowTooltip: true },
  418. {
  419. type: "op",
  420. width: checkPerm(service.dj.order.permission.update) ? 210 : 120,
  421. buttons({ scope }) {
  422. const btns = [];
  423. if (checkPerm(service.dj.order.permission.update)) {
  424. btns.push({
  425. label: "标记成功",
  426. type: "danger",
  427. onClick({ scope }: any) {
  428. Upsert.value?.edit(scope.row);
  429. }
  430. });
  431. }
  432. if (+scope.row.status === 1 || +scope.row.status === 3) {
  433. // if (checkPerm(service.dj.order.permission.toRefund)) {
  434. // btns.push({
  435. // label: "退款",
  436. // type: "danger",
  437. // async onClick({ scope }: any) {
  438. // openRefund(scope.row)
  439. // }
  440. // });
  441. // }
  442. if (checkPerm(service.dj.order.permission.notify)) {
  443. btns.push({
  444. label: "补发通知",
  445. type: "success",
  446. async onClick({ scope }: any) {
  447. const { status, msg = "" } = await service.dj.order.notify({
  448. id: scope.row.id
  449. });
  450. if (+status === 1) {
  451. ElMessage.success("补发通知成功!");
  452. Crud.value?.refresh();
  453. } else {
  454. ElMessage.error("通知失败,失败原因:" + JSON.stringify(msg));
  455. }
  456. }
  457. });
  458. }
  459. } else {
  460. if (checkPerm(service.dj.order.permission.query)) {
  461. btns.push({
  462. label: "API查单",
  463. type: "primary",
  464. async onClick({ scope }: any) {
  465. try {
  466. const data = await service.dj.order.query({ id: scope.row.id });
  467. if (+data.status === 0 || +data.status === 2) {
  468. ElMessage.warning("订单未支付,请稍后查询");
  469. } else {
  470. ElMessage.success("订单支付成功,已刷新状态!");
  471. Crud.value?.refresh();
  472. }
  473. } catch (e) {
  474. ElMessage.warning("订单未支付,请稍后查询");
  475. }
  476. }
  477. });
  478. }
  479. }
  480. return btns;
  481. }
  482. }
  483. ]
  484. });
  485. // cl-crud
  486. const Crud = useCrud(
  487. {
  488. service: service.dj.order,
  489. async onRefresh(params, { next, render }) {
  490. const { list, pagination } = await next({
  491. order: "createTime",
  492. sort: "desc",
  493. ...params
  494. });
  495. if (showSummary.value) {
  496. const { num1, num2, num3, num4, num5 } = (
  497. await service.dj.order.summary(params)
  498. )[0];
  499. summary.value.num1 = +num1;
  500. summary.value.num2 = +num2;
  501. summary.value.num3 = +num3;
  502. summary.value.num4 = +num4 > 0 ? (+num3 / +num4) * 100 : 0;
  503. summary.value.num5 = +num5;
  504. }
  505. render(list, pagination);
  506. }
  507. },
  508. (app) => {
  509. app.refresh(filterParam.value);
  510. }
  511. );
  512. </script>
  513. <style lang="scss">
  514. .orderList {
  515. .el-descriptions {
  516. .el-descriptions__label {
  517. width: 120px !important;
  518. }
  519. }
  520. }
  521. .desc .el-descriptions__label {
  522. min-width: 115px;
  523. }
  524. .tooltip {
  525. max-width: 100px !important;
  526. overflow: hidden;
  527. text-overflow: ellipsis;
  528. white-space: nowrap;
  529. }
  530. </style>