yuanmingze 3 месяцев назад
Родитель
Сommit
3414e7a408

+ 2 - 0
components.d.ts

@@ -13,6 +13,7 @@ declare module 'vue' {
   export interface GlobalComponents {
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
+    StepProgress: typeof import('./src/components/StepProgress.vue')['default']
     VanButton: typeof import('vant/es')['Button']
     VanCellGroup: typeof import('vant/es')['CellGroup']
     VanField: typeof import('vant/es')['Field']
@@ -22,5 +23,6 @@ declare module 'vue' {
     VanSkeletonParagraph: typeof import('vant/es')['SkeletonParagraph']
     VanStep: typeof import('vant/es')['Step']
     VanSteps: typeof import('vant/es')['Steps']
+    VanUploader: typeof import('vant/es')['Uploader']
   }
 }

+ 105 - 0
src/components/StepProgress.vue

@@ -0,0 +1,105 @@
+<template>
+  <div class="step-progress">
+    <div class="step-wrapper">
+      <div v-for="(step, index) in steps" :key="index" class="step">
+        <!-- 点 -->
+        <div class="circle" :class="{ active: index < activeStep }"></div>
+
+        <!-- 线(最后一个点没有线) -->
+        <div
+          v-if="index !== steps.length - 1"
+          class="line"
+          :class="{ active: index < activeStep }"
+        ></div>
+
+        <!-- 文本 -->
+        <div class="label" :class="{ active: index < activeStep }">{{ step }}</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+defineProps<{
+  /** 当前激活的步骤 (1~N) */
+  activeStep: number
+}>()
+
+/** 默认四步 */
+const steps = ['确认开票信息', '开票认证', '完成缴税', '提交开票申请']
+</script>
+
+<style scoped lang="scss">
+.step-progress {
+  width: 100%;
+  box-sizing: border-box;
+  padding: 4vw 3vw 2vw;
+  border-radius: 2vw;
+
+  .step-wrapper {
+    display: flex;
+    align-items: flex-start;
+    justify-content: space-between;
+    position: relative;
+  }
+
+  .step {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    position: relative;
+    text-align: center;
+
+    /* 点 */
+    .circle {
+      width: 4vw;
+      height: 4vw;
+      border-radius: 50%;
+      background: #dcdcdc;
+      z-index: 1;
+      transition: background 0.3s ease;
+
+      &.active {
+        background: #ff7a00;
+      }
+    }
+
+    /* 线:在点右侧 */
+    .line {
+      position: absolute;
+      top: 2vw; /* 与圆心对齐 */
+      left: 50%;
+      width: 100%;
+      height: 0.6vw;
+      background: #e0e0e0;
+      border-radius: 1vw;
+      z-index: 0;
+      transition: background 0.3s ease;
+
+      &.active {
+        background: linear-gradient(90deg, #ff7a00, #ffa94d);
+      }
+    }
+
+    /* 文本 */
+    .label {
+      margin-top: 1.6vw;
+      font-size: 3.2vw;
+      color: #aaa;
+      white-space: nowrap;
+      transition: color 0.3s ease;
+
+      &.active {
+        color: #ff7a00;
+        font-weight: 600;
+      }
+    }
+  }
+
+  /* 最后一个点右边不画线 */
+  .step:last-child .line {
+    display: none;
+  }
+}
+</style>

+ 0 - 29
src/constants/website.ts

@@ -1,29 +0,0 @@
-export const WEBSITE_BRAND = '锋信易®'
-export const WEBSITE_COPYRIGHT = '版权公告 © 2023-2025 要易云科技(北京)有限责任公司。版权所有。'
-export const WEBSITE_ICP = '京ICP备2022024690号-3'
-
-export default {
-  title: WEBSITE_BRAND,
-  copyright: WEBSITE_COPYRIGHT,
-  isFirstPage: true, // 配置首页不可关闭
-  key: 'f', // 配置主键,目前用于存储
-  initRouteList: ['Login', 'Decl', 'financialTaxNotify', 'Home', 'UserPage', 'About', '404'], // 配置初始路由列表
-  whiteList: ['Login', '404'], // 配置无权限可以访问的页面
-  whiteTagList: ['/login', '/404', '/401', '/lock', '/checkEnt'], // 配置不添加tags页面 ('/advanced-router/mutative-detail/*'——*为通配符)
-  homePage: {
-    label: '首页',
-    value: '/dashboard/index',
-    params: {},
-    query: {},
-    close: false,
-  },
-  // 配置菜单的属性
-  menu: {
-    props: {
-      label: 'name',
-      path: 'path',
-      icon: 'icon',
-      children: 'children',
-    },
-  },
-}

+ 6 - 1
src/router/routes.ts

@@ -30,7 +30,12 @@ export const routes: RouteRecordRaw[] = [
     component: () => import('@/views/invoice-information/detail.vue'),
     meta: { title: '应缴税信息', requiresAuth: true },
   },
-
+  {
+    path: '/identity-upload',
+    name: 'UploadIdentityInformation',
+    component: () => import('@/views/identity-upload/index.vue'),
+    meta: { title: '开票认证', requiresAuth: true },
+  },
   {
     path: '/:pathMatch(.*)*',
     redirect: '/login',

+ 10 - 3
src/services/modules/invoiceInformation/index.ts

@@ -1,16 +1,23 @@
 import http from '../../index'
-import type { GetConfirmInvoiceInfoRequest, InvoiceTaxDetailResponse } from './type.d'
+import type { InvoiceOrderIdRequest, InvoiceTaxDetailResponse } from './type.d'
 
-export const getConfirmInvoiceInfoApi = (data: GetConfirmInvoiceInfoRequest) => {
+export const getConfirmInvoiceInfoApi = (data: InvoiceOrderIdRequest) => {
   return http.get({
     url: '/admin/invoice-order/get-confirm-invoice-info',
     params: data,
   })
 }
 
-export const getInvoiceTaxApi = (data: GetConfirmInvoiceInfoRequest) => {
+export const getInvoiceTaxApi = (data: InvoiceOrderIdRequest) => {
   return http.get<InvoiceTaxDetailResponse>({
     url: '/admin/invoice-order/get-invoice-tax',
     params: data,
   })
 }
+
+export const getStatusApi = (data: InvoiceOrderIdRequest) => {
+  return http.get({
+    url: '/admin/invoice-order/get-status',
+    params: data,
+  })
+}

+ 1 - 1
src/services/modules/invoiceInformation/type.d.ts

@@ -10,7 +10,7 @@ export interface ApiResponse<T> {
 /**
  * 获取税费信息请求参数
  */
-export interface GetConfirmInvoiceInfoRequest {
+export interface InvoiceOrderIdRequest {
   invoiceOrderId: string
 }
 

+ 172 - 0
src/views/identity-upload/index.vue

@@ -0,0 +1,172 @@
+<template>
+  <div class="identity-page">
+    <van-nav-bar title="自然人开票" fixed placeholder />
+
+    <!-- 上传区域 -->
+    <div class="upload-section">
+      <div class="upload-item" v-for="(item, index) in uploadList" :key="index">
+        <van-uploader
+          v-model="item.fileList"
+          :max-count="1"
+          :after-read="(file) => handleAfterRead(file, index)"
+          :preview-size="previewSize"
+          :deletable="true"
+          :upload-text="item.label"
+          accept="image/*"
+          :multiple="false"
+        >
+          <template #default>
+            <div class="upload-box" v-if="!item.fileList.length">
+              <van-icon name="photograph" class="camera-icon" />
+            </div>
+          </template>
+        </van-uploader>
+        <p class="upload-text">{{ item.label }}</p>
+      </div>
+    </div>
+
+    <!-- 说明文字 -->
+    <div class="info-section">
+      <p class="info-title">当前操作可能用于:</p>
+      <ol class="info-list">
+        <li>开具数电发票</li>
+        <li>上传身份证信息登录电子税务局完成实名认证</li>
+        <li>办理其他涉税、高风险业务</li>
+      </ol>
+      <p class="info-tip">请您在如未认证用途中完成认证操作。</p>
+    </div>
+
+    <!-- 下一步按钮 -->
+    <div class="btn-section">
+      <van-button block type="primary" round class="next-btn" :disabled="!isReady" @click="onNext">
+        下一步
+      </van-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { showToast } from 'vant'
+
+/** 上传项 */
+interface UploadItem {
+  label: string
+  fileList: any[]
+  field: string
+}
+const uploadList = ref<UploadItem[]>([
+  { label: '请上传身份证人像面', field: 'front', fileList: [] },
+  { label: '请上传身份证国徽面', field: 'back', fileList: [] },
+])
+
+/** 图片选择后处理*/
+const handleAfterRead = (file: any, index: number) => {
+  const target = uploadList.value[index]
+  if (!target) return // ✅ 防御性检查,防止未定义
+
+  showToast('上传中...')
+  setTimeout(() => {
+    target.fileList = [
+      {
+        url: URL.createObjectURL(file.file),
+        file: file.file,
+      },
+    ]
+    showToast('上传成功')
+  }, 800)
+}
+
+/** 是否完成上传 */
+const isReady = computed(() => uploadList.value.every((item) => item.fileList.length > 0))
+
+/** 预览尺寸 */
+const previewSize = '35vw'
+
+/** 下一步 */
+const onNext = () => {
+  if (!isReady.value) return showToast('请先上传身份证正反面')
+  showToast('上传完成,进入下一步')
+}
+</script>
+
+<style scoped>
+.identity-page {
+  background-color: #f7f8fa;
+  min-height: 100vh;
+  padding: 4vw;
+  box-sizing: border-box;
+  font-size: 3.6vw;
+  color: #333;
+}
+
+/* 上传区域 */
+.upload-section {
+  display: flex;
+  justify-content: space-between;
+  margin: 10vw 0;
+}
+.upload-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  text-align: center;
+}
+.upload-box {
+  width: 35vw;
+  height: 22vw;
+  border-radius: 2vw;
+  background: #f0f0f0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 3vw;
+  box-shadow: inset 0 0 2vw rgba(0, 0, 0, 0.05);
+}
+.camera-icon {
+  font-size: 8vw;
+  color: #ff7a00;
+}
+.upload-text {
+  font-size: 3.4vw;
+  color: #666;
+}
+
+/* 说明文字 */
+.info-section {
+  margin: 8vw 0 12vw;
+  line-height: 1.8;
+}
+.info-title {
+  font-weight: 600;
+  margin-bottom: 2vw;
+}
+.info-list {
+  padding-left: 5vw;
+  color: #444;
+}
+.info-list li {
+  margin-bottom: 1.5vw;
+}
+.info-tip {
+  color: #999;
+  font-size: 3.2vw;
+  margin-top: 3vw;
+}
+
+/* 按钮 */
+.btn-section {
+  padding: 0 5vw;
+}
+.next-btn {
+  font-size: 4vw;
+  height: 12vw;
+  background: linear-gradient(90deg, #ff7a00, #ff9800);
+  border: none;
+}
+.next-btn:disabled {
+  background: #ffd1a1;
+  color: #fff;
+}
+</style>

+ 2 - 2
src/views/invoice-information/detail.vue

@@ -70,7 +70,7 @@ import { ref, reactive, onMounted } from 'vue'
 import { showToast } from 'vant'
 import { getInvoiceTaxApi } from '@/services/modules/invoiceInformation'
 import type {
-  GetConfirmInvoiceInfoRequest,
+  InvoiceOrderIdRequest,
   InvoiceTaxDetailApiResponse,
   InvoiceTaxDetailItem,
 } from '@/services/modules/invoiceInformation/type.d.ts'
@@ -79,7 +79,7 @@ const loading = ref(true)
 const taxList = ref<(InvoiceTaxDetailItem & { open: boolean })[]>([])
 const totalAmount = ref('0.00')
 
-const params = reactive<GetConfirmInvoiceInfoRequest>({
+const params = reactive<InvoiceOrderIdRequest>({
   invoiceOrderId: '12345',
 })
 

+ 20 - 3
src/views/invoice-information/index.vue

@@ -23,6 +23,9 @@
 
       <!-- 加载完成后显示真实内容 -->
       <template v-else>
+        <div class="card">
+          <StepProgress :activeStep="1" />
+        </div>
         <!-- 卡片:申请人信息 -->
         <div class="card">
           <div class="card-header">
@@ -107,6 +110,7 @@
         round
         color="linear-gradient(90deg, #ff7a00, #ffa94d)"
         class="next-btn"
+        @click="nextBtn"
       >
         下一步
       </van-button>
@@ -116,15 +120,16 @@
 
 <script setup lang="ts">
 import { useRouter } from 'vue-router'
-import { getConfirmInvoiceInfoApi } from '@/services/modules/invoiceInformation'
-import type { GetConfirmInvoiceInfoRequest } from '@/services/modules/invoiceInformation/type.d.ts'
+import { getConfirmInvoiceInfoApi, getStatusApi } from '@/services/modules/invoiceInformation'
+import type { InvoiceOrderIdRequest } from '@/services/modules/invoiceInformation/type.d.ts'
 import { reactive, onMounted, ref } from 'vue'
+import StepProgress from '@/components/StepProgress.vue'
 
 const router = useRouter()
 const loading = ref(true)
 const invoiceInfo = ref<any>({})
 
-const params = reactive<GetConfirmInvoiceInfoRequest>({
+const params = reactive<InvoiceOrderIdRequest>({
   invoiceOrderId: '12345',
 })
 
@@ -142,6 +147,18 @@ const getConfirmInvoiceInfo = async () => {
   }
 }
 
+const nextBtn = async () => {
+  const res = await getStatusApi(params)
+  if (res.code === 0) {
+    // 当前账户没有身份照片,跳转上传证件页面
+    if (!res.data.isIdImgReady) {
+      router.push({
+        path: '/identity-upload',
+      })
+    }
+  }
+}
+
 const toDetail = () => {
   router.push({
     path: '/invoice-information/detail',

+ 2 - 3
vite.config.ts

@@ -4,18 +4,17 @@ import Components from 'unplugin-vue-components/vite'
 import { VantResolver } from '@vant/auto-import-resolver'
 import { defineConfig, loadEnv, UserConfig } from 'vite'
 import vue from '@vitejs/plugin-vue'
-import vueDevTools from 'vite-plugin-vue-devtools'
 
 // https://vite.dev/config/
 export default defineConfig(({ mode }): UserConfig => {
   const root = process.cwd()
   const env = loadEnv(mode, root)
-  const isProduction = mode === 'production'
+  // const isProduction = mode === 'production'
 
   return {
     plugins: [
       vue(),
-      !isProduction && vueDevTools(),
+
       AutoImport({
         resolvers: [VantResolver()],
       }),