detail.vue 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. <template>
  2. <div class="tax-page">
  3. <!-- 顶部导航 -->
  4. <van-nav-bar
  5. title="应缴税信息"
  6. fixed
  7. placeholder
  8. safe-area-inset-top
  9. left-arrow
  10. @click-left="onBack"
  11. />
  12. <div class="page-content">
  13. <!-- 骨架屏 -->
  14. <div v-if="loading" class="skeleton">
  15. <div v-for="n in 4" :key="n" class="skeleton-card shimmer"></div>
  16. </div>
  17. <!-- 有数据 -->
  18. <div v-else-if="taxList.length" class="card">
  19. <h3 class="card-title">应纳税费信息</h3>
  20. <div v-for="(item, index) in taxList" :key="index" class="tax-item">
  21. <div class="tax-header" @click="toggle(index)">
  22. <div class="tax-name">{{ item.taxName ?? '未知税种' }}</div>
  23. <div class="tax-amount amount--weak">
  24. {{ item.amount ?? '0.00' }}
  25. <van-icon :name="item.open ? 'arrow-up' : 'arrow-down'" class="arrow" />
  26. </div>
  27. </div>
  28. <transition name="expand">
  29. <div v-show="item.open" class="tax-detail">
  30. <div class="tax-row">
  31. <span class="label">减免税额</span>
  32. <span class="value amount--weak">
  33. {{ item.deductionAmount ?? '-' }}
  34. </span>
  35. </div>
  36. <div class="tax-row">
  37. <span class="label">应纳税额</span>
  38. <span class="value amount--strong">
  39. {{ item.originAmount ?? '-' }}
  40. </span>
  41. </div>
  42. </div>
  43. </transition>
  44. </div>
  45. </div>
  46. <!-- 无数据 -->
  47. <div v-else class="empty">您的税费明细生成中,请稍后再试</div>
  48. <div v-if="!loading && taxList.length" class="tax-notice" role="note">
  49. <van-icon name="info" class="tax-notice-icon" aria-hidden="true" />
  50. <span>税费为预估仅供参考,以税局核定、实际扣款为准。</span>
  51. </div>
  52. </div>
  53. <!-- 底部栏 -->
  54. <footer class="footer" v-if="!loading">
  55. <span>应补(退)税额</span>
  56. <span class="amount">¥ {{ totalAmount }}</span>
  57. </footer>
  58. </div>
  59. </template>
  60. <script setup lang="ts">
  61. import { ref, reactive, onMounted } from 'vue'
  62. import { showToast } from 'vant'
  63. import { getInvoiceTaxApi } from '@/services/modules/invoiceInformation'
  64. import type {
  65. PushRecordIdRequest,
  66. InvoiceTaxDetailResponse,
  67. InvoiceTaxDetailApiResponse,
  68. InvoiceTaxDetailItem,
  69. } from '@/services/modules/invoiceInformation/type.d.ts'
  70. import { useUserStore } from '@/stores/modules/user'
  71. const userStore = useUserStore()
  72. const loading = ref(true)
  73. const taxList = ref<(InvoiceTaxDetailItem & { open: boolean })[]>([])
  74. const totalAmount = ref('0.00')
  75. const params = reactive<PushRecordIdRequest>({
  76. pushRecordId: userStore.pushRecordId,
  77. })
  78. const toggle = (index: number) => {
  79. const item = taxList.value[index]
  80. if (!item) return
  81. item.open = !item.open
  82. }
  83. const onBack = () => history.back()
  84. const invoiceTaxInfo = ref<InvoiceTaxDetailResponse>({})
  85. const getInvoiceTax = async () => {
  86. loading.value = true
  87. try {
  88. const res: InvoiceTaxDetailApiResponse = await getInvoiceTaxApi(params)
  89. if ((res.code === 0 || res.code === '0') && res.data) {
  90. invoiceTaxInfo.value = res.data
  91. const detailItems: InvoiceTaxDetailItem[] = res.data.detailItems ?? []
  92. if (detailItems.length > 0) {
  93. taxList.value = detailItems.map((item: InvoiceTaxDetailItem) => ({
  94. ...item,
  95. open: true, // 默认展开
  96. }))
  97. totalAmount.value = res.data.totalAmount || '0.00'
  98. } else {
  99. taxList.value = []
  100. showToast('您的税费明细生成中,请稍后再试')
  101. }
  102. } else {
  103. taxList.value = []
  104. showToast(res?.msg || '您的税费明细生成中,请稍后再试')
  105. }
  106. } catch (err: any) {
  107. console.error('请求失败', err)
  108. showToast('您的税费明细生成中,请稍后再试')
  109. taxList.value = []
  110. } finally {
  111. loading.value = false
  112. }
  113. }
  114. onMounted(() => {
  115. getInvoiceTax()
  116. })
  117. </script>
  118. <style lang="scss" scoped>
  119. /* ================= 页面根 ================= */
  120. .tax-page {
  121. display: flex;
  122. flex-direction: column;
  123. min-height: 100vh;
  124. height: 100%;
  125. background: #f6f7fb;
  126. font-size: 3.6vw;
  127. color: #333;
  128. /* 给 fixed footer 预留空间 */
  129. padding-bottom: calc(16vw + env(safe-area-inset-bottom));
  130. /* 内容区 */
  131. .page-content {
  132. flex: 1;
  133. overflow-y: auto;
  134. -webkit-overflow-scrolling: touch;
  135. padding: 4vw;
  136. }
  137. }
  138. /* ================= 顶部 ================= */
  139. .van-nav-bar {
  140. background: #fff;
  141. border-bottom: 1px solid #f2f2f2;
  142. font-weight: 600;
  143. }
  144. :deep(.van-icon-arrow-left) {
  145. color: #0072f8;
  146. font-size: 28px;
  147. }
  148. /* ================= 卡片 ================= */
  149. .card {
  150. background: #fff;
  151. border-radius: 3vw;
  152. padding: 4vw;
  153. box-shadow: 0 1vw 3vw rgba(0, 0, 0, 0.05);
  154. .card-title {
  155. font-size: 4.2vw;
  156. font-weight: 600;
  157. margin-bottom: 3vw;
  158. padding-left: 2vw;
  159. border-left: 1vw solid #0072f8;
  160. }
  161. }
  162. .tax-notice {
  163. display: flex;
  164. align-items: center;
  165. gap: 1.5vw;
  166. margin-top: 3vw;
  167. padding: 2.5vw 2vw;
  168. border-radius: 1.5vw;
  169. background: #fff8e8;
  170. color: #b5791f;
  171. font-size: 3.2vw;
  172. line-height: 1.6;
  173. .tax-notice-icon {
  174. flex-shrink: 0;
  175. color: #d99a2b;
  176. font-size: 4vw;
  177. }
  178. }
  179. /* ================= 税项 ================= */
  180. .tax-item {
  181. border-top: 0.3vw solid #f0f0f0;
  182. .tax-header {
  183. display: flex;
  184. justify-content: space-between;
  185. align-items: center;
  186. padding: 3vw 0;
  187. font-size: 4vw;
  188. .tax-name {
  189. font-weight: 500;
  190. color: #222;
  191. }
  192. .tax-amount {
  193. display: flex;
  194. align-items: center;
  195. gap: 1vw;
  196. .arrow {
  197. font-size: 3.2vw;
  198. color: #bbb;
  199. transition: transform 0.3s ease;
  200. }
  201. }
  202. }
  203. .tax-detail {
  204. .tax-row {
  205. display: flex;
  206. justify-content: space-between;
  207. padding: 1.5vw 0 1.5vw 2vw;
  208. font-size: 3.4vw;
  209. color: #999;
  210. }
  211. }
  212. /* 金额语义 */
  213. .amount--weak {
  214. color: #999;
  215. font-weight: 400;
  216. }
  217. .amount--strong {
  218. color: #222;
  219. font-weight: 600;
  220. }
  221. }
  222. /* ================= 展开动画 ================= */
  223. .expand-enter-active,
  224. .expand-leave-active {
  225. transition:
  226. max-height 0.35s ease,
  227. opacity 0.3s ease;
  228. }
  229. .expand-enter-from,
  230. .expand-leave-to {
  231. max-height: 0;
  232. opacity: 0;
  233. }
  234. .expand-enter-to,
  235. .expand-leave-from {
  236. max-height: 200vw;
  237. opacity: 1;
  238. }
  239. /* ================= 底部汇总栏 ================= */
  240. .footer {
  241. position: fixed;
  242. left: 0;
  243. right: 0;
  244. bottom: 0;
  245. z-index: 100;
  246. background: #fff;
  247. display: flex;
  248. justify-content: space-between;
  249. align-items: center;
  250. padding: 3vw 5vw;
  251. padding-bottom: calc(3vw + env(safe-area-inset-bottom));
  252. font-size: 4vw;
  253. border-top: 0.3vw solid #eee;
  254. .amount {
  255. color: #0072f8;
  256. font-weight: 600;
  257. }
  258. }
  259. /* ================= 空态 ================= */
  260. .empty {
  261. text-align: center;
  262. padding: 20vw 0;
  263. color: #aaa;
  264. font-size: 3.8vw;
  265. }
  266. </style>