first commit
21
.env
Normal file
@@ -0,0 +1,21 @@
|
||||
VITE_API_URL=
|
||||
VITE_API_PREFIX=/api/v1
|
||||
|
||||
VITE_COMPANY_NAME=北京正信环宇科技有限公司
|
||||
VITE_APP_NAME=愉悦查
|
||||
|
||||
VITE_INQUIRE_AES_KEY=ff83609b2b24fc73196aac3d3dfb874f
|
||||
|
||||
VITE_WECHAT_APP_ID=wxb786749edb73eb04
|
||||
|
||||
VITE_ICP_RECORD=京ICP备2025158088号-1
|
||||
VITE_PUBLIC_SECURITY_RECORD=xxxxxxxxxxxxxx
|
||||
VITE_SHOW_PUBLIC_SECURITY_RECORD=false
|
||||
VITE_SERVICE_URL=https://work.weixin.qq.com/kfid/kfc93b53b25551b611d
|
||||
VITE_CHAT_AES_KEY=qw5w6SFE2D1jmxyd
|
||||
VITE_CHAT_AES_IV=345GDFED433223DF
|
||||
|
||||
VITE_SHARE_TITLE=愉悦查|大数据风险报告查询与代理平台,支持个人和企业多场景风控应用
|
||||
VITE_SHARE_DESC=提供个人信用评估、入职背调、信贷风控、企业风险监测等服务
|
||||
VITE_SHARE_IMG=https://www.yuyuecha.com/logo.png
|
||||
VITE_TOKEN_VERSION=1.0
|
||||
30
.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
29
README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# bdqr-webview-v2
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
pnpm build
|
||||
```
|
||||
23
env.example
Normal file
@@ -0,0 +1,23 @@
|
||||
VITE_API_URL=
|
||||
VITE_API_PREFIX=/api/v1
|
||||
|
||||
VITE_COMPANY_NAME=北京正信环宇科技有限公司
|
||||
VITE_APP_NAME=愉悦查
|
||||
|
||||
VITE_ICP_RECORD=京ICP备2025158088号-1
|
||||
VITE_PUBLIC_SECURITY_RECORD=
|
||||
VITE_SHOW_PUBLIC_SECURITY_RECORD=false
|
||||
|
||||
VITE_SERVICE_URL=https://work.weixin.qq.com/kfid/kfc93b53b25551b611d
|
||||
VITE_INQUIRE_AES_KEY=ff83609b2b24fc73196aac3d3dfb874f
|
||||
|
||||
VITE_WECHAT_APP_ID=wx442ee1ac1ee75917
|
||||
|
||||
|
||||
VITE_CHAT_AES_KEY=qw5w6SFE2D1jmxyd
|
||||
VITE_CHAT_AES_IV=345GDFED433223DF
|
||||
|
||||
VITE_SHARE_TITLE=愉悦查|大数据风险报告查询与代理平台,支持个人和企业多场景风控应用
|
||||
VITE_SHARE_DESC=提供个人信用评估、入职背调、信贷风控、企业风险监测等服务
|
||||
VITE_SHARE_IMG=https://www.yuyuecha.com/logo.png
|
||||
VITE_TOKEN_VERSION=1.0
|
||||
213
index.html
Normal file
@@ -0,0 +1,213 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=3, user-scalable=no"
|
||||
/>
|
||||
|
||||
<!-- 基础SEO信息 -->
|
||||
<title>
|
||||
愉悦查|大数据风险报告查询与代理平台,支持个人和企业多场景风控应用
|
||||
</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="愉悦查,专业大数据风险报告查询与代理平台,支持个人信用查询、小微企业风控、贷前风险背调等多场景报告应用,免费开通代理权限,助力高效识别信用与风险。"
|
||||
/>
|
||||
<meta
|
||||
name="keywords"
|
||||
content="大数据风险报告查询、大数据风险评估、大数据分析报告、个人大数据风险查询、小微企业风险、贷前风险背调、代理管理平台、免费开通代理、风险管控平台、信用风险分析、企业风险报告、贷前信用审核、失信人名单查询、被执行人信息、信用黑名单查询"
|
||||
/>
|
||||
<meta name="author" content="愉悦查" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="googlebot" content="index, follow" />
|
||||
<meta name="baidu-site-verification" content="" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://www.zhinengcha.cn/" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="愉悦查|大数据风险报告查询与代理平台,支持个人和企业多场景风控应用"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="愉悦查,专业大数据风险报告查询与代理平台,支持个人信用查询、小微企业风控、贷前风险背调等多场景报告应用,免费开通代理权限,助力高效识别信用与风险。"
|
||||
/>
|
||||
<meta property="og:site_name" content="愉悦查" />
|
||||
<meta property="og:locale" content="zh_CN" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary" />
|
||||
<meta property="twitter:url" content="https://www.zhinengcha.cn/" />
|
||||
<meta
|
||||
property="twitter:title"
|
||||
content="愉悦查|大数据风险报告查询与代理平台,支持个人和企业多场景风控应用"
|
||||
/>
|
||||
<meta
|
||||
property="twitter:description"
|
||||
content="愉悦查,专业大数据风险报告查询与代理平台,支持个人信用查询、小微企业风控、贷前风险背调等多场景报告应用,免费开通代理权限,助力高效识别信用与风险。"
|
||||
/>
|
||||
|
||||
<!-- 其他重要meta标签 -->
|
||||
<meta name="theme-color" content="#3498db" />
|
||||
<meta name="msapplication-TileColor" content="#3498db" />
|
||||
<meta name="application-name" content="愉悦查" />
|
||||
<meta name="apple-mobile-web-app-title" content="愉悦查" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
|
||||
<!-- 结构化数据 -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "愉悦查",
|
||||
"url": "https://www.zhinengcha.cn/",
|
||||
"description": "专业大数据风险报告查询与代理平台,支持个人信用查询、小微企业风控、贷前风险背调等多场景报告应用",
|
||||
"potentialAction": {
|
||||
"@type": "SearchAction",
|
||||
"target": "https://www.zhinengcha.cn/search?q={search_term_string}",
|
||||
"query-input": "required name=search_term_string"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
"name": "愉悦查",
|
||||
"url": "https://www.zhinengcha.cn/",
|
||||
"description": "专业大数据风险报告查询与代理平台,支持个人和企业多场景风控应用"
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
|
||||
<script>
|
||||
window.jWeixin = window.wx;
|
||||
delete window.wx;
|
||||
</script>
|
||||
|
||||
<!-- 预加载关键资源 -->
|
||||
<link rel="preconnect" href="https://www.zhinengcha.cn" />
|
||||
<link rel="preconnect" href="https://res.wx.qq.com" />
|
||||
<link rel="dns-prefetch" href="https://www.zhinengcha.cn" />
|
||||
<link rel="dns-prefetch" href="https://res.wx.qq.com" />
|
||||
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
8
jsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
42
package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "bdqr-webview-v2",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vant/area-data": "^2.0.0",
|
||||
"@vueuse/core": "^11.3.0",
|
||||
"axios": "^1.7.7",
|
||||
"crypto-js": "^4.2.0",
|
||||
"echarts": "^5.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.2.6",
|
||||
"qrcode": "^1.5.4",
|
||||
"vant": "^4.9.9",
|
||||
"vue": "^3.5.12",
|
||||
"vue-echarts": "^7.0.3",
|
||||
"vue-router": "^4.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vant/auto-import-resolver": "^1.2.1",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"pinyin-pro": "^3.27.0",
|
||||
"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",
|
||||
"vite-plugin-vue-devtools": "^7.5.4"
|
||||
},
|
||||
"packageManager": "pnpm@10.9.0+sha512.0486e394640d3c1fb3c9d43d49cf92879ff74f8516959c235308f5a8f62e2e19528a65cdc2a3058f587cde71eba3d5b56327c8c33a97e4c4051ca48a10ca2d5f"
|
||||
}
|
||||
3402
pnpm-lock.yaml
generated
Normal file
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
public/image/clickCaptcha.jpg
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
public/image/help/13.jpg
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
public/image/help/14.jpg
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
public/image/help/15.jpg
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
public/image/help/18.jpg
Normal file
|
After Width: | Height: | Size: 864 KiB |
BIN
public/image/help/19.jpg
Normal file
|
After Width: | Height: | Size: 619 KiB |
BIN
public/image/help/20.jpg
Normal file
|
After Width: | Height: | Size: 558 KiB |
BIN
public/image/help/21.jpg
Normal file
|
After Width: | Height: | Size: 719 KiB |
BIN
public/image/help/22.jpg
Normal file
|
After Width: | Height: | Size: 878 KiB |
BIN
public/image/help/23.jpg
Normal file
|
After Width: | Height: | Size: 929 KiB |
BIN
public/image/help/24.jpg
Normal file
|
After Width: | Height: | Size: 259 KiB |
BIN
public/image/help/25.jpg
Normal file
|
After Width: | Height: | Size: 297 KiB |
BIN
public/image/help/direct-earnings.jpg
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
public/image/help/invite-earnings.jpg
Normal file
|
After Width: | Height: | Size: 886 KiB |
BIN
public/image/help/invite-step1.jpg
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
public/image/help/invite-step2.jpg
Normal file
|
After Width: | Height: | Size: 258 KiB |
BIN
public/image/help/invite-step3.jpg
Normal file
|
After Width: | Height: | Size: 213 KiB |
BIN
public/image/help/report-calculation.jpg
Normal file
|
After Width: | Height: | Size: 367 KiB |
BIN
public/image/help/report-cost.jpg
Normal file
|
After Width: | Height: | Size: 307 KiB |
BIN
public/image/help/report-efficiency.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
public/image/help/report-push.jpg
Normal file
|
After Width: | Height: | Size: 940 KiB |
BIN
public/image/help/report-secret-1.jpg
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
public/image/help/report-secret-2.jpg
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
public/image/help/report-step1.jpg
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
public/image/help/report-step2.jpg
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
public/image/help/report-step3.jpg
Normal file
|
After Width: | Height: | Size: 179 KiB |
BIN
public/image/help/report-step4.jpg
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
public/image/help/report-step5.jpg
Normal file
|
After Width: | Height: | Size: 175 KiB |
BIN
public/image/help/report-types.jpg
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
public/image/help/vip-guide.jpg
Normal file
|
After Width: | Height: | Size: 257 KiB |
BIN
public/image/poster/backgroundcheck/backgroundcheck_01.png
Normal file
|
After Width: | Height: | Size: 340 KiB |
BIN
public/image/poster/backgroundcheck/backgroundcheck_02.png
Normal file
|
After Width: | Height: | Size: 345 KiB |
BIN
public/image/poster/backgroundcheck/backgroundcheck_03.png
Normal file
|
After Width: | Height: | Size: 323 KiB |
BIN
public/image/poster/backgroundcheck/backgroundcheck_04.png
Normal file
|
After Width: | Height: | Size: 347 KiB |
BIN
public/image/poster/companyinfo/companyinfo_01.png
Normal file
|
After Width: | Height: | Size: 324 KiB |
BIN
public/image/poster/companyinfo/companyinfo_02.png
Normal file
|
After Width: | Height: | Size: 366 KiB |
BIN
public/image/poster/companyinfo/companyinfo_03.png
Normal file
|
After Width: | Height: | Size: 375 KiB |
BIN
public/image/poster/companyinfo/companyinfo_04.png
Normal file
|
After Width: | Height: | Size: 313 KiB |
|
After Width: | Height: | Size: 395 KiB |
|
After Width: | Height: | Size: 328 KiB |
|
After Width: | Height: | Size: 358 KiB |
|
After Width: | Height: | Size: 343 KiB |
BIN
public/image/poster/homeservice/homeservice_01.png
Normal file
|
After Width: | Height: | Size: 324 KiB |
BIN
public/image/poster/homeservice/homeservice_02.png
Normal file
|
After Width: | Height: | Size: 338 KiB |
BIN
public/image/poster/homeservice/homeservice_03.png
Normal file
|
After Width: | Height: | Size: 343 KiB |
BIN
public/image/poster/homeservice/homeservice_04.png
Normal file
|
After Width: | Height: | Size: 336 KiB |
BIN
public/image/poster/invitation/invitation_01.png
Normal file
|
After Width: | Height: | Size: 421 KiB |
BIN
public/image/poster/invitation/invitation_02.png
Normal file
|
After Width: | Height: | Size: 356 KiB |
BIN
public/image/poster/invitation/invitation_03.png
Normal file
|
After Width: | Height: | Size: 356 KiB |
BIN
public/image/poster/invitation/invitation_04.png
Normal file
|
After Width: | Height: | Size: 321 KiB |
BIN
public/image/poster/marriage/marriage_01.png
Normal file
|
After Width: | Height: | Size: 324 KiB |
BIN
public/image/poster/marriage/marriage_02.png
Normal file
|
After Width: | Height: | Size: 324 KiB |
BIN
public/image/poster/marriage/marriage_03.png
Normal file
|
After Width: | Height: | Size: 335 KiB |
BIN
public/image/poster/marriage/marriage_04.png
Normal file
|
After Width: | Height: | Size: 331 KiB |
BIN
public/image/poster/riskassessment/riskassessment_01.png
Normal file
|
After Width: | Height: | Size: 391 KiB |
BIN
public/image/poster/riskassessment/riskassessment_02.png
Normal file
|
After Width: | Height: | Size: 387 KiB |
BIN
public/image/poster/riskassessment/riskassessment_03.png
Normal file
|
After Width: | Height: | Size: 380 KiB |
BIN
public/image/poster/riskassessment/riskassessment_04.png
Normal file
|
After Width: | Height: | Size: 376 KiB |
BIN
public/inquire_icons/benrenbuliang.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/inquire_icons/dashuju.png
Normal file
|
After Width: | Height: | Size: 870 B |
BIN
public/inquire_icons/default.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
public/inquire_icons/duiwaitouzi.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/inquire_icons/hunlianzhuangkuang.png
Normal file
|
After Width: | Height: | Size: 838 B |
BIN
public/inquire_icons/jiedaishenqing.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/inquire_icons/jingyingyichang.png
Normal file
|
After Width: | Height: | Size: 852 B |
BIN
public/inquire_icons/renzhijilu.png
Normal file
|
After Width: | Height: | Size: 977 B |
BIN
public/inquire_icons/rongzilishi.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
public/inquire_icons/shenqingxingwei.png
Normal file
|
After Width: | Height: | Size: 970 B |
BIN
public/inquire_icons/shesufengxian.png
Normal file
|
After Width: | Height: | Size: 983 B |
BIN
public/inquire_icons/shourupinggu.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
public/inquire_icons/sifashesu.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/inquire_icons/touzijilu.png
Normal file
|
After Width: | Height: | Size: 956 B |
BIN
public/inquire_icons/weiyueshixin.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/inquire_icons/xiehaozhuanwang.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/inquire_icons/xingzhengchufa.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/inquire_icons/xuelixinxi.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/inquire_icons/zaiwangshichang.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/inquire_icons/zhifubiaoxian.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/logo.png
Normal file
|
After Width: | Height: | Size: 196 KiB |
50
public/robots.txt
Normal file
@@ -0,0 +1,50 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# 允许访问主要页面
|
||||
Allow: /agent
|
||||
Allow: /help
|
||||
Allow: /help/guide
|
||||
Allow: /example
|
||||
Allow: /service
|
||||
Allow: /privacyPolicy
|
||||
Allow: /userAgreement
|
||||
Allow: /agentManageAgreement
|
||||
Allow: /agentSerivceAgreement
|
||||
Allow: /authorization
|
||||
|
||||
# 禁止访问私有页面
|
||||
Disallow: /login
|
||||
Disallow: /me
|
||||
Disallow: /historyQuery
|
||||
Disallow: /promote
|
||||
Disallow: /withdraw
|
||||
Disallow: /report
|
||||
Disallow: /inquire/
|
||||
Disallow: /payment/
|
||||
Disallow: /agent/promoteDetails
|
||||
Disallow: /agent/rewardsDetails
|
||||
Disallow: /agent/promote
|
||||
Disallow: /agent/invitation
|
||||
Disallow: /agent/agentVip
|
||||
Disallow: /agent/vipApply
|
||||
Disallow: /agent/vipConfig
|
||||
Disallow: /agent/withdraw
|
||||
Disallow: /agent/withdrawDetails
|
||||
Disallow: /agent/invitationAgentApply/
|
||||
Disallow: /agent/subordinateList
|
||||
Disallow: /agent/subordinateDetail/
|
||||
|
||||
# 禁止访问API接口
|
||||
Disallow: /api/
|
||||
|
||||
# 禁止访问静态资源目录(可选)
|
||||
Disallow: /assets/
|
||||
Disallow: /js/
|
||||
Disallow: /css/
|
||||
|
||||
# 网站地图
|
||||
Sitemap: https://www.zhinengcha.cn/sitemap.xml
|
||||
|
||||
# 爬取延迟(毫秒)
|
||||
Crawl-delay: 1
|
||||
44
public/site.webmanifest
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "愉悦查|大数据风险报告查询与代理平台,支持个人和企业多场景风控应用",
|
||||
"short_name": "愉悦查",
|
||||
"description": "专业大数据风险报告查询与代理平台,支持个人信用查询、小微企业风控、贷前风险背调等多场景报告应用",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#3498db",
|
||||
"orientation": "portrait-primary",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon-16x16.png",
|
||||
"sizes": "16x16",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/favicon-32x32.png",
|
||||
"sizes": "32x32",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/apple-touch-icon.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
"business",
|
||||
"finance",
|
||||
"utilities"
|
||||
],
|
||||
"lang": "zh-CN",
|
||||
"dir": "ltr"
|
||||
}
|
||||
69
public/sitemap.xml
Normal file
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://www.zhinengcha.cn/</loc>
|
||||
<lastmod>2025-08-01</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.zhinengcha.cn/agent</loc>
|
||||
<lastmod>2025-08-01</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.zhinengcha.cn/help</loc>
|
||||
<lastmod>2025-08-01</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.zhinengcha.cn/help/guide</loc>
|
||||
<lastmod>2025-08-01</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.zhinengcha.cn/example</loc>
|
||||
<lastmod>2025-08-01</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.zhinengcha.cn/service</loc>
|
||||
<lastmod>2025-08-01</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.zhinengcha.cn/privacyPolicy</loc>
|
||||
<lastmod>2025-08-01</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.zhinengcha.cn/userAgreement</loc>
|
||||
<lastmod>2025-08-01</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.zhinengcha.cn/agentManageAgreement</loc>
|
||||
<lastmod>2025-08-01</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.zhinengcha.cn/agentSerivceAgreement</loc>
|
||||
<lastmod>2025-08-01</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.zhinengcha.cn/authorization</loc>
|
||||
<lastmod>2025-08-01</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
152
scripts/generate-dictionaries.js
Normal file
@@ -0,0 +1,152 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const projectRoot = path.resolve(__dirname, '..');
|
||||
|
||||
const FILE_CONFIGS = [
|
||||
{
|
||||
input: path.join(projectRoot, 'IVYZ3P9M_学历信息查询(实时版)_返回字段说明.md'),
|
||||
output: path.join(projectRoot, 'src', 'data', 'ivyz3p9m-dictionary.json'),
|
||||
handler: parseIvyz3p9mDictionary,
|
||||
},
|
||||
{
|
||||
input: path.join(projectRoot, '多头借贷小时级_返回字段说明.md'),
|
||||
output: path.join(projectRoot, 'src', 'data', 'multiLoanHourlyDictionary.json'),
|
||||
handler: parseMultiLoanDictionary,
|
||||
},
|
||||
];
|
||||
|
||||
function parseMarkdownTable(sectionContent, predicate = () => true) {
|
||||
const rows = [];
|
||||
|
||||
sectionContent.split('\n').forEach((line) => {
|
||||
if (!line.trim().startsWith('|')) return;
|
||||
|
||||
const cells = line
|
||||
.split('|')
|
||||
.slice(1, -1)
|
||||
.map((cell) => cell.trim());
|
||||
|
||||
if (cells.length === 0) return;
|
||||
if (cells.some((cell) => /^-+$/.test(cell))) return;
|
||||
|
||||
if (predicate(cells)) {
|
||||
rows.push(cells);
|
||||
}
|
||||
});
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
function parseIvyz3p9mDictionary(markdown) {
|
||||
const result = {
|
||||
educationLevel: {},
|
||||
learningForm: {},
|
||||
specialties: {},
|
||||
schools: {},
|
||||
};
|
||||
|
||||
const sections = markdown.split(/\n(?=## )/);
|
||||
|
||||
sections.forEach((rawSection) => {
|
||||
const headerMatch = rawSection.match(/^##\s*(.+)\n/);
|
||||
if (!headerMatch) return;
|
||||
|
||||
const title = headerMatch[1].trim();
|
||||
const body = rawSection.slice(headerMatch[0].length);
|
||||
|
||||
const isEducation = /学历层次/.test(title);
|
||||
const isLearning = /学习形式/.test(title);
|
||||
const isSchool = /学校/.test(title);
|
||||
const isSpecialty = /专业/.test(title);
|
||||
|
||||
if (!(isEducation || isLearning || isSchool || isSpecialty)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = parseMarkdownTable(body, (cells) => cells.length >= 2);
|
||||
|
||||
rows.forEach((cells) => {
|
||||
const code = cells[0];
|
||||
const name = cells[1];
|
||||
if (!code || !name) return;
|
||||
|
||||
if (isEducation) {
|
||||
result.educationLevel[code] = name;
|
||||
} else if (isLearning) {
|
||||
result.learningForm[code] = name;
|
||||
} else if (isSchool) {
|
||||
result.schools[code] = name;
|
||||
} else if (isSpecialty) {
|
||||
result.specialties[code] = name;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseMultiLoanDictionary(markdown) {
|
||||
const dictionary = {};
|
||||
let headerCells = [];
|
||||
|
||||
markdown.split('\n').forEach((line) => {
|
||||
if (!line.trim().startsWith('|')) return;
|
||||
|
||||
const cells = line
|
||||
.split('|')
|
||||
.slice(1, -1)
|
||||
.map((cell) => cell.trim());
|
||||
|
||||
if (cells.some((cell) => /^-+$/.test(cell))) return;
|
||||
if (cells.length < 2) return;
|
||||
|
||||
if (cells.includes('标签代码')) {
|
||||
headerCells = cells;
|
||||
return;
|
||||
}
|
||||
|
||||
if (headerCells.length === 0) return;
|
||||
|
||||
const data = Object.fromEntries(headerCells.map((header, index) => [header, cells[index] || '']));
|
||||
const code = data['标签代码'];
|
||||
if (!code) return;
|
||||
|
||||
dictionary[code] = {
|
||||
productName: data['产品名称'] || '',
|
||||
labelCategory: data['标签类别'] || '',
|
||||
description: data['业务含义'] || '',
|
||||
dataType: data['类型'] || '',
|
||||
length: data['长度'] || '',
|
||||
};
|
||||
});
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
function ensureDirectoryExists(filePath) {
|
||||
const dir = path.dirname(filePath);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
function run() {
|
||||
FILE_CONFIGS.forEach(({ input, output, handler }) => {
|
||||
if (!fs.existsSync(input)) {
|
||||
console.warn(`Input file not found: ${input}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const markdown = fs.readFileSync(input, 'utf-8');
|
||||
const data = handler(markdown);
|
||||
|
||||
ensureDirectoryExists(output);
|
||||
fs.writeFileSync(output, JSON.stringify(data, null, 2), 'utf-8');
|
||||
console.log(`Generated dictionary: ${path.relative(projectRoot, output)}`);
|
||||
});
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
321
src/App.vue
Normal file
@@ -0,0 +1,321 @@
|
||||
<script setup>
|
||||
import { RouterView, useRouter, useRoute } from "vue-router";
|
||||
const { isWeChat } = useEnv();
|
||||
import { useAgentStore } from "@/stores/agentStore";
|
||||
import { useUserStore } from "@/stores/userStore";
|
||||
import { useDialogStore } from "@/stores/dialogStore";
|
||||
import { useAuthStore } from "@/stores/authStore";
|
||||
import { useAppStore } from "@/stores/appStore";
|
||||
import { useWeixinShare } from "@/composables/useWeixinShare";
|
||||
import BindPhoneDialog from "@/components/BindPhoneDialog.vue";
|
||||
import BindPhoneOnlyDialog from "@/components/BindPhoneOnlyDialog.vue";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const agentStore = useAgentStore();
|
||||
const userStore = useUserStore();
|
||||
const dialogStore = useDialogStore();
|
||||
const authStore = useAuthStore();
|
||||
const appStore = useAppStore();
|
||||
const { setDynamicShare } = useWeixinShare();
|
||||
|
||||
onMounted(async () => {
|
||||
// 初始化应用配置
|
||||
await appStore.fetchAppConfig();
|
||||
|
||||
// 恢复微信授权状态(页面刷新或回调时)
|
||||
authStore.restoreFromStorage();
|
||||
|
||||
// 检查是否是微信授权回调
|
||||
const url = new URL(window.location.href);
|
||||
const code = url.searchParams.get("code");
|
||||
const state = url.searchParams.get("state");
|
||||
|
||||
if (code && state) {
|
||||
// 这是微信授权回调,处理授权结果
|
||||
console.log("Handling WeChat auth callback");
|
||||
await handleWeixinAuthCallback(code);
|
||||
} else {
|
||||
// 正常初始化:加载用户信息等
|
||||
await initializeApp();
|
||||
}
|
||||
getWeixinAuthUrl();
|
||||
|
||||
// 延迟配置微信分享
|
||||
setTimeout(async () => {
|
||||
if (isWeChat.value && window.jWeixin) {
|
||||
await setDynamicShare();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// 监听路由变化更新分享配置
|
||||
router.afterEach(async () => {
|
||||
if (isWeChat.value && window.jWeixin && !authStore.isWeixinAuthing) {
|
||||
await setDynamicShare();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 处理微信授权回调
|
||||
*/
|
||||
const handleWeixinAuthCallback = async (code) => {
|
||||
try {
|
||||
console.log("🔄 WeChat auth callback: code=", code);
|
||||
|
||||
// 调用后端接口交换 token
|
||||
const { data, error } = await useApiFetch("/user/wxh5Auth")
|
||||
.post({ code })
|
||||
.json();
|
||||
|
||||
console.log("📡 wxh5Auth response:", {
|
||||
code: data.value?.code,
|
||||
hasToken: !!data.value?.data?.accessToken,
|
||||
error: error.value
|
||||
});
|
||||
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
// 保存 token
|
||||
const token = data.value.data.accessToken;
|
||||
localStorage.setItem("token", token);
|
||||
localStorage.setItem("refreshAfter", data.value.data.refreshAfter);
|
||||
localStorage.setItem("accessExpire", data.value.data.accessExpire);
|
||||
// ⚠️ 重要:保存 token 后立即设置 tokenVersion,防止被 checkTokenVersion 清除
|
||||
const tokenVersion = import.meta.env.VITE_TOKEN_VERSION || "1.1";
|
||||
localStorage.setItem("tokenVersion", tokenVersion);
|
||||
|
||||
console.log("✅ Token saved successfully, token:", token.substring(0, 20) + "...");
|
||||
console.log("✅ Token saved to localStorage, userId:", data.value.data.userId || "unknown");
|
||||
console.log(`✅ TokenVersion set to ${tokenVersion}`);
|
||||
|
||||
// 验证 token 是否真的保存成功
|
||||
const savedToken = localStorage.getItem("token");
|
||||
if (savedToken === token) {
|
||||
console.log("✅ Token verification: localStorage中的token与保存的token一致");
|
||||
} else {
|
||||
console.error("❌ Token verification failed: localStorage中的token与保存的token不一致");
|
||||
}
|
||||
|
||||
// 清除 URL 中的 code 和 state 参数
|
||||
const url = new URL(window.location.href);
|
||||
const params = new URLSearchParams(url.search);
|
||||
params.delete("code");
|
||||
params.delete("state");
|
||||
const newUrl = `${url.origin}${url.pathname}${params.toString() ? "?" + params.toString() : ""}`;
|
||||
window.history.replaceState({}, "", newUrl);
|
||||
console.log("✅ URL cleaned, removed code and state parameters");
|
||||
|
||||
// 获取用户信息
|
||||
console.log("👤 Fetching user info...");
|
||||
try {
|
||||
await userStore.fetchUserInfo();
|
||||
console.log("✅ User info fetched:", {
|
||||
mobile: userStore.mobile,
|
||||
isLoggedIn: userStore.isLoggedIn
|
||||
});
|
||||
} catch (userInfoErr) {
|
||||
console.error("❌ Failed to fetch user info:", userInfoErr);
|
||||
// 用户信息获取失败,清除 token,跳转到登录
|
||||
authStore.resetAuthState();
|
||||
await router.replace("/login");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取代理信息(已登录用户都尝试获取,后端会判断是否是代理)
|
||||
try {
|
||||
await agentStore.fetchAgentStatus();
|
||||
} catch (agentErr) {
|
||||
console.warn("Warning: Failed to fetch agent status:", agentErr);
|
||||
// 不中断流程,只是警告
|
||||
}
|
||||
|
||||
// 标记授权完成
|
||||
authStore.completeWeixinAuth();
|
||||
console.log("✅ WeChat auth marked as complete");
|
||||
|
||||
// 获取 pendingRoute 并跳转
|
||||
const pendingRoute = authStore.pendingRoute
|
||||
console.log("🎯 pendingRoute:", pendingRoute);
|
||||
|
||||
// if (pendingRoute) {
|
||||
// // ⚠️ 重要:必须先跳转再清除,否则清除后 pendingRoute 为 null
|
||||
// console.log("🚀 Navigating to pendingRoute:", pendingRoute);
|
||||
// await router.replace(pendingRoute);
|
||||
// authStore.clearPendingRoute();
|
||||
// console.log("✅ Navigated to pendingRoute and cleared it");
|
||||
// } else {
|
||||
// // 默认跳转到首页
|
||||
// console.log("📍 No pendingRoute found, navigating to home");
|
||||
// await router.replace("/");
|
||||
// }
|
||||
} else {
|
||||
console.error("❌ WeChat auth failed:", {
|
||||
code: data.value?.code,
|
||||
message: data.value?.msg || data.value?.message,
|
||||
error: error.value
|
||||
});
|
||||
|
||||
// 授权失败,重置状态
|
||||
authStore.resetAuthState();
|
||||
// 跳转到登录页
|
||||
await router.replace("/login");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("❌ Error handling WeChat auth callback:", err);
|
||||
authStore.resetAuthState();
|
||||
await router.replace("/login");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化应用:检查 token,加载用户信息
|
||||
*/
|
||||
const initializeApp = async () => {
|
||||
// 检查 token 版本
|
||||
checkTokenVersion();
|
||||
|
||||
// 尝试刷新 token
|
||||
await refreshTokenIfNeeded();
|
||||
|
||||
// 如果有 token,加载用户信息和代理信息
|
||||
const token = localStorage.getItem("token");
|
||||
if (token) {
|
||||
try {
|
||||
await userStore.fetchUserInfo();
|
||||
// 已登录用户都尝试获取代理信息,后端会判断是否是代理
|
||||
await agentStore.fetchAgentStatus();
|
||||
} catch (err) {
|
||||
console.error("Error loading user info:", err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查 token 版本,清除不兼容的旧 token
|
||||
* ⚠️ 注意:只有在确实需要清除旧 token 时才清除,避免误清除新保存的 token
|
||||
*/
|
||||
const checkTokenVersion = () => {
|
||||
const CURRENT_TOKEN_VERSION = import.meta.env.VITE_TOKEN_VERSION || "1.1";
|
||||
const storedVersion = localStorage.getItem("tokenVersion");
|
||||
const hasToken = !!localStorage.getItem("token");
|
||||
|
||||
// 如果 tokenVersion 不存在或版本不匹配
|
||||
if (!storedVersion || storedVersion !== CURRENT_TOKEN_VERSION) {
|
||||
// 只有在有 token 的情况下才清除(避免清除刚保存的新 token)
|
||||
if (hasToken) {
|
||||
console.log(`Token version mismatch: storedVersion=${storedVersion}, currentVersion=${CURRENT_TOKEN_VERSION}, clearing old auth data`);
|
||||
// Token 版本不匹配,清除旧数据
|
||||
localStorage.removeItem("token");
|
||||
localStorage.removeItem("refreshAfter");
|
||||
localStorage.removeItem("accessExpire");
|
||||
localStorage.removeItem("userInfo");
|
||||
localStorage.removeItem("agentInfo");
|
||||
}
|
||||
// 无论是否有 token,都设置新的 tokenVersion
|
||||
localStorage.setItem("tokenVersion", CURRENT_TOKEN_VERSION);
|
||||
} else {
|
||||
console.log(`Token version check passed: storedVersion=${storedVersion}, currentVersion=${CURRENT_TOKEN_VERSION}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 在需要时刷新 token
|
||||
*/
|
||||
const refreshTokenIfNeeded = async () => {
|
||||
const token = localStorage.getItem("token");
|
||||
if (!token) return;
|
||||
|
||||
const accessExpire = localStorage.getItem("accessExpire");
|
||||
const refreshAfter = localStorage.getItem("refreshAfter");
|
||||
const now = Date.now();
|
||||
|
||||
// 检查 token 是否已过期
|
||||
if (accessExpire) {
|
||||
const expireTime = parseInt(accessExpire) * 1000;
|
||||
if (now > expireTime) {
|
||||
console.log("Token expired");
|
||||
return; // Token 已过期,不刷新,由路由守卫处理
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否需要刷新
|
||||
if (refreshAfter) {
|
||||
const refreshTime = parseInt(refreshAfter) * 1000;
|
||||
if (now < refreshTime) {
|
||||
return; // 还不需要刷新
|
||||
}
|
||||
}
|
||||
|
||||
// 执行 token 刷新
|
||||
try {
|
||||
const { data, error } = await useApiFetch("/user/getToken").post().json();
|
||||
if (data.value && !error.value && data.value.code === 200) {
|
||||
localStorage.setItem("token", data.value.data.accessToken);
|
||||
localStorage.setItem("refreshAfter", data.value.data.refreshAfter);
|
||||
localStorage.setItem("accessExpire", data.value.data.accessExpire);
|
||||
console.log("Token refreshed successfully");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error refreshing token:", err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 发起微信授权 URL
|
||||
* 这个逻辑已经在路由守卫中实现了
|
||||
* 这里保留这个函数的备份,以防需要其他地方调用
|
||||
*/
|
||||
const getWeixinAuthUrl = () => {
|
||||
const isAuthenticated = localStorage.getItem("token");
|
||||
|
||||
// 检查 token 是否过期
|
||||
const accessExpire = localStorage.getItem("accessExpire");
|
||||
const now = Date.now();
|
||||
let isTokenExpired = false;
|
||||
if (accessExpire) {
|
||||
isTokenExpired = now > parseInt(accessExpire) * 1000;
|
||||
}
|
||||
console.log("WeChat auth check:", {
|
||||
isWeChat: isWeChat.value,
|
||||
isAuthenticated,
|
||||
isTokenExpired
|
||||
});
|
||||
if (isWeChat.value && !isAuthenticated && !isTokenExpired) {
|
||||
console.log("🔄 Initiating WeChat auth flow");
|
||||
// 如果正在授权中或已完成授权,则阻止重复授权
|
||||
console.log("Auth store state:", {
|
||||
isWeixinAuthing: authStore.isWeixinAuthing,
|
||||
weixinAuthComplete: authStore.weixinAuthComplete
|
||||
});
|
||||
if (authStore.isWeixinAuthing || authStore.weixinAuthComplete) {
|
||||
return;
|
||||
}
|
||||
// 保存目标路由
|
||||
authStore.startWeixinAuth(route);
|
||||
console.log("🔖 Saved pendingRoute for WeChat auth:", route.fullPath);
|
||||
const appId = import.meta.env.VITE_WECHAT_APP_ID;
|
||||
const url = new URL(window.location.href);
|
||||
const params = new URLSearchParams(url.search);
|
||||
params.delete("code");
|
||||
params.delete("state");
|
||||
const cleanUrl = `${url.origin}${url.pathname}${params.toString() ? "?" + params.toString() : ""
|
||||
}`;
|
||||
const redirectUri = encodeURIComponent(cleanUrl);
|
||||
const weixinAuthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_base&state=snsapi_base#wechat_redirect`;
|
||||
|
||||
console.log(
|
||||
"🔄 Triggering WeChat auth from route guard, pendingRoute:",
|
||||
route.fullPath
|
||||
);
|
||||
window.location.href = weixinAuthUrl;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterView />
|
||||
<BindPhoneDialog />
|
||||
<BindPhoneOnlyDialog />
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
280
src/api/agent.js
Normal file
@@ -0,0 +1,280 @@
|
||||
import useApiFetch from "@/composables/useApiFetch";
|
||||
|
||||
/**
|
||||
* 代理相关API调用统一管理
|
||||
*/
|
||||
|
||||
/**
|
||||
* 构建查询字符串的辅助函数
|
||||
* @param {object} params - 查询参数对象
|
||||
* @returns {string} 查询字符串
|
||||
*/
|
||||
function buildQueryString(params) {
|
||||
const queryParams = new URLSearchParams();
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (
|
||||
params[key] !== undefined &&
|
||||
params[key] !== null &&
|
||||
params[key] !== ""
|
||||
) {
|
||||
queryParams.append(key, params[key]);
|
||||
}
|
||||
});
|
||||
const queryString = queryParams.toString();
|
||||
return queryString ? `?${queryString}` : "";
|
||||
}
|
||||
|
||||
// ==================== 公开接口(无需登录) ====================
|
||||
|
||||
/**
|
||||
* 获取推广链接数据
|
||||
* @param {string} linkIdentifier - 推广链接标识
|
||||
*/
|
||||
export function getLinkData(linkIdentifier) {
|
||||
return useApiFetch(
|
||||
`/agent/link?link_identifier=${encodeURIComponent(linkIdentifier)}`
|
||||
)
|
||||
.get()
|
||||
.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过邀请码申请成为代理(已注册用户)
|
||||
* @param {object} params - 申请参数
|
||||
* @param {string} params.mobile - 手机号
|
||||
* @param {string} params.code - 验证码
|
||||
* @param {string} params.invite_code - 邀请码(必填)
|
||||
* @param {string} params.region - 区域(可选)
|
||||
*/
|
||||
export function applyForAgent(params) {
|
||||
return useApiFetch("/agent/apply").post(params).json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过邀请码注册(同时注册用户和代理)
|
||||
* @param {object} params - 注册参数
|
||||
* @param {string} params.mobile - 手机号
|
||||
* @param {string} params.code - 验证码
|
||||
* @param {string} params.invite_code - 邀请码(必填)
|
||||
* @param {string} params.region - 区域(可选)
|
||||
* @param {string} params.wechat_id - 微信号(可选)
|
||||
*/
|
||||
export function registerByInviteCode(params) {
|
||||
return useApiFetch("/agent/register/invite").post(params).json();
|
||||
}
|
||||
|
||||
// ==================== 需要登录的接口 ====================
|
||||
|
||||
/**
|
||||
* 获取代理信息
|
||||
*/
|
||||
export function getAgentInfo() {
|
||||
return useApiFetch("/agent/info").get().json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理等级特权信息
|
||||
*/
|
||||
export function getLevelPrivilege() {
|
||||
return useApiFetch("/agent/level/privilege").get().json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成推广链接
|
||||
* @param {object} params - 生成参数
|
||||
* @param {number} params.product_id - 产品ID
|
||||
* @param {number} params.set_price - 设定价格
|
||||
*/
|
||||
export function generateLink(params) {
|
||||
return useApiFetch("/agent/generating_link").post(params).json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取产品配置
|
||||
*/
|
||||
export function getProductConfig() {
|
||||
return useApiFetch("/agent/product_config").get().json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取团队统计
|
||||
*/
|
||||
export function getTeamStatistics() {
|
||||
return useApiFetch("/agent/team/statistics").get().json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取团队列表
|
||||
* @param {object} params - 查询参数
|
||||
* @param {number} params.page - 页码
|
||||
* @param {number} params.page_size - 每页数量
|
||||
*/
|
||||
export function getTeamList(params) {
|
||||
const queryString = buildQueryString(params || {});
|
||||
return useApiFetch(`/agent/team/list${queryString}`).get().json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下级列表
|
||||
* @param {object} params - 查询参数
|
||||
* @param {number} params.page - 页码
|
||||
* @param {number} params.page_size - 每页数量
|
||||
*/
|
||||
export function getSubordinateList(params) {
|
||||
const queryString = buildQueryString(params || {});
|
||||
return useApiFetch(`/agent/subordinate/list${queryString}`).get().json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取收益信息
|
||||
*/
|
||||
export function getRevenueInfo() {
|
||||
return useApiFetch("/agent/revenue").get().json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取转化率统计
|
||||
*/
|
||||
export function getConversionRate() {
|
||||
return useApiFetch("/agent/conversion/rate").get().json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取佣金记录
|
||||
* @param {object} params - 查询参数
|
||||
* @param {number} params.page - 页码
|
||||
* @param {number} params.page_size - 每页数量
|
||||
*/
|
||||
export function getCommissionList(params) {
|
||||
const queryString = buildQueryString(params || {});
|
||||
return useApiFetch(`/agent/commission/list${queryString}`).get().json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取返佣记录(推广返佣)
|
||||
* @param {object} params - 查询参数
|
||||
* @param {number} params.page - 页码
|
||||
* @param {number} params.page_size - 每页数量
|
||||
* @param {number} params.rebate_type - 返佣类型(可选):1=直接上级返佣,2=钻石上级返佣,3=黄金上级返佣
|
||||
*/
|
||||
export function getRebateList(params) {
|
||||
const queryString = buildQueryString(params || {});
|
||||
return useApiFetch(`/agent/rebate/list${queryString}`).get().json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取升级返佣记录
|
||||
* @param {object} params - 查询参数
|
||||
* @param {number} params.page - 页码
|
||||
* @param {number} params.page_size - 每页数量
|
||||
*/
|
||||
export function getUpgradeRebateList(params) {
|
||||
const queryString = buildQueryString(params || {});
|
||||
return useApiFetch(`/agent/rebate/upgrade/list${queryString}`).get().json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取升级记录
|
||||
* @param {object} params - 查询参数
|
||||
* @param {number} params.page - 页码
|
||||
* @param {number} params.page_size - 每页数量
|
||||
*/
|
||||
export function getUpgradeList(params) {
|
||||
const queryString = buildQueryString(params || {});
|
||||
return useApiFetch(`/agent/upgrade/list${queryString}`).get().json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请升级
|
||||
* @param {object} params - 升级参数
|
||||
* @param {number} params.to_level - 目标等级:2=黄金,3=钻石
|
||||
*/
|
||||
export function applyUpgrade(params) {
|
||||
return useApiFetch("/agent/upgrade/apply").post(params).json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 钻石代理升级下级
|
||||
* @param {object} params - 升级参数
|
||||
* @param {number} params.subordinate_id - 下级代理ID
|
||||
* @param {number} params.to_level - 目标等级(只能是2=黄金)
|
||||
*/
|
||||
export function upgradeSubordinate(params) {
|
||||
return useApiFetch("/agent/upgrade/subordinate").post(params).json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取提现列表
|
||||
* @param {object} params - 查询参数
|
||||
* @param {number} params.page - 页码
|
||||
* @param {number} params.page_size - 每页数量
|
||||
*/
|
||||
export function getWithdrawalList(params) {
|
||||
const queryString = buildQueryString(params || {});
|
||||
return useApiFetch(`/agent/withdrawal/list${queryString}`).get().json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请提现
|
||||
* @param {object} params - 提现参数
|
||||
* @param {number} params.amount - 提现金额
|
||||
* @param {string} params.payee_account - 收款账户
|
||||
* @param {string} params.payee_name - 收款人姓名
|
||||
*/
|
||||
export function applyWithdrawal(params) {
|
||||
return useApiFetch("/agent/withdrawal/apply").post(params).json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 实名认证
|
||||
* @param {object} params - 实名认证参数
|
||||
* @param {string} params.name - 姓名
|
||||
* @param {string} params.id_card - 身份证号
|
||||
* @param {string} params.mobile - 手机号
|
||||
* @param {string} params.code - 验证码
|
||||
*/
|
||||
export function realNameAuth(params) {
|
||||
return useApiFetch("/agent/real_name").post(params).json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成邀请码
|
||||
* @param {object} params - 生成参数
|
||||
* @param {number} params.count - 生成数量
|
||||
* @param {number} params.expire_days - 过期天数(可选,0表示不过期)
|
||||
* @param {string} params.remark - 备注(可选)
|
||||
*/
|
||||
export function generateInviteCode(params) {
|
||||
return useApiFetch("/agent/invite_code/generate").post(params).json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取邀请码列表
|
||||
* @param {object} params - 查询参数
|
||||
* @param {number} params.page - 页码
|
||||
* @param {number} params.page_size - 每页数量
|
||||
* @param {number} params.status - 状态(可选):0=未使用,1=已使用,2=已失效
|
||||
*/
|
||||
export function getInviteCodeList(params) {
|
||||
const queryString = buildQueryString(params || {});
|
||||
return useApiFetch(`/agent/invite_code/list${queryString}`).get().json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除邀请码
|
||||
* @param {object} params - 删除参数
|
||||
* @param {number} params.id - 邀请码ID
|
||||
*/
|
||||
export function deleteInviteCode(params) {
|
||||
return useApiFetch("/agent/invite_code/delete").post(params).json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取邀请链接
|
||||
* @param {object} params - 请求参数
|
||||
* @param {string} params.invite_code - 邀请码
|
||||
*/
|
||||
export function getInviteLink(params) {
|
||||
const queryString = buildQueryString(params || {});
|
||||
return useApiFetch(`/agent/invite_link${queryString}`).get().json();
|
||||
}
|
||||
31
src/api/user.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import axios from "axios";
|
||||
import useApiFetch from "@/composables/useApiFetch";
|
||||
|
||||
// 获取API基础URL(与生产规则一致:VITE_API_URL)
|
||||
const baseURL = import.meta.env.VITE_API_URL;
|
||||
|
||||
// 手机号验证码登录
|
||||
export function mobileCodeLogin(params) {
|
||||
return useApiFetch("/user/mobileCodeLogin").post(params).json();
|
||||
}
|
||||
|
||||
// 统一认证
|
||||
export function unifiedAuth(params) {
|
||||
return useApiFetch("/user/auth").post(params).json();
|
||||
}
|
||||
|
||||
// 绑定手机号
|
||||
export function bindMobile(params) {
|
||||
return useApiFetch("/user/bindMobile").post(params).json();
|
||||
}
|
||||
|
||||
// 注销账号API
|
||||
export function cancelAccount() {
|
||||
return axios({
|
||||
method: "post",
|
||||
url: `${baseURL}/api/user/cancel`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
26
src/assets/base.css
Normal file
@@ -0,0 +1,26 @@
|
||||
*,
|
||||
*::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;
|
||||
}
|
||||
202
src/assets/colors.css
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 统一颜色变量管理文件
|
||||
* 用于规范化项目中的所有颜色使用
|
||||
* 支持颜色统一管理
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* ===== 主题色系 ===== */
|
||||
--color-primary: #FF6A19;
|
||||
--color-primary-second: #FF8A3D;
|
||||
--color-primary-50: #fff5f0;
|
||||
--color-primary-100: #ffebe1;
|
||||
--color-primary-200: #ffd7c3;
|
||||
--color-primary-300: #ffc3a5;
|
||||
--color-primary-400: #ffaf87;
|
||||
--color-primary-500: #FF6A19;
|
||||
--color-primary-600: #cc5534;
|
||||
--color-primary-700: #994028;
|
||||
--color-primary-800: #662b1c;
|
||||
--color-primary-900: #33150e;
|
||||
|
||||
/* 主题色透明度变体 */
|
||||
--color-primary-light: rgba(255, 106, 25, 0.1);
|
||||
--color-primary-medium: rgba(255, 106, 25, 0.15);
|
||||
--color-primary-dark: rgba(255, 106, 25, 0.8);
|
||||
|
||||
/* ===== 语义化颜色 ===== */
|
||||
--color-success: #07c160;
|
||||
--color-success-50: #f0f9f0;
|
||||
--color-success-100: #e1f5e1;
|
||||
--color-success-200: #c3ebc3;
|
||||
--color-success-300: #a5e1a5;
|
||||
--color-success-400: #87d787;
|
||||
--color-success-500: #07c160;
|
||||
--color-success-600: #059a4c;
|
||||
--color-success-700: #047338;
|
||||
--color-success-800: #024c24;
|
||||
--color-success-900: #012510;
|
||||
--color-success-light: rgba(7, 193, 96, 0.1);
|
||||
--color-success-dark: rgba(7, 193, 96, 0.8);
|
||||
|
||||
--color-warning: #ff976a;
|
||||
--color-warning-50: #fff5f0;
|
||||
--color-warning-100: #ffebe1;
|
||||
--color-warning-200: #ffd7c3;
|
||||
--color-warning-300: #ffc3a5;
|
||||
--color-warning-400: #ffaf87;
|
||||
--color-warning-500: #ff976a;
|
||||
--color-warning-600: #cc7955;
|
||||
--color-warning-700: #995b40;
|
||||
--color-warning-800: #663d2a;
|
||||
--color-warning-900: #331f15;
|
||||
--color-warning-light: rgba(255, 151, 106, 0.1);
|
||||
--color-warning-dark: rgba(255, 151, 106, 0.8);
|
||||
|
||||
--color-danger: #ee0a24;
|
||||
--color-danger-light: rgba(238, 10, 36, 0.1);
|
||||
--color-danger-dark: rgba(238, 10, 36, 0.8);
|
||||
|
||||
--color-info: #1989fa;
|
||||
--color-info-light: rgba(25, 137, 250, 0.1);
|
||||
--color-info-dark: rgba(25, 137, 250, 0.8);
|
||||
|
||||
/* ===== 中性色系 ===== */
|
||||
--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-text-quaternary: #c8c9cc;
|
||||
--color-text-disabled: #c8c9cc;
|
||||
--color-text-white: #ffffff;
|
||||
|
||||
/* ===== 背景颜色 ===== */
|
||||
--color-bg-primary: #ffffff;
|
||||
--color-bg-secondary: #fafafa;
|
||||
--color-bg-tertiary: #f8f8f8;
|
||||
--color-bg-quaternary: #f2f3f5;
|
||||
--color-bg-overlay: rgba(0, 0, 0, 0.5);
|
||||
--color-bg-mask: rgba(0, 0, 0, 0.8);
|
||||
|
||||
/* ===== 边框颜色 ===== */
|
||||
--color-border-primary: #ebedf0;
|
||||
--color-border-secondary: #f2f3f5;
|
||||
--color-border-tertiary: #dcdee0;
|
||||
--color-border-focus: var(--color-primary);
|
||||
|
||||
/* ===== 状态颜色 ===== */
|
||||
--color-active: #f2f3f5;
|
||||
--color-hover: rgba(0, 0, 0, 0.05);
|
||||
--color-focus: var(--color-primary-light);
|
||||
|
||||
/* ===== 阴影颜色 ===== */
|
||||
--color-shadow-light: rgba(0, 0, 0, 0.1);
|
||||
--color-shadow-medium: rgba(0, 0, 0, 0.15);
|
||||
--color-shadow-dark: rgba(0, 0, 0, 0.2);
|
||||
|
||||
/* ===== 业务特定颜色 ===== */
|
||||
--color-service-personal: #FF6A19; /* 个人大数据 */
|
||||
--color-service-company: #FF8A3D; /* 小微企业 */
|
||||
--color-service-loan: #FFB366; /* 贷前背调 */
|
||||
|
||||
/* ===== 渐变色彩 ===== */
|
||||
--gradient-primary: linear-gradient(
|
||||
135deg,
|
||||
var(--color-primary-400),
|
||||
var(--color-primary-600)
|
||||
);
|
||||
--gradient-success: linear-gradient(
|
||||
135deg,
|
||||
var(--color-success),
|
||||
var(--color-success-dark)
|
||||
);
|
||||
--gradient-warning: linear-gradient(
|
||||
135deg,
|
||||
var(--color-warning),
|
||||
var(--color-warning-dark)
|
||||
);
|
||||
--gradient-danger: linear-gradient(
|
||||
135deg,
|
||||
var(--color-danger),
|
||||
var(--color-danger-dark)
|
||||
);
|
||||
}
|
||||
|
||||
/* ===== 工具类 ===== */
|
||||
.text-primary {
|
||||
color: var(--color-primary) !important;
|
||||
}
|
||||
.text-primary-second {
|
||||
color: var(--color-primary-second) !important;
|
||||
}
|
||||
.text-success {
|
||||
color: var(--color-success) !important;
|
||||
}
|
||||
.text-warning {
|
||||
color: var(--color-warning) !important;
|
||||
}
|
||||
.text-danger {
|
||||
color: var(--color-danger) !important;
|
||||
}
|
||||
.text-info {
|
||||
color: var(--color-info) !important;
|
||||
}
|
||||
|
||||
.bg-primary {
|
||||
background-color: var(--color-primary) !important;
|
||||
}
|
||||
.bg-primary-second {
|
||||
background-color: var(--color-primary-second) !important;
|
||||
}
|
||||
.bg-success {
|
||||
background-color: var(--color-success) !important;
|
||||
}
|
||||
.bg-warning {
|
||||
background-color: var(--color-warning) !important;
|
||||
}
|
||||
.bg-danger {
|
||||
background-color: var(--color-danger) !important;
|
||||
}
|
||||
.bg-info {
|
||||
background-color: var(--color-info) !important;
|
||||
}
|
||||
|
||||
.border-primary {
|
||||
border-color: var(--color-primary) !important;
|
||||
}
|
||||
.border-primary-second {
|
||||
border-color: var(--color-primary-second) !important;
|
||||
}
|
||||
.border-success {
|
||||
border-color: var(--color-success) !important;
|
||||
}
|
||||
.border-warning {
|
||||
border-color: var(--color-warning) !important;
|
||||
}
|
||||
.border-danger {
|
||||
border-color: var(--color-danger) !important;
|
||||
}
|
||||
.border-info {
|
||||
border-color: var(--color-info) !important;
|
||||
}
|
||||
|
||||
/* ===== 响应式颜色工具类 ===== */
|
||||
@media (max-width: 768px) {
|
||||
.text-primary-mobile {
|
||||
color: var(--color-primary) !important;
|
||||
}
|
||||
.bg-primary-mobile {
|
||||
background-color: var(--color-primary) !important;
|
||||
}
|
||||
}
|
||||
BIN
src/assets/images/banner.png
Normal file
|
After Width: | Height: | Size: 49 KiB |