PlaymateRecordCard.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. <script setup lang="ts">
  2. import { useI18n } from 'vue-i18n'
  3. import { formatNumber } from '~/utils/helpers'
  4. import MaleIconSvg from '~/assets/icons/male.svg'
  5. import FemaleIconSvg from '~/assets/icons/female.svg'
  6. import { statusPlaymateKeyMap, type OrderStatus } from '~/types/order'
  7. type Gender = 0 | 1 | 2 // 0=unknown, 1=male, 2=female
  8. const props = withDefaults(
  9. defineProps<{
  10. orderNo: string
  11. orderTime: string
  12. statusText: OrderStatus
  13. gameName: string
  14. gameIcon: string
  15. coins: number
  16. quantity: number
  17. unit: string
  18. total: number
  19. avatar: string
  20. nickname: string
  21. gender: Gender
  22. customerRemark?: string
  23. refundApply?: boolean
  24. showActions?: boolean
  25. actionLoading?: boolean
  26. }>(),
  27. {
  28. customerRemark: '',
  29. refundApply: false,
  30. showActions: true,
  31. actionLoading: false,
  32. },
  33. )
  34. const emit = defineEmits<{
  35. (e: 'accept' | 'refuse' | 'startService' | 'finishService' | 'submitProof', payload: typeof props): void
  36. }>()
  37. const { t } = useI18n()
  38. const img = useImage()
  39. const handleRefuse = () => emit('refuse', props)
  40. const handleAccept = () => emit('accept', props)
  41. const handleStartService = () => emit('startService', props)
  42. const handleFinishService = () => emit('finishService', props)
  43. const handleSubmitProof = () => emit('submitProof', props)
  44. const isDiscount = computed(() => {
  45. return props.total < props.coins * props.quantity
  46. })
  47. </script>
  48. <template>
  49. <article class="playmate-record-card">
  50. <header class="playmate-record-card__header">
  51. <p class="playmate-record-card__no">
  52. {{ orderTime }}
  53. </p>
  54. <p
  55. v-if="!refundApply"
  56. :class="['playmate-record-card__status', `playmate-record-card__status--${statusText}`]"
  57. >
  58. {{ t(statusPlaymateKeyMap[statusText]) }}
  59. </p>
  60. <p
  61. v-else
  62. class="playmate-record-card__status playmate-record-card__status--refunding"
  63. >
  64. {{ t('order.refundPlaymate.refunding') }}
  65. </p>
  66. </header>
  67. <div class="playmate-record-card__divider" />
  68. <section class="playmate-record-card__body">
  69. <div class="playmate-record-card__game">
  70. <div
  71. class="playmate-record-card__game-icon"
  72. aria-hidden="true"
  73. >
  74. <NuxtImg
  75. v-if="gameIcon"
  76. :src="gameIcon"
  77. :alt="gameName"
  78. loading="lazy"
  79. :placeholder="img('/placeholder.png')"
  80. />
  81. </div>
  82. <div class="playmate-record-card__game-info">
  83. <p class="playmate-record-card__game-name">
  84. {{ gameName }}
  85. </p>
  86. <p class="playmate-record-card__game-id">
  87. {{ orderNo }}
  88. </p>
  89. </div>
  90. </div>
  91. <div class="playmate-record-card__price">
  92. <div class="playmate-record-card__price-row">
  93. <div
  94. v-if="isDiscount"
  95. class="playmate-record-card__discount-tag"
  96. >
  97. <span>{{ t('order.detail.discount') }}</span>
  98. </div>
  99. <span
  100. class="playmate-record-card__coin"
  101. aria-hidden="true"
  102. />
  103. <span class="playmate-record-card__amount">
  104. {{ formatNumber(total) }}
  105. </span>
  106. </div>
  107. <p class="playmate-record-card__unit">
  108. {{ unit }} x{{ quantity }}
  109. </p>
  110. </div>
  111. </section>
  112. <section class="playmate-record-card__request">
  113. <div class="playmate-record-card__user-row">
  114. <div class="playmate-record-card__avatar">
  115. <NuxtImg
  116. :src="avatar"
  117. :alt="nickname"
  118. loading="lazy"
  119. :placeholder="img('/avatar.png')"
  120. />
  121. </div>
  122. <div class="playmate-record-card__user-meta">
  123. <p class="playmate-record-card__nickname">
  124. {{ nickname }}
  125. </p>
  126. <template v-if="gender">
  127. <MaleIconSvg
  128. v-if="gender === 1"
  129. />
  130. <FemaleIconSvg
  131. v-if="gender === 2"
  132. />
  133. </template>
  134. </div>
  135. </div>
  136. <template v-if="customerRemark">
  137. <div class="playmate-record-card__request-divider" />
  138. <div class="playmate-record-card__request-content">
  139. <p class="playmate-record-card__request-title">
  140. {{ t('order.playmateRecord.requestTitle') }}
  141. </p>
  142. <p class="playmate-record-card__request-text">
  143. {{ customerRemark }}
  144. </p>
  145. </div>
  146. </template>
  147. </section>
  148. <footer
  149. v-if="showActions"
  150. class="playmate-record-card__footer"
  151. >
  152. <div class="playmate-record-card__actions">
  153. <button
  154. v-if="refundApply"
  155. type="button"
  156. class="playmate-record-card__btn playmate-record-card__btn--outline playmate-record-card__btn--refund"
  157. :disabled="actionLoading"
  158. @click.stop="handleSubmitProof"
  159. >
  160. {{ t('order.refundPlaymate.entry') }}
  161. </button>
  162. <button
  163. v-if="statusText === 'pending'"
  164. type="button"
  165. class="playmate-record-card__btn playmate-record-card__btn--outline"
  166. :disabled="actionLoading"
  167. @click.stop="handleRefuse"
  168. >
  169. {{ t('order.playmateRecord.refuse') }}
  170. </button>
  171. <button
  172. v-if="statusText === 'pending'"
  173. type="button"
  174. class="playmate-record-card__btn playmate-record-card__btn--primary"
  175. :disabled="actionLoading"
  176. @click.stop="handleAccept"
  177. >
  178. {{ t('order.playmateRecord.acceptOrders') }}
  179. </button>
  180. <button
  181. v-if="statusText === 'accepted'"
  182. type="button"
  183. class="playmate-record-card__btn playmate-record-card__btn--primary"
  184. :disabled="actionLoading"
  185. @click.stop="handleStartService"
  186. >
  187. {{ t('order.playmateRecord.startService') }}
  188. </button>
  189. <button
  190. v-if="statusText === 'processing'"
  191. type="button"
  192. class="playmate-record-card__btn playmate-record-card__btn--primary"
  193. :disabled="actionLoading"
  194. @click.stop="handleFinishService"
  195. >
  196. {{ t('order.playmateRecord.finishService') }}
  197. </button>
  198. </div>
  199. </footer>
  200. </article>
  201. </template>
  202. <style scoped lang="scss">
  203. .playmate-record-card {
  204. width: 100%;
  205. margin: 0 auto;
  206. display: flex;
  207. flex-direction: column;
  208. align-items: center;
  209. padding: 2px 0 8px 0;
  210. border-radius: 12px;
  211. background: #fff;
  212. }
  213. .playmate-record-card__header {
  214. width: 100%;
  215. height: 30px;
  216. padding: 0 12px;
  217. display: flex;
  218. align-items: center;
  219. justify-content: space-between;
  220. }
  221. .playmate-record-card__no {
  222. font-size: 11px;
  223. line-height: 14px;
  224. color: var(--color-text-secondary, #4E5969);
  225. }
  226. .playmate-record-card__status {
  227. font-size: 12px;
  228. line-height: 15px;
  229. font-weight: 600;
  230. color: #86909C;
  231. &--pending {
  232. color: #FFB836;
  233. }
  234. &--completed {
  235. color: #3FBFBD;
  236. }
  237. &--accepted {
  238. color: #3FBFBD;
  239. }
  240. &--processing {
  241. color: #FFB836;
  242. }
  243. &--processed {
  244. color: #3FBFBD;
  245. }
  246. &--refused,
  247. &--cancelled,
  248. &--refunded {
  249. color: #86909C;
  250. }
  251. &--refunding {
  252. color: #f23051;
  253. }
  254. }
  255. .playmate-record-card__divider {
  256. width: calc(100% - 24px);
  257. height: 1px;
  258. background: #F2F3F5;
  259. }
  260. .playmate-record-card__body {
  261. width: 100%;
  262. padding: 2px 12px;
  263. display: flex;
  264. align-items: center;
  265. justify-content: space-between;
  266. }
  267. .playmate-record-card__game {
  268. display: flex;
  269. align-items: center;
  270. gap: 8px;
  271. min-width: 0;
  272. }
  273. .playmate-record-card__game-icon {
  274. @include size(50px);
  275. flex-shrink: 0;
  276. display: flex;
  277. align-items: center;
  278. justify-content: center;
  279. img {
  280. width: 100%;
  281. height: 100%;
  282. display: block;
  283. object-fit: contain;
  284. }
  285. }
  286. .playmate-record-card__game-info {
  287. display: flex;
  288. flex-direction: column;
  289. gap: 3px;
  290. }
  291. .playmate-record-card__game-name {
  292. font-size: 14px;
  293. line-height: 17px;
  294. font-weight: 600;
  295. color: var(--color-text-primary, #1D2129);
  296. white-space: nowrap;
  297. overflow: hidden;
  298. text-overflow: ellipsis;
  299. }
  300. .playmate-record-card__game-id {
  301. font-size: 11px;
  302. line-height: 14px;
  303. color: #86909C;
  304. }
  305. .playmate-record-card__price {
  306. width: 52px;
  307. display: flex;
  308. flex-direction: column;
  309. align-items: flex-end;
  310. justify-content: center;
  311. gap: 4px;
  312. flex-shrink: 0;
  313. }
  314. .playmate-record-card__price-row {
  315. display: flex;
  316. align-items: center;
  317. justify-content: center;
  318. gap: 2px;
  319. }
  320. .playmate-record-card__discount-tag {
  321. height: 18px;
  322. padding: 0 5px;
  323. display: flex;
  324. justify-content: center;
  325. align-items: center;
  326. border-radius: 4px;
  327. background-color: #ff6f32;
  328. margin-right: 3px;
  329. span {
  330. color: #fff;
  331. font-size: 11px;
  332. font-weight: 400;
  333. line-height: 14px;
  334. white-space: nowrap;
  335. }
  336. }
  337. .playmate-record-card__coin {
  338. display: inline-block;
  339. @include bg('~/assets/images/common/coin.png', contain);
  340. @include size(18px);
  341. }
  342. .playmate-record-card__amount {
  343. font-family: 'Poppins';
  344. font-size: 16px;
  345. line-height: 17px;
  346. font-weight: 600;
  347. color: var(--color-text-primary, #1D2129);
  348. text-align: right;
  349. white-space: nowrap;
  350. }
  351. .playmate-record-card__unit {
  352. font-size: 14px;
  353. line-height: 16px;
  354. color: var(--color-text-primary, #1D2129);
  355. text-align: right;
  356. white-space: nowrap;
  357. }
  358. .playmate-record-card__request {
  359. margin-top: 4px;
  360. width: calc(100% - 24px);
  361. padding: 8px;
  362. background: #F9FAFB;
  363. border-radius: 12px;
  364. display: flex;
  365. flex-direction: column;
  366. gap: 4px;
  367. }
  368. .playmate-record-card__user-row {
  369. display: flex;
  370. align-items: center;
  371. gap: 8px;
  372. }
  373. .playmate-record-card__avatar {
  374. @include size(23px);
  375. border-radius: 50%;
  376. overflow: hidden;
  377. flex-shrink: 0;
  378. img {
  379. width: 100%;
  380. height: 100%;
  381. display: block;
  382. object-fit: cover;
  383. }
  384. }
  385. .playmate-record-card__user-meta {
  386. display: inline-flex;
  387. align-items: center;
  388. gap: 3px;
  389. min-width: 0;
  390. }
  391. .playmate-record-card__nickname {
  392. font-size: 12px;
  393. line-height: 14px;
  394. font-weight: 400;
  395. color: var(--color-text-primary, #1D2129);
  396. white-space: nowrap;
  397. overflow: hidden;
  398. text-overflow: ellipsis;
  399. }
  400. .playmate-record-card__request-divider {
  401. width: 100%;
  402. height: 1px;
  403. background: #E5E6EB;
  404. }
  405. .playmate-record-card__request-content {
  406. color: var(--color-text-secondary, #4E5969);
  407. }
  408. .playmate-record-card__request-title {
  409. font-size: 12px;
  410. line-height: 12px;
  411. font-weight: 600;
  412. margin-bottom: 2px;
  413. }
  414. .playmate-record-card__request-text {
  415. font-size: 11px;
  416. line-height: 14px;
  417. font-weight: 400;
  418. text-align: justify;
  419. white-space: pre-wrap;
  420. }
  421. .playmate-record-card__footer {
  422. width: 100%;
  423. height: 38px;
  424. padding: 0 12px;
  425. display: flex;
  426. align-items: flex-end;
  427. justify-content: flex-end;
  428. overflow: hidden;
  429. }
  430. .playmate-record-card__actions {
  431. display: flex;
  432. align-items: center;
  433. gap: 6px;
  434. }
  435. .playmate-record-card__btn {
  436. height: 30px;
  437. padding: 4px 10px;
  438. border-radius: 30px;
  439. font-size: 12px;
  440. font-weight: 600;
  441. line-height: 16px;
  442. display: inline-flex;
  443. align-items: center;
  444. justify-content: center;
  445. border: 1px solid transparent;
  446. background: transparent;
  447. white-space: nowrap;
  448. }
  449. .playmate-record-card__btn--outline {
  450. border-color: #E5E6EB;
  451. color: #454A50;
  452. background: #fff;
  453. &.playmate-record-card__btn--refund {
  454. border-color: #4ED2FF;
  455. color: #3FBFBD;
  456. }
  457. }
  458. .playmate-record-card__btn--primary {
  459. border: none;
  460. color: #fff;
  461. background: linear-gradient(90deg, #2F95FF 28.365%, #50FFD8 100%);
  462. }
  463. .playmate-record-card__btn:disabled {
  464. opacity: 0.6;
  465. cursor: not-allowed;
  466. }
  467. </style>