Przeglądaj źródła

Merge branch 'feature-guokaida-260908'

yuanmingze 1 tydzień temu
rodzic
commit
ef76ae093e

+ 1 - 0
package.json

@@ -17,6 +17,7 @@
     "format": "prettier --write src/"
   },
   "dependencies": {
+    "@vant/area-data": "^2.2.0",
     "axios": "^1.12.2",
     "nanoid": "^5.1.6",
     "normalize.css": "^8.0.1",

+ 25 - 0
pnpm-lock.yaml

@@ -8,6 +8,9 @@ importers:
 
   .:
     dependencies:
+      '@vant/area-data':
+        specifier: ^2.2.0
+        version: 2.2.0
       axios:
         specifier: ^1.12.2
         version: 1.12.2
@@ -524,36 +527,42 @@ packages:
     engines: {node: '>= 10.0.0'}
     cpu: [arm]
     os: [linux]
+    libc: [glibc]
 
   '@parcel/watcher-linux-arm-musl@2.5.1':
     resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm]
     os: [linux]
+    libc: [musl]
 
   '@parcel/watcher-linux-arm64-glibc@2.5.1':
     resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@parcel/watcher-linux-arm64-musl@2.5.1':
     resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm64]
     os: [linux]
+    libc: [musl]
 
   '@parcel/watcher-linux-x64-glibc@2.5.1':
     resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
     engines: {node: '>= 10.0.0'}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@parcel/watcher-linux-x64-musl@2.5.1':
     resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
     engines: {node: '>= 10.0.0'}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@parcel/watcher-win32-arm64@2.5.1':
     resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
@@ -621,56 +630,67 @@ packages:
     resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==}
     cpu: [arm]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-arm-musleabihf@4.52.5':
     resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==}
     cpu: [arm]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-arm64-gnu@4.52.5':
     resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-arm64-musl@4.52.5':
     resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==}
     cpu: [arm64]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-loong64-gnu@4.52.5':
     resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==}
     cpu: [loong64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-ppc64-gnu@4.52.5':
     resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==}
     cpu: [ppc64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-riscv64-gnu@4.52.5':
     resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==}
     cpu: [riscv64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-riscv64-musl@4.52.5':
     resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==}
     cpu: [riscv64]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-s390x-gnu@4.52.5':
     resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==}
     cpu: [s390x]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-x64-gnu@4.52.5':
     resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-x64-musl@4.52.5':
     resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-openharmony-arm64@4.52.5':
     resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==}
@@ -774,6 +794,9 @@ packages:
     resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  '@vant/area-data@2.2.0':
+    resolution: {integrity: sha512-H1payEoXLnUqTNPdt2zftv0yoZBP8AZF69XntMqqGOn5LGtuaZ/VeRXQnJLiBPgGuHs2sXPgZ4gUVGp4Cg154w==}
+
   '@vant/popperjs@1.3.0':
     resolution: {integrity: sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==}
 
@@ -2538,6 +2561,8 @@ snapshots:
       '@typescript-eslint/types': 8.46.2
       eslint-visitor-keys: 4.2.1
 
+  '@vant/area-data@2.2.0': {}
+
   '@vant/popperjs@1.3.0': {}
 
   '@vant/use@1.6.0(vue@3.5.22(typescript@5.9.3))':

+ 12 - 0
src/router/routes.ts

@@ -72,6 +72,18 @@ export const routes: RouteRecordRaw[] = [
     component: () => import('@/views/wechat-redirect/index.vue'),
     meta: { title: '路由跳转中', requiresAuth: false },
   },
+  {
+    path: '/compliance-plan',
+    name: 'CompliancePlan',
+    component: () => import('@/views/compliance-plan/index.vue'),
+    meta: { title: '页面跳转中', requiresAuth: false },
+  },
+  {
+    path: '/compliance-registration',
+    name: 'ComplianceRegistration',
+    component: () => import('@/views/compliance-registration/index.vue'),
+    meta: { title: '页面跳转中', requiresAuth: false },
+  },
   {
     path: '/:pathMatch(.*)*',
     redirect: '/login',

+ 18 - 0
src/services/modules/compliance-plan/index.ts

@@ -0,0 +1,18 @@
+import http from '@/services'
+
+export interface CompliancePlanRequest {
+  name: string
+  phone: string
+  company: string
+  education: string
+  interest: string
+}
+
+export const submitInfoCollection = (data: CompliancePlanRequest) => {
+  return http.post({
+    url: '/admin/info/collection',
+    data,
+    silent: true,
+    timeout: 30000,
+  })
+}

+ 233 - 0
src/views/compliance-plan/index.vue

@@ -0,0 +1,233 @@
+<template>
+  <main class="redirect-page">
+    <div class="decoration decoration-top" aria-hidden="true"></div>
+    <div class="decoration decoration-bottom" aria-hidden="true"></div>
+
+    <section class="redirect-card" aria-labelledby="redirect-title" aria-live="polite">
+      <div class="icon-wrap" aria-hidden="true">
+        <van-icon name="guide-o" />
+      </div>
+
+      <h1 id="redirect-title">正在前往合规方案页面</h1>
+      <p class="description">页面即将自动跳转,请稍候</p>
+
+      <div class="loading-row">
+        <van-loading type="spinner" size="18px" color="#1677ff" />
+        <span>正在为你打开新页面...</span>
+      </div>
+
+      <button type="button" class="redirect-button" @click="redirectToCompliancePlan">
+        立即前往
+        <van-icon name="arrow" />
+      </button>
+
+      <p class="fallback-tip">若未自动跳转,请点击上方按钮</p>
+    </section>
+
+    <footer class="page-footer">
+      <van-icon name="shield-o" aria-hidden="true" />
+      <span>安全跳转 · 请放心访问</span>
+    </footer>
+  </main>
+</template>
+
+<script setup lang="ts">
+import { onBeforeUnmount, onMounted } from 'vue'
+
+const COMPLIANCE_PLAN_URL = 'https://preapi.yaoyi.net/h5/#/pages/artile/compliance-plan'
+const REDIRECT_DELAY = 1000
+
+let redirectTimer: number | undefined
+
+const redirectToCompliancePlan = (): void => {
+  window.location.replace(COMPLIANCE_PLAN_URL)
+}
+
+onMounted(() => {
+  redirectTimer = window.setTimeout(redirectToCompliancePlan, REDIRECT_DELAY)
+})
+
+onBeforeUnmount(() => {
+  if (redirectTimer !== undefined) {
+    window.clearTimeout(redirectTimer)
+  }
+})
+</script>
+
+<style scoped lang="scss">
+.redirect-page {
+  position: relative;
+  display: flex;
+  min-height: 100vh;
+  min-height: 100dvh;
+  align-items: center;
+  justify-content: center;
+  overflow: hidden;
+  padding: calc(28px + env(safe-area-inset-top, 0px)) 22px
+    calc(68px + env(safe-area-inset-bottom, 0px));
+  background: linear-gradient(160deg, #f7faff 0%, #edf5ff 54%, #f7fbff 100%);
+  color: #1f2937;
+  font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif;
+  box-sizing: border-box;
+}
+
+.decoration {
+  position: absolute;
+  border-radius: 50%;
+  pointer-events: none;
+}
+
+.decoration-top {
+  top: -92px;
+  right: -76px;
+  width: 238px;
+  height: 238px;
+  background: radial-gradient(circle, rgba(39, 133, 255, 0.18) 0%, rgba(39, 133, 255, 0) 70%);
+}
+
+.decoration-bottom {
+  bottom: -116px;
+  left: -90px;
+  width: 286px;
+  height: 286px;
+  background: radial-gradient(circle, rgba(80, 173, 255, 0.14) 0%, rgba(80, 173, 255, 0) 70%);
+}
+
+.redirect-card {
+  position: relative;
+  z-index: 1;
+  width: 100%;
+  max-width: 360px;
+  padding: 38px 24px 26px;
+  border: 1px solid rgba(255, 255, 255, 0.92);
+  border-radius: 22px;
+  background: rgba(255, 255, 255, 0.88);
+  box-shadow: 0 18px 48px rgba(49, 98, 157, 0.12);
+  text-align: center;
+  box-sizing: border-box;
+  backdrop-filter: blur(12px);
+
+  h1,
+  p {
+    margin: 0;
+  }
+
+  h1 {
+    margin-top: 22px;
+    color: #172033;
+    font-size: 21px;
+    font-weight: 700;
+    line-height: 1.4;
+  }
+}
+
+.icon-wrap {
+  position: relative;
+  display: flex;
+  width: 68px;
+  height: 68px;
+  align-items: center;
+  justify-content: center;
+  margin: 0 auto;
+  border-radius: 22px;
+  background: linear-gradient(145deg, #1677ff 0%, #39a5ff 100%);
+  box-shadow: 0 12px 28px rgba(22, 119, 255, 0.26);
+  color: #fff;
+  font-size: 32px;
+
+  &::after {
+    position: absolute;
+    inset: -7px;
+    border: 1px solid rgba(22, 119, 255, 0.12);
+    border-radius: 27px;
+    content: '';
+  }
+}
+
+.redirect-card .description {
+  margin-top: 8px;
+  color: #7a8495;
+  font-size: 14px;
+  line-height: 1.6;
+}
+
+.loading-row {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 9px;
+  min-height: 46px;
+  margin-top: 24px;
+  border-radius: 12px;
+  background: #f4f8fd;
+  color: #526176;
+  font-size: 13px;
+}
+
+.redirect-button {
+  display: flex;
+  width: 100%;
+  height: 46px;
+  align-items: center;
+  justify-content: center;
+  gap: 5px;
+  margin-top: 18px;
+  padding: 0;
+  border: 0;
+  border-radius: 23px;
+  background: linear-gradient(90deg, #1677ff 0%, #2998ff 100%);
+  box-shadow: 0 9px 20px rgba(22, 119, 255, 0.22);
+  color: #fff;
+  font-size: 15px;
+  font-weight: 600;
+  cursor: pointer;
+  -webkit-tap-highlight-color: transparent;
+
+  &:active {
+    opacity: 0.86;
+    transform: translateY(1px);
+  }
+
+  &:focus-visible {
+    outline: 2px solid #1677ff;
+    outline-offset: 3px;
+  }
+}
+
+.redirect-card .fallback-tip {
+  margin-top: 13px;
+  color: #a0a8b4;
+  font-size: 12px;
+  line-height: 1.5;
+}
+
+.page-footer {
+  position: absolute;
+  right: 0;
+  bottom: calc(24px + env(safe-area-inset-bottom, 0px));
+  left: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 5px;
+  color: #9ba8b8;
+  font-size: 12px;
+}
+
+@media (max-width: 360px) {
+  .redirect-page {
+    padding-right: 16px;
+    padding-left: 16px;
+  }
+
+  .redirect-card {
+    padding: 32px 20px 23px;
+  }
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .redirect-button:active {
+    transform: none;
+  }
+}
+</style>

+ 233 - 0
src/views/compliance-registration/index.vue

@@ -0,0 +1,233 @@
+<template>
+  <main class="redirect-page">
+    <div class="glow glow-top" aria-hidden="true"></div>
+    <div class="glow glow-bottom" aria-hidden="true"></div>
+
+    <section class="redirect-card" aria-labelledby="redirect-title" aria-live="polite">
+      <div class="icon-wrap" aria-hidden="true">
+        <van-icon name="records-o" />
+      </div>
+
+      <h1 id="redirect-title">正在前往培训报名页面</h1>
+      <p class="description">报名页面即将打开,请稍候</p>
+
+      <div class="loading-status">
+        <van-loading type="spinner" size="18px" color="#1677ff" />
+        <span>正在安全跳转...</span>
+      </div>
+
+      <button type="button" class="redirect-button" @click="redirectToRegistration">
+        立即前往
+        <van-icon name="arrow" />
+      </button>
+
+      <p class="fallback-tip">若未自动跳转,请点击上方按钮</p>
+    </section>
+
+    <footer class="page-footer">
+      <van-icon name="shield-o" aria-hidden="true" />
+      <span>安全跳转 · 请放心访问</span>
+    </footer>
+  </main>
+</template>
+
+<script setup lang="ts">
+import { onBeforeUnmount, onMounted } from 'vue'
+
+const REGISTRATION_URL = 'https://preapi.yaoyi.net/h5/#/pages/artile//training-registration'
+const REDIRECT_DELAY = 1000
+
+let redirectTimer: number | undefined
+
+const redirectToRegistration = (): void => {
+  window.location.replace(REGISTRATION_URL)
+}
+
+onMounted(() => {
+  redirectTimer = window.setTimeout(redirectToRegistration, REDIRECT_DELAY)
+})
+
+onBeforeUnmount(() => {
+  if (redirectTimer !== undefined) {
+    window.clearTimeout(redirectTimer)
+  }
+})
+</script>
+
+<style scoped lang="scss">
+.redirect-page {
+  position: relative;
+  display: flex;
+  min-height: 100vh;
+  min-height: 100dvh;
+  align-items: center;
+  justify-content: center;
+  overflow: hidden;
+  padding: calc(28px + env(safe-area-inset-top, 0px)) 22px
+    calc(68px + env(safe-area-inset-bottom, 0px));
+  background: linear-gradient(160deg, #f7faff 0%, #edf5ff 54%, #f7fbff 100%);
+  color: #1f2937;
+  font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif;
+  box-sizing: border-box;
+}
+
+.glow {
+  position: absolute;
+  border-radius: 50%;
+  pointer-events: none;
+}
+
+.glow-top {
+  top: -92px;
+  right: -76px;
+  width: 238px;
+  height: 238px;
+  background: radial-gradient(circle, rgba(39, 133, 255, 0.18) 0%, transparent 70%);
+}
+
+.glow-bottom {
+  bottom: -116px;
+  left: -90px;
+  width: 286px;
+  height: 286px;
+  background: radial-gradient(circle, rgba(80, 173, 255, 0.14) 0%, transparent 70%);
+}
+
+.redirect-card {
+  position: relative;
+  z-index: 1;
+  width: 100%;
+  max-width: 360px;
+  padding: 38px 24px 26px;
+  border: 1px solid rgba(255, 255, 255, 0.92);
+  border-radius: 22px;
+  background: rgba(255, 255, 255, 0.88);
+  box-shadow: 0 18px 48px rgba(49, 98, 157, 0.12);
+  text-align: center;
+  box-sizing: border-box;
+  backdrop-filter: blur(12px);
+
+  h1,
+  p {
+    margin: 0;
+  }
+
+  h1 {
+    margin-top: 22px;
+    color: #172033;
+    font-size: 21px;
+    font-weight: 700;
+    line-height: 1.4;
+  }
+}
+
+.icon-wrap {
+  position: relative;
+  display: flex;
+  width: 68px;
+  height: 68px;
+  align-items: center;
+  justify-content: center;
+  margin: 0 auto;
+  border-radius: 22px;
+  background: linear-gradient(145deg, #1677ff 0%, #39a5ff 100%);
+  box-shadow: 0 12px 28px rgba(22, 119, 255, 0.26);
+  color: #fff;
+  font-size: 31px;
+
+  &::after {
+    position: absolute;
+    inset: -7px;
+    border: 1px solid rgba(22, 119, 255, 0.12);
+    border-radius: 27px;
+    content: '';
+  }
+}
+
+.redirect-card .description {
+  margin-top: 8px;
+  color: #7a8495;
+  font-size: 14px;
+  line-height: 1.6;
+}
+
+.loading-status {
+  display: flex;
+  min-height: 46px;
+  align-items: center;
+  justify-content: center;
+  gap: 9px;
+  margin-top: 24px;
+  border-radius: 12px;
+  background: #f4f8fd;
+  color: #526176;
+  font-size: 13px;
+}
+
+.redirect-button {
+  display: flex;
+  width: 100%;
+  height: 46px;
+  align-items: center;
+  justify-content: center;
+  gap: 5px;
+  margin-top: 18px;
+  padding: 0;
+  border: 0;
+  border-radius: 23px;
+  background: linear-gradient(90deg, #1677ff 0%, #2998ff 100%);
+  box-shadow: 0 9px 20px rgba(22, 119, 255, 0.22);
+  color: #fff;
+  font-size: 15px;
+  font-weight: 600;
+  cursor: pointer;
+  -webkit-tap-highlight-color: transparent;
+
+  &:active {
+    opacity: 0.86;
+    transform: translateY(1px);
+  }
+
+  &:focus-visible {
+    outline: 2px solid #1677ff;
+    outline-offset: 3px;
+  }
+}
+
+.redirect-card .fallback-tip {
+  margin-top: 13px;
+  color: #a0a8b4;
+  font-size: 12px;
+  line-height: 1.5;
+}
+
+.page-footer {
+  position: absolute;
+  right: 0;
+  bottom: calc(24px + env(safe-area-inset-bottom, 0px));
+  left: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 5px;
+  color: #9ba8b8;
+  font-size: 12px;
+}
+
+@media (max-width: 360px) {
+  .redirect-page {
+    padding-right: 16px;
+    padding-left: 16px;
+  }
+
+  .redirect-card {
+    padding: 32px 20px 23px;
+  }
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .redirect-button:active {
+    transform: none;
+  }
+}
+</style>

+ 46 - 0
src/views/compliance-registration/validation.ts

@@ -0,0 +1,46 @@
+import type { FieldRule } from 'vant'
+
+const requiredText = (label: string, maxLength: number): FieldRule[] => [
+  {
+    required: true,
+    validator: (value: unknown) => typeof value === 'string' && Boolean(value.trim()),
+    message: `请输入${label}`,
+  },
+  {
+    validator: (value: unknown) => typeof value === 'string' && [...value].length <= maxLength,
+    message: `${label}最多${maxLength}个字`,
+  },
+]
+
+export const registrationRules: Record<string, FieldRule[]> = {
+  name: requiredText('真实姓名', 50),
+  gender: [
+    {
+      required: true,
+      validator: (value: unknown) => value === '男' || value === '女',
+      message: '请选择性别(男或女)',
+    },
+  ],
+  phone: [
+    { required: true, message: '请输入手机号' },
+    { pattern: /^1[3-9]\d{9}$/, message: '手机号格式错误' },
+  ],
+  idNumber: [
+    { required: true, message: '请输入身份证号' },
+    {
+      pattern: /^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dX]$/,
+      message: '身份证号格式错误',
+    },
+  ],
+  company: requiredText('单位全称', 100),
+  position: requiredText('职务', 50),
+  education: [{ required: true, message: '请至少选择一项学历报名' }],
+  photo: [
+    { required: true, message: '请添加证书使用的照片' },
+    {
+      validator: (value: unknown) => Array.isArray(value) && value.length >= 1 && value.length <= 2,
+      message: '照片数量须为1~2张',
+    },
+  ],
+  address: requiredText('详细地址', 100),
+}

+ 1 - 0
vite.config.ts

@@ -38,6 +38,7 @@ export default defineConfig(({ mode }): UserConfig => {
           // 使用 Vite 原生 manualChunks 替代 chunk-split 插件
           manualChunks(id: string) {
             if (id.includes('node_modules')) {
+              if (id.includes('@vant/area-data')) return 'area-data'
               if (id.includes('lodash-es')) return 'vendor-lodash'
               if (id.includes('echarts')) return 'vendor-echarts'
               return 'vendor'