first commit
231
.gitignore
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
# ============================================
|
||||
# Node.js / Vue / Vite
|
||||
# ============================================
|
||||
|
||||
# 依赖目录
|
||||
node_modules/
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# 构建输出
|
||||
dist/
|
||||
dist-ssr/
|
||||
build/
|
||||
*.local
|
||||
|
||||
# Vite
|
||||
.vite/
|
||||
vite.config.js.timestamp-*
|
||||
|
||||
# 缓存
|
||||
.cache/
|
||||
.parcel-cache/
|
||||
|
||||
# ============================================
|
||||
# Python
|
||||
# ============================================
|
||||
|
||||
# Python 字节码
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
|
||||
# Python 虚拟环境
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
.venv/
|
||||
|
||||
# Python 包管理
|
||||
*.egg
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
eggs/
|
||||
.eggs/
|
||||
wheels/
|
||||
*.whl
|
||||
|
||||
# Python 环境变量
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pytest
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# ============================================
|
||||
# 生成的 PDF 文件
|
||||
# ============================================
|
||||
|
||||
# 生成的 PDF 报告(保留示例数据文件)
|
||||
*.pdf
|
||||
!public/example.json
|
||||
|
||||
# ============================================
|
||||
# IDE / 编辑器
|
||||
# ============================================
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# JetBrains IDEs (WebStorm, PyCharm, etc.)
|
||||
.idea/
|
||||
*.iml
|
||||
*.iws
|
||||
*.ipr
|
||||
|
||||
# Sublime Text
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# Vim
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.vim/
|
||||
|
||||
# Emacs
|
||||
*~
|
||||
\#*\#
|
||||
.\#*
|
||||
|
||||
# ============================================
|
||||
# 操作系统
|
||||
# ============================================
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
._*
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
*.stackdump
|
||||
[Dd]esktop.ini
|
||||
$RECYCLE.BIN/
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
*.lnk
|
||||
|
||||
# Linux
|
||||
*~
|
||||
.fuse_hidden*
|
||||
.directory
|
||||
.Trash-*
|
||||
.nfs*
|
||||
|
||||
# ============================================
|
||||
# 日志文件
|
||||
# ============================================
|
||||
|
||||
# 日志
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# ============================================
|
||||
# 临时文件
|
||||
# ============================================
|
||||
|
||||
# 临时文件
|
||||
*.tmp
|
||||
*.temp
|
||||
*.bak
|
||||
*.backup
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# 压缩文件(可选,根据项目需要)
|
||||
# *.zip
|
||||
# *.tar
|
||||
# *.tar.gz
|
||||
# *.rar
|
||||
|
||||
# ============================================
|
||||
# 测试覆盖率
|
||||
# ============================================
|
||||
|
||||
# 测试覆盖率报告
|
||||
coverage/
|
||||
.nyc_output/
|
||||
*.lcov
|
||||
|
||||
# ============================================
|
||||
# 其他
|
||||
# ============================================
|
||||
|
||||
# 锁文件(可选,根据团队约定)
|
||||
# package-lock.json
|
||||
# yarn.lock
|
||||
# pnpm-lock.yaml
|
||||
|
||||
# 自动生成的文件
|
||||
auto-imports.d.ts
|
||||
components.d.ts
|
||||
|
||||
# 本地配置文件
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# 调试文件
|
||||
.vscode/launch.json
|
||||
.vscode/settings.json
|
||||
|
||||
# 文档构建
|
||||
docs/_build/
|
||||
site/
|
||||
|
||||
# 数据库文件(如果有)
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# 敏感信息
|
||||
secrets/
|
||||
*.pem
|
||||
*.key
|
||||
*.cert
|
||||
|
||||
1048
CSpecialList.vue
Normal file
270
PDF生成环境说明.md
Normal file
@@ -0,0 +1,270 @@
|
||||
# PDF 生成环境说明文档
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本项目使用 Python 脚本将 JSON 数据转换为 PDF 报告。主要依赖 WeasyPrint 库进行 HTML 到 PDF 的转换。
|
||||
|
||||
## 🔧 环境要求
|
||||
|
||||
### Python 版本
|
||||
- **Python 3.8+** (推荐 Python 3.9 或更高版本)
|
||||
- 支持 Windows、Linux、macOS
|
||||
|
||||
### 系统依赖
|
||||
|
||||
#### Windows
|
||||
- 需要安装 **GTK+ 运行时库**(WeasyPrint 的必需依赖)
|
||||
- 推荐使用 Conda 环境(自动处理依赖)
|
||||
|
||||
#### Linux
|
||||
- 需要安装系统级字体库和 GTK+ 相关库
|
||||
- Ubuntu/Debian: `sudo apt-get install python3-dev python3-pip python3-cffi libcairo2 libpango-1.0-0 libpangocairo-1.0-0 libgdk-pixbuf2.0-0 libffi-dev shared-mime-info`
|
||||
- CentOS/RHEL: `sudo yum install python3-devel python3-pip cairo pango gdk-pixbuf2 libffi`
|
||||
|
||||
#### macOS
|
||||
- 需要安装 GTK+ 和相关库
|
||||
- 推荐使用 Homebrew: `brew install cairo pango gdk-pixbuf libffi`
|
||||
|
||||
## 📦 Python 依赖包
|
||||
|
||||
项目依赖以下 Python 包(见 `requirements.txt`):
|
||||
|
||||
| 包名 | 版本要求 | 说明 |
|
||||
|------|---------|------|
|
||||
| jinja2 | >=3.1.2 | HTML 模板引擎,用于渲染报告模板 |
|
||||
| weasyprint | >=60.0 | PDF 生成库,将 HTML 转换为 PDF |
|
||||
|
||||
## 🚀 安装步骤
|
||||
|
||||
### 方法一:使用 Conda(推荐,Windows 用户首选)
|
||||
|
||||
Conda 会自动处理所有系统依赖,包括 GTK+ 运行时库。
|
||||
|
||||
```bash
|
||||
# 1. 创建 Conda 环境(可选)
|
||||
conda create -n pdf-generator python=3.9
|
||||
conda activate pdf-generator
|
||||
|
||||
# 2. 安装 WeasyPrint(自动安装所有依赖)
|
||||
conda install -c conda-forge weasyprint
|
||||
|
||||
# 3. 安装其他依赖
|
||||
pip install jinja2
|
||||
```
|
||||
|
||||
### 方法二:使用 pip + 手动安装系统依赖
|
||||
|
||||
#### Windows
|
||||
|
||||
1. **安装 GTK+ 运行时库**
|
||||
- 下载地址:https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases
|
||||
- 下载并安装最新的 `gtk3-runtime-*.exe`(64位版本)
|
||||
- 安装到默认路径:`C:\Program Files\GTK3-Runtime Win64`
|
||||
- 确保安装程序自动添加到 PATH,或手动添加:`C:\Program Files\GTK3-Runtime Win64\bin`
|
||||
|
||||
2. **安装 Python 依赖**
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. **重启终端**(重要!让 PATH 生效)
|
||||
|
||||
#### Linux
|
||||
|
||||
```bash
|
||||
# 1. 安装系统依赖(Ubuntu/Debian 示例)
|
||||
sudo apt-get update
|
||||
sudo apt-get install python3-dev python3-pip python3-cffi \
|
||||
libcairo2 libpango-1.0-0 libpangocairo-1.0-0 \
|
||||
libgdk-pixbuf2.0-0 libffi-dev shared-mime-info
|
||||
|
||||
# 2. 安装 Python 依赖
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
#### macOS
|
||||
|
||||
```bash
|
||||
# 1. 安装系统依赖(使用 Homebrew)
|
||||
brew install cairo pango gdk-pixbuf libffi
|
||||
|
||||
# 2. 安装 Python 依赖
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 方法三:使用虚拟环境(推荐用于生产环境)
|
||||
|
||||
```bash
|
||||
# 1. 创建虚拟环境
|
||||
python -m venv venv
|
||||
|
||||
# 2. 激活虚拟环境
|
||||
# Windows:
|
||||
venv\Scripts\activate
|
||||
# Linux/macOS:
|
||||
source venv/bin/activate
|
||||
|
||||
# 3. 安装依赖
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## ✅ 验证安装
|
||||
|
||||
运行以下命令验证环境是否正确配置:
|
||||
|
||||
```bash
|
||||
python -c "from weasyprint import HTML; print('WeasyPrint 安装成功!')"
|
||||
```
|
||||
|
||||
如果出现错误,请参考下面的"常见问题"部分。
|
||||
|
||||
## 📝 使用方法
|
||||
|
||||
### 基本用法
|
||||
|
||||
```bash
|
||||
python generate_pdf.py <数据文件> [输出文件]
|
||||
```
|
||||
|
||||
### 示例
|
||||
|
||||
```bash
|
||||
# 使用示例数据生成 PDF
|
||||
python generate_pdf.py public/example.json output.pdf
|
||||
|
||||
# 指定输出文件名(默认为 report.pdf)
|
||||
python generate_pdf.py public/example.json my_report.pdf
|
||||
```
|
||||
|
||||
### 数据文件格式
|
||||
|
||||
- 支持 JSON 格式文件
|
||||
- 文件应包含 `DWBG8B4D` 或 `CDWBG8B4D` 的谛听报告数据
|
||||
- 可选包含 `FLXG7E8F` 或 `FLXG0V4B` 的司法涉诉数据
|
||||
|
||||
## 🐛 常见问题
|
||||
|
||||
### 问题 1: `OSError: cannot load library 'libgobject-2.0-0'`
|
||||
|
||||
**原因**:Windows 系统缺少 GTK+ 运行时库。
|
||||
|
||||
**解决方案**:
|
||||
1. **推荐**:使用 Conda 安装 WeasyPrint
|
||||
```bash
|
||||
conda install -c conda-forge weasyprint
|
||||
```
|
||||
|
||||
2. **或**:手动安装 GTK+ 运行时库
|
||||
- 下载:https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases
|
||||
- 安装后重启终端
|
||||
|
||||
### 问题 2: `ImportError: No module named 'weasyprint'`
|
||||
|
||||
**原因**:WeasyPrint 未安装或不在当前 Python 环境中。
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
pip install weasyprint
|
||||
# 或使用 Conda
|
||||
conda install -c conda-forge weasyprint
|
||||
```
|
||||
|
||||
### 问题 3: `ImportError: No module named 'jinja2'`
|
||||
|
||||
**原因**:Jinja2 模板引擎未安装。
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
pip install jinja2
|
||||
```
|
||||
|
||||
### 问题 4: 生成的 PDF 中文显示为方块
|
||||
|
||||
**原因**:系统缺少中文字体。
|
||||
|
||||
**解决方案**:
|
||||
- **Windows**:系统自带中文字体,通常无需额外配置
|
||||
- **Linux**:安装中文字体
|
||||
```bash
|
||||
sudo apt-get install fonts-wqy-microhei fonts-wqy-zenhei
|
||||
```
|
||||
- **macOS**:系统自带中文字体
|
||||
|
||||
### 问题 5: Conda 安装 WeasyPrint 时卡住或报错
|
||||
|
||||
**原因**:Conda 依赖解析时间过长或网络问题。
|
||||
|
||||
**解决方案**:
|
||||
1. 更新 Conda:
|
||||
```bash
|
||||
conda update conda
|
||||
```
|
||||
|
||||
2. 使用国内镜像源(如果在中国):
|
||||
```bash
|
||||
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
|
||||
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free
|
||||
conda config --add channels conda-forge
|
||||
```
|
||||
|
||||
3. 或使用 pip + GTK+ 运行时库的方式
|
||||
|
||||
### 问题 6: 权限错误(Linux/macOS)
|
||||
|
||||
**原因**:没有权限安装系统依赖。
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 使用 sudo 安装系统依赖
|
||||
sudo apt-get install ... # Ubuntu/Debian
|
||||
sudo yum install ... # CentOS/RHEL
|
||||
```
|
||||
|
||||
## 🔍 环境检查清单
|
||||
|
||||
在开始使用前,请确认:
|
||||
|
||||
- [ ] Python 版本 >= 3.8
|
||||
- [ ] WeasyPrint 已正确安装
|
||||
- [ ] Jinja2 已正确安装
|
||||
- [ ] (Windows)GTK+ 运行时库已安装并添加到 PATH
|
||||
- [ ] (Linux/macOS)系统依赖已安装
|
||||
- [ ] 可以成功导入 WeasyPrint:`python -c "from weasyprint import HTML"`
|
||||
|
||||
## 📚 相关资源
|
||||
|
||||
- **WeasyPrint 官方文档**:https://weasyprint.org/
|
||||
- **Jinja2 官方文档**:https://jinja.palletsprojects.com/
|
||||
- **GTK+ Windows 运行时**:https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases
|
||||
- **Conda 文档**:https://docs.conda.io/
|
||||
|
||||
## 💡 最佳实践
|
||||
|
||||
1. **使用虚拟环境**:避免污染系统 Python 环境
|
||||
2. **Windows 用户优先使用 Conda**:自动处理所有依赖,最简单
|
||||
3. **定期更新依赖**:`pip install --upgrade weasyprint jinja2`
|
||||
4. **测试环境**:在生成大量 PDF 前,先用示例数据测试
|
||||
|
||||
## 🔄 更新依赖
|
||||
|
||||
```bash
|
||||
# 更新所有依赖
|
||||
pip install --upgrade -r requirements.txt
|
||||
|
||||
# 或单独更新
|
||||
pip install --upgrade weasyprint jinja2
|
||||
```
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
如果遇到问题:
|
||||
|
||||
1. 检查 Python 版本:`python --version`
|
||||
2. 检查已安装的包:`pip list | grep -E "weasyprint|jinja2"`
|
||||
3. 查看错误信息:运行 `python generate_pdf.py` 查看详细错误
|
||||
4. 验证环境:运行 `python -c "from weasyprint import HTML; print('OK')"`
|
||||
|
||||
---
|
||||
|
||||
**最后更新**:2025-01-20
|
||||
|
||||
137
README.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# 报告查看器 - 独立 Vue3 项目
|
||||
|
||||
这是一个独立的 Vue3 项目,用于展示报告组件。项目使用本地 JSON 文件(`example.json`)作为数据源,不依赖网络请求。
|
||||
|
||||
## 📋 已包含的组件
|
||||
|
||||
1. **DWBG6A2C** - 司南报告(包含所有子模块)
|
||||
2. **FLXG0V4B** - 司法涉诉
|
||||
3. **QYGL3F8E** - 人企关系加强版(包含所有子模块)
|
||||
4. **JRZQ4B6C** - 信贷表现
|
||||
5. **JRZQ09J8** - 收入评估
|
||||
6. **QCXG9P1C** - 名下车辆
|
||||
7. **DWBG8B4D** - 谛听多维报告(包含所有子模块)
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 安装依赖
|
||||
|
||||
```bash
|
||||
cd report-viewer
|
||||
npm install
|
||||
# 或
|
||||
pnpm install
|
||||
# 或
|
||||
yarn install
|
||||
```
|
||||
|
||||
### 2. 启动开发服务器
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
访问:`http://localhost:3000`
|
||||
|
||||
### 3. 构建生产版本
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## 📁 项目结构
|
||||
|
||||
```
|
||||
report-viewer/
|
||||
├── public/
|
||||
│ └── example.json # 示例数据文件
|
||||
├── src/
|
||||
│ ├── components/ # 基础组件
|
||||
│ │ ├── BaseReport.vue # ⭐ 核心组件
|
||||
│ │ ├── GaugeChart.vue
|
||||
│ │ ├── ShareReportButton.vue
|
||||
│ │ └── ...
|
||||
│ ├── views/
|
||||
│ │ └── Report.vue # ⭐ 报告页面
|
||||
│ ├── ui/ # ⭐ 业务组件
|
||||
│ │ ├── DWBG6A2C/ # 司南报告
|
||||
│ │ ├── CFLXG0V4B/ # 司法涉诉
|
||||
│ │ ├── CQYGL3F8E/ # 人企关系加强版
|
||||
│ │ ├── JRZQ4B6C/ # 信贷表现
|
||||
│ │ ├── JRZQ09J8/ # 收入评估
|
||||
│ │ ├── CQCXG9P1C.vue # 名下车辆
|
||||
│ │ └── CDWBG8B4D/ # 谛听多维报告
|
||||
│ ├── assets/ # 样式和图片
|
||||
│ └── composables/ # 工具函数(已移除网络请求)
|
||||
├── package.json
|
||||
├── vite.config.js
|
||||
└── tailwind.config.js
|
||||
```
|
||||
|
||||
## ⚙️ 配置说明
|
||||
|
||||
### 数据源
|
||||
|
||||
项目从 `public/example.json` 加载示例数据。可以直接修改该文件来更新显示的内容。
|
||||
|
||||
### 已移除的功能
|
||||
|
||||
- ✅ 产品背景图片(已移除背景图显示)
|
||||
- ✅ 网络请求(改为从本地 JSON 文件加载)
|
||||
- ✅ API 调用(ShareReportButton 在示例模式下不进行网络请求)
|
||||
|
||||
## 📝 使用说明
|
||||
|
||||
1. 启动项目后,会自动加载 `public/example.json` 中的数据
|
||||
2. 数据会自动排序并显示在报告页面中
|
||||
3. 所有组件都会根据数据进行渲染
|
||||
|
||||
## 🔧 注意事项
|
||||
|
||||
1. **图片资源**:确保所有必要的图片文件已复制到 `src/assets/images/` 目录
|
||||
2. **样式依赖**:项目依赖 Tailwind CSS 和 Vant UI,确保正确引入
|
||||
3. **浏览器兼容**:需要现代浏览器支持(ES2015+)
|
||||
4. **数据格式**:`example.json` 必须符合组件期望的数据结构
|
||||
|
||||
## 📦 依赖说明
|
||||
|
||||
### 核心依赖
|
||||
|
||||
- **vue** ^3.5.12 - Vue 框架
|
||||
- **vue-router** ^4.4.5 - 路由管理
|
||||
- **vant** ^4.9.9 - UI 组件库
|
||||
- **echarts** ^5.5.1 - 图表库
|
||||
- **vue-echarts** ^7.0.3 - Vue ECharts 封装
|
||||
- **@vueuse/core** ^11.3.0 - Vue 工具库
|
||||
- **lodash** ^4.17.21 - 工具函数库
|
||||
|
||||
### 开发依赖
|
||||
|
||||
- **vite** ^5.4.10 - 构建工具
|
||||
- **tailwindcss** ^3.4.15 - CSS 框架
|
||||
- **@vitejs/plugin-vue** - Vite Vue 插件
|
||||
- **unplugin-auto-import** - 自动导入插件
|
||||
- **unplugin-vue-components** - 自动组件导入插件
|
||||
|
||||
## 🐛 常见问题
|
||||
|
||||
### Q: 图片加载失败?
|
||||
A: 确保图片文件已复制到 `src/assets/images/report/` 目录
|
||||
|
||||
### Q: 样式不生效?
|
||||
A: 确保 `tailwind.config.js` 中包含了正确的 content 路径
|
||||
|
||||
### Q: 数据不显示?
|
||||
A: 检查 `public/example.json` 文件格式是否正确,确保数据符合组件期望的结构
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
如有问题,请检查:
|
||||
1. 控制台错误信息
|
||||
2. `public/example.json` 文件是否存在且格式正确
|
||||
3. 依赖是否正确安装
|
||||
4. 文件路径是否正确
|
||||
|
||||
---
|
||||
|
||||
**提示**:这是一个独立的 Vue3 项目,使用本地 JSON 文件作为数据源,无需后端 API 支持。
|
||||
764
generate_pdf.py
Normal file
@@ -0,0 +1,764 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
谛听多维报告 PDF 生成器
|
||||
使用 WeasyPrint 将 HTML 模板转换为 PDF
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Optional
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
|
||||
# 尝试导入 WeasyPrint,提供友好的错误提示
|
||||
try:
|
||||
from weasyprint import HTML, CSS
|
||||
from weasyprint.text.fonts import FontConfiguration
|
||||
WEASYPRINT_AVAILABLE = True
|
||||
except OSError as e:
|
||||
if 'libgobject' in str(e) or 'gobject' in str(e).lower():
|
||||
WEASYPRINT_AVAILABLE = False
|
||||
WEASYPRINT_ERROR = "WeasyPrint 需要 GTK+ 运行时库。\n\n" \
|
||||
"Windows 用户推荐使用 Conda 安装(最简单):\n" \
|
||||
" conda install -c conda-forge weasyprint\n\n" \
|
||||
"或参考 install_weasyprint_windows.md 文件中的详细安装指南。"
|
||||
else:
|
||||
WEASYPRINT_AVAILABLE = False
|
||||
WEASYPRINT_ERROR = f"WeasyPrint 导入失败: {e}\n\n请参考 install_weasyprint_windows.md 文件。"
|
||||
except ImportError as e:
|
||||
WEASYPRINT_AVAILABLE = False
|
||||
WEASYPRINT_ERROR = f"WeasyPrint 未安装: {e}\n\n请运行: pip install weasyprint"
|
||||
|
||||
|
||||
class ReportDataProcessor:
|
||||
"""报告数据处理器"""
|
||||
|
||||
@staticmethod
|
||||
def mask_name(name: Optional[str]) -> str:
|
||||
"""姓名脱敏"""
|
||||
if not name:
|
||||
return ''
|
||||
if len(name) == 1:
|
||||
return '*'
|
||||
if len(name) == 2:
|
||||
return name[0] + '*'
|
||||
return name[0] + '*' * (len(name) - 2) + name[-1]
|
||||
|
||||
@staticmethod
|
||||
def mask_phone(phone: Optional[str]) -> str:
|
||||
"""手机号脱敏"""
|
||||
if not phone:
|
||||
return ''
|
||||
if len(phone) == 11:
|
||||
return phone[:3] + '****' + phone[7:]
|
||||
return phone
|
||||
|
||||
@staticmethod
|
||||
def mask_id_card(id_card: Optional[str]) -> str:
|
||||
"""身份证号脱敏"""
|
||||
if not id_card:
|
||||
return ''
|
||||
return re.sub(r'^(.{6})(?:\d+)(.{4})$', r'\1****\2', id_card)
|
||||
|
||||
@staticmethod
|
||||
def format_interval(interval: Optional[str], unit: str = "") -> str:
|
||||
"""格式化区间表达式"""
|
||||
if not interval or interval == "-" or interval == "0":
|
||||
return interval or "-"
|
||||
|
||||
try:
|
||||
# 处理特殊格式,如 "3,6(个月)"
|
||||
if "(" in interval and ")" in interval:
|
||||
match = re.match(r'^(\d+(?:,\d+)*)\((.+)\)$', interval)
|
||||
if match:
|
||||
numbers = [n.strip() for n in match.group(1).split(",")]
|
||||
time_unit = match.group(2)
|
||||
if len(numbers) == 2:
|
||||
return f"{numbers[0]}-{numbers[1]}{time_unit}"
|
||||
return f"{', '.join(numbers)}{time_unit}"
|
||||
|
||||
# 处理区间表达式
|
||||
pattern = r'^([\[\(])(\d+(?:\.\d+)?),(\d+(?:\.\d+)?|\+)([\]\)])$'
|
||||
match = re.match(pattern, interval)
|
||||
|
||||
if not match:
|
||||
return interval
|
||||
|
||||
left_bracket, left_value, right_value, right_bracket = match.groups()
|
||||
is_left_inclusive = left_bracket == "["
|
||||
is_right_inclusive = right_bracket == "]"
|
||||
is_right_infinity = right_value == "+"
|
||||
|
||||
if is_right_infinity:
|
||||
if is_left_inclusive:
|
||||
return f"≥{left_value}{unit}"
|
||||
else:
|
||||
return f">{left_value}{unit}"
|
||||
else:
|
||||
left_num = float(left_value)
|
||||
right_num = float(right_value)
|
||||
|
||||
if left_num == right_num:
|
||||
return f"{int(left_num)}{unit}"
|
||||
else:
|
||||
if is_left_inclusive and is_right_inclusive:
|
||||
return f"{int(left_num)}-{int(right_num)}{unit}"
|
||||
elif is_left_inclusive and not is_right_inclusive:
|
||||
return f"{int(left_num)}-{int(right_num) - 1}{unit}"
|
||||
elif not is_left_inclusive and is_right_inclusive:
|
||||
return f"{int(left_num) + 1}-{int(right_num)}{unit}"
|
||||
else:
|
||||
return f"{int(left_num) + 1}-{int(right_num) - 1}{unit}"
|
||||
except Exception as e:
|
||||
print(f"区间格式化失败: {e}, 原数据: {interval}")
|
||||
return interval
|
||||
|
||||
@staticmethod
|
||||
def format_amount_interval(interval: Optional[str]) -> str:
|
||||
"""格式化金额区间"""
|
||||
return ReportDataProcessor.format_interval(interval, "元")
|
||||
|
||||
@staticmethod
|
||||
def format_institution_interval(interval: Optional[str]) -> str:
|
||||
"""格式化机构数量区间"""
|
||||
return ReportDataProcessor.format_interval(interval, "家")
|
||||
|
||||
@staticmethod
|
||||
def get_check_suggest_class(check_suggest: Optional[str]) -> str:
|
||||
"""获取审核建议样式类"""
|
||||
suggest = check_suggest or '建议拒绝'
|
||||
if '拒绝' in suggest:
|
||||
return 'pdf-value-danger'
|
||||
elif '通过' in suggest:
|
||||
return 'pdf-value-success'
|
||||
else:
|
||||
return 'pdf-value-warning'
|
||||
|
||||
@staticmethod
|
||||
def get_fraud_risk_level(score: int) -> str:
|
||||
"""获取反欺诈风险等级"""
|
||||
if score == -1:
|
||||
return '未评估'
|
||||
if score >= 80:
|
||||
return '高风险'
|
||||
if score >= 60:
|
||||
return '中风险'
|
||||
return '低风险'
|
||||
|
||||
@staticmethod
|
||||
def get_credit_level(score: int) -> str:
|
||||
"""获取信用等级"""
|
||||
if score == -1:
|
||||
return '未评估'
|
||||
if score >= 800:
|
||||
return '信用较好'
|
||||
if score >= 500:
|
||||
return '信用良好'
|
||||
return '信用一般'
|
||||
|
||||
@staticmethod
|
||||
def get_fraud_score_bg_class(score: int) -> str:
|
||||
"""获取反欺诈评分背景样式类"""
|
||||
if score == -1:
|
||||
return 'pdf-score-default'
|
||||
if score >= 80:
|
||||
return 'pdf-score-high'
|
||||
if score >= 60:
|
||||
return 'pdf-score-medium'
|
||||
return 'pdf-score-low'
|
||||
|
||||
@staticmethod
|
||||
def get_credit_score_bg_class(score: int) -> str:
|
||||
"""获取信用评分背景样式类"""
|
||||
if score == -1:
|
||||
return 'pdf-score-default'
|
||||
if score >= 800:
|
||||
return 'pdf-score-low'
|
||||
if score >= 500:
|
||||
return 'pdf-score-info'
|
||||
return 'pdf-score-medium'
|
||||
|
||||
@staticmethod
|
||||
def get_risk_tag_class(level: str) -> str:
|
||||
"""获取风险标签样式类"""
|
||||
if level == '高风险':
|
||||
return 'pdf-tag-danger'
|
||||
if level == '中风险':
|
||||
return 'pdf-tag-warning'
|
||||
if level == '低风险':
|
||||
return 'pdf-tag-success'
|
||||
if level == '信用较好':
|
||||
return 'pdf-tag-success'
|
||||
if level == '信用良好':
|
||||
return 'pdf-tag-info'
|
||||
if level == '信用一般':
|
||||
return 'pdf-tag-warning'
|
||||
return 'pdf-tag-default'
|
||||
|
||||
@staticmethod
|
||||
def get_risk_level_class(level: str) -> str:
|
||||
"""获取风险等级样式类"""
|
||||
if level == '高风险':
|
||||
return 'pdf-score-high'
|
||||
if level == '中风险':
|
||||
return 'pdf-score-medium'
|
||||
if level == '低风险':
|
||||
return 'pdf-score-low'
|
||||
return 'pdf-score-default'
|
||||
|
||||
@staticmethod
|
||||
def get_risk_flag_text(flag: int) -> str:
|
||||
"""获取风险标识文本"""
|
||||
if flag == 1:
|
||||
return '高风险'
|
||||
if flag == 2:
|
||||
return '低风险'
|
||||
return '未查得'
|
||||
|
||||
@staticmethod
|
||||
def get_risk_flag_tag_class(flag: int) -> str:
|
||||
"""获取风险标识标签样式类"""
|
||||
if flag == 1:
|
||||
return 'pdf-tag-danger'
|
||||
if flag == 2:
|
||||
return 'pdf-tag-success'
|
||||
return 'pdf-tag-default'
|
||||
|
||||
@staticmethod
|
||||
def get_result_text(result: Optional[str]) -> str:
|
||||
"""获取验证结果文本"""
|
||||
if result == '一致':
|
||||
return '核验一致'
|
||||
if result == '不一致':
|
||||
return '核验不一致'
|
||||
return result or '未查得'
|
||||
|
||||
@staticmethod
|
||||
def get_verification_result_class(result: Optional[str]) -> str:
|
||||
"""获取验证结果样式类"""
|
||||
if result == '一致':
|
||||
return 'pdf-result-success'
|
||||
if result == '不一致':
|
||||
return 'pdf-result-danger'
|
||||
return 'pdf-result-default'
|
||||
|
||||
@staticmethod
|
||||
def get_high_risk_count(risk_warning: Dict[str, Any]) -> int:
|
||||
"""获取高风险数量"""
|
||||
high_risk_fields = [
|
||||
'idCardTwoElementMismatch', 'phoneThreeElementMismatch',
|
||||
'shortPhoneDuration', 'noPhoneDuration',
|
||||
'hasCriminalRecord', 'isEconomyFront', 'isDisrupSocial', 'isKeyPerson', 'isTrafficRelated',
|
||||
'hitHighRiskBankLastTwoYears', 'hitHighRiskNonBankLastTwoYears',
|
||||
'hitCivilCase', 'hitCriminalRisk', 'hitAdministrativeCase', 'hitPreservationReview',
|
||||
'hitExecutionCase', 'hitBankruptcyAndLiquidation', 'hitDirectlyUnderCase', 'hitCompensationCase',
|
||||
'frequentApplicationRecent', 'frequentNonBankApplications', 'highDebtPressure', 'frequentBankApplications',
|
||||
'frequentRentalApplications', 'veryFrequentRentalApplications'
|
||||
]
|
||||
return sum(risk_warning.get(field, 0) for field in high_risk_fields)
|
||||
|
||||
@staticmethod
|
||||
def get_middle_risk_count(risk_warning: Dict[str, Any]) -> int:
|
||||
"""获取中风险数量"""
|
||||
middle_risk_fields = [
|
||||
'idCardPhoneProvinceMismatch', 'isAntiFraudInfo',
|
||||
'hitCurrentOverdue',
|
||||
'moreFrequentNonBankApplications', 'highFraudGangLevel', 'moreFrequentBankApplications'
|
||||
]
|
||||
return sum(risk_warning.get(field, 0) for field in middle_risk_fields)
|
||||
|
||||
@staticmethod
|
||||
def get_all_risks(risk_warning: Dict[str, Any]) -> list:
|
||||
"""获取所有风险列表"""
|
||||
risks = []
|
||||
|
||||
risk_mapping = {
|
||||
'idCardTwoElementMismatch': {
|
||||
'description': '身份证二要素信息对比结果不一致',
|
||||
'detail': '身份证号与姓名信息不匹配',
|
||||
'level': '高风险'
|
||||
},
|
||||
'phoneThreeElementMismatch': {
|
||||
'description': '手机三要素简版不一致',
|
||||
'detail': '手机号与身份证号、姓名信息不匹配',
|
||||
'level': '高风险'
|
||||
},
|
||||
'shortPhoneDuration': {
|
||||
'description': '手机在网时长极短',
|
||||
'detail': '手机号在网时间过短,存在风险',
|
||||
'level': '高风险'
|
||||
},
|
||||
'idCardPhoneProvinceMismatch': {
|
||||
'description': '身份证号手机号归属省不一致',
|
||||
'detail': '身份证归属地与手机号归属地不匹配',
|
||||
'level': '中风险'
|
||||
},
|
||||
'hasCriminalRecord': {
|
||||
'description': '该用户有前科',
|
||||
'detail': '用户存在犯罪前科记录',
|
||||
'level': '高风险'
|
||||
},
|
||||
'isKeyPerson': {
|
||||
'description': '该用户为重点人员',
|
||||
'detail': '用户被列为重点监管人员',
|
||||
'level': '高风险'
|
||||
},
|
||||
'hitHighRiskBankLastTwoYears': {
|
||||
'description': '近两年命中银行高风险',
|
||||
'detail': '近两年在银行机构存在高风险记录',
|
||||
'level': '高风险'
|
||||
},
|
||||
'hitCurrentOverdue': {
|
||||
'description': '该用户命中当前逾期',
|
||||
'detail': '用户当前存在逾期记录',
|
||||
'level': '中风险'
|
||||
},
|
||||
'frequentApplicationRecent': {
|
||||
'description': '近期申请机构极为频繁',
|
||||
'detail': '近期在多个机构频繁申请贷款',
|
||||
'level': '高风险'
|
||||
}
|
||||
}
|
||||
|
||||
for key, info in risk_mapping.items():
|
||||
if risk_warning.get(key, 0):
|
||||
badge_class = 'pdf-tag-danger' if info['level'] == '高风险' else 'pdf-tag-warning'
|
||||
risks.append({
|
||||
'key': key,
|
||||
'description': info['description'],
|
||||
'detail': info['detail'],
|
||||
'level': info['level'],
|
||||
'badge_class': badge_class
|
||||
})
|
||||
|
||||
return risks
|
||||
|
||||
@staticmethod
|
||||
def get_overdue_status_text(status: Optional[str]) -> str:
|
||||
"""获取逾期状态文本"""
|
||||
if status == '逾期':
|
||||
return '逾期'
|
||||
if status == '未逾期':
|
||||
return '未逾期'
|
||||
return '未知'
|
||||
|
||||
@staticmethod
|
||||
def get_overdue_status_tag_class(status: Optional[str]) -> str:
|
||||
"""获取逾期状态标签样式类"""
|
||||
if status == '逾期':
|
||||
return 'pdf-tag-danger'
|
||||
if status == '未逾期':
|
||||
return 'pdf-tag-success'
|
||||
return 'pdf-tag-default'
|
||||
|
||||
@staticmethod
|
||||
def get_overdue_time_text(status: Optional[str]) -> str:
|
||||
"""获取逾期时间文本"""
|
||||
if status == '逾期':
|
||||
return '逾期'
|
||||
if status == '未逾期':
|
||||
return '正常'
|
||||
return '未知'
|
||||
|
||||
@staticmethod
|
||||
def get_overdue_time_class(status: Optional[str]) -> str:
|
||||
"""获取逾期时间样式类"""
|
||||
if status == '逾期':
|
||||
return 'pdf-time-danger'
|
||||
if status == '未逾期':
|
||||
return 'pdf-time-success'
|
||||
return 'pdf-time-default'
|
||||
|
||||
|
||||
def process_judicial_data(judicial_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""处理司法涉诉数据"""
|
||||
processor = ReportDataProcessor()
|
||||
|
||||
# 处理不同的数据结构
|
||||
lawsuit_stat = None
|
||||
breach_case_list = []
|
||||
consumption_restriction_list = []
|
||||
|
||||
# 如果数据在 judicial_data 下
|
||||
if 'lawsuitStat' in judicial_data:
|
||||
lawsuit_stat = judicial_data.get('lawsuitStat', {})
|
||||
breach_case_list = judicial_data.get('breachCaseList', [])
|
||||
consumption_restriction_list = judicial_data.get('consumptionRestrictionList', [])
|
||||
# 如果数据在 entout.data 下
|
||||
elif 'entout' in judicial_data:
|
||||
entout_data = judicial_data.get('entout', {}).get('data', {})
|
||||
lawsuit_stat = entout_data
|
||||
breach_case_list = judicial_data.get('breachCaseList', [])
|
||||
consumption_restriction_list = judicial_data.get('consumptionRestrictionList', [])
|
||||
# 如果直接就是 lawsuitStat 结构
|
||||
elif 'count' in judicial_data or 'civil' in judicial_data:
|
||||
lawsuit_stat = judicial_data
|
||||
|
||||
if not lawsuit_stat:
|
||||
lawsuit_stat = {}
|
||||
|
||||
# 处理案件统计
|
||||
count = lawsuit_stat.get('count', {})
|
||||
|
||||
# 处理各类案件
|
||||
civil = lawsuit_stat.get('civil', {})
|
||||
criminal = lawsuit_stat.get('criminal', {})
|
||||
administrative = lawsuit_stat.get('administrative', {})
|
||||
implement = lawsuit_stat.get('implement', {})
|
||||
preservation = lawsuit_stat.get('preservation', {})
|
||||
bankrupt = lawsuit_stat.get('bankrupt', {})
|
||||
|
||||
# 法院曝光台数据(用于替代谛听报告的法院曝光台)
|
||||
# 从 multCourtInfo 或司法涉诉数据中提取
|
||||
court_exposure = {
|
||||
'legal_cases': [],
|
||||
'execution_cases': [],
|
||||
'disin_cases': breach_case_list,
|
||||
'limit_cases': consumption_restriction_list
|
||||
}
|
||||
|
||||
# 如果有执行案件,添加到法院曝光台
|
||||
if implement and implement.get('cases'):
|
||||
court_exposure['execution_cases'] = implement.get('cases', [])
|
||||
|
||||
# 如果有民事案件,添加到法院曝光台
|
||||
if civil and civil.get('cases'):
|
||||
court_exposure['legal_cases'].extend(civil.get('cases', []))
|
||||
|
||||
# 如果有刑事案件,添加到法院曝光台
|
||||
if criminal and criminal.get('cases'):
|
||||
court_exposure['legal_cases'].extend(criminal.get('cases', []))
|
||||
|
||||
return {
|
||||
'has_data': bool(count or civil or criminal or administrative or implement or preservation or bankrupt or breach_case_list or consumption_restriction_list),
|
||||
'count': count,
|
||||
'civil': civil,
|
||||
'criminal': criminal,
|
||||
'administrative': administrative,
|
||||
'implement': implement,
|
||||
'preservation': preservation,
|
||||
'bankrupt': bankrupt,
|
||||
'breach_case_list': breach_case_list,
|
||||
'consumption_restriction_list': consumption_restriction_list,
|
||||
'court_exposure': court_exposure
|
||||
}
|
||||
|
||||
|
||||
def process_report_data(data: Dict[str, Any], judicial_data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""处理报告数据,准备模板变量"""
|
||||
processor = ReportDataProcessor()
|
||||
|
||||
base_info = data.get('baseInfo', {})
|
||||
check_suggest = data.get('checkSuggest', '')
|
||||
fraud_score = data.get('fraudScore', -1)
|
||||
credit_score = data.get('creditScore', -1)
|
||||
verify_rule = data.get('verifyRule', '')
|
||||
fraud_rule = data.get('fraudRule', '')
|
||||
|
||||
element_verification_detail = data.get('elementVerificationDetail', {})
|
||||
risk_warning = data.get('riskWarning', {})
|
||||
overdue_risk_product = data.get('overdueRiskProduct', {})
|
||||
loan_evaluation_verification_detail = data.get('loanEvaluationVerificationDetail', {})
|
||||
leasing_risk_assessment = data.get('leasingRiskAssessment', {})
|
||||
risk_supervision = data.get('riskSupervision', {})
|
||||
|
||||
# 处理基本信息
|
||||
base_info_processed = {
|
||||
'name_masked': processor.mask_name(base_info.get('name')),
|
||||
'age': base_info.get('age', ''),
|
||||
'sex': base_info.get('sex', ''),
|
||||
'phone_masked': processor.mask_phone(base_info.get('phone')),
|
||||
'id_card_masked': processor.mask_id_card(base_info.get('idCard')),
|
||||
'location': base_info.get('location', ''),
|
||||
'phone_area': base_info.get('phoneArea', '')
|
||||
}
|
||||
|
||||
# 处理要素核查
|
||||
element_verification = None
|
||||
if element_verification_detail:
|
||||
sfzeys_flag = element_verification_detail.get('sfzeysFlag', 0)
|
||||
sjsys_flag = element_verification_detail.get('sjsysFlag', 0)
|
||||
person_check_details = element_verification_detail.get('personCheckDetails', {})
|
||||
phone_check_details = element_verification_detail.get('phoneCheckDetails', {})
|
||||
|
||||
if person_check_details or phone_check_details:
|
||||
element_verification = {
|
||||
'sfzeys_flag': sfzeys_flag,
|
||||
'sfzeys_flag_text': processor.get_risk_flag_text(sfzeys_flag),
|
||||
'sfzeys_flag_tag_class': processor.get_risk_flag_tag_class(sfzeys_flag),
|
||||
'sjsys_flag': sjsys_flag,
|
||||
'sjsys_flag_text': processor.get_risk_flag_text(sjsys_flag),
|
||||
'sjsys_flag_tag_class': processor.get_risk_flag_tag_class(sjsys_flag),
|
||||
'person_check_details': person_check_details,
|
||||
'phone_check_details': phone_check_details,
|
||||
'person_result_text': processor.get_result_text(person_check_details.get('result')),
|
||||
'person_result_class': processor.get_verification_result_class(person_check_details.get('result')),
|
||||
'phone_result_text': processor.get_result_text(phone_check_details.get('result')),
|
||||
'phone_result_class': processor.get_verification_result_class(phone_check_details.get('result'))
|
||||
}
|
||||
|
||||
# 处理风险预警
|
||||
risk_warning_processed = None
|
||||
if risk_warning:
|
||||
risks = processor.get_all_risks(risk_warning)
|
||||
if risks or risk_warning.get('totalRiskCounts'):
|
||||
risk_warning_processed = {
|
||||
'has_data': True,
|
||||
'total_risk_counts': risk_warning.get('totalRiskCounts', 0),
|
||||
'high_risk_count': processor.get_high_risk_count(risk_warning),
|
||||
'middle_risk_count': processor.get_middle_risk_count(risk_warning),
|
||||
'level': risk_warning.get('level', '-'),
|
||||
'risks': risks
|
||||
}
|
||||
|
||||
# 处理逾期风险
|
||||
overdue_risk_processed = None
|
||||
if overdue_risk_product:
|
||||
has_unsettled_overdue = overdue_risk_product.get('hasUnsettledOverdue')
|
||||
overdue_risk_processed = {
|
||||
'has_data': True,
|
||||
'status_text': processor.get_overdue_status_text(has_unsettled_overdue),
|
||||
'status_tag_class': processor.get_overdue_status_tag_class(has_unsettled_overdue),
|
||||
'current_overdue_institution_count': processor.format_institution_interval(
|
||||
overdue_risk_product.get('currentOverdueInstitutionCount')
|
||||
),
|
||||
'current_overdue_amount': processor.format_amount_interval(
|
||||
overdue_risk_product.get('currentOverdueAmount')
|
||||
),
|
||||
'settled_institution_count': processor.format_institution_interval(
|
||||
overdue_risk_product.get('settledInstitutionCount')
|
||||
),
|
||||
'total_loan_institutions': processor.format_institution_interval(
|
||||
overdue_risk_product.get('totalLoanInstitutions')
|
||||
),
|
||||
'time_1day_text': processor.get_overdue_time_text(overdue_risk_product.get('overdueLast1Day')),
|
||||
'time_1day_class': processor.get_overdue_time_class(overdue_risk_product.get('overdueLast1Day')),
|
||||
'time_7days_text': processor.get_overdue_time_text(overdue_risk_product.get('overdueLast7Days')),
|
||||
'time_7days_class': processor.get_overdue_time_class(overdue_risk_product.get('overdueLast7Days')),
|
||||
'time_14days_text': processor.get_overdue_time_text(overdue_risk_product.get('overdueLast14Days')),
|
||||
'time_14days_class': processor.get_overdue_time_class(overdue_risk_product.get('overdueLast14Days')),
|
||||
'time_30days_text': processor.get_overdue_time_text(overdue_risk_product.get('overdueLast30Days')),
|
||||
'time_30days_class': processor.get_overdue_time_class(overdue_risk_product.get('overdueLast30Days'))
|
||||
}
|
||||
|
||||
# 处理借贷评估
|
||||
loan_evaluation_processed = None
|
||||
if loan_evaluation_verification_detail:
|
||||
risk_flag = loan_evaluation_verification_detail.get('riskFlag', 0)
|
||||
organ_loan_performances = loan_evaluation_verification_detail.get('organLoanPerformances', [])
|
||||
|
||||
if organ_loan_performances:
|
||||
processed_performances = []
|
||||
for item in organ_loan_performances:
|
||||
apply_count = item.get('applyCount', '')
|
||||
type_name = '银行机构' if apply_count == '银行' else '非银机构'
|
||||
processed_performances.append({
|
||||
'type_name': type_name,
|
||||
'last7Day': item.get('last7Day', '0/0'),
|
||||
'last15Day': item.get('last15Day', '0/0'),
|
||||
'last1Month': item.get('last1Month', '0/0')
|
||||
})
|
||||
|
||||
loan_evaluation_processed = {
|
||||
'has_data': True,
|
||||
'risk_flag': risk_flag,
|
||||
'risk_flag_text': processor.get_risk_flag_text(risk_flag),
|
||||
'risk_flag_tag_class': processor.get_risk_flag_tag_class(risk_flag),
|
||||
'organ_loan_performances': processed_performances
|
||||
}
|
||||
|
||||
# 处理租赁风险评估
|
||||
leasing_risk_processed = None
|
||||
if leasing_risk_assessment:
|
||||
risk_flag = leasing_risk_assessment.get('riskFlag', 0)
|
||||
leasing_risk_processed = {
|
||||
'has_data': True,
|
||||
'risk_flag': risk_flag,
|
||||
'risk_flag_text': processor.get_risk_flag_text(risk_flag),
|
||||
'risk_flag_tag_class': processor.get_risk_flag_tag_class(risk_flag),
|
||||
'institution_total': leasing_risk_assessment.get('threeCInstitutionApplicationCountLast3Days', '0/0'),
|
||||
'institution_weekend': leasing_risk_assessment.get('threeCInstitutionApplicationCountLast3DaysWeekend', '0/0'),
|
||||
'institution_night': leasing_risk_assessment.get('threeCInstitutionApplicationCountLast3DaysNight', '0/0'),
|
||||
'platform_total': leasing_risk_assessment.get('threeCPlatformApplicationCountLast3Days', '0/0'),
|
||||
'platform_weekend': leasing_risk_assessment.get('threeCPlatformApplicationCountLast3DaysWeekend', '0/0'),
|
||||
'platform_night': leasing_risk_assessment.get('threeCPlatformApplicationCountLast3DaysNight', '0/0')
|
||||
}
|
||||
|
||||
# 处理运营商核验
|
||||
operator_verification = None
|
||||
if element_verification_detail:
|
||||
online_risk_flag = element_verification_detail.get('onlineRiskFlag', 0)
|
||||
online_risk_list = element_verification_detail.get('onlineRiskList', {})
|
||||
phone_vail_risk_flag = element_verification_detail.get('phoneVailRiskFlag', 0)
|
||||
phone_vail_risks = element_verification_detail.get('phoneVailRisks', {})
|
||||
belong_risk_flag = element_verification_detail.get('belongRiskFlag', 0)
|
||||
belong_risks = element_verification_detail.get('belongRisks', {})
|
||||
|
||||
if online_risk_list or phone_vail_risks or belong_risks:
|
||||
operator_verification = {
|
||||
'has_data': True,
|
||||
'online_risk_flag': online_risk_flag,
|
||||
'online_risk_flag_text': processor.get_risk_flag_text(online_risk_flag),
|
||||
'online_risk_flag_tag_class': processor.get_risk_flag_tag_class(online_risk_flag),
|
||||
'online_risk_list': online_risk_list,
|
||||
'phone_vail_risk_flag': phone_vail_risk_flag,
|
||||
'phone_vail_risk_flag_text': processor.get_risk_flag_text(phone_vail_risk_flag),
|
||||
'phone_vail_risk_flag_tag_class': processor.get_risk_flag_tag_class(phone_vail_risk_flag),
|
||||
'phone_vail_risks': phone_vail_risks,
|
||||
'belong_risk_flag': belong_risk_flag,
|
||||
'belong_risk_flag_text': processor.get_risk_flag_text(belong_risk_flag),
|
||||
'belong_risk_flag_tag_class': processor.get_risk_flag_tag_class(belong_risk_flag),
|
||||
'belong_risks': belong_risks
|
||||
}
|
||||
|
||||
# 处理公安重点人员核验
|
||||
key_person_verification = None
|
||||
if element_verification_detail:
|
||||
high_risk_flag = element_verification_detail.get('highRiskFlag', 0)
|
||||
key_person_check_list = element_verification_detail.get('keyPersonCheckList', {})
|
||||
anti_fraud_info = element_verification_detail.get('antiFraudInfo', {})
|
||||
|
||||
if key_person_check_list or anti_fraud_info:
|
||||
key_person_verification = {
|
||||
'has_data': True,
|
||||
'high_risk_flag': high_risk_flag,
|
||||
'high_risk_flag_text': processor.get_risk_flag_text(high_risk_flag),
|
||||
'high_risk_flag_tag_class': processor.get_risk_flag_tag_class(high_risk_flag),
|
||||
'key_person_check_list': key_person_check_list,
|
||||
'anti_fraud_info': anti_fraud_info
|
||||
}
|
||||
|
||||
# 处理法院曝光台(使用司法涉诉数据)
|
||||
court_exposure_processed = None
|
||||
if judicial_data:
|
||||
judicial_processed = process_judicial_data(judicial_data)
|
||||
if judicial_processed.get('has_data'):
|
||||
court_exposure_processed = judicial_processed.get('court_exposure', {})
|
||||
|
||||
# 处理司法涉诉数据
|
||||
judicial_processed = None
|
||||
if judicial_data:
|
||||
judicial_processed = process_judicial_data(judicial_data)
|
||||
|
||||
# 计算风险评分相关
|
||||
fraud_risk_level = processor.get_fraud_risk_level(fraud_score)
|
||||
credit_level = processor.get_credit_level(credit_score)
|
||||
|
||||
return {
|
||||
'base_info': base_info_processed,
|
||||
'check_suggest': check_suggest,
|
||||
'check_suggest_class': processor.get_check_suggest_class(check_suggest),
|
||||
'fraud_score': fraud_score,
|
||||
'fraud_score_display': '未命中' if fraud_score == -1 else str(fraud_score),
|
||||
'fraud_risk_level': fraud_risk_level,
|
||||
'fraud_score_bg_class': processor.get_fraud_score_bg_class(fraud_score),
|
||||
'fraud_risk_tag_class': processor.get_risk_tag_class(fraud_risk_level),
|
||||
'credit_score': credit_score,
|
||||
'credit_score_display': '未命中' if credit_score == -1 else str(credit_score),
|
||||
'credit_level': credit_level,
|
||||
'credit_score_bg_class': processor.get_credit_score_bg_class(credit_score),
|
||||
'credit_risk_tag_class': processor.get_risk_tag_class(credit_level),
|
||||
'verify_rule': verify_rule,
|
||||
'verify_rule_class': processor.get_risk_level_class(verify_rule),
|
||||
'verify_rule_tag_class': processor.get_risk_tag_class(verify_rule),
|
||||
'fraud_rule': fraud_rule,
|
||||
'fraud_rule_class': processor.get_risk_level_class(fraud_rule),
|
||||
'fraud_rule_tag_class': processor.get_risk_tag_class(fraud_rule),
|
||||
'element_verification': element_verification,
|
||||
'operator_verification': operator_verification,
|
||||
'key_person_verification': key_person_verification,
|
||||
'overdue_risk': overdue_risk_processed,
|
||||
'court_exposure': court_exposure_processed,
|
||||
'loan_evaluation': loan_evaluation_processed,
|
||||
'judicial_data': judicial_processed
|
||||
}
|
||||
|
||||
|
||||
def generate_pdf(data_file: str, output_file: str, template_dir: str = 'templates'):
|
||||
"""生成 PDF 文件"""
|
||||
# 检查 WeasyPrint 是否可用
|
||||
if not WEASYPRINT_AVAILABLE:
|
||||
print("=" * 60)
|
||||
print("错误:WeasyPrint 不可用")
|
||||
print("=" * 60)
|
||||
print(WEASYPRINT_ERROR)
|
||||
print("=" * 60)
|
||||
raise RuntimeError("WeasyPrint 未正确安装,请参考错误信息进行安装")
|
||||
|
||||
# 读取数据文件
|
||||
with open(data_file, 'r', encoding='utf-8') as f:
|
||||
if data_file.endswith('.json'):
|
||||
json_data = json.load(f)
|
||||
else:
|
||||
raise ValueError("不支持的文件格式,请使用 JSON 文件")
|
||||
|
||||
# 如果是数组,查找 DWBG8B4D 和司法涉诉数据
|
||||
report_data = None
|
||||
judicial_data = None
|
||||
|
||||
if isinstance(json_data, list):
|
||||
for item in json_data:
|
||||
api_id = item.get('data', {}).get('apiID', '')
|
||||
if api_id in ['DWBG8B4D', 'CDWBG8B4D']:
|
||||
report_data = item.get('data', {}).get('data', {})
|
||||
elif api_id in ['FLXG7E8F', 'FLXG0V4B', 'CFLXG0V4B']:
|
||||
# 司法涉诉数据可能在 data.data.judicial_data 或 data.data.entout
|
||||
data_content = item.get('data', {}).get('data', {})
|
||||
if 'judicial_data' in data_content:
|
||||
judicial_data = data_content.get('judicial_data', {})
|
||||
elif 'entout' in data_content:
|
||||
judicial_data = data_content
|
||||
else:
|
||||
judicial_data = data_content
|
||||
else:
|
||||
report_data = json_data
|
||||
|
||||
if not report_data:
|
||||
raise ValueError("未找到 DWBG8B4D 数据")
|
||||
|
||||
# 处理数据
|
||||
template_vars = process_report_data(report_data, judicial_data)
|
||||
|
||||
# 加载模板
|
||||
env = Environment(
|
||||
loader=FileSystemLoader(template_dir),
|
||||
autoescape=select_autoescape(['html', 'xml'])
|
||||
)
|
||||
template = env.get_template('report_template.html')
|
||||
|
||||
# 渲染 HTML
|
||||
html_content = template.render(**template_vars)
|
||||
|
||||
# 生成 PDF
|
||||
font_config = FontConfiguration()
|
||||
html_doc = HTML(string=html_content)
|
||||
html_doc.write_pdf(output_file, font_config=font_config)
|
||||
|
||||
print(f"PDF 已生成: {output_file}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("用法: python generate_pdf.py <数据文件> [输出文件]")
|
||||
print("示例: python generate_pdf.py public/example.json output.pdf")
|
||||
sys.exit(1)
|
||||
|
||||
data_file = sys.argv[1]
|
||||
output_file = sys.argv[2] if len(sys.argv) > 2 else 'report.pdf'
|
||||
|
||||
# 确保模板目录存在
|
||||
template_dir = Path('templates')
|
||||
if not template_dir.exists():
|
||||
template_dir.mkdir()
|
||||
print(f"已创建模板目录: {template_dir}")
|
||||
|
||||
try:
|
||||
generate_pdf(data_file, output_file)
|
||||
except Exception as e:
|
||||
print(f"生成 PDF 失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
110
index.html
Normal file
@@ -0,0 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=3, user-scalable=no"
|
||||
/>
|
||||
<title>报告查看器</title>
|
||||
<style>
|
||||
html {
|
||||
font-size: 16px;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
html {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 481px) and (max-width: 768px) {
|
||||
html {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
#app-loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #fff;
|
||||
z-index: 9999;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 5px solid #ccc;
|
||||
border-top: 5px solid #3498db;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app-loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<div class="loading-text">加载中</div>
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const loadingElement = document.getElementById('app-loading');
|
||||
if (loadingElement) {
|
||||
loadingElement.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
loadingElement.parentNode.removeChild(loadingElement);
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
35
package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "report-viewer",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^11.3.0",
|
||||
"axios": "^1.7.7",
|
||||
"echarts": "^5.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"vant": "^4.9.9",
|
||||
"vue": "^3.5.12",
|
||||
"vue-echarts": "^7.0.3",
|
||||
"vue-router": "^4.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vant/auto-import-resolver": "^1.3.0",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.49",
|
||||
"sass-embedded": "^1.81.0",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"terser": "^5.43.1",
|
||||
"unplugin-auto-import": "^0.18.5",
|
||||
"unplugin-vue-components": "^0.27.5",
|
||||
"vite": "^5.4.10"
|
||||
},
|
||||
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
|
||||
}
|
||||
3837
pnpm-lock.yaml
generated
Normal file
7
postcss.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
||||
8541
public/example.json
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
jinja2>=3.1.2
|
||||
weasyprint>=60.0
|
||||
8
src/App.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// App 根组件,仅用于路由
|
||||
</script>
|
||||
|
||||
25
src/assets/base.css
Normal file
@@ -0,0 +1,25 @@
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
html {
|
||||
margin: auto !important;
|
||||
/* @apply max-w-lg; */
|
||||
min-width: 320px;
|
||||
}
|
||||
body {
|
||||
background-color: #f8f8f8;
|
||||
min-height: 100vh;
|
||||
transition: color 0.5s, background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif;
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
54
src/assets/colors.css
Normal file
@@ -0,0 +1,54 @@
|
||||
/* 统一颜色变量管理文件 */
|
||||
:root {
|
||||
/* ===== 主题色系 ===== */
|
||||
--color-primary: #5d7eeb;
|
||||
--color-primary-50: #f0f3ff;
|
||||
--color-primary-100: #e1e8ff;
|
||||
--color-primary-200: #c3d1ff;
|
||||
--color-primary-300: #a5baff;
|
||||
--color-primary-400: #87a3ff;
|
||||
--color-primary-500: #5d7eeb;
|
||||
--color-primary-600: #4a63bc;
|
||||
--color-primary-700: #38488d;
|
||||
--color-primary-800: #252d5e;
|
||||
--color-primary-900: #13122f;
|
||||
|
||||
--color-primary-light: rgba(93, 126, 235, 0.1);
|
||||
--color-primary-medium: rgba(93, 126, 235, 0.15);
|
||||
--color-primary-dark: rgba(93, 126, 235, 0.8);
|
||||
|
||||
/* ===== 语义化颜色 ===== */
|
||||
--color-success: #07c160;
|
||||
--color-warning: #ff976a;
|
||||
--color-danger: #ee0a24;
|
||||
--color-info: #1989fa;
|
||||
|
||||
/* ===== 中性色系 ===== */
|
||||
--color-gray-50: #fafafa;
|
||||
--color-gray-100: #f5f5f5;
|
||||
--color-gray-200: #e5e5e5;
|
||||
--color-gray-300: #d4d4d4;
|
||||
--color-gray-400: #a3a3a3;
|
||||
--color-gray-500: #737373;
|
||||
--color-gray-600: #525252;
|
||||
--color-gray-700: #404040;
|
||||
--color-gray-800: #262626;
|
||||
--color-gray-900: #171717;
|
||||
|
||||
/* ===== 文本颜色 ===== */
|
||||
--color-text-primary: #323233;
|
||||
--color-text-secondary: #646566;
|
||||
--color-text-tertiary: #969799;
|
||||
|
||||
/* ===== 背景颜色 ===== */
|
||||
--color-bg-primary: #ffffff;
|
||||
--color-bg-secondary: #fafafa;
|
||||
--color-bg-tertiary: #f8f8f8;
|
||||
|
||||
/* ===== 边框颜色 ===== */
|
||||
--color-border-primary: #ebedf0;
|
||||
}
|
||||
|
||||
.bg-primary {
|
||||
background-color: var(--color-primary) !important;
|
||||
}
|
||||
75
src/assets/images/empty.svg
Normal file
@@ -0,0 +1,75 @@
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1024 1024" height="1024px" width="1024px">
|
||||
<title>空空如也</title>
|
||||
<defs>
|
||||
<rect rx="22.1405405" height="1024" width="1024" y="0" x="0" id="path-1"></rect>
|
||||
<linearGradient id="linearGradient-3" y2="64.8840762%" x2="50%" y1="-33.7184979%" x1="115.913479%">
|
||||
<stop offset="0%" stop-color="#6CADFF"></stop>
|
||||
<stop offset="100%" stop-opacity="0" stop-color="#FFFFFF"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient-4" y2="100%" x2="70.4980572%" y1="-20.569195%" x1="10.5031837%">
|
||||
<stop offset="0%" stop-color="#6CADFF"></stop>
|
||||
<stop offset="100%" stop-opacity="0" stop-color="#FFFFFF"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient-5" y2="104.73608%" x2="38.801584%" y1="-97.78046%" x1="100.191761%">
|
||||
<stop offset="0%" stop-color="#6CADFF"></stop>
|
||||
<stop offset="100%" stop-color="#FFFFFF"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient-6" y2="100%" x2="50%" y1="-27.9013949%" x1="50%">
|
||||
<stop offset="0%" stop-color="#6CADFF"></stop>
|
||||
<stop offset="100%" stop-opacity="0" stop-color="#FFFFFF"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient-7" y2="100%" x2="50%" y1="-27.9013949%" x1="50%">
|
||||
<stop offset="0%" stop-color="#6CADFF"></stop>
|
||||
<stop offset="100%" stop-opacity="0" stop-color="#FFFFFF"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient-8" y2="100%" x2="50%" y1="-221.1569%" x1="50%">
|
||||
<stop offset="0%" stop-color="#D2D2D2"></stop>
|
||||
<stop offset="100%" stop-opacity="0" stop-color="#D2D2D2"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient-9" y2="53.7335012%" x2="73.0360423%" y1="48.1527472%" x1="67.5652976%">
|
||||
<stop offset="0%" stop-opacity="0" stop-color="#858585"></stop>
|
||||
<stop offset="100%" stop-opacity="0.5" stop-color="#616161"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g fill-rule="evenodd" fill="none" stroke-width="1" stroke="none" id="空空如也">
|
||||
<g>
|
||||
<mask fill="white" id="mask-2">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<g id="蒙版"></g>
|
||||
<g mask="url(#mask-2)" id="编组-3">
|
||||
<g transform="translate(0, 238.0108)">
|
||||
<g fill-rule="evenodd" fill="none" stroke-width="1" stroke="none" id="编组-2">
|
||||
<g fill-rule="nonzero" transform="translate(162.5599, 0)" id="编组">
|
||||
<polygon points="592.450826 498.100707 589.3555 489.432325 587.255101 479.632083 586.398359 469.500567 586.868185 459.341443 588.001295 452.660716 589.852963 446.421689 592.367915 440.541544 595.711972 435.158313 599.857498 430.520452 604.887401 426.517537 606.932527 428.091097 608.342006 430.133964 609.060564 432.508107 609.032927 435.075494 608.286732 439.989418 607.070711 451.308006 606.794343 463.813666 607.540538 469.25211 608.894743 472.371623 610.248947 473.448269 612.072979 473.393057 614.864299 471.791891 619.037461 467.319668 626.084854 456.884481 638.963619 434.136879 651.870021 411.527309 659.000324 400.705634 667.015006 390.325661 673.758394 383.451689 680.225413 378.42734 686.471338 374.921338 693.159452 372.491982 699.156645 371.470549 704.628738 371.691399 709.8521 373.126928 714.191083 375.639102 717.783871 379.283135 720.32646 383.755358 721.818849 389.331833 722.150491 396.343837 721.155565 403.328234 718.530066 411.610128 713.942351 421.493188 708.884811 429.471413 700.400302 443.19175 696.006046 451.031943 692.219799 458.706498 689.345568 466.270628 688.212458 472.09556 688.46119 475.601562 689.760121 477.920492 692.109252 479.438839 695.232214 479.714902 698.299903 479.052351 705.070927 476.540176 711.924862 473.393057 718.778797 470.383969 725.384001 468.258282 729.805894 467.595731 734.144877 467.8994 738.511497 469.224503 741.966102 471.377796 744.702148 474.055608 746.857821 477.368366 748.985858 483.000054 750.008421 489.349506 750.036057 495.864596 749.206952 502.158835 746.526179 511.40695 742.104286 520.737884 736.21764 529.406266 728.866242 537.16364 723.283601 541.608256 717.203498 545.25229 710.570657 548.123346 703.606175 550.000575 696.199503 550.745946 688.212458 550.359458 675.554788 548.09574 661.238907 544.396494 646.923027 539.537783 632.855878 533.464394 623.76336 528.688502 615.582857 523.526121 608.203822 517.977252 601.543344 511.683013 596.375256 505.140317 592.533736 498.349164" fill="url(#linearGradient-3)" id="路径"></polygon>
|
||||
<polygon points="39.9075893 440.458725 31.7823599 436.096928 23.6571305 430.216783 16.222822 423.287598 9.75580266 415.447406 4.58771456 406.834236 1.21602073 397.834578 0.0552736694 391.623157 0 385.411737 1.05019972 379.117498 3.59278851 378.896647 5.99719313 379.421167 8.12522941 380.635845 9.83871316 382.54068 12.6023966 386.654021 19.2905106 395.87453 27.4986505 405.315889 31.6994494 408.849497 34.7947749 410.257419 36.5082587 410.146993 37.8348267 408.877103 38.8297528 405.840409 38.9403001 399.711807 37.1439059 387.26136 31.3401706 361.863552 25.7022563 336.465744 23.7676779 323.601202 22.8003886 310.488203 23.242578 300.881206 24.6520566 292.820163 26.9459139 286.056616 30.2899709 279.789983 34.0762172 275.014091 38.2770161 271.508089 43.1134622 269.078734 48.0051819 268.0573 53.1179963 268.333363 57.9820792 269.934529 62.8185253 273.081649 67.7655187 278.050785 71.6899493 283.903324 75.3103746 291.798729 78.5162474 302.206309 80.1191838 311.509637 83.0210515 327.383267 85.0385404 336.134468 87.4153082 344.361149 90.3448127 351.870067 93.4125013 356.977234 95.9550901 359.378984 98.4700421 360.234779 101.206089 359.765472 103.748678 357.888243 105.572709 355.320856 108.916766 348.916191 111.873907 342.014613 114.913959 335.195853 118.368563 329.23289 121.215157 325.782101 124.642125 323.159501 128.78765 321.254665 132.794991 320.509295 136.636511 320.674933 140.450394 321.696366 145.81194 324.429391 150.841844 328.459913 155.236101 333.291018 158.856526 338.508611 162.670409 345.575827 166.788298 354.823942 170.381086 364.762215 173.448775 375.639102 175.604448 386.764446 176.5441 397.641334 176.378279 404.874188 175.41099 411.499703 173.697506 417.628304 171.04437 423.315205 167.396308 428.173916 162.642772 432.314863 157.032495 435.62762 150.344381 438.691921 142.440246 441.424946 130.058944 444.572066 116.2958 446.835784 102.173378 448.160887 87.9956817 448.547375 74.0390802 447.967642 61.3537731 446.504508 49.4422973 443.964727 40.1563208 440.707182" fill="url(#linearGradient-4)" id="路径"></polygon>
|
||||
<path fill="url(#linearGradient-5)" id="形状结合" d="M648.498327,284.510663 L644.71208,289.9215 L641.589118,295.939676 L639.350534,302.344341 L638.217424,308.914643 L638.355608,315.567765 L640.013818,322.165674 L642.224765,326.058164 L644.988449,328.92922 L647.94559,331.579426 L650.571089,334.505696 L652.284573,338.039304 L652.83731,343.588173 L651.648926,349.689168 L649.576163,355.458887 L646.619022,360.952544 L640.428371,370.780391 L633.408615,379.945687 L625.615028,388.53125 L630.092195,393.086292 L633.159883,398.524736 L634.735183,404.570518 L634.707546,410.947577 L633.215157,417.186603 L630.755479,423.066748 L627.411422,428.477585 L623.210623,433.336296 L620.115297,436.234959 L616.384325,438.25022 L612.459894,438.691921 L608.535464,438.25022 L595.960704,435.517195 L583.77286,431.624705 L571.944295,426.57275 L566.555112,423.729299 L561.608118,420.250904 L557.545504,416.027138 L554.864731,410.91997 L554.035626,406.723811 L554.25672,403.769935 L555.251646,401.699462 L557.877146,399.049256 L561.41466,396.895963 L562.685955,396.178199 L566.30638,393.886875 L569.180611,390.822574 L571.253374,386.129501 L572.524668,380.939514 L574.127604,370.55954 L575.150167,359.075314 L575.896362,352.781075 L577.444025,347.121781 L579.212782,343.864236 L581.783008,341.352061 L585.292886,339.557651 L592.533736,338.260154 L599.74695,336.935051 L604.389938,334.754152 L608.176185,331.827883 L611.243873,328.128637 L614.974846,321.696366 L618.180719,314.518725 L618.899277,312.751921 L622.630249,304.663271 L624.785922,300.908813 L627.273238,297.568449 L630.313289,294.86303 L634.292994,292.461281 L642.611681,288.430759 L646.28738,286.387892 L648.498327,284.510663 Z M619.009824,341.73855 L615.195941,346.514442 L606.158696,359.323771 L600.935334,367.854122 L595.463241,378.013245 L590.626795,388.807313 L586.702364,400.346752 L584.795423,408.269764 L583.717586,416.192776 L583.468855,424.115788 L595.325057,424.115788 L596.319983,416.109957 L599.912771,395.929742 L602.925186,383.313657 L607.153622,369.234437 L612.432257,355.238037 L619.009824,341.73855 Z"></path>
|
||||
<polygon points="125.250135 60.7614951 125.250135 60.7614951 124.559214 54.3016178 122.431178 48.4214731 119.059484 43.2314863 114.55468 38.9249014 109.165497 35.7777818 102.947209 33.9005525 96.4525532 33.5416704 90.3171759 34.7011355 84.6239879 37.21331 79.6769945 40.9677686 75.6972903 45.7712671 72.8783332 51.6238055 66.8258663 52.3415696 61.3537731 54.5500746 56.6278743 58.111289 52.9245385 62.9147875 50.658318 68.5464754 49.9673972 74.3990138 50.8517759 80.2515521 53.3114542 85.8004211 57.1529742 90.4934943 61.9894203 93.8890708 67.5444241 95.931938 73.569254 96.4564579 123.011551 96.4564579 127.626903 95.7663001 131.634244 94.1375276 135.171759 91.5149279 137.963079 88.1193514 139.78711 84.1992549 140.699126 79.6442132 140.367484 74.9787463 139.013279 70.8654057 136.664148 67.1661597 133.513549 64.1294653 129.727302 62.0037792" fill="url(#linearGradient-6)" id="路径"></polygon>
|
||||
<polygon points="329.569254 33.7073083 329.569254 33.5416704 329.127065 28.130833 327.911044 23.0788777 325.921192 18.3305919 321.665119 11.9259273 316.054842 6.65312145 311.715859 3.89249014 306.934686 1.84962298 301.656051 0.496913635 296.266868 0 291.071143 0.35888207 286.041239 1.49074091 278.993846 4.61025428 272.858469 9.22050857 269.376228 13.0301798 266.557271 17.3643709 264.318687 22.305901 259.205873 22.8856335 254.507611 24.2383429 250.196265 26.364029 246.299471 29.2074792 243.010688 32.6030557 240.302278 36.5783648 238.284789 40.9677686 237.096405 45.6608418 236.681853 50.7956161 237.234589 55.902784 238.588794 60.5682509 240.744467 64.8748357 243.591061 68.7673259 246.990392 72.0524771 250.970096 74.7855021 255.364353 76.7731567 260.062615 77.9878345 265.203066 78.3743228 326.612113 78.3743228 332.360574 77.6565587 337.501026 75.5860852 341.950556 72.3285403 345.488071 68.1047744 347.892475 63.1080317 348.997949 57.4211312 348.611033 51.6514118 346.842276 46.378606 343.885134 41.7683517 339.877793 37.9862868 335.041347 35.2808681 329.403433 33.8453398" fill="url(#linearGradient-7)" id="路径"></polygon>
|
||||
</g>
|
||||
<polygon points="1024 550.359458 999.596675 534.403009 973.839145 519.164324 946.672136 504.615797 918.040376 490.785034 889.159883 478.224161 859.118644 466.491478 827.833747 455.669804 795.305193 445.703925 762.693728 437.007936 729.114974 429.360987 694.541293 422.735472 658.917413 417.186603 623.348807 412.880018 587.034006 409.760505 549.945374 407.883276 512 407.193118 474.054626 407.883276 436.965994 409.760505 400.623556 412.880018 365.082587 417.186603 329.458707 422.735472 294.885026 429.360987 261.306272 437.007936 228.694807 445.703925 196.166253 455.669804 164.881356 466.491478 134.840117 478.224161 105.931987 490.785034 77.3278635 504.615797 50.160855 519.164324 24.4033251 534.403009 0 550.359458" fill-rule="nonzero" fill="url(#linearGradient-8)" id="路径"></polygon>
|
||||
</g>
|
||||
<polygon points="168.585366 389.215532 314.612039 389.215532 461.536985 469.266954 317.214646 465.594981" fill-rule="nonzero" fill="url(#linearGradient-9)" stroke="none" id="矩形"></polygon>
|
||||
<polygon points="481.155803 208.25638 479.722777 299.451613 688.569371 236.303345" fill-rule="nonzero" fill="#B8D6FF" stroke="none" id="路径"></polygon>
|
||||
<polygon points="314.788219 244.959395 481.155803 208.631248 481.155803 264.00952" fill-rule="nonzero" fill="#9CC6FF" stroke="none" id="路径"></polygon>
|
||||
<polygon points="314.788219 244.959395 511.147006 264.384388 511.147006 512.547202 314.788219 465.075243" fill-rule="nonzero" fill="#64ADFF" stroke="none" id="路径"></polygon>
|
||||
<polygon points="314.788219 244.959395 511.283486 263.617612 489.889892 383.428742 314.788219 346.994453" fill-rule="nonzero" fill="#429BFF" stroke="none" id="矩形"></polygon>
|
||||
<polygon points="511.147006 264.384388 688.569371 236.303345 688.569371 458.600245 511.147006 512.547202" opacity="0.99" fill-rule="nonzero" fill="#9CC5FF" stroke="none" id="路径"></polygon>
|
||||
<polygon points="511.283486 264.997809 671.897967 239.34913 688.569371 344.292902 535.025228 383.428742" fill-rule="nonzero" fill="#64ADFF" stroke="none" id="矩形"></polygon>
|
||||
<polygon points="314.788219 244.959395 267.566573 324.806343 465.801946 362.565804 511.147006 264.384388" fill-rule="nonzero" fill="#9CC6FF" stroke="none" id="路径"></polygon>
|
||||
<polygon points="511.147006 264.384388 566.898574 362.565804 745.583366 317.786082 688.569371 236.303345" opacity="0.99" fill-rule="nonzero" fill="#9DC6FF" stroke="none" id="路径"></polygon>
|
||||
<path stroke-dasharray="11.05477807439905,8.29108355579929" fill="none" stroke-width="5.52738904" stroke="#9DC6FF" id="路径-19" d="M583.139543,151.843183 C532.695151,184.875351 501.824507,214.257045 511.001904,232.450753 C523.475834,257.179661 544.659409,246.913618 547.874,236.537816 C551.088588,226.162013 542.242035,205.908265 523.475834,216.933951 C504.709635,227.959637 484.261479,247.732311 479.722777,267.098145"></path>
|
||||
<g transform="translate(555.0939, 41.4059)" fill-rule="evenodd" fill="none" stroke-width="1" stroke="none" id="飞机">
|
||||
<polygon points="163.057977 9.09494702e-13 0 30.854292 41.4554178 58.3378535" fill-rule="nonzero" fill="#9DC6FF" id="路径-16备份"></polygon>
|
||||
<polygon points="163.057977 0 41.4554178 58.3378535 41.4554178 104.894966" fill-rule="nonzero" fill="#64ADFF" id="路径-16备份-2"></polygon>
|
||||
<polygon points="163.057977 0 41.4554178 58.3378535 65.4910753 84.1769753" fill-rule="nonzero" fill="#429BFF" id="路径-16备份-2"></polygon>
|
||||
<polygon points="163.057977 0 58.9237102 70.0692202 108.951745 102.134572" fill-rule="nonzero" fill="#9DC6FF" id="路径-16备份"></polygon>
|
||||
<line stroke-linecap="round" stroke-width="2.76369452" stroke="#429BFF" id="路径-16" y2="32.1173295" x2="0.501635492" y1="58.3378535" x1="41.4554178"></line>
|
||||
<line stroke-linecap="round" stroke-width="2.76369452" stroke="#429BFF" id="路径-17" y2="101.744072" x2="107.648858" y1="71.0666294" x1="59.7211085"></line>
|
||||
<line stroke-linecap="round" stroke-width="2.76369452" stroke="#429BFF" id="路径-18" y2="103.502333" x2="41.4554178" y1="58.3378535" x1="41.4554178"></line>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 15 KiB |
BIN
src/assets/images/public_security_record_icon.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/assets/images/report/ajgl.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/assets/images/report/ajlxfb.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/assets/images/report/backgroundcheck_inquire_bg.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
src/assets/images/report/backgroundcheck_report_bg.png
Normal file
|
After Width: | Height: | Size: 190 KiB |
BIN
src/assets/images/report/bysj.png
Normal file
|
After Width: | Height: | Size: 816 B |
BIN
src/assets/images/report/dkxwfx.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
src/assets/images/report/dqfx_inquire_bg.jpg
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
src/assets/images/report/dqfx_report_bg.jpg
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
src/assets/images/report/dwtzls.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
src/assets/images/report/fqzgz.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
src/assets/images/report/fqzpf.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
src/assets/images/report/fsbq.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
src/assets/images/report/fx.png
Normal file
|
After Width: | Height: | Size: 286 B |
BIN
src/assets/images/report/fxbs.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
src/assets/images/report/fxgl.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
src/assets/images/report/fxmd.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
src/assets/images/report/fxzbxq.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
src/assets/images/report/fxzl.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
src/assets/images/report/fybgt.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
src/assets/images/report/gazdryhy.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
src/assets/images/report/gazdryhycp.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
src/assets/images/report/gfx.png
Normal file
|
After Width: | Height: | Size: 504 B |
BIN
src/assets/images/report/gl.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src/assets/images/report/glfxjd.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
src/assets/images/report/glfxjd2.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
src/assets/images/report/glsfz.png
Normal file
|
After Width: | Height: | Size: 619 B |
BIN
src/assets/images/report/glsjh.png
Normal file
|
After Width: | Height: | Size: 493 B |
BIN
src/assets/images/report/glzdryhy2.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
src/assets/images/report/grdsj_inquire_bg.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
src/assets/images/report/grdsj_report_bg.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
src/assets/images/report/gsdfx.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
src/assets/images/report/hktj.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
src/assets/images/report/hkylfx.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
src/assets/images/report/homeservice_inquire_bg.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
src/assets/images/report/homeservice_report_bg.jpg
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
src/assets/images/report/j24gyfkqk.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
src/assets/images/report/jdpg.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
src/assets/images/report/jdpggl.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
src/assets/images/report/jgfx.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
src/assets/images/report/jgztxx.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
src/assets/images/report/jyyc.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
src/assets/images/report/khlxjdbx.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
src/assets/images/report/lsjdxw.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
src/assets/images/report/lyqk.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
src/assets/images/report/lyxxxq.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
src/assets/images/report/marriage_inquire_bg.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/assets/images/report/marriage_report_bg.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
src/assets/images/report/ms.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
src/assets/images/report/mzfxbz.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
src/assets/images/report/qsgg.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
src/assets/images/report/qspc.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
src/assets/images/report/rkpm.png
Normal file
|
After Width: | Height: | Size: 1000 B |
BIN
src/assets/images/report/rzls.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
src/assets/images/report/sagg.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
src/assets/images/report/sdszhycp.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
src/assets/images/report/sdszryhy.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
src/assets/images/report/sfxxhy.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
src/assets/images/report/sfz.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
src/assets/images/report/shjy.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
src/assets/images/report/sjh.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/images/report/sjqsfx.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
src/assets/images/report/slbg_inquire_icon.png
Normal file
|
After Width: | Height: | Size: 366 B |
BIN
src/assets/images/report/srbq.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/images/report/srpg.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/assets/images/report/ssfxfx.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
src/assets/images/report/ssfxztgl.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
src/assets/images/report/sswf.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
src/assets/images/report/swfx.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
src/assets/images/report/sxaj.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
src/assets/images/report/sxxq.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
src/assets/images/report/title.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src/assets/images/report/title_inquire_bg.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
src/assets/images/report/title_inquire_bg_green.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
src/assets/images/report/title_inquire_bg_red.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
src/assets/images/report/wmz.png
Normal file
|
After Width: | Height: | Size: 656 B |
BIN
src/assets/images/report/wxts_icon.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
src/assets/images/report/xgaj.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
src/assets/images/report/xl.png
Normal file
|
After Width: | Height: | Size: 983 B |
BIN
src/assets/images/report/xs.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
src/assets/images/report/xwqy_inquire_bg.jpg
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
src/assets/images/report/xwqy_report_bg.jpg
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
src/assets/images/report/xxlx.png
Normal file
|
After Width: | Height: | Size: 926 B |
BIN
src/assets/images/report/xxxs.png
Normal file
|
After Width: | Height: | Size: 744 B |
BIN
src/assets/images/report/xypf.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |