zjk
2026-07-06 0ca0f47ba131ca7b842138cf0af492552ba331dc
feat: init
27个文件已添加
1个文件已修改
27429 ■■■■■ 已修改文件
.gitignore 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
demo.html 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.html 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json 13524 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pnpm-lock.yaml 8102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/manifest.json 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/appointment-scan/index.vue 472 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/device-init/index.vue 167 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/home/index.vue 602 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/manual-id-input/index.vue 577 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/online-complete/index.vue 303 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/online-face-collect/index.vue 670 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/online-face-verify/index.vue 731 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/online-host-verify/index.vue 729 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/online-id-scan/index.vue 534 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/online-print-ticket/index.vue 509 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/static/device-id-scan.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/device-scan.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/kmN54.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/print-ticket-demo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/step-arrow-active.png 补丁 | 查看 | 原始文档 | blame | 历史
src/static/step-arrow-inactive.png 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
New file
@@ -0,0 +1,3 @@
dist/
node_modules/
# project.private.config.json
README.md
@@ -0,0 +1,85 @@
# 访客机终端项目 (Visitor Kiosk UniApp)
## 项目简介
本项目是一个基于 UniApp + Vue 3 + Vite 构建的访客机终端静态页面项目。主要用于提供来访人员在访客机终端上进行自助登记、身份验证、人脸核实及打印访客凭条等自助服务功能。
## 技术栈
- **核心框架**: [Vue 3](https://v3.vuejs.org/) (Composition API)
- **跨端框架**: [UniApp](https://uniapp.dcloud.net.cn/)
- **构建工具**: [Vite 5](https://vitejs.dev/)
- **包管理器**: pnpm (推荐)
- **运行环境**: Node.js >= 18
## 目录结构
```text
visitor-kiosk-uniapp/
├── src/
│   ├── pages/                   # 页面目录
│   │   ├── device-init/         # 1. 设备初始化页
│   │   ├── home/                # 2. 首页(入口)
│   │   ├── appointment-scan/    # 3. 预约扫码页
│   │   ├── online-id-scan/      # 4. 现场登记-身份证扫描页
│   │   ├── manual-id-input/     # 5. 现场登记-手动输入身份证页
│   │   ├── online-face-verify/  # 6. 人脸核验页
│   │   ├── online-face-collect/ # 7. 人脸信息采集页
│   │   ├── online-host-verify/  # 8. 拜访人(被访人)核验页
│   │   ├── online-print-ticket/ # 9. 打印访客凭条页
│   │   └── online-complete/     # 10. 登记完成页
│   ├── static/                  # 静态资源文件 (图片等)
│   ├── App.vue                  # 根组件
│   ├── main.js                  # 入口文件
│   ├── manifest.json            # UniApp 应用配置文件
│   └── pages.json               # 页面路由配置
├── package.json                 # 项目依赖与配置
├── vite.config.js               # Vite 配置文件
└── README.md                    # 项目文档
```
## 核心业务流程
1. **设备初始化**:检查设备状态与网络情况。
2. **选择业务**:用户可在首页选择「预约访客」或「现场登记」。
3. **身份核验**:
   - **预约访客**:扫描预约码或凭证。
   - **现场登记**:扫描身份证或手动输入证件号,随后进行人脸采集或核验。
4. **被访人确认**:确认要拜访的主机/人员信息。
5. **完成登记**:打印访客凭条,流程结束。
## 安装与运行
### 1. 安装依赖
建议使用 `pnpm` 安装依赖:
```bash
npm install -g pnpm
pnpm install
```
### 2. 运行项目 (开发环境)
项目支持多端运行,常用指令如下:
- 运行到 H5 端:
  ```bash
  pnpm dev:h5
  ```
- 运行到微信小程序:
  ```bash
  pnpm dev:mp-weixin
  ```
*(更多终端运行指令请参考 `package.json` 中的 `scripts` 字段)*
### 3. 项目打包 (生产环境)
- 打包 H5 端:
  ```bash
  pnpm build:h5
  ```
- 打包微信小程序:
  ```bash
  pnpm build:mp-weixin
  ```
## 开发规范
基于项目的代码质量规则,请遵循以下规范进行开发:
- **代码风格**:使用 Vue 3 的 `<script setup>` 语法,缩进使用 4 个空格,不使用制表符。
- **命名规范**:组件、接口等使用 `PascalCase`(如 `MyComponent`),变量和函数使用 `camelCase`(如 `myFunction`),私有属性或方法以 `_` 开头。
- **避免冗余**:遵循 DRY 原则,对于重复逻辑,请封装成可复用的函数或组件。避免在循环中创建新的函数或对象。
- **错误处理**:涉及异步操作时,必须使用 `try/catch` 块进行捕获,并记录包含上下文的错误日志。
- **注释要求**:逻辑复杂的部分需添加单行 `//` 或多行 `/* */` 中文注释,确保代码具有自解释性。
demo.html
New file
@@ -0,0 +1,143 @@
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Inter%3Awght%40100..900&display=swap"
      rel="stylesheet"
    />
    <style>
      *, ::before, ::after {
        box-sizing: border-box;
      }
      body {
        margin: 0;
      }
    </style>
  </head>
  <body>
    <div
      data-pencil-name="设备初始化页面"
      style="background-color: #F4F7FC; box-sizing: border-box; height: 1920px; overflow: hidden; position: relative; width: 1080px"
    >
      <div
        data-pencil-name="背景遮罩"
        style="background-color: #111111F2; box-sizing: border-box; height: 1920px; left: 0px; position: absolute; top: 0px; width: 1080px; z-index: 0"
      ></div>
      <div
        data-pencil-name="设备初始化弹窗"
        style="align-items: flex-start; background-color: #FFFFFF; border-radius: 28px; box-sizing: border-box; display: flex; flex-direction: column; gap: 56px; height: fit-content; justify-content: flex-start; left: 170px; padding: 52px 80px; position: absolute; top: 620px; width: 740px; z-index: 1"
      >
        <div
          data-pencil-name="标题"
          style="box-sizing: border-box; color: #111111; font-family: Inter, system-ui, sans-serif; font-size: 34px; font-style: normal; font-weight: 700; letter-spacing: 0px; line-height: 41px; text-align: center; width: 100%"
        >
          设备激活
        </div>
        <div
          data-pencil-name="表单区"
          style="align-items: flex-start; box-sizing: border-box; display: flex; flex-direction: column; flex-shrink: 0; gap: 32px; height: fit-content; justify-content: flex-start; width: 100%"
        >
          <div
            data-pencil-name="设备ID:行"
            style="align-items: center; box-sizing: border-box; display: flex; flex-direction: row; flex-shrink: 0; gap: 16px; height: 64px; justify-content: flex-start; width: 100%"
          >
            <div
              data-pencil-name="设备ID:标签"
              style="box-sizing: border-box; color: #222222; flex-shrink: 0; font-family: Inter, system-ui, sans-serif; font-size: 20px; font-style: normal; font-weight: 700; letter-spacing: 0px; line-height: 24px; text-align: left; width: 92px"
            >
              设备ID:
            </div>
            <div
              data-pencil-name="设备ID:输入框"
              style="align-items: center; background-color: #F1F1F1; border-radius: 12px; border: 1px solid #CFCFCF; box-sizing: border-box; display: flex; flex-direction: row; flex: 1 1 0; gap: 0px; height: 56px; justify-content: flex-start; padding: 0px 16px"
            >
              <div
                data-pencil-name="设备ID:占位"
                style="box-sizing: border-box; color: #A3A3A3; font-family: Inter, system-ui, sans-serif; font-size: 18px; font-style: normal; font-weight: 600; letter-spacing: 0px; line-height: 22px; text-align: left; white-space: nowrap"
              >
                自动回填
              </div>
            </div>
          </div>
          <div
            data-pencil-name="IP地址:行"
            style="align-items: center; box-sizing: border-box; display: flex; flex-direction: row; flex-shrink: 0; gap: 16px; height: 64px; justify-content: flex-start; width: 100%"
          >
            <div
              data-pencil-name="IP地址:标签"
              style="box-sizing: border-box; color: #222222; flex-shrink: 0; font-family: Inter, system-ui, sans-serif; font-size: 20px; font-style: normal; font-weight: 700; letter-spacing: 0px; line-height: 24px; text-align: left; width: 92px"
            >
              IP地址:
            </div>
            <div
              data-pencil-name="IP地址:输入框"
              style="align-items: center; background-color: #FFFFFF; border-radius: 12px; border: 1px solid #CFCFCF; box-sizing: border-box; display: flex; flex-direction: row; flex: 1 1 0; gap: 0px; height: 56px; justify-content: flex-start; padding: 0px 16px"
            >
              <div
                data-pencil-name="IP地址:占位"
                style="box-sizing: border-box; color: #A3A3A3; font-family: Inter, system-ui, sans-serif; font-size: 18px; font-style: normal; font-weight: 600; letter-spacing: 0px; line-height: 22px; text-align: left; white-space: nowrap"
              >
                请输入
              </div>
            </div>
          </div>
          <div
            data-pencil-name="端口号:行"
            style="align-items: center; box-sizing: border-box; display: flex; flex-direction: row; flex-shrink: 0; gap: 16px; height: 64px; justify-content: flex-start; width: 100%"
          >
            <div
              data-pencil-name="端口号:标签"
              style="box-sizing: border-box; color: #222222; flex-shrink: 0; font-family: Inter, system-ui, sans-serif; font-size: 20px; font-style: normal; font-weight: 700; letter-spacing: 0px; line-height: 24px; text-align: left; width: 92px"
            >
              端口号:
            </div>
            <div
              data-pencil-name="端口号:输入框"
              style="align-items: center; background-color: #FFFFFF; border-radius: 12px; border: 1px solid #CFCFCF; box-sizing: border-box; display: flex; flex-direction: row; flex: 1 1 0; gap: 0px; height: 56px; justify-content: flex-start; padding: 0px 16px"
            >
              <div
                data-pencil-name="端口号:占位"
                style="box-sizing: border-box; color: #A3A3A3; font-family: Inter, system-ui, sans-serif; font-size: 18px; font-style: normal; font-weight: 600; letter-spacing: 0px; line-height: 22px; text-align: left; white-space: nowrap"
              >
                请输入
              </div>
            </div>
          </div>
        </div>
        <div
          data-pencil-name="按钮区"
          style="align-items: center; box-sizing: border-box; display: flex; flex-direction: row; flex-shrink: 0; gap: 28px; height: 64px; justify-content: center; width: 100%"
        >
          <div
            data-pencil-name="重置按钮外框"
            style="align-items: center; background-color: #F5F5F5; border-radius: 20px; border: 1px solid #F5F5F5; box-sizing: border-box; display: flex; flex-direction: row; flex-shrink: 0; gap: 0px; height: 56px; justify-content: center; width: 220px"
          >
            <div
              data-pencil-name="重置按钮文字"
              style="box-sizing: border-box; color: #666666; font-family: Inter, system-ui, sans-serif; font-size: 20px; font-style: normal; font-weight: 700; letter-spacing: 0px; line-height: 24px; text-align: left; white-space: nowrap"
            >
              重置
            </div>
          </div>
          <div
            data-pencil-name="激活按钮"
            style="align-items: center; background-color: #2F6BFF; border-radius: 18px; box-sizing: border-box; display: flex; flex-direction: row; flex-shrink: 0; gap: 0px; height: 56px; justify-content: center; width: 220px"
          >
            <div
              data-pencil-name="激活按钮文字"
              style="box-sizing: border-box; color: #FFFFFF; font-family: Inter, system-ui, sans-serif; font-size: 20px; font-style: normal; font-weight: 700; letter-spacing: 0px; line-height: 24px; text-align: left; white-space: nowrap"
            >
              激活
            </div>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>
index.html
New file
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <script>
      var coverSupport =
        'CSS' in window &&
        typeof CSS.supports === 'function' &&
        (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
      document.write(
        '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
          (coverSupport ? ', viewport-fit=cover' : '') +
          '" />',
      )
    </script>
    <title>访客机终端</title>
    <!--preload-links-->
    <!--app-context-->
  </head>
  <body>
    <div id="app"><!--app-html--></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>
package-lock.json
New file
Diff too large
package.json
New file
@@ -0,0 +1,72 @@
{
  "name": "visitor-kiosk-uniapp",
  "version": "1.0.0",
  "private": true,
  "description": "访客机终端 uni-app 静态页面项目",
  "scripts": {
    "dev:custom": "uni -p",
    "dev:h5": "uni",
    "dev:h5:ssr": "uni --ssr",
    "dev:mp-alipay": "uni -p mp-alipay",
    "dev:mp-baidu": "uni -p mp-baidu",
    "dev:mp-jd": "uni -p mp-jd",
    "dev:mp-kuaishou": "uni -p mp-kuaishou",
    "dev:mp-lark": "uni -p mp-lark",
    "dev:mp-qq": "uni -p mp-qq",
    "dev:mp-toutiao": "uni -p mp-toutiao",
    "dev:mp-harmony": "uni -p mp-harmony",
    "dev:mp-weixin": "uni -p mp-weixin",
    "dev:mp-xhs": "uni -p mp-xhs",
    "dev:quickapp-webview": "uni -p quickapp-webview",
    "dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
    "dev:quickapp-webview-union": "uni -p quickapp-webview-union",
    "build:custom": "uni build -p",
    "build:h5": "uni build",
    "build:h5:ssr": "uni build --ssr",
    "build:mp-alipay": "uni build -p mp-alipay",
    "build:mp-baidu": "uni build -p mp-baidu",
    "build:mp-jd": "uni build -p mp-jd",
    "build:mp-kuaishou": "uni build -p mp-kuaishou",
    "build:mp-lark": "uni build -p mp-lark",
    "build:mp-qq": "uni build -p mp-qq",
    "build:mp-toutiao": "uni build -p mp-toutiao",
    "build:mp-harmony": "uni build -p mp-harmony",
    "build:mp-weixin": "uni build -p mp-weixin",
    "build:mp-xhs": "uni build -p mp-xhs",
    "build:quickapp-webview": "uni build -p quickapp-webview",
    "build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
    "build:quickapp-webview-union": "uni build -p quickapp-webview-union"
  },
  "dependencies": {
    "@dcloudio/uni-app": "3.0.0-5000720260410001",
    "@dcloudio/uni-app-harmony": "3.0.0-5000720260410001",
    "@dcloudio/uni-app-plus": "3.0.0-5000720260410001",
    "@dcloudio/uni-components": "3.0.0-5000720260410001",
    "@dcloudio/uni-h5": "3.0.0-5000720260410001",
    "@dcloudio/uni-mp-alipay": "3.0.0-5000720260410001",
    "@dcloudio/uni-mp-baidu": "3.0.0-5000720260410001",
    "@dcloudio/uni-mp-harmony": "3.0.0-5000720260410001",
    "@dcloudio/uni-mp-jd": "3.0.0-5000720260410001",
    "@dcloudio/uni-mp-kuaishou": "3.0.0-5000720260410001",
    "@dcloudio/uni-mp-lark": "3.0.0-5000720260410001",
    "@dcloudio/uni-mp-qq": "3.0.0-5000720260410001",
    "@dcloudio/uni-mp-toutiao": "3.0.0-5000720260410001",
    "@dcloudio/uni-mp-weixin": "3.0.0-5000720260410001",
    "@dcloudio/uni-mp-xhs": "3.0.0-5000720260410001",
    "@dcloudio/uni-quickapp-webview": "3.0.0-5000720260410001",
    "vue": "^3.4.21",
    "vue-i18n": "^9.1.9"
  },
  "devDependencies": {
    "@dcloudio/types": "^3.4.8",
    "@dcloudio/uni-automator": "3.0.0-5000720260410001",
    "@dcloudio/uni-cli-shared": "3.0.0-5000720260410001",
    "@dcloudio/uni-stacktracey": "3.0.0-5000720260410001",
    "@dcloudio/vite-plugin-uni": "3.0.0-5000720260410001",
    "@vue/runtime-core": "^3.4.21",
    "vite": "5.2.8"
  },
  "engines": {
    "node": ">=18"
  }
}
pnpm-lock.yaml
New file
Diff too large
src/App.vue
New file
@@ -0,0 +1,27 @@
<script>
export default {
  onLaunch() {
    console.log('App Launch')
  },
  onShow() {
    console.log('App Show')
  },
  onHide() {
    console.log('App Hide')
  },
}
</script>
<style>
page {
  width: 100%;
  height: 100%;
  font-family: Inter, "PingFang SC", "Microsoft YaHei", sans-serif;
  background: #f4f7fc;
}
view,
text {
  box-sizing: border-box;
}
</style>
src/main.js
New file
@@ -0,0 +1,9 @@
import { createSSRApp } from 'vue'
import App from './App.vue'
export function createApp() {
  const app = createSSRApp(App)
  return {
    app,
  }
}
src/manifest.json
New file
@@ -0,0 +1,51 @@
{
  "name": "访客机终端",
  "appid": "",
  "description": "访客机终端静态页面示例",
  "versionName": "1.0.0",
  "versionCode": "100",
  "transformPx": false,
  "app-plus": {
    "usingComponents": true,
    "nvueStyleCompiler": "uni-app",
    "compilerVersion": 3,
    "splashscreen": {
      "alwaysShowBeforeRender": true,
      "waiting": true,
      "autoclose": true,
      "delay": 0
    },
    "modules": {},
    "distribute": {
      "android": {
        "permissions": [
          "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
          "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
          "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
          "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
          "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
          "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
          "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
          "<uses-permission android:name=\"android.permission.CAMERA\"/>",
          "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
          "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
          "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
          "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
          "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
          "<uses-feature android:name=\"android.hardware.camera\"/>",
          "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
        ]
      },
      "ios": {},
      "sdkConfigs": {}
    }
  },
  "quickapp": {},
  "mp-harmony": {
    "usingComponents": true
  },
  "uniStatistics": {
    "enable": false
  },
  "vueVersion": "3"
}
src/pages.json
New file
@@ -0,0 +1,89 @@
{
  "pages": [
    {
      "path": "pages/device-init/index",
      "style": {
        "navigationStyle": "custom",
        "disableScroll": true,
        "backgroundColor": "#F4F7FC"
      }
    },
    {
      "path": "pages/home/index",
      "style": {
        "navigationStyle": "custom",
        "disableScroll": true,
        "backgroundColor": "#2550D9"
      }
    },
    {
      "path": "pages/appointment-scan/index",
      "style": {
        "navigationStyle": "custom",
        "disableScroll": true,
        "backgroundColor": "#2550D9"
      }
    },
    {
      "path": "pages/online-id-scan/index",
      "style": {
        "navigationStyle": "custom",
        "disableScroll": true,
        "backgroundColor": "#2550D9"
      }
    },
    {
      "path": "pages/manual-id-input/index",
      "style": {
        "navigationStyle": "custom",
        "disableScroll": true,
        "backgroundColor": "#2550D9"
      }
    },
    {
      "path": "pages/online-face-verify/index",
      "style": {
        "navigationStyle": "custom",
        "disableScroll": true,
        "backgroundColor": "#2550D9"
      }
    },
    {
      "path": "pages/online-face-collect/index",
      "style": {
        "navigationStyle": "custom",
        "disableScroll": true,
        "backgroundColor": "#2550D9"
      }
    },
    {
      "path": "pages/online-host-verify/index",
      "style": {
        "navigationStyle": "custom",
        "disableScroll": true,
        "backgroundColor": "#2550D9"
      }
    },
    {
      "path": "pages/online-print-ticket/index",
      "style": {
        "navigationStyle": "custom",
        "disableScroll": true,
        "backgroundColor": "#2550D9"
      }
    },
    {
      "path": "pages/online-complete/index",
      "style": {
        "navigationStyle": "custom",
        "disableScroll": true,
        "backgroundColor": "#2550D9"
      }
    }
  ],
  "globalStyle": {
    "navigationBarTextStyle": "black",
    "backgroundColor": "#F4F7FC",
    "backgroundTextStyle": "light"
  }
}
src/pages/appointment-scan/index.vue
New file
@@ -0,0 +1,472 @@
<template>
  <view class="page-container">
    <!-- 左侧柔光氛围 -->
    <view class="bg-glow"></view>
    <!-- 扫码页头部 -->
    <view class="header-area">
      <view class="left-action" @click="goBack">
        <view class="back-btn">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <polyline points="15 18 9 12 15 6"></polyline>
          </svg>
        </view>
        <text class="page-title">预约签到</text>
      </view>
    </view>
    <!-- 步骤卡片 -->
    <view class="steps-card">
      <view class="steps-row">
        <!-- 步骤1: 预约码验证 (Active) -->
        <view class="step-item">
          <view class="step-circle active-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#2F6BFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
              <rect x="7" y="7" width="3" height="3"></rect>
              <rect x="14" y="7" width="3" height="3"></rect>
              <rect x="7" y="14" width="3" height="3"></rect>
              <rect x="14" y="14" width="3" height="3"></rect>
            </svg>
          </view>
          <text class="step-text active-text">预约码验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-active.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤2: 身份证验证 -->
        <view class="step-item">
          <view class="step-circle inactive-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#C7D7F6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M16 10h2"></path>
              <path d="M16 14h2"></path>
              <path d="M6.17 15a3 3 0 0 1 5.66 0"></path>
              <circle cx="9" cy="11" r="2"></circle>
              <rect x="2" y="5" width="20" height="14" rx="2"></rect>
            </svg>
          </view>
          <text class="step-text inactive-text">身份证验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-inactive.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤3: 人脸验证 -->
        <view class="step-item">
          <view class="step-circle inactive-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#C7D7F6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M3 7V5a2 2 0 0 1 2-2h2"></path>
              <path d="M17 3h2a2 2 0 0 1 2 2v2"></path>
              <path d="M21 17v2a2 2 0 0 1-2 2h-2"></path>
              <path d="M7 21H5a2 2 0 0 1-2-2v-2"></path>
              <path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
              <path d="M9 9h.01"></path>
              <path d="M15 9h.01"></path>
            </svg>
          </view>
          <text class="step-text inactive-text">人脸验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-inactive.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤4: 接待人验证 -->
        <view class="step-item">
          <view class="step-circle inactive-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#C7D7F6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
              <circle cx="9" cy="7" r="4"></circle>
              <polyline points="16 11 18 13 22 9"></polyline>
            </svg>
          </view>
          <text class="step-text inactive-text">接待人验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-inactive.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤5: 打印凭条 -->
        <view class="step-item">
          <view class="step-circle inactive-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#C7D7F6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <polyline points="6 9 6 2 18 2 18 9"></polyline>
              <path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path>
              <rect x="6" y="14" width="12" height="8"></rect>
            </svg>
          </view>
          <text class="step-text inactive-text">打印凭条</text>
        </view>
      </view>
    </view>
    <!-- 扫码主体区 -->
    <view class="main-card">
      <text class="main-title">请将您的预约二维码放置到扫码位置</text>
      <text class="main-desc">保持二维码完整、清晰,距离扫码口约 10-20 厘米</text>
      <!-- 设备展示区 -->
      <view class="device-area">
        <image src="../../static/device-scan.png" class="device-img" mode="aspectFit"></image>
      </view>
      <text class="helper-text">若识别失败,请调整手机亮度、避免反光,或联系工作人员协助</text>
    </view>
    <!-- 扫码页底部 -->
    <view class="bottom-area">
      <!-- 帮助提示卡 -->
      <view class="help-card">
        <view class="highlight-bar"></view>
        <view class="card-title-row">
          <view class="icon-base">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <circle cx="12" cy="12" r="10"></circle>
              <line x1="12" y1="16" x2="12" y2="12"></line>
              <line x1="12" y1="8" x2="12.01" y2="8"></line>
            </svg>
          </view>
          <text class="card-title">扫码小贴士</text>
        </view>
        <text class="help-content">请使用预约成功后收到的二维码,\n保持二维码完整显示后再靠近扫码区域</text>
      </view>
      <!-- 操作提示卡 -->
      <view class="action-card">
        <view class="highlight-bar"></view>
        <view class="card-title-row">
          <view class="icon-base">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <line x1="19" y1="12" x2="5" y2="12"></line>
              <polyline points="12 19 5 12 12 5"></polyline>
            </svg>
          </view>
          <text class="card-title">需要返回?</text>
        </view>
        <view class="main-btn" @click="goBack">
          <text class="btn-text">返回上一步</text>
        </view>
      </view>
    </view>
  </view>
</template>
<script setup>
const goBack = () => {
  uni.navigateBack({
    fail: () => {
      uni.redirectTo({
        url: '/pages/home/index'
      });
    }
  });
};
</script>
<style scoped>
/* 使用 px 单位,严格对应 1080x1920 设计稿 */
.page-container {
  width: 1080px;
  height: 1920px;
  box-sizing: border-box;
  background: linear-gradient(180deg, #3B78FF 0%, #2860E8 50%, #163EBC 100%);
  padding: 30px 36px 40px 36px;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}
.bg-glow {
  position: absolute;
  width: 300px;
  height: 300px;
  border-radius: 50%;
  background: rgba(127, 177, 255, 0.1);
  left: -128px;
  top: 412px;
  filter: blur(50px);
  pointer-events: none;
}
/* 扫码页头部 */
.header-area {
  width: 1008px; /* 1080 - 36*2 */
  height: 88px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 24px;
  z-index: 1;
}
.left-action {
  display: flex;
  align-items: center;
  gap: 16px;
  height: 100%;
  cursor: pointer;
}
.back-btn {
  width: 44px;
  height: 44px;
  border-radius: 22px;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.26);
  box-shadow: 0px 10px 22px rgba(15, 46, 133, 0.18);
  display: flex;
  align-items: center;
  justify-content: center;
}
.page-title {
  font-family: Inter, sans-serif;
  font-size: 30px;
  font-weight: 700;
  color: #FFFFFF;
  line-height: 1.2;
}
/* 步骤卡片 */
.steps-card {
  width: 1008px;
  display: flex;
  flex-direction: column;
  margin-bottom: 24px;
  z-index: 1;
}
.steps-row {
  width: 100%;
  height: 138px;
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: space-between;
}
.step-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 120px;
}
.step-circle {
  width: 82px;
  height: 82px;
  border-radius: 41px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.active-circle {
  background: #FFFFFF;
  border: 2px solid rgba(255, 255, 255, 0.4);
  box-shadow: 0px 12px 24px rgba(15, 45, 125, 0.27);
}
.inactive-circle {
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0px 8px 14px rgba(8, 32, 86, 0.07);
}
.step-num {
  font-size: 30px;
  font-weight: 700;
  color: rgba(255, 255, 255, 0.5);
}
.step-text {
  font-family: Inter, sans-serif;
  font-size: 23px;
  text-align: center;
  white-space: nowrap;
}
.active-text {
  font-weight: 900;
  color: #FFFFFF;
}
.inactive-text {
  font-weight: 600;
  color: rgba(226, 235, 255, 0.66);
}
.step-line {
  flex: 1;
  height: 82px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.arrow-img {
  width: 92px;
  height: 82px;
}
/* 扫码主体区 */
.main-card {
  width: 1008px;
  height: 1200px;
  border-radius: 34px;
  background: linear-gradient(180deg, #FFFFFF 0%, #F7FAFF 100%);
  border: 1px solid #DEE9FF;
  box-shadow: 0px 22px 52px rgba(13, 46, 134, 0.25);
  padding: 32px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  margin-bottom: 24px;
  z-index: 1;
}
.main-title {
  font-family: Inter, sans-serif;
  font-size: 42px;
  font-weight: 700;
  color: #1F2A44;
  text-align: center;
  line-height: 1.2;
}
.main-desc {
  font-family: Inter, sans-serif;
  font-size: 22px;
  font-weight: 500;
  color: #4A5672;
  text-align: center;
  line-height: 1.3;
}
.device-area {
  width: 100%;
  height: 820px;
  display: flex;
  justify-content: center;
  align-items: center;
}
.device-img {
  width: 100%;
  height: 100%;
}
.helper-text {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 500;
  color: #7B88A8;
  text-align: center;
}
/* 扫码页底部 */
.bottom-area {
  width: 1008px;
  height: 176px;
  display: flex;
  flex-direction: row;
  gap: 18px;
  z-index: 1;
}
.help-card {
  width: 360px;
  height: 176px;
  border-radius: 26px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.11) 0%, rgba(234, 242, 255, 0.08) 100%);
  border: 2px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0px 14px 30px rgba(10, 36, 107, 0.21);
  padding: 22px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 16px;
  position: relative;
}
.action-card {
  flex: 1;
  height: 176px;
  border-radius: 26px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.11) 0%, rgba(234, 242, 255, 0.08) 100%);
  border: 2px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0px 14px 30px rgba(10, 36, 107, 0.21);
  padding: 20px 22px 22px 22px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 12px;
  position: relative;
}
.highlight-bar {
  width: 108px;
  height: 6px;
  border-radius: 2px;
  background: #A8CBFF;
}
.card-title-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.icon-base {
  width: 38px;
  height: 38px;
  border-radius: 19px;
  background: rgba(255, 255, 255, 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
}
.card-title {
  font-family: Inter, sans-serif;
  font-size: 21px;
  font-weight: 800;
  color: #FFFFFF;
  line-height: 1.2;
}
.help-content {
  font-family: Inter, sans-serif;
  font-size: 16px;
  font-weight: 500;
  color: #EAF1FF;
  line-height: 1.4;
  white-space: pre-line;
}
.main-btn {
  width: 100%;
  height: 56px;
  border-radius: 18px;
  background: linear-gradient(180deg, #FFFFFF 0%, #F4F8FF 100%);
  border: 1px solid rgba(255, 255, 255, 0.5);
  box-shadow: 0px 8px 18px rgba(7, 29, 90, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.btn-text {
  font-family: Inter, sans-serif;
  font-size: 19px;
  font-weight: 800;
  color: #1F49D8;
  line-height: 1.2;
}
</style>
src/pages/device-init/index.vue
New file
@@ -0,0 +1,167 @@
<script setup>
const formRows = [
  { label: '设备ID:', placeholder: '自动回填', readonly: true },
  { label: 'IP地址:', placeholder: '请输入', readonly: false },
  { label: '端口号:', placeholder: '请输入', readonly: false },
]
const goHome = () => {
  uni.redirectTo({
    url: '/pages/home/index',
  })
}
</script>
<template>
  <view class="page">
    <view class="overlay" />
    <view class="dialog">
      <text class="title">设备激活</text>
      <view class="form-block">
        <view
          v-for="item in formRows"
          :key="item.label"
          class="form-row"
        >
          <text class="form-label">{{ item.label }}</text>
          <view class="form-input" :class="{ readonly: item.readonly }">
            <text class="placeholder">{{ item.placeholder }}</text>
          </view>
        </view>
      </view>
      <view class="button-row">
        <view class="btn btn-reset">
          <text class="btn-reset-text">重置</text>
        </view>
        <view class="btn btn-primary" @click="goHome">
          <text class="btn-primary-text">激活</text>
        </view>
      </view>
    </view>
  </view>
</template>
<style scoped>
.page {
  position: relative;
  width: 100%;
  min-height: 100%;
  background: #f4f7fc;
  overflow: hidden;
}
.overlay {
  position: absolute;
  inset: 0;
  background: rgba(17, 17, 17, 0.95);
}
.dialog {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 1480rpx;
  padding: 36.1rpx 55.6rpx;
  border-radius: 19.4rpx;
  background: #ffffff;
  display: flex;
  flex-direction: column;
  gap: 38.9rpx;
}
.title {
  width: 100%;
  color: #111111;
  font-size: 23.6rpx;
  line-height: 28.3rpx;
  font-weight: 700;
  text-align: center;
}
.form-block {
  display: flex;
  flex-direction: column;
  gap: 22.2rpx;
}
.form-row {
  height: 44.4rpx;
  display: flex;
  align-items: center;
  gap: 11.1rpx;
}
.form-label {
  width: 63.9rpx;
  color: #222222;
  font-size: 13.9rpx;
  line-height: 16.7rpx;
  font-weight: 700;
}
.form-input {
  flex: 1;
  height: 38.9rpx;
  padding: 0 11.1rpx;
  border: 1px solid #cfcfcf;
  border-radius: 8.3rpx;
  background: #ffffff;
  display: flex;
  align-items: center;
}
.form-input.readonly {
  background: #f1f1f1;
}
.placeholder {
  color: #a3a3a3;
  font-size: 12.5rpx;
  line-height: 15rpx;
  font-weight: 600;
}
.button-row {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 19.4rpx;
}
.btn {
  width: 152.8rpx;
  height: 38.9rpx;
  border-radius: 13.9rpx;
  display: flex;
  justify-content: center;
  align-items: center;
}
.btn-reset {
  background: #f5f5f5;
  border: 1px solid #f5f5f5;
}
.btn-primary {
  background: #2f6bff;
}
.btn-reset-text,
.btn-primary-text {
  font-size: 13.9rpx;
  line-height: 16.7rpx;
  font-weight: 700;
}
.btn-reset-text {
  color: #666666;
}
.btn-primary-text {
  color: #ffffff;
}
</style>
src/pages/home/index.vue
New file
@@ -0,0 +1,602 @@
<script setup>
const goAppointment = () => {
  uni.navigateTo({
    url: '/pages/appointment-scan/index',
  })
}
const goOnline = () => {
  uni.navigateTo({
    url: '/pages/online-id-scan/index',
  })
}
</script>
<template>
  <view class="screen">
    <view class="bg-orbit orbit-one" />
    <view class="bg-orbit orbit-two" />
    <view class="bg-wave wave-one" />
    <view class="bg-wave wave-two" />
    <view class="top-bar">
      <text class="logo">LOGO</text>
      <view class="admin-btn">
        <view class="admin-icon">
          <view class="admin-head" />
          <view class="admin-body" />
        </view>
        <text class="admin-text">管理员登录</text>
      </view>
    </view>
    <view class="hero">
      <view class="hero-copy">
        <view class="welcome-badge">
          <text class="welcome-text">欢迎到访</text>
        </view>
        <text class="company-name">成都喜望科技有限公司</text>
      </view>
      <view class="hero-figure">
        <view class="kiosk">
          <view class="kiosk-head" />
          <view class="kiosk-screen">
            <view class="screen-bar short" />
            <view class="screen-bar long" />
            <view class="screen-input" />
            <view class="screen-input" />
            <view class="screen-btn white" />
            <view class="screen-btn orange" />
          </view>
          <view class="kiosk-slot" />
          <view class="kiosk-base" />
        </view>
        <view class="person">
          <view class="person-head" />
          <view class="person-body" />
          <view class="person-arm" />
          <view class="person-leg left" />
          <view class="person-leg right" />
        </view>
      </view>
    </view>
    <view class="entry-panel">
      <view class="entry-card blue" @click="goAppointment">
        <text class="entry-title">预约登记</text>
        <view class="entry-icon calendar">
          <view class="cal-top" />
          <view class="cal-ring left" />
          <view class="cal-ring right" />
          <view class="clock-circle" />
          <view class="clock-hand long" />
          <view class="clock-hand short" />
        </view>
      </view>
      <view class="entry-card purple" @click="goOnline">
        <text class="entry-title">现场登记</text>
        <view class="entry-icon form">
          <view class="form-line short" />
          <view class="form-line" />
          <view class="form-line" />
          <view class="form-badge" />
          <view class="form-tab" />
        </view>
      </view>
      <view class="entry-card orange">
        <text class="entry-title">签离</text>
        <view class="entry-icon signout">
          <view class="sign-card">
            <view class="sign-line" />
            <view class="sign-line short" />
          </view>
          <view class="sign-arrow" />
        </view>
      </view>
    </view>
  </view>
</template>
<style scoped>
.screen {
  position: relative;
  width: 750rpx;
  min-height: 1333.3rpx;
  padding: 70rpx 54rpx 48rpx;
  overflow: hidden;
  background: linear-gradient(180deg, #3d8bff 0%, #1f5de7 56%, #1646ca 100%);
}
.bg-orbit,
.bg-wave {
  position: absolute;
  pointer-events: none;
}
.bg-orbit {
  border-radius: 50%;
  border: 2rpx solid rgba(255, 255, 255, 0.14);
}
.orbit-one {
  top: -160rpx;
  left: 180rpx;
  width: 760rpx;
  height: 360rpx;
  border-left-color: transparent;
  border-bottom-color: transparent;
  transform: rotate(12deg);
}
.orbit-two {
  top: -90rpx;
  left: 260rpx;
  width: 540rpx;
  height: 220rpx;
  border-left-color: transparent;
  border-bottom-color: transparent;
  box-shadow: 0 0 18rpx rgba(255, 255, 255, 0.22);
  transform: rotate(10deg);
}
.bg-wave {
  left: -80rpx;
  right: -80rpx;
  border-radius: 50%;
  border: 2rpx solid rgba(89, 180, 255, 0.18);
}
.wave-one {
  bottom: -110rpx;
  height: 280rpx;
}
.wave-two {
  bottom: -170rpx;
  height: 340rpx;
  border-color: rgba(89, 180, 255, 0.12);
}
.top-bar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
}
.logo {
  color: rgba(255, 255, 255, 0.96);
  font-size: 54rpx;
  font-weight: 300;
  letter-spacing: 1rpx;
}
.admin-btn {
  margin-top: 6rpx;
  height: 54rpx;
  padding: 0 20rpx;
  border-radius: 16rpx;
  border: 1rpx solid rgba(255, 255, 255, 0.28);
  background: rgba(255, 255, 255, 0.14);
  display: flex;
  align-items: center;
  gap: 10rpx;
  box-shadow: 0 10rpx 18rpx rgba(15, 46, 133, 0.16);
}
.admin-icon {
  position: relative;
  width: 22rpx;
  height: 22rpx;
}
.admin-head {
  position: absolute;
  left: 6rpx;
  top: 0;
  width: 10rpx;
  height: 10rpx;
  border-radius: 50%;
  background: #ffffff;
}
.admin-body {
  position: absolute;
  left: 1rpx;
  bottom: 1rpx;
  width: 20rpx;
  height: 10rpx;
  border-radius: 10rpx 10rpx 6rpx 6rpx;
  background: rgba(255, 255, 255, 0.95);
}
.admin-text {
  color: rgba(255, 255, 255, 0.98);
  font-size: 15rpx;
  font-weight: 600;
}
.hero {
  margin-top: 42rpx;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
}
.hero-copy {
  padding-top: 56rpx;
}
.welcome-badge {
  width: 228rpx;
  height: 58rpx;
  padding-left: 26rpx;
  background: linear-gradient(100deg, rgba(18, 78, 180, 0.42), rgba(18, 78, 180, 0.12));
  clip-path: polygon(0 0, 100% 0, 88% 100%, 0 100%);
  display: flex;
  align-items: center;
}
.welcome-text {
  color: #ffffff;
  font-size: 28rpx;
  font-style: italic;
  font-weight: 800;
  letter-spacing: 2rpx;
}
.company-name {
  display: block;
  width: 360rpx;
  margin-top: 42rpx;
  color: #ffffff;
  font-size: 30rpx;
  font-weight: 800;
  line-height: 1.4;
}
.hero-figure {
  position: relative;
  width: 214rpx;
  height: 314rpx;
}
.kiosk {
  position: absolute;
  left: 18rpx;
  top: 18rpx;
  width: 110rpx;
  height: 248rpx;
  border-radius: 10rpx 10rpx 8rpx 8rpx;
  background: linear-gradient(180deg, #f5f7fb 0%, #d4e0f1 46%, #2860e8 47%, #1b4bc7 100%);
  transform: skewY(-6deg);
  box-shadow: 0 18rpx 26rpx rgba(7, 38, 122, 0.2);
}
.kiosk-head {
  width: 28rpx;
  height: 8rpx;
  border-radius: 999rpx;
  background: #98a7b8;
  margin: 10rpx auto 8rpx;
}
.kiosk-screen {
  width: 72rpx;
  height: 112rpx;
  margin: 0 auto;
  border-radius: 8rpx;
  padding: 8rpx 6rpx;
  background: linear-gradient(180deg, #2f6bff 0%, #1e58db 100%);
  display: flex;
  flex-direction: column;
  gap: 6rpx;
}
.screen-bar,
.screen-input,
.screen-btn {
  border-radius: 999rpx;
  background: rgba(255, 255, 255, 0.9);
}
.screen-bar.short {
  width: 30rpx;
  height: 4rpx;
}
.screen-bar.long {
  width: 50rpx;
  height: 5rpx;
}
.screen-input {
  width: 60rpx;
  height: 10rpx;
}
.screen-btn {
  width: 60rpx;
  height: 16rpx;
}
.screen-btn.orange {
  background: #ffbd47;
}
.kiosk-slot {
  width: 28rpx;
  height: 10rpx;
  margin: 20rpx auto 0;
  border-radius: 999rpx;
  background: rgba(255, 255, 255, 0.72);
}
.kiosk-base {
  position: absolute;
  left: 16rpx;
  right: 16rpx;
  bottom: 32rpx;
  height: 4rpx;
  background: rgba(31, 42, 68, 0.42);
}
.person {
  position: absolute;
  right: 0;
  top: 0;
  width: 102rpx;
  height: 286rpx;
}
.person-head {
  position: absolute;
  right: 36rpx;
  top: 6rpx;
  width: 28rpx;
  height: 28rpx;
  border-radius: 50%;
  background: #ffd2a7;
}
.person-body {
  position: absolute;
  right: 22rpx;
  top: 36rpx;
  width: 68rpx;
  height: 120rpx;
  border-radius: 24rpx 24rpx 12rpx 12rpx;
  background: linear-gradient(180deg, #35a0ff 0%, #1a68eb 100%);
}
.person-arm {
  position: absolute;
  left: 18rpx;
  top: 92rpx;
  width: 58rpx;
  height: 18rpx;
  border-radius: 12rpx;
  background: #2c86ff;
  transform: rotate(-24deg);
  transform-origin: right center;
}
.person-leg {
  position: absolute;
  bottom: 0;
  width: 18rpx;
  height: 120rpx;
  border-radius: 12rpx;
  background: #17338b;
}
.person-leg.left {
  left: 28rpx;
  transform: rotate(4deg);
}
.person-leg.right {
  right: 26rpx;
  transform: rotate(-3deg);
}
.entry-panel {
  position: relative;
  z-index: 1;
  margin-top: 26rpx;
  padding: 54rpx 52rpx 42rpx;
  border-radius: 36rpx;
  border: 2rpx solid #b7c9e7;
  background: #e8e8e8;
  box-shadow: 0 12rpx 28rpx rgba(10, 52, 153, 0.2);
}
.entry-card {
  height: 160rpx;
  border-radius: 24rpx;
  padding: 0 42rpx;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.entry-card + .entry-card {
  margin-top: 58rpx;
}
.entry-card.blue {
  background: linear-gradient(135deg, #5f9df5 0%, #3c76eb 100%);
}
.entry-card.purple {
  background: linear-gradient(135deg, #7f82f2 0%, #5f53e8 100%);
}
.entry-card.orange {
  background: linear-gradient(135deg, #f7c64b 0%, #ffad37 100%);
}
.entry-title {
  color: #ffffff;
  font-size: 34rpx;
  font-weight: 800;
}
.entry-icon {
  position: relative;
  width: 92rpx;
  height: 92rpx;
}
.calendar {
  border-radius: 22rpx;
  background: linear-gradient(180deg, rgba(196, 226, 255, 0.72), rgba(138, 191, 255, 0.62));
}
.cal-top {
  position: absolute;
  left: 14rpx;
  top: 18rpx;
  width: 64rpx;
  height: 10rpx;
  border-radius: 999rpx;
  background: rgba(255, 255, 255, 0.8);
}
.cal-ring {
  position: absolute;
  top: -6rpx;
  width: 8rpx;
  height: 18rpx;
  border-radius: 999rpx;
  background: rgba(106, 160, 239, 0.7);
}
.cal-ring.left {
  left: 20rpx;
}
.cal-ring.right {
  right: 20rpx;
}
.clock-circle {
  position: absolute;
  right: 4rpx;
  bottom: 4rpx;
  width: 44rpx;
  height: 44rpx;
  border-radius: 50%;
  border: 6rpx solid rgba(58, 122, 228, 0.88);
}
.clock-hand {
  position: absolute;
  right: 24rpx;
  bottom: 19rpx;
  width: 4rpx;
  border-radius: 999rpx;
  background: rgba(255, 255, 255, 0.95);
  transform-origin: bottom center;
}
.clock-hand.long {
  height: 18rpx;
  transform: rotate(-24deg);
}
.clock-hand.short {
  height: 12rpx;
  transform: rotate(46deg);
}
.form {
  border-radius: 20rpx;
  background: linear-gradient(180deg, rgba(232, 227, 255, 0.7), rgba(168, 150, 255, 0.56));
}
.form-line {
  position: absolute;
  left: 18rpx;
  height: 8rpx;
  border-radius: 999rpx;
  background: rgba(255, 255, 255, 0.9);
}
.form-line.short {
  top: 20rpx;
  width: 46rpx;
}
.form-line:nth-of-type(2) {
  top: 38rpx;
  width: 52rpx;
}
.form-line:nth-of-type(3) {
  top: 56rpx;
  width: 42rpx;
}
.form-badge {
  position: absolute;
  right: 10rpx;
  top: 34rpx;
  width: 28rpx;
  height: 28rpx;
  border-radius: 50%;
  background: #6a5af3;
}
.form-tab {
  position: absolute;
  right: 8rpx;
  bottom: 10rpx;
  width: 36rpx;
  height: 22rpx;
  border-radius: 12rpx;
  background: rgba(106, 90, 243, 0.78);
}
.signout {
  display: flex;
  align-items: center;
  justify-content: center;
}
.sign-card {
  position: absolute;
  left: 10rpx;
  top: 14rpx;
  width: 50rpx;
  height: 60rpx;
  border-radius: 8rpx;
  background: rgba(255, 242, 188, 0.4);
}
.sign-line {
  width: 28rpx;
  height: 8rpx;
  margin: 14rpx auto 0;
  border-radius: 999rpx;
  background: rgba(255, 255, 255, 0.9);
}
.sign-line.short {
  width: 20rpx;
  margin-top: 8rpx;
}
.sign-arrow {
  position: absolute;
  right: 8rpx;
  top: 22rpx;
  width: 34rpx;
  height: 16rpx;
  background: rgba(255, 161, 60, 0.95);
  clip-path: polygon(0 35%, 62% 35%, 62% 0, 100% 50%, 62% 100%, 62% 65%, 0 65%);
}
</style>
src/pages/manual-id-input/index.vue
New file
@@ -0,0 +1,577 @@
<template>
  <view class="page-container">
    <!-- 左侧柔光氛围 -->
    <view class="bg-glow"></view>
    <!-- 扫码页头部 -->
    <view class="header-area">
      <view class="left-action" @click="goBack">
        <view class="back-btn">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <polyline points="15 18 9 12 15 6"></polyline>
          </svg>
        </view>
        <text class="page-title">预约登记</text>
      </view>
    </view>
    <!-- 步骤卡片 -->
    <view class="steps-card">
      <view class="steps-row">
        <!-- 步骤1: 预约码验证 (Completed) -->
        <view class="step-item">
          <view class="step-circle completed-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
              <rect x="7" y="7" width="3" height="3"></rect>
              <rect x="14" y="7" width="3" height="3"></rect>
              <rect x="7" y="14" width="3" height="3"></rect>
              <rect x="14" y="14" width="3" height="3"></rect>
            </svg>
          </view>
          <text class="step-text completed-text">预约码验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-active.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤2: 身份证验证 (Active) -->
        <view class="step-item">
          <view class="step-circle active-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#2F6BFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M16 10h2"></path>
              <path d="M16 14h2"></path>
              <path d="M6.17 15a3 3 0 0 1 5.66 0"></path>
              <circle cx="9" cy="11" r="2"></circle>
              <rect x="2" y="5" width="20" height="14" rx="2"></rect>
            </svg>
          </view>
          <text class="step-text active-text">身份证验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-inactive.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤3: 人脸验证 -->
        <view class="step-item">
          <view class="step-circle inactive-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#C7D7F6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M3 7V5a2 2 0 0 1 2-2h2"></path>
              <path d="M17 3h2a2 2 0 0 1 2 2v2"></path>
              <path d="M21 17v2a2 2 0 0 1-2 2h-2"></path>
              <path d="M7 21H5a2 2 0 0 1-2-2v-2"></path>
              <path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
              <path d="M9 9h.01"></path>
              <path d="M15 9h.01"></path>
            </svg>
          </view>
          <text class="step-text inactive-text">人脸验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-inactive.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤4: 接待人验证 -->
        <view class="step-item">
          <view class="step-circle inactive-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#C7D7F6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
              <circle cx="9" cy="7" r="4"></circle>
              <polyline points="16 11 18 13 22 9"></polyline>
            </svg>
          </view>
          <text class="step-text inactive-text">接待人验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-inactive.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤5: 打印凭条 -->
        <view class="step-item">
          <view class="step-circle inactive-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#C7D7F6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <polyline points="6 9 6 2 18 2 18 9"></polyline>
              <path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path>
              <rect x="6" y="14" width="12" height="8"></rect>
            </svg>
          </view>
          <text class="step-text inactive-text">打印凭条</text>
        </view>
      </view>
    </view>
    <!-- 扫码主体区 -->
    <view class="main-card">
      <text class="main-title">手动录入身份证号</text>
      <view class="input-form-card">
        <view class="input-box" @click="showKeyboard = true">
          <text class="input-text" :class="{'placeholder': !idNumber}">{{ idNumber || '请输入您的身份证号码' }}</text>
        </view>
        <view class="input-helper-row">
          <text class="input-helper-text">请输入 18 位身份证号码,X 需大写</text>
        </view>
      </view>
      <view class="form-action-row" v-if="idNumber.length > 0">
        <view class="clear-btn" @click="clearInput">
          <text class="clear-btn-text">清空</text>
        </view>
        <view class="confirm-btn" @click="goNext">
          <text class="confirm-btn-text">确认</text>
        </view>
      </view>
      <view class="form-action-row" v-else>
        <view class="clear-btn-disabled">
          <text class="clear-btn-text-disabled">清空</text>
        </view>
        <view class="confirm-btn-disabled">
          <text class="confirm-btn-text-disabled">确认</text>
        </view>
      </view>
    </view>
    <!-- 自定义键盘 -->
    <view class="custom-keyboard" v-if="showKeyboard">
      <view class="keyboard-row">
        <view class="key-btn" @click="inputKey('1')"><text>1</text></view>
        <view class="key-btn" @click="inputKey('2')"><text>2</text></view>
        <view class="key-btn" @click="inputKey('3')"><text>3</text></view>
      </view>
      <view class="keyboard-row">
        <view class="key-btn" @click="inputKey('4')"><text>4</text></view>
        <view class="key-btn" @click="inputKey('5')"><text>5</text></view>
        <view class="key-btn" @click="inputKey('6')"><text>6</text></view>
      </view>
      <view class="keyboard-row">
        <view class="key-btn" @click="inputKey('7')"><text>7</text></view>
        <view class="key-btn" @click="inputKey('8')"><text>8</text></view>
        <view class="key-btn" @click="inputKey('9')"><text>9</text></view>
      </view>
      <view class="keyboard-row">
        <view class="key-btn spec-key" @click="inputKey('X')"><text>X</text></view>
        <view class="key-btn" @click="inputKey('0')"><text>0</text></view>
        <view class="key-btn spec-key" @click="deleteKey">
          <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#1F2A44" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M21 4H8l-7 8 7 8h13a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z"></path>
            <line x1="18" y1="9" x2="12" y2="15"></line>
            <line x1="12" y1="9" x2="18" y2="15"></line>
          </svg>
        </view>
      </view>
      <view class="keyboard-close" @click="showKeyboard = false">
        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#7B88A8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <polyline points="6 9 12 15 18 9"></polyline>
        </svg>
      </view>
    </view>
  </view>
</template>
<script setup>
import { ref } from 'vue';
const idNumber = ref('');
const showKeyboard = ref(false);
const inputKey = (key) => {
  if (idNumber.value.length < 18) {
    idNumber.value += key;
  }
};
const deleteKey = () => {
  if (idNumber.value.length > 0) {
    idNumber.value = idNumber.value.slice(0, -1);
  }
};
const clearInput = () => {
  idNumber.value = '';
};
const goBack = () => {
  uni.navigateBack({
    fail: () => {
      uni.redirectTo({
        url: '/pages/home/index'
      });
    }
  });
};
const goNext = () => {
  uni.navigateTo({
    url: '/pages/online-face-scan/index'
  });
};
</script>
<style scoped>
/* 使用 px 单位,严格对应 1080x1920 设计稿 */
.page-container {
  width: 1080px;
  height: 1920px;
  box-sizing: border-box;
  background: linear-gradient(180deg, #3B78FF 0%, #2860E8 50%, #163EBC 100%);
  padding: 30px 36px 40px 36px;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}
.bg-glow {
  position: absolute;
  width: 300px;
  height: 300px;
  border-radius: 50%;
  background: rgba(127, 177, 255, 0.1);
  left: -128px;
  top: 412px;
  filter: blur(50px);
  pointer-events: none;
}
/* 扫码页头部 */
.header-area {
  width: 1008px;
  height: 88px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 24px;
  z-index: 1;
}
.left-action {
  display: flex;
  align-items: center;
  gap: 16px;
  height: 100%;
  cursor: pointer;
}
.back-btn {
  width: 44px;
  height: 44px;
  border-radius: 22px;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.26);
  box-shadow: 0px 10px 22px rgba(15, 46, 133, 0.18);
  display: flex;
  align-items: center;
  justify-content: center;
}
.page-title {
  font-family: Inter, sans-serif;
  font-size: 30px;
  font-weight: 700;
  color: #FFFFFF;
  line-height: 1.2;
}
/* 步骤卡片 */
.steps-card {
  width: 1008px;
  display: flex;
  flex-direction: column;
  margin-bottom: 24px;
  z-index: 1;
}
.steps-row {
  width: 100%;
  height: 138px;
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: space-between;
}
.step-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 120px;
}
.step-circle {
  width: 82px;
  height: 82px;
  border-radius: 41px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.completed-circle {
  background: #78A8FF;
  border: 1px solid #DDE9FF;
  box-shadow: 0px 10px 18px rgba(14, 49, 138, 0.22);
}
.active-circle {
  background: #FFFFFF;
  border: 2px solid rgba(255, 255, 255, 0.4);
  box-shadow: 0px 12px 24px rgba(15, 45, 125, 0.27);
}
.inactive-circle {
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0px 8px 14px rgba(8, 32, 86, 0.07);
}
.step-text {
  font-family: Inter, sans-serif;
  font-size: 23px;
  text-align: center;
  white-space: nowrap;
}
.completed-text {
  font-weight: 800;
  color: rgba(255, 255, 255, 0.94);
}
.active-text {
  font-weight: 900;
  color: #FFFFFF;
}
.inactive-text {
  font-weight: 600;
  color: rgba(226, 235, 255, 0.66);
}
.step-line {
  flex: 1;
  height: 82px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.arrow-img {
  width: 92px;
  height: 82px;
}
/* 主体区 */
.main-card {
  width: 1008px;
  height: 720px;
  border-radius: 34px;
  background: linear-gradient(180deg, #FFFFFF 0%, #F7FAFF 100%);
  border: 1px solid #DEE9FF;
  box-shadow: 0px 18px 44px rgba(13, 46, 134, 0.22);
  padding: 38px 32px 28px 32px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 40px;
  z-index: 1;
}
.main-title {
  font-family: Inter, sans-serif;
  font-size: 42px;
  font-weight: 800;
  color: #1F2A44;
  text-align: center;
  line-height: 1.2;
}
.input-form-card {
  width: 820px;
  height: 230px;
  border-radius: 28px;
  background: #F8FAFF;
  border: 1px solid #D9E1F2;
  padding: 26px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.input-box {
  width: 100%;
  height: 118px;
  border-radius: 22px;
  background: #FFFFFF;
  border: 1px solid #D9E1F2;
  padding: 0 28px;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
}
.input-text {
  font-family: Inter, sans-serif;
  font-size: 40px;
  font-weight: 700;
  color: #1F2A44;
  letter-spacing: 2px;
}
.input-text.placeholder {
  font-size: 24px;
  font-weight: 500;
  color: #A0ACC4;
  letter-spacing: normal;
}
.input-helper-row {
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.input-helper-text {
  font-family: Inter, sans-serif;
  font-size: 17px;
  font-weight: 500;
  color: #7B88A8;
  line-height: 1.2;
}
.form-action-row {
  width: 820px;
  height: 112px;
  display: flex;
  gap: 16px;
}
.clear-btn {
  width: 232px;
  height: 100%;
  border-radius: 24px;
  background: #EEF4FF;
  border: 1px solid #D6E3FF;
  display: flex;
  align-items: center;
  justify-content: center;
}
.clear-btn-text {
  font-family: Inter, sans-serif;
  font-size: 24px;
  font-weight: 700;
  color: #2F6BFF;
}
.confirm-btn {
  flex: 1;
  height: 100%;
  border-radius: 24px;
  background: linear-gradient(180deg, #5A8FFF 0%, #2F6BFF 100%);
  border: 1px solid rgba(255, 255, 255, 0.33);
  box-shadow: 0px 10px 22px rgba(47, 107, 255, 0.18);
  display: flex;
  align-items: center;
  justify-content: center;
}
.confirm-btn-text {
  font-family: Inter, sans-serif;
  font-size: 24px;
  font-weight: 700;
  color: #FFFFFF;
}
.clear-btn-disabled {
  width: 232px;
  height: 100%;
  border-radius: 24px;
  background: #F3F6FB;
  border: 1px solid #E1E7F0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.clear-btn-text-disabled {
  font-family: Inter, sans-serif;
  font-size: 24px;
  font-weight: 700;
  color: #A8B3C4;
}
.confirm-btn-disabled {
  flex: 1;
  height: 100%;
  border-radius: 24px;
  background: #E5EAF2;
  border: 1px solid #D8DEE8;
  display: flex;
  align-items: center;
  justify-content: center;
}
.confirm-btn-text-disabled {
  font-family: Inter, sans-serif;
  font-size: 24px;
  font-weight: 700;
  color: #9AA6B8;
}
/* 自定义键盘 */
.custom-keyboard {
  width: 1008px;
  height: 300px;
  margin-top: 40px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  z-index: 10;
  position: relative;
}
.keyboard-row {
  display: flex;
  gap: 12px;
  height: 64px;
}
.key-btn {
  flex: 1;
  background: rgba(255, 255, 255, 0.95);
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 32px;
  font-weight: 700;
  color: #1F2A44;
  box-shadow: 0 4px 10px rgba(0,0,0,0.1);
}
.key-btn:active {
  background: #E5EAF2;
}
.spec-key {
  background: #DDE4F0;
}
.keyboard-close {
  position: absolute;
  top: -40px;
  right: 20px;
  width: 60px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.8);
  border-radius: 12px 12px 0 0;
}
</style>
src/pages/online-complete/index.vue
New file
@@ -0,0 +1,303 @@
<template>
  <view class="page-container">
    <!-- 左侧柔光氛围 -->
    <view class="bg-glow"></view>
    <!-- 扫码页头部 -->
    <view class="header-area">
      <view class="left-action" @click="goBack">
        <view class="back-btn">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <polyline points="15 18 9 12 15 6"></polyline>
          </svg>
        </view>
        <text class="page-title">预约登记</text>
      </view>
    </view>
    <!-- 成功主体卡 -->
    <view class="success-card">
      <view class="success-badge">
        <text class="success-badge-text">登记已完成</text>
      </view>
      <view class="success-icon-area">
        <view class="icon-outer-circle">
          <view class="icon-inner-circle">
            <svg width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
              <polyline points="20 6 9 17 4 12"></polyline>
            </svg>
          </view>
        </view>
      </view>
      <text class="success-title">欢迎来访,签到成功</text>
      <view class="reminder-bar">
        <view class="reminder-icon-bg">
          <text class="reminder-icon-text">i</text>
        </view>
        <text class="reminder-text">请妥善保管个人证件与访客凭条,避免遗失</text>
      </view>
      <view class="action-area">
        <view class="home-btn" @click="goHome">
          <text class="home-btn-text">返回首页</text>
        </view>
        <view class="countdown-hint">
          <text class="countdown-text">页面将在 {{ countdown }} 秒后自动返回首页</text>
        </view>
      </view>
    </view>
  </view>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const countdown = ref(15);
let timer = null;
onMounted(() => {
  timer = setInterval(() => {
    countdown.value--;
    if (countdown.value <= 0) {
      clearInterval(timer);
      goHome();
    }
  }, 1000);
});
onUnmounted(() => {
  if (timer) {
    clearInterval(timer);
  }
});
const goBack = () => {
  goHome();
};
const goHome = () => {
  uni.reLaunch({
    url: '/pages/home/index'
  });
};
</script>
<style scoped>
.page-container {
  width: 1080px;
  height: 1920px;
  box-sizing: border-box;
  background: linear-gradient(180deg, #3B78FF 0%, #2860E8 50%, #163EBC 100%);
  padding: 30px 36px 40px 36px;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}
.bg-glow {
  position: absolute;
  width: 300px;
  height: 300px;
  border-radius: 50%;
  background: rgba(127, 177, 255, 0.1);
  left: -128px;
  top: 412px;
  filter: blur(50px);
  pointer-events: none;
}
/* 扫码页头部 */
.header-area {
  width: 1008px;
  height: 88px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 24px;
  z-index: 1;
}
.left-action {
  display: flex;
  align-items: center;
  gap: 16px;
  height: 100%;
  cursor: pointer;
}
.back-btn {
  width: 44px;
  height: 44px;
  border-radius: 22px;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.26);
  box-shadow: 0px 10px 22px rgba(15, 46, 133, 0.18);
  display: flex;
  align-items: center;
  justify-content: center;
}
.page-title {
  font-family: Inter, sans-serif;
  font-size: 30px;
  font-weight: 700;
  color: #FFFFFF;
  line-height: 1.2;
}
/* 成功主体卡 */
.success-card {
  width: 1008px;
  height: 1160px;
  border-radius: 34px;
  background: linear-gradient(180deg, #FFFFFF 0%, #F7FAFF 100%);
  border: 1px solid #DEE9FF;
  box-shadow: 0px 22px 52px rgba(13, 46, 134, 0.25);
  padding: 48px 40px 40px 40px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 30px;
  z-index: 1;
  margin-top: 100px;
}
.success-badge {
  width: 168px;
  height: 48px;
  border-radius: 24px;
  background: #E9FFF5;
  display: flex;
  align-items: center;
  justify-content: center;
}
.success-badge-text {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 700;
  color: #18B67A;
}
.success-icon-area {
  width: 220px;
  height: 220px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 40px 0;
}
.icon-outer-circle {
  width: 180px;
  height: 180px;
  border-radius: 90px;
  background: #E9FFF5;
  display: flex;
  align-items: center;
  justify-content: center;
}
.icon-inner-circle {
  width: 132px;
  height: 132px;
  border-radius: 66px;
  background: #18B67A;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 10px 20px rgba(24, 182, 122, 0.3);
}
.success-title {
  font-family: Inter, sans-serif;
  font-size: 48px;
  font-weight: 700;
  color: #1F2A44;
  line-height: 1.2;
}
.reminder-bar {
  height: 72px;
  border-radius: 20px;
  background: #FFFFFF;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 0 30px;
  margin-bottom: auto;
}
.reminder-icon-bg {
  width: 28px;
  height: 28px;
  border-radius: 14px;
  background: #EAF1FF;
  display: flex;
  align-items: center;
  justify-content: center;
}
.reminder-icon-text {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 700;
  color: #2F6BFF;
  line-height: 1;
}
.reminder-text {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 500;
  color: #4A5672;
}
.action-area {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  padding-top: 8px;
}
.home-btn {
  width: 100%;
  height: 88px;
  border-radius: 18px;
  background: #2F6BFF;
  box-shadow: 0px 12px 26px rgba(47, 107, 255, 0.23);
  display: flex;
  align-items: center;
  justify-content: center;
}
.home-btn-text {
  font-family: Inter, sans-serif;
  font-size: 28px;
  font-weight: 700;
  color: #FFFFFF;
}
.countdown-hint {
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.countdown-text {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 600;
  color: #7B88A8;
}
</style>
src/pages/online-face-collect/index.vue
New file
@@ -0,0 +1,670 @@
<template>
  <view class="page-container">
    <!-- 左侧柔光氛围 -->
    <view class="bg-glow"></view>
    <!-- 扫码页头部 -->
    <view class="header-area">
      <view class="left-action" @click="goBack">
        <view class="back-btn">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <polyline points="15 18 9 12 15 6"></polyline>
          </svg>
        </view>
        <text class="page-title">预约登记</text>
      </view>
    </view>
    <!-- 步骤卡片 -->
    <view class="steps-card">
      <view class="steps-row">
        <!-- 步骤1: 预约码验证 (Completed) -->
        <view class="step-item">
          <view class="step-circle completed-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
              <rect x="7" y="7" width="3" height="3"></rect>
              <rect x="14" y="7" width="3" height="3"></rect>
              <rect x="7" y="14" width="3" height="3"></rect>
              <rect x="14" y="14" width="3" height="3"></rect>
            </svg>
          </view>
          <text class="step-text completed-text">预约码验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-active.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤2: 身份证验证 (Completed) -->
        <view class="step-item">
          <view class="step-circle completed-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M16 10h2"></path>
              <path d="M16 14h2"></path>
              <path d="M6.17 15a3 3 0 0 1 5.66 0"></path>
              <circle cx="9" cy="11" r="2"></circle>
              <rect x="2" y="5" width="20" height="14" rx="2"></rect>
            </svg>
          </view>
          <text class="step-text completed-text">身份证验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-active.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤3: 人脸验证 (Active) -->
        <view class="step-item">
          <view class="step-circle active-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#2F6BFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M3 7V5a2 2 0 0 1 2-2h2"></path>
              <path d="M17 3h2a2 2 0 0 1 2 2v2"></path>
              <path d="M21 17v2a2 2 0 0 1-2 2h-2"></path>
              <path d="M7 21H5a2 2 0 0 1-2-2v-2"></path>
              <path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
              <path d="M9 9h.01"></path>
              <path d="M15 9h.01"></path>
            </svg>
          </view>
          <text class="step-text active-text">人脸验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-inactive.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤4: 接待人验证 -->
        <view class="step-item">
          <view class="step-circle inactive-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#C7D7F6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
              <circle cx="9" cy="7" r="4"></circle>
              <polyline points="16 11 18 13 22 9"></polyline>
            </svg>
          </view>
          <text class="step-text inactive-text">接待人验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-inactive.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤5: 打印凭条 -->
        <view class="step-item">
          <view class="step-circle inactive-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#C7D7F6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <polyline points="6 9 6 2 18 2 18 9"></polyline>
              <path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path>
              <rect x="6" y="14" width="12" height="8"></rect>
            </svg>
          </view>
          <text class="step-text inactive-text">打印凭条</text>
        </view>
      </view>
    </view>
    <!-- 人脸采集主体区 -->
    <view class="main-card">
      <view class="title-group">
        <text class="main-title">请缓慢转动人脸</text>
        <text class="sub-title">请根据屏幕引导缓慢转动头部,系统将采集多角度面部信息</text>
        <view class="name-pill">
          <view class="name-icon">
            <text class="name-icon-text">人</text>
          </view>
          <text class="name-text">姓名:张琪琪</text>
        </view>
      </view>
      <!-- 人脸采集卡 -->
      <view class="face-capture-card">
        <view class="status-pill">
          <text class="status-text">人脸采集中...</text>
        </view>
        <view class="viewfinder-area">
          <view class="circular-viewfinder-box">
            <view class="circular-inner-box">
              <view class="head-ellipse"></view>
              <view class="shoulder-ellipse"></view>
            </view>
          </view>
          <text class="viewfinder-hint">请缓慢转动人脸采集</text>
        </view>
      </view>
      <text class="bottom-subtext">采集过程中请勿离开取景区域,保持自然表情并按提示缓慢转动头部</text>
    </view>
    <!-- 底部区域 -->
    <view class="bottom-area">
      <!-- 帮助提示卡 -->
      <view class="help-card">
        <view class="help-highlight-bar"></view>
        <view class="help-title-row">
          <view class="help-icon-bg">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <circle cx="12" cy="12" r="10"></circle>
              <line x1="12" y1="16" x2="12" y2="12"></line>
              <line x1="12" y1="8" x2="12.01" y2="8"></line>
            </svg>
          </view>
          <text class="help-title">采集提示</text>
        </view>
        <text class="help-content">请跟随屏幕提示轻微左右转头;
采集完成后系统将自动进入下一步接待人验证</text>
      </view>
      <!-- 操作提示卡 -->
      <view class="action-card">
        <view class="action-highlight-bar" style="background: #6EA2FF;"></view>
        <view class="action-title-row">
          <view class="action-icon-bg">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <circle cx="12" cy="12" r="10"></circle>
              <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
              <line x1="12" y1="17" x2="12.01" y2="17"></line>
            </svg>
          </view>
          <text class="action-title">采集异常?</text>
        </view>
        <view class="action-btn" @click="goNext">
          <text class="action-btn-text">重新开始采集</text>
        </view>
      </view>
    </view>
  </view>
</template>
<script setup>
const goBack = () => {
  uni.navigateBack({
    fail: () => {
      uni.redirectTo({
        url: '/pages/home/index'
      });
    }
  });
};
const goNext = () => {
  uni.navigateTo({
    url: '/pages/online-host-verify/index'
  });
};
</script>
<style scoped>
.page-container {
  width: 1080px;
  height: 1920px;
  box-sizing: border-box;
  background: linear-gradient(180deg, #3B78FF 0%, #2860E8 50%, #163EBC 100%);
  padding: 30px 36px 40px 36px;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}
.bg-glow {
  position: absolute;
  width: 300px;
  height: 300px;
  border-radius: 50%;
  background: rgba(127, 177, 255, 0.1);
  left: -128px;
  top: 412px;
  filter: blur(50px);
  pointer-events: none;
}
/* 扫码页头部 */
.header-area {
  width: 1008px;
  height: 88px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 24px;
  z-index: 1;
}
.left-action {
  display: flex;
  align-items: center;
  gap: 16px;
  height: 100%;
  cursor: pointer;
}
.back-btn {
  width: 44px;
  height: 44px;
  border-radius: 22px;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.26);
  box-shadow: 0px 10px 22px rgba(15, 46, 133, 0.18);
  display: flex;
  align-items: center;
  justify-content: center;
}
.page-title {
  font-family: Inter, sans-serif;
  font-size: 30px;
  font-weight: 700;
  color: #FFFFFF;
  line-height: 1.2;
}
/* 步骤卡片 */
.steps-card {
  width: 1008px;
  display: flex;
  flex-direction: column;
  margin-bottom: 24px;
  z-index: 1;
}
.steps-row {
  width: 100%;
  height: 138px;
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: space-between;
}
.step-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 120px;
}
.step-circle {
  width: 82px;
  height: 82px;
  border-radius: 41px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.completed-circle {
  background: #78A8FF;
  border: 1px solid #DDE9FF;
  box-shadow: 0px 10px 18px rgba(14, 49, 138, 0.22);
}
.active-circle {
  background: #FFFFFF;
  border: 2px solid rgba(255, 255, 255, 0.4);
  box-shadow: 0px 12px 24px rgba(15, 45, 125, 0.27);
}
.inactive-circle {
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0px 8px 14px rgba(8, 32, 86, 0.07);
}
.step-text {
  font-family: Inter, sans-serif;
  font-size: 23px;
  text-align: center;
  white-space: nowrap;
}
.completed-text {
  font-weight: 800;
  color: rgba(255, 255, 255, 0.94);
}
.active-text {
  font-weight: 900;
  color: #FFFFFF;
}
.inactive-text {
  font-weight: 600;
  color: rgba(226, 235, 255, 0.66);
}
.step-line {
  flex: 1;
  height: 82px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.arrow-img {
  width: 92px;
  height: 82px;
}
/* 主体区 */
.main-card {
  width: 1008px;
  height: 1200px;
  border-radius: 34px;
  background: linear-gradient(180deg, #FFFFFF 0%, #F7FAFF 100%);
  border: 1px solid #DEE9FF;
  box-shadow: 0px 22px 52px rgba(13, 46, 134, 0.25);
  padding: 32px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  z-index: 1;
}
.title-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.main-title {
  font-family: Inter, sans-serif;
  font-size: 40px;
  font-weight: 700;
  color: #1F2A44;
  line-height: 1.2;
}
.sub-title {
  font-family: Inter, sans-serif;
  font-size: 22px;
  font-weight: 500;
  color: #4A5672;
  line-height: 1.3;
  margin-top: 4px;
}
.name-pill {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}
.name-icon {
  width: 18px;
  height: 18px;
  border-radius: 9px;
  background: #F3F6FC;
  display: flex;
  align-items: center;
  justify-content: center;
}
.name-icon-text {
  font-family: Inter, sans-serif;
  font-size: 10px;
  font-weight: 700;
  color: #7B88A8;
}
.name-text {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 500;
  color: #7B88A8;
}
/* 人脸采集卡 */
.face-capture-card {
  width: 880px;
  height: 876px;
  border-radius: 36px;
  background: linear-gradient(180deg, #101C3D 0%, #0A132B 100%);
  border: 1px solid #243B72;
  box-shadow: 0px 14px 34px rgba(8, 17, 38, 0.23);
  padding: 30px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 26px;
}
.status-pill {
  width: 236px;
  height: 52px;
  border-radius: 26px;
  background: #132852;
  border: 1px solid #2D4F8D;
  box-shadow: 0px 6px 16px rgba(11, 23, 48, 0.2);
  display: flex;
  align-items: center;
  justify-content: center;
}
.status-text {
  font-family: Inter, sans-serif;
  font-size: 20px;
  font-weight: 700;
  color: #F4F8FF;
}
.viewfinder-area {
  width: 760px;
  height: 652px;
  border-radius: 32px;
  background: linear-gradient(180deg, #07111F 0%, #050A15 100%);
  border: 1px solid #223B71;
  box-shadow: 0px 10px 24px rgba(5, 10, 21, 0.2);
  padding: 24px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 28px;
}
.circular-viewfinder-box {
  width: 532px;
  height: 532px;
  border-radius: 266px;
  background: #0A1427;
  border: 12px solid #1FC9AD;
  box-shadow: 0px 10px 26px rgba(10, 20, 39, 0.27);
  display: flex;
  align-items: center;
  justify-content: center;
}
.circular-inner-box {
  width: 392px;
  height: 392px;
  border-radius: 196px;
  background: #09111F;
  border: 1px solid #1B335E;
  box-shadow: 0px 8px 20px rgba(9, 17, 31, 0.27);
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
}
/* 人脸轮廓 */
.head-ellipse {
  width: 128px;
  height: 150px;
  border-radius: 50%;
  background: #F7FAFF;
  margin-top: 40px;
  opacity: 0.15;
}
.shoulder-ellipse {
  width: 300px;
  height: 176px;
  border-radius: 50%;
  background: #F7FAFF;
  position: absolute;
  bottom: -40px;
  opacity: 0.15;
}
.viewfinder-hint {
  font-family: Inter, sans-serif;
  font-size: 24px;
  font-weight: 700;
  color: #EAF2FF;
}
.bottom-subtext {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 500;
  color: #7B88A8;
  text-align: center;
  line-height: 1.3;
}
/* 底部区域 */
.bottom-area {
  width: 1008px;
  height: 176px;
  display: flex;
  gap: 20px;
  margin-top: 24px;
  z-index: 1;
}
.help-card {
  width: 388px;
  height: 100%;
  border-radius: 26px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.11) 0%, rgba(234, 242, 255, 0.08) 100%);
  border: 2px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0px 14px 30px rgba(10, 36, 107, 0.21);
  padding: 22px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 16px;
  position: relative;
  overflow: hidden;
}
.help-highlight-bar {
  position: absolute;
  top: 0;
  left: 22px;
  width: 108px;
  height: 6px;
  background: #A8CBFF;
  border-radius: 2px;
}
.help-title-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}
.help-icon-bg {
  width: 38px;
  height: 38px;
  border-radius: 19px;
  background: rgba(255, 255, 255, 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
}
.help-title {
  font-family: Inter, sans-serif;
  font-size: 21px;
  font-weight: 800;
  color: #FFFFFF;
}
.help-content {
  font-family: Inter, sans-serif;
  font-size: 16px;
  font-weight: 550;
  color: #EAF1FF;
  line-height: 1.4;
  white-space: pre-wrap;
}
.action-card {
  flex: 1;
  height: 100%;
  border-radius: 26px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.11) 0%, rgba(234, 242, 255, 0.08) 100%);
  border: 2px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0px 14px 30px rgba(10, 36, 107, 0.21);
  padding: 20px 22px 22px 22px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 12px;
  position: relative;
  overflow: hidden;
}
.action-highlight-bar {
  position: absolute;
  top: 0;
  left: 22px;
  width: 108px;
  height: 6px;
  border-radius: 2px;
}
.action-title-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}
.action-icon-bg {
  width: 38px;
  height: 38px;
  border-radius: 19px;
  background: rgba(255, 255, 255, 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
}
.action-title {
  font-family: Inter, sans-serif;
  font-size: 21px;
  font-weight: 800;
  color: #FFFFFF;
}
.action-btn {
  width: 100%;
  height: 56px;
  border-radius: 18px;
  background: linear-gradient(180deg, #FFFFFF 0%, #F4F8FF 100%);
  border: 1px solid rgba(255, 255, 255, 0.5);
  box-shadow: 0px 8px 18px rgba(7, 29, 90, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 10px;
}
.action-btn-text {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 700;
  color: #1F49D8;
}
</style>
src/pages/online-face-verify/index.vue
New file
@@ -0,0 +1,731 @@
<template>
  <view class="page-container">
    <!-- 左侧柔光氛围 -->
    <view class="bg-glow"></view>
    <!-- 扫码页头部 -->
    <view class="header-area">
      <view class="left-action" @click="goBack">
        <view class="back-btn">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <polyline points="15 18 9 12 15 6"></polyline>
          </svg>
        </view>
        <text class="page-title">预约登记</text>
      </view>
    </view>
    <!-- 步骤卡片 -->
    <view class="steps-card">
      <view class="steps-row">
        <!-- 步骤1: 预约码验证 (Completed) -->
        <view class="step-item">
          <view class="step-circle completed-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
              <rect x="7" y="7" width="3" height="3"></rect>
              <rect x="14" y="7" width="3" height="3"></rect>
              <rect x="7" y="14" width="3" height="3"></rect>
              <rect x="14" y="14" width="3" height="3"></rect>
            </svg>
          </view>
          <text class="step-text completed-text">预约码验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-active.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤2: 身份证验证 (Completed) -->
        <view class="step-item">
          <view class="step-circle completed-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M16 10h2"></path>
              <path d="M16 14h2"></path>
              <path d="M6.17 15a3 3 0 0 1 5.66 0"></path>
              <circle cx="9" cy="11" r="2"></circle>
              <rect x="2" y="5" width="20" height="14" rx="2"></rect>
            </svg>
          </view>
          <text class="step-text completed-text">身份证验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-active.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤3: 人脸验证 (Active) -->
        <view class="step-item">
          <view class="step-circle active-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#2F6BFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M3 7V5a2 2 0 0 1 2-2h2"></path>
              <path d="M17 3h2a2 2 0 0 1 2 2v2"></path>
              <path d="M21 17v2a2 2 0 0 1-2 2h-2"></path>
              <path d="M7 21H5a2 2 0 0 1-2-2v-2"></path>
              <path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
              <path d="M9 9h.01"></path>
              <path d="M15 9h.01"></path>
            </svg>
          </view>
          <text class="step-text active-text">人脸验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-inactive.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤4: 接待人验证 -->
        <view class="step-item">
          <view class="step-circle inactive-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#C7D7F6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
              <circle cx="9" cy="7" r="4"></circle>
              <polyline points="16 11 18 13 22 9"></polyline>
            </svg>
          </view>
          <text class="step-text inactive-text">接待人验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-inactive.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤5: 打印凭条 -->
        <view class="step-item">
          <view class="step-circle inactive-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#C7D7F6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <polyline points="6 9 6 2 18 2 18 9"></polyline>
              <path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path>
              <rect x="6" y="14" width="12" height="8"></rect>
            </svg>
          </view>
          <text class="step-text inactive-text">打印凭条</text>
        </view>
      </view>
    </view>
    <!-- 人脸验证主体区 -->
    <view class="main-card">
      <view class="title-group">
        <text class="main-title">请面向屏幕完成人脸验证</text>
        <text class="sub-title">请摘下口罩和帽子,保持面部位于取景框内并正视摄像头</text>
        <view class="name-pill">
          <view class="name-icon">
            <text class="name-icon-text">人</text>
          </view>
          <text class="name-text">姓名:张琪琪</text>
        </view>
      </view>
      <!-- 人脸采集卡 -->
      <view class="face-capture-card">
        <view class="status-pill">
          <view class="status-dot"></view>
          <text class="status-text">人脸比对中...</text>
        </view>
        <view class="viewfinder-area">
          <view class="viewfinder-box">
            <!-- 纯CSS绘制人脸轮廓和扫描线 -->
            <view class="head-ellipse"></view>
            <view class="shoulder-ellipse"></view>
            <view class="scan-line"></view>
          </view>
          <text class="viewfinder-hint">请将脸部置于框内,保持静止</text>
        </view>
        <view class="hints-row">
          <view class="hint-item">
            <view class="hint-dot" style="background: #6EA2FF;"></view>
            <text class="hint-text">请正视屏幕</text>
          </view>
          <view class="hint-item">
            <view class="hint-dot" style="background: #86C7FF;"></view>
            <text class="hint-text">保持静止 2 秒</text>
          </view>
          <view class="hint-item">
            <view class="hint-dot" style="background: #FFC36B;"></view>
            <text class="hint-text">避免逆光遮挡</text>
          </view>
        </view>
      </view>
      <text class="bottom-subtext">系统将自动完成人脸采集与比对,请勿频繁移动或离开识别区域</text>
    </view>
    <!-- 底部区域 -->
    <view class="bottom-area">
      <!-- 帮助提示卡 -->
      <view class="help-card">
        <view class="help-highlight-bar"></view>
        <view class="help-title-row">
          <view class="help-icon-bg">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <circle cx="12" cy="12" r="10"></circle>
              <line x1="12" y1="16" x2="12" y2="12"></line>
              <line x1="12" y1="8" x2="12.01" y2="8"></line>
            </svg>
          </view>
          <text class="help-title">识别提示</text>
        </view>
        <text class="help-content">请确保现场光线适中,面部无遮挡;
识别成功后系统将自动进入下一步接待人验证</text>
      </view>
      <!-- 操作提示卡 -->
      <view class="action-card">
        <view class="action-highlight-bar"></view>
        <view class="action-title-row">
          <view class="action-icon-bg">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <circle cx="12" cy="12" r="10"></circle>
              <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
              <line x1="12" y1="17" x2="12.01" y2="17"></line>
            </svg>
          </view>
          <text class="action-title">识别失败?</text>
        </view>
        <view class="action-btn" @click="goNext">
          <text class="action-btn-text">重新采集人脸</text>
        </view>
      </view>
    </view>
  </view>
</template>
<script setup>
const goBack = () => {
  uni.navigateBack({
    fail: () => {
      uni.redirectTo({
        url: '/pages/home/index'
      });
    }
  });
};
const goNext = () => {
  // 临时跳转到下一个页面
  uni.navigateTo({
    url: '/pages/online-face-collect/index'
  });
};
</script>
<style scoped>
.page-container {
  width: 1080px;
  height: 1920px;
  box-sizing: border-box;
  background: linear-gradient(180deg, #3B78FF 0%, #2860E8 50%, #163EBC 100%);
  padding: 30px 36px 40px 36px;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}
.bg-glow {
  position: absolute;
  width: 300px;
  height: 300px;
  border-radius: 50%;
  background: rgba(127, 177, 255, 0.1);
  left: -128px;
  top: 412px;
  filter: blur(50px);
  pointer-events: none;
}
/* 扫码页头部 */
.header-area {
  width: 1008px;
  height: 88px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 24px;
  z-index: 1;
}
.left-action {
  display: flex;
  align-items: center;
  gap: 16px;
  height: 100%;
  cursor: pointer;
}
.back-btn {
  width: 44px;
  height: 44px;
  border-radius: 22px;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.26);
  box-shadow: 0px 10px 22px rgba(15, 46, 133, 0.18);
  display: flex;
  align-items: center;
  justify-content: center;
}
.page-title {
  font-family: Inter, sans-serif;
  font-size: 30px;
  font-weight: 700;
  color: #FFFFFF;
  line-height: 1.2;
}
/* 步骤卡片 */
.steps-card {
  width: 1008px;
  display: flex;
  flex-direction: column;
  margin-bottom: 24px;
  z-index: 1;
}
.steps-row {
  width: 100%;
  height: 138px;
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: space-between;
}
.step-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 120px;
}
.step-circle {
  width: 82px;
  height: 82px;
  border-radius: 41px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.completed-circle {
  background: #78A8FF;
  border: 1px solid #DDE9FF;
  box-shadow: 0px 10px 18px rgba(14, 49, 138, 0.22);
}
.active-circle {
  background: #FFFFFF;
  border: 2px solid rgba(255, 255, 255, 0.4);
  box-shadow: 0px 12px 24px rgba(15, 45, 125, 0.27);
}
.inactive-circle {
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0px 8px 14px rgba(8, 32, 86, 0.07);
}
.step-text {
  font-family: Inter, sans-serif;
  font-size: 23px;
  text-align: center;
  white-space: nowrap;
}
.completed-text {
  font-weight: 800;
  color: rgba(255, 255, 255, 0.94);
}
.active-text {
  font-weight: 900;
  color: #FFFFFF;
}
.inactive-text {
  font-weight: 600;
  color: rgba(226, 235, 255, 0.66);
}
.step-line {
  flex: 1;
  height: 82px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.arrow-img {
  width: 92px;
  height: 82px;
}
/* 主体区 */
.main-card {
  width: 1008px;
  height: 1200px;
  border-radius: 34px;
  background: linear-gradient(180deg, #FFFFFF 0%, #F7FAFF 100%);
  border: 1px solid #DEE9FF;
  box-shadow: 0px 22px 52px rgba(13, 46, 134, 0.25);
  padding: 32px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  z-index: 1;
}
.title-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.main-title {
  font-family: Inter, sans-serif;
  font-size: 40px;
  font-weight: 700;
  color: #1F2A44;
  line-height: 1.2;
}
.sub-title {
  font-family: Inter, sans-serif;
  font-size: 22px;
  font-weight: 500;
  color: #4A5672;
  line-height: 1.3;
  margin-top: 4px;
}
.name-pill {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}
.name-icon {
  width: 18px;
  height: 18px;
  border-radius: 9px;
  background: #F3F6FC;
  display: flex;
  align-items: center;
  justify-content: center;
}
.name-icon-text {
  font-family: Inter, sans-serif;
  font-size: 10px;
  font-weight: 700;
  color: #7B88A8;
}
.name-text {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 500;
  color: #7B88A8;
}
/* 人脸采集卡 */
.face-capture-card {
  width: 880px;
  height: 876px;
  border-radius: 36px;
  background: linear-gradient(180deg, #101C3D 0%, #0A132B 100%);
  border: 1px solid #243B72;
  box-shadow: 0px 14px 34px rgba(8, 17, 38, 0.23);
  padding: 30px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 26px;
}
.status-pill {
  width: 236px;
  height: 52px;
  border-radius: 26px;
  background: #132852;
  border: 1px solid #2D4F8D;
  box-shadow: 0px 6px 16px rgba(11, 23, 48, 0.2);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
.status-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #55D6FF;
  box-shadow: 0px 0px 10px rgba(85, 214, 255, 0.4);
}
.status-text {
  font-family: Inter, sans-serif;
  font-size: 20px;
  font-weight: 700;
  color: #F4F8FF;
}
.viewfinder-area {
  width: 760px;
  height: 652px;
  border-radius: 32px;
  background: linear-gradient(180deg, #07111F 0%, #050A15 100%);
  border: 1px solid #223B71;
  box-shadow: 0px 10px 24px rgba(5, 10, 21, 0.2);
  padding: 24px 24px 28px 24px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 28px;
}
.viewfinder-box {
  width: 536px;
  height: 528px;
  border-radius: 30px;
  background: #0A1427;
  border: 2px solid #3B66B8;
  box-shadow: 0px 10px 24px rgba(10, 20, 39, 0.27);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
  overflow: hidden;
}
/* 人脸轮廓 */
.head-ellipse {
  width: 128px;
  height: 150px;
  border-radius: 50%;
  background: #F7FAFF;
  position: absolute;
  top: 100px;
  opacity: 0.15; /* 降低不透明度模拟轮廓 */
}
.shoulder-ellipse {
  width: 300px;
  height: 176px;
  border-radius: 50%;
  background: #F7FAFF;
  position: absolute;
  bottom: 0px;
  opacity: 0.15;
}
/* 扫描线动画 */
.scan-line {
  width: 372px;
  height: 5px;
  border-radius: 3px;
  background: #55D6FF;
  box-shadow: 0px 0px 12px rgba(85, 214, 255, 0.33);
  position: absolute;
  top: 50px;
  animation: scan 2s infinite alternate ease-in-out;
}
@keyframes scan {
  0% { top: 50px; }
  100% { top: 450px; }
}
.viewfinder-hint {
  font-family: Inter, sans-serif;
  font-size: 17px;
  font-weight: 600;
  color: #D8E7FF;
}
.hints-row {
  width: 100%;
  height: 34px;
  display: flex;
  justify-content: center;
  gap: 18px;
}
.hint-item {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.hint-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
}
.hint-text {
  font-family: Inter, sans-serif;
  font-size: 16px;
  font-weight: 700;
  color: #DCE6FF;
}
.bottom-subtext {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 500;
  color: #7B88A8;
  text-align: center;
  line-height: 1.3;
}
/* 底部区域 */
.bottom-area {
  width: 1008px;
  height: 176px;
  display: flex;
  gap: 20px;
  margin-top: 24px;
  z-index: 1;
}
.help-card {
  width: 388px;
  height: 100%;
  border-radius: 26px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.11) 0%, rgba(234, 242, 255, 0.08) 100%);
  border: 2px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0px 14px 30px rgba(10, 36, 107, 0.21);
  padding: 22px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 16px;
  position: relative;
  overflow: hidden;
}
.help-highlight-bar {
  position: absolute;
  top: 0;
  left: 22px;
  width: 108px;
  height: 6px;
  background: #A8CBFF;
  border-radius: 2px;
}
.help-title-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}
.help-icon-bg {
  width: 38px;
  height: 38px;
  border-radius: 19px;
  background: rgba(255, 255, 255, 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
}
.help-title {
  font-family: Inter, sans-serif;
  font-size: 21px;
  font-weight: 800;
  color: #FFFFFF;
}
.help-content {
  font-family: Inter, sans-serif;
  font-size: 16px;
  font-weight: 550;
  color: #EAF1FF;
  line-height: 1.4;
  white-space: pre-wrap;
}
.action-card {
  flex: 1;
  height: 100%;
  border-radius: 26px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.11) 0%, rgba(234, 242, 255, 0.08) 100%);
  border: 2px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0px 14px 30px rgba(10, 36, 107, 0.21);
  padding: 20px 22px 22px 22px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 12px;
  position: relative;
  overflow: hidden;
}
.action-highlight-bar {
  position: absolute;
  top: 0;
  left: 22px;
  width: 108px;
  height: 6px;
  background: #6EA2FF;
  border-radius: 2px;
}
.action-title-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}
.action-icon-bg {
  width: 38px;
  height: 38px;
  border-radius: 19px;
  background: rgba(255, 255, 255, 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
}
.action-title {
  font-family: Inter, sans-serif;
  font-size: 21px;
  font-weight: 800;
  color: #FFFFFF;
}
.action-btn {
  width: 100%;
  height: 56px;
  border-radius: 18px;
  background: linear-gradient(180deg, #FFFFFF 0%, #F4F8FF 100%);
  border: 1px solid rgba(255, 255, 255, 0.5);
  box-shadow: 0px 8px 18px rgba(7, 29, 90, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 10px;
}
.action-btn-text {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 700;
  color: #1F49D8;
}
</style>
src/pages/online-host-verify/index.vue
New file
@@ -0,0 +1,729 @@
<template>
  <view class="page-container">
    <!-- 左侧柔光氛围 -->
    <view class="bg-glow"></view>
    <!-- 扫码页头部 -->
    <view class="header-area">
      <view class="left-action" @click="goBack">
        <view class="back-btn">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <polyline points="15 18 9 12 15 6"></polyline>
          </svg>
        </view>
        <text class="page-title">预约登记</text>
      </view>
    </view>
    <!-- 步骤卡片 -->
    <view class="steps-card">
      <view class="steps-row">
        <!-- 步骤1: 预约码验证 (Completed) -->
        <view class="step-item">
          <view class="step-circle completed-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
              <rect x="7" y="7" width="3" height="3"></rect>
              <rect x="14" y="7" width="3" height="3"></rect>
              <rect x="7" y="14" width="3" height="3"></rect>
              <rect x="14" y="14" width="3" height="3"></rect>
            </svg>
          </view>
          <text class="step-text completed-text">预约码验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-active.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤2: 身份证验证 (Completed) -->
        <view class="step-item">
          <view class="step-circle completed-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M16 10h2"></path>
              <path d="M16 14h2"></path>
              <path d="M6.17 15a3 3 0 0 1 5.66 0"></path>
              <circle cx="9" cy="11" r="2"></circle>
              <rect x="2" y="5" width="20" height="14" rx="2"></rect>
            </svg>
          </view>
          <text class="step-text completed-text">身份证验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-active.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤3: 人脸验证 (Completed) -->
        <view class="step-item">
          <view class="step-circle completed-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M3 7V5a2 2 0 0 1 2-2h2"></path>
              <path d="M17 3h2a2 2 0 0 1 2 2v2"></path>
              <path d="M21 17v2a2 2 0 0 1-2 2h-2"></path>
              <path d="M7 21H5a2 2 0 0 1-2-2v-2"></path>
              <path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
              <path d="M9 9h.01"></path>
              <path d="M15 9h.01"></path>
            </svg>
          </view>
          <text class="step-text completed-text">人脸验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-active.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤4: 接待人验证 (Active) -->
        <view class="step-item">
          <view class="step-circle active-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#2F6BFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
              <circle cx="9" cy="7" r="4"></circle>
              <polyline points="16 11 18 13 22 9"></polyline>
            </svg>
          </view>
          <text class="step-text active-text">接待人验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-inactive.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤5: 打印凭条 -->
        <view class="step-item">
          <view class="step-circle inactive-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#C7D7F6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <polyline points="6 9 6 2 18 2 18 9"></polyline>
              <path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path>
              <rect x="6" y="14" width="12" height="8"></rect>
            </svg>
          </view>
          <text class="step-text inactive-text">打印凭条</text>
        </view>
      </view>
    </view>
    <!-- 接待人验证主体区 -->
    <view class="main-card">
      <view class="title-group">
        <text class="main-title">请接待人面向屏幕</text>
        <text class="sub-title">请接待人站在设备前,正视屏幕完成身份确认</text>
        <view class="name-pill">
          <view class="name-icon">
            <text class="name-icon-text">人</text>
          </view>
          <text class="name-text">姓名:张琪琪</text>
        </view>
      </view>
      <!-- 人脸采集卡 -->
      <view class="face-capture-card">
        <view class="status-pill">
          <view class="status-dot"></view>
          <text class="status-text">接待人核验中...</text>
        </view>
        <view class="viewfinder-area">
          <view class="viewfinder-box">
            <!-- 纯CSS绘制人脸轮廓和扫描线 -->
            <view class="head-ellipse"></view>
            <view class="shoulder-ellipse"></view>
            <view class="scan-line"></view>
          </view>
          <text class="viewfinder-hint">请将脸部置于框内,保持静止</text>
        </view>
        <view class="hints-row">
          <view class="hint-item">
            <view class="hint-dot" style="background: #6EA2FF;"></view>
            <text class="hint-text">请正视屏幕</text>
          </view>
          <view class="hint-item">
            <view class="hint-dot" style="background: #86C7FF;"></view>
            <text class="hint-text">保持静止 2 秒</text>
          </view>
          <view class="hint-item">
            <view class="hint-dot" style="background: #FFC36B;"></view>
            <text class="hint-text">避免逆光遮挡</text>
          </view>
        </view>
      </view>
      <text class="bottom-subtext">系统将自动完成人脸采集与比对,请勿频繁移动或离开识别区域</text>
    </view>
    <!-- 底部区域 -->
    <view class="bottom-area">
      <!-- 帮助提示卡 -->
      <view class="help-card">
        <view class="help-highlight-bar"></view>
        <view class="help-title-row">
          <view class="help-icon-bg">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <circle cx="12" cy="12" r="10"></circle>
              <line x1="12" y1="16" x2="12" y2="12"></line>
              <line x1="12" y1="8" x2="12.01" y2="8"></line>
            </svg>
          </view>
          <text class="help-title">验证提示</text>
        </view>
        <text class="help-content">请由接待人本人操作;
接待人可以直接使用门禁卡刷卡验证</text>
      </view>
      <!-- 操作提示卡 -->
      <view class="action-card">
        <view class="action-highlight-bar" style="background: #6EA2FF;"></view>
        <view class="action-title-row">
          <view class="action-icon-bg">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <circle cx="12" cy="12" r="10"></circle>
              <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
              <line x1="12" y1="17" x2="12.01" y2="17"></line>
            </svg>
          </view>
          <text class="action-title">验证失败?</text>
        </view>
        <view class="action-btn" @click="goNext">
          <text class="action-btn-text">重新验证接待人</text>
        </view>
      </view>
    </view>
  </view>
</template>
<script setup>
const goBack = () => {
  uni.navigateBack({
    fail: () => {
      uni.redirectTo({
        url: '/pages/home/index'
      });
    }
  });
};
const goNext = () => {
  uni.navigateTo({
    url: '/pages/online-print-ticket/index'
  });
};
</script>
<style scoped>
.page-container {
  width: 1080px;
  height: 1920px;
  box-sizing: border-box;
  background: linear-gradient(180deg, #3B78FF 0%, #2860E8 50%, #163EBC 100%);
  padding: 30px 36px 40px 36px;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}
.bg-glow {
  position: absolute;
  width: 300px;
  height: 300px;
  border-radius: 50%;
  background: rgba(127, 177, 255, 0.1);
  left: -128px;
  top: 412px;
  filter: blur(50px);
  pointer-events: none;
}
/* 扫码页头部 */
.header-area {
  width: 1008px;
  height: 88px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 24px;
  z-index: 1;
}
.left-action {
  display: flex;
  align-items: center;
  gap: 16px;
  height: 100%;
  cursor: pointer;
}
.back-btn {
  width: 44px;
  height: 44px;
  border-radius: 22px;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.26);
  box-shadow: 0px 10px 22px rgba(15, 46, 133, 0.18);
  display: flex;
  align-items: center;
  justify-content: center;
}
.page-title {
  font-family: Inter, sans-serif;
  font-size: 30px;
  font-weight: 700;
  color: #FFFFFF;
  line-height: 1.2;
}
/* 步骤卡片 */
.steps-card {
  width: 1008px;
  display: flex;
  flex-direction: column;
  margin-bottom: 24px;
  z-index: 1;
}
.steps-row {
  width: 100%;
  height: 138px;
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: space-between;
}
.step-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 120px;
}
.step-circle {
  width: 82px;
  height: 82px;
  border-radius: 41px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.completed-circle {
  background: #78A8FF;
  border: 1px solid #DDE9FF;
  box-shadow: 0px 10px 18px rgba(14, 49, 138, 0.22);
}
.active-circle {
  background: #FFFFFF;
  border: 2px solid rgba(255, 255, 255, 0.4);
  box-shadow: 0px 12px 24px rgba(15, 45, 125, 0.27);
}
.inactive-circle {
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0px 8px 14px rgba(8, 32, 86, 0.07);
}
.step-text {
  font-family: Inter, sans-serif;
  font-size: 23px;
  text-align: center;
  white-space: nowrap;
}
.completed-text {
  font-weight: 800;
  color: rgba(255, 255, 255, 0.94);
}
.active-text {
  font-weight: 900;
  color: #FFFFFF;
}
.inactive-text {
  font-weight: 600;
  color: rgba(226, 235, 255, 0.66);
}
.step-line {
  flex: 1;
  height: 82px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.arrow-img {
  width: 92px;
  height: 82px;
}
/* 主体区 */
.main-card {
  width: 1008px;
  height: 1200px;
  border-radius: 34px;
  background: linear-gradient(180deg, #FFFFFF 0%, #F7FAFF 100%);
  border: 1px solid #DEE9FF;
  box-shadow: 0px 22px 52px rgba(13, 46, 134, 0.25);
  padding: 32px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  z-index: 1;
}
.title-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.main-title {
  font-family: Inter, sans-serif;
  font-size: 40px;
  font-weight: 700;
  color: #1F2A44;
  line-height: 1.2;
}
.sub-title {
  font-family: Inter, sans-serif;
  font-size: 22px;
  font-weight: 500;
  color: #4A5672;
  line-height: 1.3;
  margin-top: 4px;
}
.name-pill {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}
.name-icon {
  width: 18px;
  height: 18px;
  border-radius: 9px;
  background: #F3F6FC;
  display: flex;
  align-items: center;
  justify-content: center;
}
.name-icon-text {
  font-family: Inter, sans-serif;
  font-size: 10px;
  font-weight: 700;
  color: #7B88A8;
}
.name-text {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 500;
  color: #7B88A8;
}
/* 人脸采集卡 */
.face-capture-card {
  width: 880px;
  height: 876px;
  border-radius: 36px;
  background: linear-gradient(180deg, #101C3D 0%, #0A132B 100%);
  border: 1px solid #243B72;
  box-shadow: 0px 14px 34px rgba(8, 17, 38, 0.23);
  padding: 30px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 26px;
}
.status-pill {
  width: 236px;
  height: 52px;
  border-radius: 26px;
  background: #132852;
  border: 1px solid #2D4F8D;
  box-shadow: 0px 6px 16px rgba(11, 23, 48, 0.2);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
.status-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #55D6FF;
  box-shadow: 0px 0px 10px rgba(85, 214, 255, 0.4);
}
.status-text {
  font-family: Inter, sans-serif;
  font-size: 20px;
  font-weight: 700;
  color: #F4F8FF;
}
.viewfinder-area {
  width: 760px;
  height: 652px;
  border-radius: 32px;
  background: linear-gradient(180deg, #07111F 0%, #050A15 100%);
  border: 1px solid #223B71;
  box-shadow: 0px 10px 24px rgba(5, 10, 21, 0.2);
  padding: 24px 24px 28px 24px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 28px;
}
.viewfinder-box {
  width: 536px;
  height: 528px;
  border-radius: 30px;
  background: #0A1427;
  border: 2px solid #3B66B8;
  box-shadow: 0px 10px 24px rgba(10, 20, 39, 0.27);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
  overflow: hidden;
}
/* 人脸轮廓 */
.head-ellipse {
  width: 128px;
  height: 150px;
  border-radius: 50%;
  background: #F7FAFF;
  position: absolute;
  top: 100px;
  opacity: 0.15;
}
.shoulder-ellipse {
  width: 300px;
  height: 176px;
  border-radius: 50%;
  background: #F7FAFF;
  position: absolute;
  bottom: 0px;
  opacity: 0.15;
}
/* 扫描线动画 */
.scan-line {
  width: 372px;
  height: 5px;
  border-radius: 3px;
  background: #55D6FF;
  box-shadow: 0px 0px 12px rgba(85, 214, 255, 0.33);
  position: absolute;
  top: 50px;
  animation: scan 2s infinite alternate ease-in-out;
}
@keyframes scan {
  0% { top: 50px; }
  100% { top: 450px; }
}
.viewfinder-hint {
  font-family: Inter, sans-serif;
  font-size: 17px;
  font-weight: 600;
  color: #D8E7FF;
}
.hints-row {
  width: 100%;
  height: 34px;
  display: flex;
  justify-content: center;
  gap: 18px;
}
.hint-item {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.hint-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
}
.hint-text {
  font-family: Inter, sans-serif;
  font-size: 16px;
  font-weight: 700;
  color: #DCE6FF;
}
.bottom-subtext {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 500;
  color: #7B88A8;
  text-align: center;
  line-height: 1.3;
}
/* 底部区域 */
.bottom-area {
  width: 1008px;
  height: 176px;
  display: flex;
  gap: 20px;
  margin-top: 24px;
  z-index: 1;
}
.help-card {
  width: 388px;
  height: 100%;
  border-radius: 26px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.11) 0%, rgba(234, 242, 255, 0.08) 100%);
  border: 2px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0px 14px 30px rgba(10, 36, 107, 0.21);
  padding: 22px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 16px;
  position: relative;
  overflow: hidden;
}
.help-highlight-bar {
  position: absolute;
  top: 0;
  left: 22px;
  width: 108px;
  height: 6px;
  background: #A8CBFF;
  border-radius: 2px;
}
.help-title-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}
.help-icon-bg {
  width: 38px;
  height: 38px;
  border-radius: 19px;
  background: rgba(255, 255, 255, 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
}
.help-title {
  font-family: Inter, sans-serif;
  font-size: 21px;
  font-weight: 800;
  color: #FFFFFF;
}
.help-content {
  font-family: Inter, sans-serif;
  font-size: 16px;
  font-weight: 550;
  color: #EAF1FF;
  line-height: 1.4;
  white-space: pre-wrap;
}
.action-card {
  flex: 1;
  height: 100%;
  border-radius: 26px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.11) 0%, rgba(234, 242, 255, 0.08) 100%);
  border: 2px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0px 14px 30px rgba(10, 36, 107, 0.21);
  padding: 20px 22px 22px 22px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 12px;
  position: relative;
  overflow: hidden;
}
.action-highlight-bar {
  position: absolute;
  top: 0;
  left: 22px;
  width: 108px;
  height: 6px;
  border-radius: 2px;
}
.action-title-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}
.action-icon-bg {
  width: 38px;
  height: 38px;
  border-radius: 19px;
  background: rgba(255, 255, 255, 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
}
.action-title {
  font-family: Inter, sans-serif;
  font-size: 21px;
  font-weight: 800;
  color: #FFFFFF;
}
.action-btn {
  width: 100%;
  height: 56px;
  border-radius: 18px;
  background: linear-gradient(180deg, #FFFFFF 0%, #F4F8FF 100%);
  border: 1px solid rgba(255, 255, 255, 0.5);
  box-shadow: 0px 8px 18px rgba(7, 29, 90, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 10px;
}
.action-btn-text {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 700;
  color: #1F49D8;
}
</style>
src/pages/online-id-scan/index.vue
New file
@@ -0,0 +1,534 @@
<template>
  <view class="page-container">
    <!-- 左侧柔光氛围 -->
    <view class="bg-glow"></view>
    <!-- 扫码页头部 -->
    <view class="header-area">
      <view class="left-action" @click="goBack">
        <view class="back-btn">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <polyline points="15 18 9 12 15 6"></polyline>
          </svg>
        </view>
        <text class="page-title">预约登记</text>
      </view>
    </view>
    <!-- 步骤卡片 -->
    <view class="steps-card">
      <view class="steps-row">
        <!-- 步骤1: 预约码验证 (Completed) -->
        <view class="step-item">
          <view class="step-circle completed-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
              <rect x="7" y="7" width="3" height="3"></rect>
              <rect x="14" y="7" width="3" height="3"></rect>
              <rect x="7" y="14" width="3" height="3"></rect>
              <rect x="14" y="14" width="3" height="3"></rect>
            </svg>
          </view>
          <text class="step-text completed-text">预约码验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-active.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤2: 身份证验证 (Active) -->
        <view class="step-item">
          <view class="step-circle active-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#2F6BFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M16 10h2"></path>
              <path d="M16 14h2"></path>
              <path d="M6.17 15a3 3 0 0 1 5.66 0"></path>
              <circle cx="9" cy="11" r="2"></circle>
              <rect x="2" y="5" width="20" height="14" rx="2"></rect>
            </svg>
          </view>
          <text class="step-text active-text">身份证验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-inactive.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤3: 人脸验证 -->
        <view class="step-item">
          <view class="step-circle inactive-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#C7D7F6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M3 7V5a2 2 0 0 1 2-2h2"></path>
              <path d="M17 3h2a2 2 0 0 1 2 2v2"></path>
              <path d="M21 17v2a2 2 0 0 1-2 2h-2"></path>
              <path d="M7 21H5a2 2 0 0 1-2-2v-2"></path>
              <path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
              <path d="M9 9h.01"></path>
              <path d="M15 9h.01"></path>
            </svg>
          </view>
          <text class="step-text inactive-text">人脸验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-inactive.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤4: 接待人验证 -->
        <view class="step-item">
          <view class="step-circle inactive-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#C7D7F6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
              <circle cx="9" cy="7" r="4"></circle>
              <polyline points="16 11 18 13 22 9"></polyline>
            </svg>
          </view>
          <text class="step-text inactive-text">接待人验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-inactive.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤5: 打印凭条 -->
        <view class="step-item">
          <view class="step-circle inactive-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#C7D7F6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <polyline points="6 9 6 2 18 2 18 9"></polyline>
              <path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path>
              <rect x="6" y="14" width="12" height="8"></rect>
            </svg>
          </view>
          <text class="step-text inactive-text">打印凭条</text>
        </view>
      </view>
    </view>
    <!-- 扫码主体区 -->
    <view class="main-card">
      <text class="main-title">请将您的身份证放置读卡器位置</text>
      <text class="main-desc">请将身份证贴近设备感应区,系统将自动读取身份信息</text>
      <!-- 姓名提示行 -->
      <view class="name-hint-row">
        <view class="name-icon-box">
          <text class="name-icon-text">名</text>
        </view>
        <text class="name-text">姓名:张琪琦</text>
      </view>
      <!-- 设备展示区 -->
      <view class="device-area">
        <image src="../../static/device-id-scan.png" class="device-img" mode="aspectFit"></image>
      </view>
      <text class="helper-text">若识别失败,请联系工作人员协助</text>
    </view>
    <!-- 扫码页底部 -->
    <view class="bottom-area">
      <!-- 帮助提示卡 -->
      <view class="help-card">
        <view class="highlight-bar"></view>
        <view class="card-title-row">
          <view class="icon-base">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <circle cx="12" cy="12" r="10"></circle>
              <line x1="12" y1="16" x2="12" y2="12"></line>
              <line x1="12" y1="8" x2="12.01" y2="8"></line>
            </svg>
          </view>
          <text class="card-title">读卡小贴士</text>
        </view>
        <text class="help-content">请使用二代身份证原件放至读卡区位置</text>
      </view>
      <!-- 操作提示卡 -->
      <view class="action-card">
        <view class="highlight-bar"></view>
        <view class="card-title-row">
          <view class="icon-base">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <rect x="2" y="4" width="20" height="16" rx="2" ry="2"></rect>
              <line x1="6" y1="8" x2="6.01" y2="8"></line>
              <line x1="10" y1="8" x2="10.01" y2="8"></line>
              <line x1="14" y1="8" x2="14.01" y2="8"></line>
              <line x1="18" y1="8" x2="18.01" y2="8"></line>
              <line x1="6" y1="12" x2="6.01" y2="12"></line>
              <line x1="10" y1="12" x2="10.01" y2="12"></line>
              <line x1="14" y1="12" x2="14.01" y2="12"></line>
              <line x1="18" y1="12" x2="18.01" y2="12"></line>
              <line x1="8" y1="16" x2="16" y2="16"></line>
            </svg>
          </view>
          <text class="card-title">未携带身份证?</text>
        </view>
        <view class="main-btn" @click="goToManualInput">
          <text class="btn-text">手动录入信息</text>
        </view>
      </view>
    </view>
  </view>
</template>
<script setup>
const goBack = () => {
  uni.navigateBack({
    fail: () => {
      uni.redirectTo({
        url: '/pages/home/index'
      });
    }
  });
};
const goToManualInput = () => {
  uni.showToast({
    title: '手动录入功能开发中',
    icon: 'none'
  });
};
</script>
<style scoped>
/* 使用 px 单位,严格对应 1080x1920 设计稿 */
.page-container {
  width: 1080px;
  height: 1920px;
  box-sizing: border-box;
  background: linear-gradient(180deg, #3B78FF 0%, #2860E8 50%, #163EBC 100%);
  padding: 30px 36px 40px 36px;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}
.bg-glow {
  position: absolute;
  width: 300px;
  height: 300px;
  border-radius: 50%;
  background: rgba(127, 177, 255, 0.1);
  left: -128px;
  top: 412px;
  filter: blur(50px);
  pointer-events: none;
}
/* 扫码页头部 */
.header-area {
  width: 1008px; /* 1080 - 36*2 */
  height: 88px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 24px;
  z-index: 1;
}
.left-action {
  display: flex;
  align-items: center;
  gap: 16px;
  height: 100%;
  cursor: pointer;
}
.back-btn {
  width: 44px;
  height: 44px;
  border-radius: 22px;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.26);
  box-shadow: 0px 10px 22px rgba(15, 46, 133, 0.18);
  display: flex;
  align-items: center;
  justify-content: center;
}
.page-title {
  font-family: Inter, sans-serif;
  font-size: 30px;
  font-weight: 700;
  color: #FFFFFF;
  line-height: 1.2;
}
/* 步骤卡片 */
.steps-card {
  width: 1008px;
  display: flex;
  flex-direction: column;
  margin-bottom: 24px;
  z-index: 1;
}
.steps-row {
  width: 100%;
  height: 138px;
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: space-between;
}
.step-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 120px;
}
.step-circle {
  width: 82px;
  height: 82px;
  border-radius: 41px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.completed-circle {
  background: #78A8FF;
  border: 1px solid #DDE9FF;
  box-shadow: 0px 10px 18px rgba(14, 49, 138, 0.22);
}
.active-circle {
  background: #FFFFFF;
  border: 2px solid rgba(255, 255, 255, 0.4);
  box-shadow: 0px 12px 24px rgba(15, 45, 125, 0.27);
}
.inactive-circle {
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0px 8px 14px rgba(8, 32, 86, 0.07);
}
.step-text {
  font-family: Inter, sans-serif;
  font-size: 23px;
  text-align: center;
  white-space: nowrap;
}
.completed-text {
  font-weight: 800;
  color: rgba(255, 255, 255, 0.94);
}
.active-text {
  font-weight: 900;
  color: #FFFFFF;
}
.inactive-text {
  font-weight: 600;
  color: rgba(226, 235, 255, 0.66);
}
.step-line {
  flex: 1;
  height: 82px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.arrow-img {
  width: 92px;
  height: 82px;
}
/* 扫码主体区 */
.main-card {
  width: 1008px;
  height: 1200px;
  border-radius: 34px;
  background: linear-gradient(180deg, #FFFFFF 0%, #F7FAFF 100%);
  border: 1px solid #DEE9FF;
  box-shadow: 0px 22px 52px rgba(13, 46, 134, 0.25);
  padding: 32px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  margin-bottom: 24px;
  z-index: 1;
}
.main-title {
  font-family: Inter, sans-serif;
  font-size: 42px;
  font-weight: 700;
  color: #1F2A44;
  text-align: center;
  line-height: 1.2;
}
.main-desc {
  font-family: Inter, sans-serif;
  font-size: 22px;
  font-weight: 500;
  color: #4A5672;
  text-align: center;
  line-height: 1.3;
}
/* 姓名提示行 */
.name-hint-row {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 26px;
}
.name-icon-box {
  width: 18px;
  height: 18px;
  border-radius: 9px;
  background: #F3F6FC;
  display: flex;
  align-items: center;
  justify-content: center;
}
.name-icon-text {
  font-family: Inter, sans-serif;
  font-size: 10px;
  font-weight: 700;
  color: #7B88A8;
  line-height: 1.2;
}
.name-text {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 500;
  color: #7B88A8;
  line-height: 1.2;
}
.device-area {
  width: 100%;
  height: 820px;
  display: flex;
  justify-content: center;
  align-items: center;
}
.device-img {
  width: 100%;
  height: 100%;
}
.helper-text {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 500;
  color: #7B88A8;
  text-align: center;
}
/* 扫码页底部 */
.bottom-area {
  width: 1008px;
  height: 176px;
  display: flex;
  flex-direction: row;
  gap: 18px;
  z-index: 1;
}
.help-card {
  width: 360px;
  height: 176px;
  border-radius: 26px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.11) 0%, rgba(234, 242, 255, 0.08) 100%);
  border: 2px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0px 14px 30px rgba(10, 36, 107, 0.21);
  padding: 22px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 16px;
  position: relative;
}
.action-card {
  flex: 1;
  height: 176px;
  border-radius: 26px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.11) 0%, rgba(234, 242, 255, 0.08) 100%);
  border: 2px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0px 14px 30px rgba(10, 36, 107, 0.21);
  padding: 20px 22px 22px 22px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 12px;
  position: relative;
}
.highlight-bar {
  width: 108px;
  height: 6px;
  border-radius: 2px;
  background: #A8CBFF;
}
.card-title-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.icon-base {
  width: 38px;
  height: 38px;
  border-radius: 19px;
  background: rgba(255, 255, 255, 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
}
.card-title {
  font-family: Inter, sans-serif;
  font-size: 21px;
  font-weight: 800;
  color: #FFFFFF;
  line-height: 1.2;
}
.help-content {
  font-family: Inter, sans-serif;
  font-size: 16px;
  font-weight: 500;
  color: #EAF1FF;
  line-height: 1.4;
  white-space: pre-line;
}
.main-btn {
  width: 100%;
  height: 56px;
  border-radius: 18px;
  background: linear-gradient(180deg, #FFFFFF 0%, #F4F8FF 100%);
  border: 1px solid rgba(255, 255, 255, 0.5);
  box-shadow: 0px 8px 18px rgba(7, 29, 90, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.btn-text {
  font-family: Inter, sans-serif;
  font-size: 19px;
  font-weight: 800;
  color: #1F49D8;
  line-height: 1.2;
}
</style>
src/pages/online-print-ticket/index.vue
New file
@@ -0,0 +1,509 @@
<template>
  <view class="page-container">
    <!-- 左侧柔光氛围 -->
    <view class="bg-glow"></view>
    <!-- 扫码页头部 -->
    <view class="header-area">
      <view class="left-action" @click="goBack">
        <view class="back-btn">
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <polyline points="15 18 9 12 15 6"></polyline>
          </svg>
        </view>
        <text class="page-title">预约登记</text>
      </view>
    </view>
    <!-- 步骤卡片 -->
    <view class="steps-card">
      <view class="steps-row">
        <!-- 步骤1: 预约码验证 (Completed) -->
        <view class="step-item">
          <view class="step-circle completed-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
              <rect x="7" y="7" width="3" height="3"></rect>
              <rect x="14" y="7" width="3" height="3"></rect>
              <rect x="7" y="14" width="3" height="3"></rect>
              <rect x="14" y="14" width="3" height="3"></rect>
            </svg>
          </view>
          <text class="step-text completed-text">预约码验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-active.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤2: 身份证验证 (Completed) -->
        <view class="step-item">
          <view class="step-circle completed-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M16 10h2"></path>
              <path d="M16 14h2"></path>
              <path d="M6.17 15a3 3 0 0 1 5.66 0"></path>
              <circle cx="9" cy="11" r="2"></circle>
              <rect x="2" y="5" width="20" height="14" rx="2"></rect>
            </svg>
          </view>
          <text class="step-text completed-text">身份证验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-active.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤3: 人脸验证 (Completed) -->
        <view class="step-item">
          <view class="step-circle completed-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M3 7V5a2 2 0 0 1 2-2h2"></path>
              <path d="M17 3h2a2 2 0 0 1 2 2v2"></path>
              <path d="M21 17v2a2 2 0 0 1-2 2h-2"></path>
              <path d="M7 21H5a2 2 0 0 1-2-2v-2"></path>
              <path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
              <path d="M9 9h.01"></path>
              <path d="M15 9h.01"></path>
            </svg>
          </view>
          <text class="step-text completed-text">人脸验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-active.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤4: 接待人验证 (Completed) -->
        <view class="step-item">
          <view class="step-circle completed-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
              <circle cx="9" cy="7" r="4"></circle>
              <polyline points="16 11 18 13 22 9"></polyline>
            </svg>
          </view>
          <text class="step-text completed-text">接待人验证</text>
        </view>
        <view class="step-line">
          <image src="../../static/step-arrow-active.png" class="arrow-img" mode="aspectFit"></image>
        </view>
        <!-- 步骤5: 打印凭条 (Active) -->
        <view class="step-item">
          <view class="step-circle active-circle">
            <svg width="38" height="38" viewBox="0 0 24 24" fill="none" stroke="#2F6BFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <polyline points="6 9 6 2 18 2 18 9"></polyline>
              <path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path>
              <rect x="6" y="14" width="12" height="8"></rect>
            </svg>
          </view>
          <text class="step-text active-text">打印凭条</text>
        </view>
      </view>
    </view>
    <!-- 打印主体区 -->
    <view class="main-card">
      <text class="main-title">请等待打印凭条</text>
      <!-- 打印展示卡 (导出的切图) -->
      <view class="print-demo-card">
        <image src="../../static/print-ticket-demo.png" class="print-demo-img" mode="aspectFit"></image>
      </view>
    </view>
    <!-- 底部区域 -->
    <view class="bottom-area">
      <!-- 帮助提示卡 -->
      <view class="help-card">
        <view class="help-highlight-bar"></view>
        <view class="help-title-row">
          <view class="help-icon-bg">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <circle cx="12" cy="12" r="10"></circle>
              <line x1="12" y1="16" x2="12" y2="12"></line>
              <line x1="12" y1="8" x2="12.01" y2="8"></line>
            </svg>
          </view>
          <text class="help-title">取条提示</text>
        </view>
        <text class="help-content">请在凭条完全打印后再取走;
如长时间未出条,请联系现场工作人员协助</text>
      </view>
      <!-- 操作提示卡 -->
      <view class="action-card">
        <view class="action-highlight-bar" style="background: #6EA2FF;"></view>
        <view class="action-title-row">
          <view class="action-icon-bg">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <circle cx="12" cy="12" r="10"></circle>
              <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
              <line x1="12" y1="17" x2="12.01" y2="17"></line>
            </svg>
          </view>
          <text class="action-title">未打印成功?</text>
        </view>
        <view class="action-btn" @click="goNext">
          <text class="action-btn-text">重新打印凭条</text>
        </view>
      </view>
    </view>
  </view>
</template>
<script setup>
import { onMounted } from 'vue';
onMounted(() => {
  // 模拟打印完成自动跳转
  setTimeout(() => {
    goNext();
  }, 3000);
});
const goBack = () => {
  uni.navigateBack({
    fail: () => {
      uni.redirectTo({
        url: '/pages/home/index'
      });
    }
  });
};
const goNext = () => {
  uni.navigateTo({
    url: '/pages/online-complete/index'
  });
};
</script>
<style scoped>
.page-container {
  width: 1080px;
  height: 1920px;
  box-sizing: border-box;
  background: linear-gradient(180deg, #3B78FF 0%, #2860E8 50%, #163EBC 100%);
  padding: 30px 36px 40px 36px;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}
.bg-glow {
  position: absolute;
  width: 300px;
  height: 300px;
  border-radius: 50%;
  background: rgba(127, 177, 255, 0.1);
  left: -128px;
  top: 412px;
  filter: blur(50px);
  pointer-events: none;
}
/* 扫码页头部 */
.header-area {
  width: 1008px;
  height: 88px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 24px;
  z-index: 1;
}
.left-action {
  display: flex;
  align-items: center;
  gap: 16px;
  height: 100%;
  cursor: pointer;
}
.back-btn {
  width: 44px;
  height: 44px;
  border-radius: 22px;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.26);
  box-shadow: 0px 10px 22px rgba(15, 46, 133, 0.18);
  display: flex;
  align-items: center;
  justify-content: center;
}
.page-title {
  font-family: Inter, sans-serif;
  font-size: 30px;
  font-weight: 700;
  color: #FFFFFF;
  line-height: 1.2;
}
/* 步骤卡片 */
.steps-card {
  width: 1008px;
  display: flex;
  flex-direction: column;
  margin-bottom: 24px;
  z-index: 1;
}
.steps-row {
  width: 100%;
  height: 138px;
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: space-between;
}
.step-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 120px;
}
.step-circle {
  width: 82px;
  height: 82px;
  border-radius: 41px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.completed-circle {
  background: #78A8FF;
  border: 1px solid #DDE9FF;
  box-shadow: 0px 10px 18px rgba(14, 49, 138, 0.22);
}
.active-circle {
  background: #FFFFFF;
  border: 2px solid rgba(255, 255, 255, 0.4);
  box-shadow: 0px 12px 24px rgba(15, 45, 125, 0.27);
}
.inactive-circle {
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0px 8px 14px rgba(8, 32, 86, 0.07);
}
.step-text {
  font-family: Inter, sans-serif;
  font-size: 23px;
  text-align: center;
  white-space: nowrap;
}
.completed-text {
  font-weight: 800;
  color: rgba(255, 255, 255, 0.94);
}
.active-text {
  font-weight: 900;
  color: #FFFFFF;
}
.inactive-text {
  font-weight: 600;
  color: rgba(226, 235, 255, 0.66);
}
.step-line {
  flex: 1;
  height: 82px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.arrow-img {
  width: 92px;
  height: 82px;
}
/* 主体区 */
.main-card {
  width: 1008px;
  height: 1200px;
  border-radius: 34px;
  background: linear-gradient(180deg, #FFFFFF 0%, #F7FAFF 100%);
  border: 1px solid #DEE9FF;
  box-shadow: 0px 22px 52px rgba(13, 46, 134, 0.25);
  padding: 32px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  z-index: 1;
}
.main-title {
  font-family: Inter, sans-serif;
  font-size: 40px;
  font-weight: 700;
  color: #1F2A44;
  line-height: 1.2;
}
.print-demo-card {
  width: 880px;
  height: 860px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 20px;
}
.print-demo-img {
  width: 100%;
  height: 100%;
}
/* 底部区域 */
.bottom-area {
  width: 1008px;
  height: 176px;
  display: flex;
  gap: 20px;
  margin-top: 24px;
  z-index: 1;
}
.help-card {
  width: 388px;
  height: 100%;
  border-radius: 26px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.11) 0%, rgba(234, 242, 255, 0.08) 100%);
  border: 2px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0px 14px 30px rgba(10, 36, 107, 0.21);
  padding: 22px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 16px;
  position: relative;
  overflow: hidden;
}
.help-highlight-bar {
  position: absolute;
  top: 0;
  left: 22px;
  width: 108px;
  height: 6px;
  background: #A8CBFF;
  border-radius: 2px;
}
.help-title-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}
.help-icon-bg {
  width: 38px;
  height: 38px;
  border-radius: 19px;
  background: rgba(255, 255, 255, 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
}
.help-title {
  font-family: Inter, sans-serif;
  font-size: 21px;
  font-weight: 800;
  color: #FFFFFF;
}
.help-content {
  font-family: Inter, sans-serif;
  font-size: 16px;
  font-weight: 550;
  color: #EAF1FF;
  line-height: 1.4;
  white-space: pre-wrap;
}
.action-card {
  flex: 1;
  height: 100%;
  border-radius: 26px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.11) 0%, rgba(234, 242, 255, 0.08) 100%);
  border: 2px solid rgba(255, 255, 255, 0.2);
  box-shadow: 0px 14px 30px rgba(10, 36, 107, 0.21);
  padding: 20px 22px 22px 22px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 12px;
  position: relative;
  overflow: hidden;
}
.action-highlight-bar {
  position: absolute;
  top: 0;
  left: 22px;
  width: 108px;
  height: 6px;
  border-radius: 2px;
}
.action-title-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}
.action-icon-bg {
  width: 38px;
  height: 38px;
  border-radius: 19px;
  background: rgba(255, 255, 255, 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
}
.action-title {
  font-family: Inter, sans-serif;
  font-size: 21px;
  font-weight: 800;
  color: #FFFFFF;
}
.action-btn {
  width: 100%;
  height: 56px;
  border-radius: 18px;
  background: linear-gradient(180deg, #FFFFFF 0%, #F4F8FF 100%);
  border: 1px solid rgba(255, 255, 255, 0.5);
  box-shadow: 0px 8px 18px rgba(7, 29, 90, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 10px;
}
.action-btn-text {
  font-family: Inter, sans-serif;
  font-size: 18px;
  font-weight: 700;
  color: #1F49D8;
}
</style>
src/static/device-id-scan.png
src/static/device-scan.png
src/static/kmN54.png
src/static/print-ticket-demo.png
src/static/step-arrow-active.png
src/static/step-arrow-inactive.png
vite.config.js
New file
@@ -0,0 +1,6 @@
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
export default defineConfig({
  plugins: [uni()],
})