index.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. import { TimeController } from "@/controllers/TimeController";
  2. import { initPayment } from "@/modules/payment";
  3. import { css } from "@linaria/core";
  4. import { useAuth } from "@queenjs-modules/auth";
  5. import { InputNumber } from "ant-design-vue";
  6. import { queenApi } from "queenjs";
  7. import Modal from "queenjs/adapter/vue/components/modal";
  8. import { defineComponent, reactive } from "vue";
  9. import PayQrcode from "./PayQrcode";
  10. import { IconScan, IconTransfer } from "@/assets/icons";
  11. export default defineComponent({
  12. setup() {
  13. const auth = useAuth();
  14. const payment = initPayment({
  15. config: { project: "queenshow" },
  16. });
  17. const { store, https, actions } = payment;
  18. const payVersions = [
  19. {
  20. label: "个人",
  21. children: [
  22. "queenshow_person_year",
  23. "queenshow_person_month",
  24. // "queenshow_point_test",
  25. ],
  26. },
  27. {
  28. label: "团队",
  29. children: [
  30. "queenshow_team_15month",
  31. "queenshow_team_year",
  32. "queenshow_team_month",
  33. ],
  34. },
  35. ];
  36. const payWays = [
  37. {
  38. label: "快捷支付",
  39. icon: IconScan,
  40. },
  41. {
  42. label: "对公转账",
  43. icon: IconTransfer,
  44. },
  45. ];
  46. const payMethods: Record<string, { label: string; icon: string }> = {
  47. wechatPay: {
  48. label: "微信支付",
  49. icon: require("@/assets/imgs/icon-wx.png"),
  50. },
  51. aliPay: {
  52. label: "支付宝支付",
  53. icon: require("@/assets/imgs/icon-zfb.png"),
  54. },
  55. };
  56. const payPointsInfo: Record<
  57. string,
  58. {
  59. name: string;
  60. desc: string;
  61. default: { quantity: number; number: number };
  62. quantityProps?: any;
  63. numberProps?: any;
  64. quantityUnit?: string;
  65. priceTip?: string;
  66. }
  67. > = {
  68. // queenshow_point_test: {
  69. // name: "测试",
  70. // desc: "xxxx",
  71. // default: {
  72. // quantity: 1,
  73. // number: 1,
  74. // },
  75. // },
  76. queenshow_person_year: {
  77. name: "一年VIP",
  78. desc: "高性价比之选",
  79. default: {
  80. quantity: 1,
  81. number: 1,
  82. },
  83. },
  84. queenshow_person_month: {
  85. name: "月度VIP",
  86. desc: "续费价199.0元",
  87. default: {
  88. quantity: 1,
  89. number: 1,
  90. },
  91. quantityProps: {
  92. min: 1,
  93. },
  94. quantityUnit: "月",
  95. },
  96. queenshow_team_15month: {
  97. name: "15个月/人",
  98. desc: "鲲舞精心之选",
  99. priceTip: "团购30人以上",
  100. default: {
  101. quantity: 1,
  102. number: 30,
  103. },
  104. numberProps: {
  105. min: 30,
  106. },
  107. },
  108. queenshow_team_year: {
  109. name: "年VIP/人",
  110. desc: "高性价比之选",
  111. default: {
  112. quantity: 1,
  113. number: 1,
  114. },
  115. numberProps: {
  116. min: 1,
  117. },
  118. },
  119. queenshow_team_month: {
  120. name: "月VIP/人",
  121. desc: "续费价199.0元",
  122. default: {
  123. quantity: 1,
  124. number: 1,
  125. },
  126. numberProps: {
  127. min: 1,
  128. },
  129. quantityProps: {
  130. min: 1,
  131. },
  132. quantityUnit: "月",
  133. },
  134. };
  135. const state = reactive({
  136. versionIdx: 0,
  137. payWayIdx: 0,
  138. orderInfo: {
  139. productKey: "",
  140. quantity: 0,
  141. number: 0,
  142. },
  143. amountPrice: 0,
  144. });
  145. function changeVersion(idx: number) {
  146. state.versionIdx = idx;
  147. initOrderInfo(payVersions[idx].children[0]);
  148. }
  149. function initOrderInfo(productKey: string) {
  150. state.orderInfo = {
  151. productKey,
  152. ...payPointsInfo[productKey].default,
  153. };
  154. }
  155. function changeOrderInfo(
  156. type: keyof (typeof state)["orderInfo"],
  157. value: any
  158. ) {
  159. (state.orderInfo as any)[type] = value;
  160. if (type === "productKey") {
  161. initOrderInfo(value);
  162. }
  163. updateOrderAmount();
  164. }
  165. function updateOrderAmount() {
  166. https.getPayAmount(state.orderInfo).then((ret) => {
  167. state.amountPrice = ret.result;
  168. });
  169. }
  170. async function createOrderQrcode(payMod: number) {
  171. queenApi.showLoading("加载中");
  172. try {
  173. // const orderId = "64b764473d1915628fe24045";
  174. // const link = "64b764473d1915628fe24045";
  175. const { orderId, link } = await actions.createOrder(payMod, {
  176. ...state.orderInfo,
  177. amount: state.amountPrice,
  178. });
  179. const orderStatusCtrl = new TimeController({
  180. delayTime: 8000,
  181. durationTime: 3000,
  182. });
  183. orderStatusCtrl
  184. .setLoop(async () => {
  185. const { result } = await https.getOrderDetail({ id: orderId });
  186. if (result.status === 2) {
  187. orderStatusCtrl.stop();
  188. Modal.clear();
  189. auth.actions.getUserInfo();
  190. }
  191. })
  192. .start();
  193. queenApi
  194. .dialog(
  195. <PayQrcode orderId={orderId} link={link} />,
  196. {
  197. closable: false,
  198. },
  199. { payment }
  200. )
  201. .catch(() => {
  202. orderStatusCtrl.stop();
  203. });
  204. } catch (error: any) {
  205. queenApi.messageError(error.toString());
  206. } finally {
  207. queenApi.hideLoading();
  208. }
  209. }
  210. changeOrderInfo("productKey", payVersions[state.versionIdx].children[0]);
  211. return () => {
  212. const { saas } = auth.store.userInfo as any;
  213. const currPayInfo = payPointsInfo[state.orderInfo.productKey];
  214. const currPayPoint = store.payPoints.find(
  215. (d) => d.productKey === state.orderInfo.productKey
  216. );
  217. return (
  218. <div class={rootCls}>
  219. <header class="p-0.24rem">
  220. <div class="flex">
  221. <img
  222. class="w-0.48rem h-0.48rem rounded-[50%]"
  223. src={auth.store.userInfo.avatar}
  224. />
  225. <div class="ml-0.14rem space-y-0.08rem">
  226. <div class="text-0.2rem font-500 text-white">
  227. {auth.store.userInfo.name}
  228. </div>
  229. <div class="text-0.16rem font-500 text-[#888]">
  230. {saas ? "会员版" : "免费版"}
  231. </div>
  232. </div>
  233. </div>
  234. </header>
  235. <main>
  236. <section class="w-2.95rem mr-0.24rem space-y-0.2rem">
  237. <div class="flex items-center">
  238. <img
  239. class="w-0.22rem h-0.22rem mr-0.1rem"
  240. src={require("@/assets/imgs/icon-vip.png")}
  241. />
  242. <span class="text-0.22rem text-[#222]">会员版</span>
  243. </div>
  244. <p>适合个人创作设计,如个人新媒体、个人网店、直播带货等</p>
  245. <div class="splitline"></div>
  246. <ul class="pl-0.16rem leading-0.24rem space-y-0.12rem">
  247. <li>个人或企业商用授权(选购)</li>
  248. <li>VIP专属内容</li>
  249. <li>模版创建无上限</li>
  250. <li>鲲秀AI使用权限</li>
  251. <li>专属客服/正规发票</li>
  252. </ul>
  253. </section>
  254. <div class="flex-1 space-y-0.18rem">
  255. <section>
  256. <label class="">购买版本</label>
  257. {payVersions.map((d, i) => {
  258. return (
  259. <span
  260. key={i}
  261. class={["checkbox", i === state.versionIdx && "checked"]}
  262. onClick={() => changeVersion(i)}
  263. >
  264. {d.label}
  265. </span>
  266. );
  267. })}
  268. </section>
  269. <section>
  270. <label>价格选择</label>
  271. <div class="mt-0.24rem">
  272. {payVersions[state.versionIdx].children.map((d, i) => {
  273. const item = payPointsInfo[d as keyof typeof payPointsInfo];
  274. const price =
  275. store.payPoints.find((p) => p.productKey === d)?.price ||
  276. 0;
  277. return (
  278. <div
  279. key={i}
  280. class={[
  281. "checkbox priceBox",
  282. state.orderInfo.productKey === d && "checked",
  283. ]}
  284. onClick={() => changeOrderInfo("productKey", d)}
  285. >
  286. {item.priceTip && (
  287. <div class="priceTip">{item.priceTip}</div>
  288. )}
  289. <div class="text-0.16rem font-bold text-[#666] pt-0.16rem ">
  290. {item.name}
  291. </div>
  292. <div class="text-0.3rem font-bold">
  293. <span class="text-0.14rem">&yen;</span>
  294. {price.toFixed(1)}
  295. </div>
  296. <div class="text-0.14rem text-[#666] pt-0.16rem ">
  297. {item.desc}
  298. </div>
  299. </div>
  300. );
  301. })}
  302. </div>
  303. <div class="mt-0.28rem text-[#111]">
  304. {currPayInfo.numberProps && (
  305. <>
  306. <span class="text-0.16rem font-bold">人数选择:</span>
  307. <InputNumber
  308. class="inputNum"
  309. value={state.orderInfo.number}
  310. onChange={(v) => changeOrderInfo("number", v)}
  311. {...currPayInfo.numberProps}
  312. />
  313. <span class="text-0.16rem text-[#666] mr-0.85rem">
  314. </span>
  315. </>
  316. )}
  317. {currPayInfo.quantityProps && (
  318. <>
  319. <span class="text-0.16rem font-bold">时长设置:</span>
  320. <InputNumber
  321. class="inputNum"
  322. value={state.orderInfo.quantity}
  323. onChange={(v) => changeOrderInfo("quantity", v)}
  324. {...currPayInfo.quantityProps}
  325. />
  326. <span class="text-0.16rem text-[#666]">
  327. {currPayInfo.quantityUnit}
  328. </span>
  329. </>
  330. )}
  331. </div>
  332. </section>
  333. <section>
  334. <label class="">支付订单</label>
  335. <div class="payMethod flex">
  336. <div class="py-0.24rem space-y-0.14rem">
  337. {payWays.map((d, i) => {
  338. return (
  339. <div
  340. class={[
  341. "payTabItem",
  342. state.payWayIdx === i && "checked",
  343. ]}
  344. key={i}
  345. onClick={() => (state.payWayIdx = i)}
  346. >
  347. <d.icon class="mr-0.08rem" />
  348. {d.label}
  349. </div>
  350. );
  351. })}
  352. </div>
  353. <div class="flex-1 ml-0.3rem">
  354. <div class="text-0.16rem font-bold my-0.3rem">
  355. <span class="text-[#333]">实付款:</span>
  356. <span class="text-0.3rem text-[#D34D39]">
  357. {state.amountPrice}
  358. </span>
  359. <span class="text-[#D34D39]">元</span>
  360. </div>
  361. {state.payWayIdx === 0 && (
  362. <div>
  363. {currPayPoint?.payMethods.map((d, i) => {
  364. const item = payMethods[d.Key];
  365. const isLast =
  366. currPayPoint.payMethods.length === i + 1;
  367. return (
  368. <>
  369. <div
  370. class="payBtn"
  371. key={d.Key}
  372. onClick={() => createOrderQrcode(d.Value)}
  373. >
  374. <img
  375. class="h-0.2rem mr-0.1rem"
  376. src={item.icon}
  377. />
  378. {item.label}
  379. </div>
  380. {!isLast && (
  381. <span class="mx-0.1rem align-text-bottom">
  382. </span>
  383. )}
  384. </>
  385. );
  386. })}
  387. </div>
  388. )}
  389. {state.payWayIdx === 1 && (
  390. <div class="text-0.16rem space-y-0.1rem">
  391. <div class="transfer-row">
  392. <span>户名</span>成都中鱼互动科技有限公司
  393. </div>
  394. <div class="transfer-row">
  395. <span>开户行</span>中国银行成都益州支行
  396. </div>
  397. <div class="transfer-row">
  398. <span>帐号</span>1185 5686 4401
  399. </div>
  400. <div class="transfer-row">
  401. <span>备注</span>注册手机号+购买版本+人数+时长
  402. </div>
  403. </div>
  404. )}
  405. </div>
  406. </div>
  407. </section>
  408. </div>
  409. </main>
  410. </div>
  411. );
  412. };
  413. },
  414. });
  415. const rootCls = css`
  416. margin: -24px;
  417. header {
  418. background-color: #2a2d41;
  419. }
  420. main {
  421. display: flex;
  422. padding: 0.24rem;
  423. background-color: #f1f2f4;
  424. color: #666;
  425. }
  426. section {
  427. padding: 0.24rem;
  428. background-color: #fff;
  429. border-radius: 0.08rem;
  430. label {
  431. margin-right: 0.4rem;
  432. font-size: 0.2rem;
  433. color: #111;
  434. }
  435. }
  436. .checkbox {
  437. display: inline-block;
  438. padding: 0.16rem;
  439. margin-right: 0.3rem;
  440. min-width: 1.4rem;
  441. border: 1px solid rgba(51, 51, 51, 0.3);
  442. border-radius: 0.06rem;
  443. font-size: 0.18rem;
  444. color: #333;
  445. text-align: center;
  446. cursor: pointer;
  447. &.priceBox {
  448. position: relative;
  449. display: inline-flex;
  450. flex-direction: column;
  451. justify-content: space-between;
  452. width: 1.6rem;
  453. height: 1.6rem;
  454. vertical-align: top;
  455. text-align: left;
  456. }
  457. &.checked {
  458. border: 1px solid #885428;
  459. background-color: #fff7e8;
  460. color: #885428;
  461. * {
  462. color: #885428;
  463. }
  464. }
  465. .priceTip {
  466. position: absolute;
  467. top: 0;
  468. right: 0;
  469. padding: 0.06rem 0.1rem;
  470. font-size: 0.12rem;
  471. line-height: 1;
  472. background-color: #93643d;
  473. color: #fff7e8;
  474. border-top-right-radius: 0.04rem;
  475. border-bottom-left-radius: 0.04rem;
  476. }
  477. }
  478. .splitline {
  479. border-bottom: 1px solid #e0e0e0;
  480. }
  481. .payTabItem {
  482. display: flex;
  483. align-items: center;
  484. justify-content: center;
  485. width: 1.58rem;
  486. height: 0.58rem;
  487. color: #666;
  488. border-radius: 0.06rem;
  489. cursor: pointer;
  490. &.checked {
  491. background-color: #f1f2f4;
  492. color: #333;
  493. }
  494. }
  495. .payMethod {
  496. margin-top: 0.24rem;
  497. border-top: 1px solid #e0e0e0;
  498. }
  499. .payBtn {
  500. display: inline-flex;
  501. width: 2rem;
  502. justify-content: center;
  503. align-items: center;
  504. padding: 0.12rem;
  505. border: 1px solid #e0e0e0;
  506. border-radius: 0.06rem;
  507. cursor: pointer;
  508. }
  509. .inputNum {
  510. @apply w-1.8rem mx-0.16rem border-[#c9c9c9] text-[#111];
  511. }
  512. .transfer-row{
  513. span {
  514. display: inline-block;
  515. width: 0.6rem;
  516. }
  517. }
  518. `;