This commit is contained in:
Mrx
2026-02-06 17:34:00 +08:00
commit 973432b637
122 changed files with 22603 additions and 0 deletions

24
.env Normal file
View File

@@ -0,0 +1,24 @@
VITE_API_URL=https://www.quannengcha.com
# VITE_API_URL=http://127.0.0.1:8888
VITE_API_PREFIX=/api/v1
VITE_COMPANY_NAME=海南海宇大数据有限公司
VITE_APP_NAME=全能查
VITE_INQUIRE_AES_KEY=ff83609b2b24fc73196aac3d3dfb874f
VITE_WECHAT_APP_ID=wx442ee1ac1ee75917
VITE_ICP_RECORD=琼ICP备2024048057号-2
VITE_PUBLIC_SECURITY_RECORD=琼公网安备46010002000584号
VITE_SHOW_PUBLIC_SECURITY_RECORD=true
VITE_CHAT_AES_KEY=qw5w6SFE2D1jmxyd
VITE_CHAT_AES_IV=345GDFED433223DF
VITE_SHARE_TITLE=全能查|大数据风险报告查询与代理平台,支持个人和企业多场景风控应用
VITE_SHARE_DESC=提供个人信用评估、入职背调、信贷风控、企业风险监测等服务
VITE_SHARE_IMG=https://www.quannengcha.com/logo.png
VITE_TOKEN_VERSION=1.0
VITE_SERVICE_URL=https://www.quannengcha.com/service/

17
.env.example Normal file
View File

@@ -0,0 +1,17 @@
# API 基础地址(必填,请求会发往该域名)
VITE_API_URL=https://your-api-domain.com
# API 路径前缀(后端 qnc-server-v3 使用 /api/v1
VITE_API_PREFIX=/api/v1
# 可选:客服/投诉跳转链接(小程序端通过 webview 打开H5 可作备用)
# VITE_SERVICE_URL=https://www.quannengcha.com/service/
# 可选Chatwoot 在线客服H5 客服页用,未配置则用默认)
# VITE_CHATWOOT_BASE_URL=https://service.quannengcha.com
# VITE_CHATWOOT_WEBSITE_TOKEN=your-website-token
# 可选:公司名、备案号等
# VITE_COMPANY_NAME=全能查
# VITE_ICP_RECORD=京ICP备xxx号
# VITE_PUBLIC_SECURITY_RECORD=京公网安备xxx号
# VITE_SHOW_PUBLIC_SECURITY_RECORD=true
# 可选:历史查询保留天数
# VITE_QUERY_RETENTION_DAYS=30

21
.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
*.local
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.npmrc Normal file
View File

@@ -0,0 +1,3 @@
strict-peer-dependencies=false
auto-install-peers=true
shamefully-hoist=true

15
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"recommendations": [
"vue.volar",
"mrmaoddxxaa.create-uniapp-view",
"uni-helper.uni-helper-vscode",
"uni-helper.uni-app-schemas-vscode",
"uni-helper.uni-highlight-vscode",
"uni-helper.uni-ui-snippets-vscode",
"uni-helper.uni-app-snippets-vscode",
"uni-helper.uni-cloud-snippets-vscode",
"dbaeumer.vscode-eslint",
"antfu.unocss",
"wot-ui.wot-ui-intellisense"
]
}

37
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,37 @@
{
"files.associations": {
"pages.json": "jsonc",
"manifest.json": "jsonc"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
"prettier.enable": false,
"editor.formatOnSave": false,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"nvue",
"uvue",
"html",
"markdown",
"json",
"jsonc",
"yaml",
"toml",
"xml",
"gql",
"graphql",
"astro",
"svelte",
"css",
"less",
"scss",
"pcss",
"postcss"
]
}

31
components.d.ts vendored Normal file
View File

@@ -0,0 +1,31 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by vite-plugin-uni-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
declare module 'vue' {
export interface GlobalComponents {
AppFooter: typeof import('./src/components/AppFooter.vue')['default']
AppLogos: typeof import('./src/components/AppLogos.vue')['default']
InputEntry: typeof import('./src/components/InputEntry.vue')['default']
InvitePoster: typeof import('./src/components/InvitePoster.vue')['default']
PriceInputPopup: typeof import('./src/components/PriceInputPopup.vue')['default']
QRcode: typeof import('./src/components/QRcode.vue')['default']
ReportFeatures: typeof import('./src/components/ReportFeatures.vue')['default']
SectionTitle: typeof import('./src/components/SectionTitle.vue')['default']
WdBadge: typeof import('wot-design-uni/components/wd-badge/wd-badge.vue')['default']
WdButton: typeof import('wot-design-uni/components/wd-button/wd-button.vue')['default']
WdCheckbox: typeof import('wot-design-uni/components/wd-checkbox/wd-checkbox.vue')['default']
WdIcon: typeof import('wot-design-uni/components/wd-icon/wd-icon.vue')['default']
WdInputNumber: typeof import('wot-design-uni/components/wd-input-number/wd-input-number.vue')['default']
WdNavbar: typeof import('wot-design-uni/components/wd-navbar/wd-navbar.vue')['default']
WdPopup: typeof import('wot-design-uni/components/wd-popup/wd-popup.vue')['default']
WdSearch: typeof import('wot-design-uni/components/wd-search/wd-search.vue')['default']
WdSkeleton: typeof import('wot-design-uni/components/wd-skeleton/wd-skeleton.vue')['default']
WdTab: typeof import('wot-design-uni/components/wd-tab/wd-tab.vue')['default']
WdTabs: typeof import('wot-design-uni/components/wd-tabs/wd-tabs.vue')['default']
WdTag: typeof import('wot-design-uni/components/wd-tag/wd-tag.vue')['default']
}
}

3
eslint.config.js Normal file
View File

@@ -0,0 +1,3 @@
import uniHelper from '@uni-helper/eslint-config'
export default uniHelper()

21
index.html Normal file
View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" href="static/logo.svg">
<script>
const coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)')
|| CSS.supports('top: constant(a)'))
document.write(
`<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0${
coverSupport ? ', viewport-fit=cover' : ''}" />`)
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

22
jsconfig.json Normal file
View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"types": [
"vite/client",
"@dcloudio/types",
"@mini-types/alipay",
"miniprogram-api-typings",
"@uni-helper/vite-plugin-uni-pages",
"uni-echarts/global",
"z-paging/types",
"wot-design-uni/global.d.ts",
"@uni-helper/uni-types"
]
},
"vueCompilerOptions": {
"plugins": ["@uni-helper/uni-types/volar-plugin"]
}
}

80
manifest.config.js Normal file
View File

@@ -0,0 +1,80 @@
import { defineManifestConfig } from '@uni-helper/vite-plugin-uni-manifest'
export default defineManifestConfig({
'name': '',
'appid': '',
'description': '',
'versionName': '1.0.0',
'versionCode': '100',
'transformPx': false,
/* 5+App特有相关 */
'app-plus': {
usingComponents: true,
nvueStyleCompiler: 'uni-app',
compilerVersion: 3,
splashscreen: {
alwaysShowBeforeRender: true,
waiting: true,
autoclose: true,
delay: 0,
},
/* 模块配置 */
modules: {},
/* 应用发布信息 */
distribute: {
/* android打包配置 */
android: {
permissions: [
'<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>',
'<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>',
'<uses-permission android:name="android.permission.VIBRATE"/>',
'<uses-permission android:name="android.permission.READ_LOGS"/>',
'<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>',
'<uses-feature android:name="android.hardware.camera.autofocus"/>',
'<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>',
'<uses-permission android:name="android.permission.CAMERA"/>',
'<uses-permission android:name="android.permission.GET_ACCOUNTS"/>',
'<uses-permission android:name="android.permission.READ_PHONE_STATE"/>',
'<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>',
'<uses-permission android:name="android.permission.WAKE_LOCK"/>',
'<uses-permission android:name="android.permission.FLASHLIGHT"/>',
'<uses-feature android:name="android.hardware.camera"/>',
'<uses-permission android:name="android.permission.WRITE_SETTINGS"/>',
],
},
/* ios打包配置 */
ios: {},
/* SDK配置 */
sdkConfigs: {},
},
},
/* 快应用特有相关 */
'quickapp': {},
/* 小程序特有相关 */
'mp-weixin': {
appid: '',
setting: {
urlCheck: false,
},
usingComponents: true,
darkmode: true,
themeLocation: 'theme.json',
},
'mp-alipay': {
usingComponents: true,
},
'mp-baidu': {
usingComponents: true,
},
'mp-toutiao': {
usingComponents: true,
},
'h5': {
darkmode: true,
themeLocation: 'theme.json',
},
'uniStatistics': {
enable: false,
},
'vueVersion': '3',
})

86
package.json Normal file
View File

@@ -0,0 +1,86 @@
{
"name": "qnc_uniapp",
"type": "module",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "unh dev",
"build": "unh build",
"about": "unh info",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"dev:mp-weixin": "unh dev mp-weixin",
"build:mp-weixin": "unh build mp-weixin"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-4080720251210001",
"@dcloudio/uni-app-harmony": "3.0.0-4080720251210001",
"@dcloudio/uni-app-plus": "3.0.0-4080720251210001",
"@dcloudio/uni-components": "3.0.0-4080720251210001",
"@dcloudio/uni-h5": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-alipay": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-baidu": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-harmony": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-jd": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-lark": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-qq": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-toutiao": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-weixin": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-xhs": "3.0.0-4080720251210001",
"@dcloudio/uni-quickapp-webview": "3.0.0-4080720251210001",
"@uni-helper/uni-network": "^0.23.1",
"@uni-helper/uni-promises": "^0.2.1",
"@uni-helper/uni-use": "^0.19.17",
"@vueuse/core": "9.13.0",
"echarts": "^6.0.0",
"pinia": "2.2.4",
"uni-echarts": "^2.4.2",
"vue": "3.4.21",
"vue-i18n": "9.6.2",
"vue-router": "4.5.1",
"wot-design-uni": "^1.14.0",
"z-paging": "^2.8.8",
"qrcode": "^1.5.4",
"qrcode-generator": "^2.0.4"
},
"devDependencies": {
"@binbinji/vite-plugin-component-placeholder": "^0.0.15",
"@dcloudio/types": "3.4.19",
"@dcloudio/uni-automator": "3.0.0-4080720251210001",
"@dcloudio/uni-cli-shared": "3.0.0-4080720251210001",
"@dcloudio/uni-stacktracey": "3.0.0-4080720251210001",
"@dcloudio/vite-plugin-uni": "3.0.0-4080720251210001",
"@iconify-json/carbon": "^1.2.18",
"@mini-types/alipay": "^3.0.14",
"@uni-helper/eslint-config": "^0.6.1",
"@uni-helper/plugin-uni": "0.1.0",
"@uni-helper/unh": "^0.2.10",
"@uni-helper/uni-types": "^1.0.0-alpha.7",
"@uni-helper/unocss-preset-uni": "^0.2.11",
"@uni-helper/vite-plugin-uni-components": "^0.2.6",
"@uni-helper/vite-plugin-uni-layouts": "^0.1.11",
"@uni-helper/vite-plugin-uni-manifest": "^0.2.12",
"@uni-helper/vite-plugin-uni-pages": "^0.3.23",
"@uni-helper/vite-plugin-uni-platform": "^0.0.5",
"@uni-ku/root": "^1.4.1",
"@vue/runtime-core": "3.4.21",
"eslint": "^9.39.2",
"miniprogram-api-typings": "^5.0.0",
"sass": "1.64.2",
"unocss": "66.0.0",
"vite": "5.2.8"
},
"pnpm": {
"overrides": {
"unconfig": "7.3.2"
}
},
"overrides": {
"unconfig": "7.3.2"
},
"resolutions": {
"unconfig": "7.3.2"
}
}

68
pages.config.js Normal file
View File

@@ -0,0 +1,68 @@
import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'
export default defineUniPages({
pages: [
{ path: 'pages/index', type: 'home' },
{ path: 'pages/promote/index' },
{ path: 'pages/agent/index' },
{ path: 'pages/me/index' },
{ path: 'pages/help/index' },
{ path: 'pages/help/detail' },
{ path: 'pages/help/guide' },
{ path: 'pages/register/index' },
{ path: 'pages/historyQuery/index' },
{ path: 'pages/report/index' },
{ path: 'pages/service/index' },
{ path: 'pages/agentSystemGuide/index' },
{ path: 'pages/userAgreement/index' },
{ path: 'pages/privacyPolicy/index' },
{ path: 'pages/agentManageAgreement/index' },
{ path: 'pages/teamList/index' },
{ path: 'pages/agentUpgrade/index' },
{ path: 'pages/promoteDetails/index' },
{ path: 'pages/rewardsDetails/index' },
],
globalStyle: {
backgroundColor: '@bgColor',
backgroundColorBottom: '@bgColorBottom',
backgroundColorTop: '@bgColorTop',
backgroundTextStyle: '@bgTxtStyle',
navigationBarBackgroundColor: '#000000',
navigationBarTextStyle: '@navTxtStyle',
navigationBarTitleText: '全能查',
navigationStyle: 'custom',
},
tabBar: {
color: '#999',
selectedColor: '#1989fa',
backgroundColor: '#fff',
borderStyle: 'black',
list: [
{
pagePath: 'pages/index',
text: '首页',
iconPath: 'static/homelayout/index.png',
selectedIconPath: 'static/homelayout/index_active.png',
},
{
pagePath: 'pages/promote/index',
text: '推广',
iconPath: 'static/homelayout/promote.png',
selectedIconPath: 'static/homelayout/promote_active.png',
},
{
pagePath: 'pages/agent/index',
text: '数据',
iconPath: 'static/homelayout/promote.png',
selectedIconPath: 'static/homelayout/promote_active.png',
},
{
pagePath: 'pages/me/index',
text: '我的',
iconPath: 'static/homelayout/me.png',
selectedIconPath: 'static/homelayout/me_active.png',
},
],
},
subPackages: [],
})

14202
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

7
src/App.ku.vue Normal file
View File

@@ -0,0 +1,7 @@
<script setup>
// @uni-ku/root 根组件,仅保留 KuRootView不展示底部演示条
</script>
<template>
<KuRootView />
</template>

192
src/App.vue Normal file
View File

@@ -0,0 +1,192 @@
<script setup>
import { onLaunch } from '@dcloudio/uni-app'
import { wxminiLogin } from '@/api/user'
import { useUserStore } from '@/stores/userStore'
import { useAgentStore } from '@/stores/agentStore'
import useApiFetch from '@/composables/useApiFetch'
const userStore = useUserStore()
const agentStore = useAgentStore()
// 防止重复登录的标志
let isLoggingIn = false
let loginRetryCount = 0
const MAX_LOGIN_RETRY = 3
onLaunch(() => {
login()
refreshTokenIfNeeded()
getUser()
getAgentInformation()
})
// 微信登录
const login = () => {
const token = uni.getStorageSync('token')
if (token) {
return
}
// 如果正在登录中,直接返回
if (isLoggingIn) {
console.warn('登录正在进行中,跳过重复调用')
return
}
// 内部函数:执行登录逻辑
const doLogin = () => {
// 检查重试次数
if (loginRetryCount >= MAX_LOGIN_RETRY) {
console.error('登录重试次数已达上限,停止重试')
isLoggingIn = false
loginRetryCount = 0
return
}
isLoggingIn = true
loginRetryCount++
uni.login({
provider: 'weixin',
success: (res) => {
const code = res.code
if (!code) {
console.error('获取微信登录code为空')
isLoggingIn = false
return
}
console.log(`[登录] 获取code成功${loginRetryCount}次尝试`)
wxminiLogin({ code }).then((result) => {
if (result.data.value?.code === 200) {
const data = result.data.value.data
uni.setStorageSync('token', data.accessToken)
uni.setStorageSync('refreshAfter', data.refreshAfter)
uni.setStorageSync('accessExpire', data.accessExpire)
console.log('[登录] 登录成功')
isLoggingIn = false
loginRetryCount = 0
getUser()
getAgentInformation()
} else {
// 检查是否是 code 无效错误
const errorMsg = result.data.value?.msg || ''
console.error(`[登录] 登录失败:`, result.data.value)
if (errorMsg.includes('授权码无效') || errorMsg.includes('已过期') || errorMsg.includes('invalid code')) {
console.warn(`[登录] 微信code无效准备重试 (${loginRetryCount}/${MAX_LOGIN_RETRY}):`, errorMsg)
isLoggingIn = false
// 延迟后重试避免频繁请求code 无效时需要重新获取)
setTimeout(() => {
doLogin()
}, 2000) // 增加延迟时间,确保获取新的 code
} else {
// 其他错误,不重试
console.error('[登录] 登录失败,不再重试:', errorMsg)
isLoggingIn = false
loginRetryCount = 0
}
}
}).catch((err) => {
console.error('[登录] 登录请求异常:', err)
isLoggingIn = false
// 网络错误可以重试
if (err?.errMsg?.includes('network') || err?.errMsg?.includes('timeout')) {
setTimeout(() => {
doLogin()
}, 3000)
} else {
loginRetryCount = 0
}
})
},
fail: (err) => {
console.error('[登录] 获取微信登录code失败:', err)
isLoggingIn = false
// 如果是用户取消,不重试;其他错误可以重试
if (!err.errMsg?.includes('cancel')) {
setTimeout(() => {
doLogin()
}, 2000)
} else {
loginRetryCount = 0
}
}
})
}
// 开始登录
doLogin()
}
// 刷新 token
const refreshTokenIfNeeded = async () => {
const token = uni.getStorageSync('token')
const refreshAfter = uni.getStorageSync('refreshAfter')
const accessExpire = uni.getStorageSync('accessExpire')
const currentTime = new Date().getTime()
// 如果 token 已过期,直接返回
if (accessExpire) {
const accessExpireInMilliseconds = parseInt(accessExpire) * 1000 // 转换为毫秒级
if (currentTime > accessExpireInMilliseconds) {
return
}
}
// 如果没有 token直接返回
if (!token) {
return
}
// 如果有 refreshAfter检查当前时间是否超过 refreshAfter
if (refreshAfter) {
const refreshAfterInMilliseconds = parseInt(refreshAfter) * 1000 // 转换为毫秒级
if (currentTime < refreshAfterInMilliseconds) {
return
}
}
// 如果没有 refreshAfter 或者时间超过 refreshAfter执行刷新 token 的请求
try {
const { data } = await useApiFetch('/user/getToken').post().json()
if (data.value?.code === 200) {
uni.setStorageSync('token', data.value.data.accessToken)
uni.setStorageSync('refreshAfter', data.value.data.refreshAfter)
uni.setStorageSync('accessExpire', data.value.data.accessExpire)
}
} catch (error) {
console.error('刷新token失败', error)
}
}
// 获取代理信息
const getAgentInformation = async () => {
const token = uni.getStorageSync('token')
if (!token) {
return
}
try {
await agentStore.fetchAgentStatus()
} catch (error) {
console.error('获取代理信息失败', error)
}
}
// 获取用户信息
const getUser = async () => {
const token = uni.getStorageSync('token')
if (!token) {
return
}
try {
await userStore.fetchUserInfo()
} catch (error) {
console.error('获取用户信息失败', error)
}
}
</script>
<style></style>

148
src/api/agent.js Normal file
View File

@@ -0,0 +1,148 @@
import useApiFetch from '@/composables/useApiFetch'
function buildQueryString(params) {
const parts = []
Object.keys(params || {}).forEach((key) => {
const v = params[key]
if (v !== undefined && v !== null && v !== '')
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(v))}`)
})
const s = parts.join('&')
return s ? `?${s}` : ''
}
export function getAgentInfo() {
return useApiFetch('/agent/info').get().json()
}
export function getTeamStatistics() {
return useApiFetch('/agent/team/statistics').get().json()
}
export function getConversionRate() {
return useApiFetch('/agent/conversion/rate').get().json()
}
export function getRevenueInfo() {
return useApiFetch('/agent/revenue').get().json()
}
/**
* 我的推广收益明细(佣金列表)
* @param {object} params - { page, page_size }
*/
export function getCommissionList(params) {
return useApiFetch(`/agent/commission/list${buildQueryString(params)}`).get().json()
}
/**
* 下级推广收益明细(返佣列表)
* @param {object} params - { page, page_size, rebate_type? }
*/
export function getRebateList(params) {
return useApiFetch(`/agent/rebate/list${buildQueryString(params)}`).get().json()
}
export function getProductConfig() {
return useApiFetch('/agent/product_config').get().json()
}
/**
* 获取推广链接数据
* @param {string} linkIdentifier - 推广链接标识
*/
export function getLinkData(linkIdentifier) {
return useApiFetch(
`/agent/link?link_identifier=${encodeURIComponent(linkIdentifier)}`,
)
.get()
.json()
}
/**
* 生成推广链接
* @param {object} params - 生成参数
* @param {number} params.product_id - 产品ID
* @param {number} params.set_price - 设定价格
* @param {string} [params.target_path] - 目标路径模板(如 /agent/promotionInquire/
*/
export function generateLink(params) {
return useApiFetch('/agent/generating_link').post(params).json()
}
export function getTeamList(params) {
return useApiFetch(`/agent/team/list${buildQueryString(params)}`).get().json()
}
export function getSubordinateList(params) {
return useApiFetch(`/agent/subordinate/list${buildQueryString(params)}`).get().json()
}
export function applyForAgent(params) {
return useApiFetch('/agent/apply').post(params).json()
}
export function registerByInviteCode(params) {
return useApiFetch('/agent/register/invite').post(params).json()
}
export function applyWithdrawal(params) {
return useApiFetch('/agent/withdrawal/apply').post(params).json()
}
export function getWithdrawalList(params) {
return useApiFetch(`/agent/withdrawal/list${buildQueryString(params)}`).get().json()
}
export function realNameAuth(params) {
return useApiFetch('/agent/real_name').post(params).json()
}
export function generateInviteCode(params) {
return useApiFetch('/agent/invite_code/generate').post(params).json()
}
export function getInviteCodeList(params) {
return useApiFetch(`/agent/invite_code/list${buildQueryString(params)}`).get().json()
}
/**
* 删除邀请码
* @param {object} params - { id: 邀请码ID }
*/
export function deleteInviteCode(params) {
return useApiFetch('/agent/invite_code/delete').post(params).json()
}
/**
* 获取邀请链接(短链)
* @param {object} params - { invite_code: 邀请码, target_path?: 目标路径 }
*/
export function getInviteLink(params) {
const queryString = buildQueryString(params || {})
return useApiFetch(`/agent/invite_link${queryString}`).get().json()
}
/**
* 生成邀请海报(带二维码的图片)
* @param {object} params - { invite_link: 邀请链接 }
*/
export function generateInvitePoster(params) {
const queryString = buildQueryString(params || {})
return useApiFetch(`/agent/invite/poster${queryString}`).get().json()
}
/**
* 获取等级权益与升级费用
*/
export function getLevelPrivilege() {
return useApiFetch('/agent/level/privilege').get().json()
}
export function applyUpgrade(params) {
return useApiFetch('/agent/upgrade/apply').post(params).json()
}
export function upgradeSubordinate(params) {
return useApiFetch('/agent/upgrade/subordinate').post(params).json()
}

20
src/api/pay.js Normal file
View File

@@ -0,0 +1,20 @@
import useApiFetch from '@/composables/useApiFetch'
/**
* 发起支付(获取预支付参数)
* @param {object} params
* @param {string} params.id - 业务单 ID如升级记录 upgrade_id
* @param {string} params.pay_method - 支付方式: wechat, alipay, test(仅开发环境)
* @param {string} params.pay_type - 支付类型: query | agent_vip | agent_upgrade
*/
export function payment(params) {
return useApiFetch('/pay/payment').post(params).json()
}
/**
* 查询支付状态
* @param {string} orderNo - 商户订单号
*/
export function paymentCheck(orderNo) {
return useApiFetch('/pay/check').post({ order_no: orderNo }).json()
}

13
src/api/user.js Normal file
View File

@@ -0,0 +1,13 @@
import useApiFetch from '@/composables/useApiFetch'
export function mobileCodeLogin(params) {
return useApiFetch('/user/mobileCodeLogin').post(params).json()
}
export function wxminiLogin(params) {
return useApiFetch('/user/wxMiniAuth').post(params).json()
}
export function bindMobile(params) {
return useApiFetch('/user/bindMobile').post(params).json()
}

View File

@@ -0,0 +1,24 @@
<script setup>
function handleClickGithub() {
if (window?.open) {
window.open('https://github.com/uni-helper/create-uni')
}
else {
uni.showToast({
icon: 'none',
title: '请使用浏览器打开',
})
}
}
</script>
<template>
<view
i-carbon:logo-github
absolute
bottom-1rem left="50%"
translate-x="-50%"
color="#888"
@click="handleClickGithub"
/>
</template>

View File

@@ -0,0 +1,26 @@
<template>
<view inline-flex cursor-default text-2xl font-300>
<view
flex
flex-col
items-center
hover-class="drop-shadow-md drop-shadow-color-green5"
>
<image inline-block h-18 w-18 src="/static/logo.svg" />
<text mt--2 text-green5>
uni-helper
</text>
</view>
<view
text="3xl gray4"
m="x-4 y-auto"
i-carbon-add transform transition-all-500 hover:rotate-135
/>
<view flex flex-col hover-class="drop-shadow-md drop-shadow-color-purple5">
<image inline-block h-18 w-18 src="/static/vite.png" />
<text mt--2 text-purple5>
Vite
</text>
</view>
</view>
</template>

View File

@@ -0,0 +1,36 @@
<script setup>
import { ref } from 'vue'
const name = ref('')
const popupShow = ref(false)
function handleClick() {
popupShow.value = true
}
</script>
<template>
<view class="input-box">
<input
v-model="name"
placeholder="What's your name?"
>
</view>
<view>
<wd-button :disabled="!name" @click="handleClick">
Hello
</wd-button>
</view>
<wd-popup v-model="popupShow" custom-style="padding: 30px 40px;">
Hello{{ ` ${name}` }} 👏
</wd-popup>
</template>
<style scoped lang="scss">
.input-box {
margin: 1rem;
padding: 0.5rem;
border-bottom: 1px solid gray;
}
</style>

View File

@@ -0,0 +1,181 @@
<template>
<view class="invite-poster">
<view v-if="loading" class="poster-loading flex items-center justify-center py-12 text-gray-500 text-sm">
生成海报中
</view>
<view v-else-if="error" class="poster-error rounded-lg bg-gray-100 p-4 text-center text-gray-500 text-sm">
海报加载失败请复制链接分享
</view>
<view v-else class="poster-wrap rounded-xl overflow-hidden bg-white">
<image
:src="posterUrl"
mode="widthFix"
class="poster-image block w-full"
@load="onImageLoad"
@error="onImageError"
/>
<view class="p-4 border-t border-gray-100">
<view class="text-xs text-gray-500 mb-2">长按上方海报保存图片或点击下方按钮保存</view>
<wd-button type="primary" block @click="savePoster">保存海报</wd-button>
</view>
</view>
</view>
</template>
<script setup>
import { ref, watch, onMounted } from 'vue'
import { generateInvitePoster } from '@/api/agent'
const props = defineProps({
inviteLink: { type: String, default: '' },
})
const loading = ref(true)
const error = ref(false)
const posterUrl = ref('')
// 从后端API获取海报
async function generate() {
if (!props.inviteLink) {
loading.value = false
error.value = true
return
}
loading.value = true
error.value = false
try {
console.log('开始生成海报,邀请链接:', props.inviteLink)
const { data, error: apiError } = await generateInvitePoster({
invite_link: props.inviteLink,
})
console.log('海报API响应:', { data: data.value, error: apiError.value })
if (data.value && !apiError.value) {
if (data.value.code === 200 && data.value.data?.poster_url) {
posterUrl.value = data.value.data.poster_url
loading.value = false
console.log('海报生成成功')
} else {
console.error('生成海报失败:', data.value?.msg || data.value)
uni.showToast({ title: data.value?.msg || '生成海报失败', icon: 'none' })
error.value = true
loading.value = false
}
} else {
console.error('生成海报API调用失败:', apiError.value, '响应数据:', data.value)
uni.showToast({ title: apiError.value || '网络请求失败', icon: 'none' })
error.value = true
loading.value = false
}
} catch (e) {
console.error('InvitePoster generate error', e)
uni.showToast({ title: '生成海报异常: ' + (e.message || String(e)), icon: 'none' })
error.value = true
loading.value = false
}
}
function onImageLoad() {
console.log('海报图片加载成功')
}
function onImageError() {
console.error('海报图片加载失败')
error.value = true
loading.value = false
}
function savePoster() {
if (!posterUrl.value) {
uni.showToast({ title: '请等待海报生成完成', icon: 'none' })
return
}
// #ifdef H5
// H5端创建a标签下载
const link = document.createElement('a')
link.href = posterUrl.value
link.download = '邀请海报.png'
link.click()
uni.showToast({ title: '已保存', icon: 'success' })
// #endif
// #ifndef H5
// 小程序端base64 data URL需要先转换为临时文件
if (posterUrl.value.startsWith('data:')) {
// base64 data URL需要转换为临时文件
const base64Data = posterUrl.value.split(',')[1]
const fs = uni.getFileSystemManager()
const filePath = `${uni.env.USER_DATA_PATH}/invite_poster_${Date.now()}.png`
fs.writeFile({
filePath: filePath,
data: base64Data,
encoding: 'base64',
success: () => {
uni.saveImageToPhotosAlbum({
filePath: filePath,
success: () => uni.showToast({ title: '已保存到相册', icon: 'success' }),
fail: (err) => {
if (err.errMsg && err.errMsg.indexOf('auth') !== -1) {
uni.showModal({
title: '提示',
content: '需要您授权保存图片到相册',
success: (m) => m.confirm && uni.openSetting(),
})
} else {
uni.showToast({ title: '保存失败', icon: 'none' })
}
},
})
},
fail: () => uni.showToast({ title: '转换图片失败', icon: 'none' }),
})
} else {
// 普通URL直接下载
uni.downloadFile({
url: posterUrl.value,
success: (res) => {
if (res.statusCode === 200) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => uni.showToast({ title: '已保存到相册', icon: 'success' }),
fail: (err) => {
if (err.errMsg && err.errMsg.indexOf('auth') !== -1) {
uni.showModal({
title: '提示',
content: '需要您授权保存图片到相册',
success: (m) => m.confirm && uni.openSetting(),
})
} else {
uni.showToast({ title: '保存失败', icon: 'none' })
}
},
})
} else {
uni.showToast({ title: '下载失败', icon: 'none' })
}
},
fail: () => uni.showToast({ title: '下载失败', icon: 'none' }),
})
}
// #endif
}
watch(() => props.inviteLink, (val) => {
if (val) generate()
}, { immediate: false })
onMounted(() => {
if (props.inviteLink) generate()
})
</script>
<style scoped>
.poster-image {
display: block;
max-width: 100%;
height: auto;
}
</style>

View File

@@ -0,0 +1,135 @@
<template>
<wd-popup v-model="visible" position="bottom" :safe-area-inset-bottom="true" custom-style="border-radius: 16px 16px 0 0;">
<view class="min-h-[400px] bg-gray-50 text-gray-600 px-4 pb-4">
<view class="h-12 flex items-center justify-center font-semibold text-lg text-gray-800">设置客户查询价</view>
<view class="rounded-xl bg-white p-4 shadow">
<view class="text-base text-gray-700 mb-2">客户查询价 ()</view>
<input
v-model="priceStr"
type="digit"
class="border-b border-gray-200 py-2 text-2xl"
:placeholder="pricePlaceholder"
@blur="onBlurPrice"
/>
<view class="flex justify-between mt-3 text-sm">
<text>推广收益为</text>
<text class="text-orange-500 font-medium">¥ {{ promotionRevenue }}</text>
</view>
<view class="flex justify-between mt-1 text-sm">
<text>底价成本 ¥ {{ baseCost }}</text>
<text>提价成本 ¥ {{ raiseCost }}</text>
</view>
</view>
<view class="rounded-xl bg-white p-4 mt-3 shadow">
<view class="text-base mb-2 text-gray-800">收益与成本说明</view>
<text class="text-sm">推广收益 = 客户查询价 - 我的成本</text>
<text class="text-sm block mt-1">我的成本 = 实际底价 + 提价成本</text>
<text class="text-sm block mt-1">设定范围¥{{ productConfig?.price_range_min ?? 0 }} - ¥{{ productConfig?.price_range_max ?? 9999 }}</text>
</view>
<wd-button type="primary" block class="mt-4" @click="onConfirm">确认</wd-button>
</view>
</wd-popup>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
const props = defineProps({
show: { type: Boolean, default: false },
defaultPrice: { type: Number, required: true },
productConfig: { type: Object, required: true },
})
const emit = defineEmits(['update:show', 'change'])
const visible = computed({
get: () => props.show,
set: (v) => emit('update:show', v),
})
const pricePlaceholder = computed(() => {
const cfg = props.productConfig
const min = cfg?.price_range_min ?? 0
const max = cfg?.price_range_max ?? 9999
return `${min} - ${max}`
})
const priceStr = ref('')
watch(
() => props.show,
(show) => {
if (show) priceStr.value = String(props.defaultPrice ?? '')
},
)
function safeTruncate(num, decimals = 2) {
if (isNaN(num) || !isFinite(num)) return '0.00'
const factor = 10 ** decimals
const scaled = Math.trunc(num * factor)
return (scaled / factor).toFixed(decimals)
}
const baseCost = computed(() => {
if (!props.productConfig?.actual_base_price) return '0.00'
return safeTruncate(Number(props.productConfig.actual_base_price))
})
const raiseCost = computed(() => {
const cfg = props.productConfig
if (!cfg) return '0.00'
const priceNum = Number(priceStr.value) || 0
const threshold = Number(cfg.price_threshold) || 0
const rate = Number(cfg.price_fee_rate) || 0
let cost = 0
if (priceNum > threshold) cost = (priceNum - threshold) * rate
return safeTruncate(cost)
})
const costPrice = computed(() => {
const base = parseFloat(baseCost.value) || 0
const raise = parseFloat(raiseCost.value) || 0
return base + raise
})
const promotionRevenue = computed(() => {
const priceNum = Number(priceStr.value) || 0
return safeTruncate(Math.max(0, priceNum - costPrice.value))
})
function validatePrice() {
const cfg = props.productConfig
const min = Number(cfg?.price_range_min) ?? 0
const max = Number(cfg?.price_range_max) ?? Infinity
let num = Number(priceStr.value)
if (isNaN(num)) {
num = props.defaultPrice
uni.showToast({ title: '请输入有效价格', icon: 'none' })
return { valid: false, value: num }
}
if (num < min) {
uni.showToast({ title: `价格不能低于 ${min}`, icon: 'none' })
priceStr.value = String(min)
return { valid: false, value: min }
}
if (num > max) {
uni.showToast({ title: `价格不能高于 ${max}`, icon: 'none' })
priceStr.value = String(max)
return { valid: false, value: max }
}
return { valid: true, value: num }
}
function onBlurPrice() {
const { value } = validatePrice()
if (value != null) priceStr.value = String(value)
}
function onConfirm() {
const { valid, value } = validatePrice()
if (valid) {
emit('change', value)
visible.value = false
}
}
</script>

41
src/components/QRcode.vue Normal file
View File

@@ -0,0 +1,41 @@
<template>
<wd-popup v-model="visible" position="bottom" :safe-area-inset-bottom="true" custom-style="border-radius: 16px 16px 0 0;">
<view class="p-4 pb-6 bg-white">
<view class="text-center font-semibold text-lg mb-3">{{ title }}</view>
<view class="rounded-lg bg-gray-100 p-3 mb-3 break-all text-sm text-gray-700">{{ fullLink || '暂无链接' }}</view>
<view class="flex gap-3">
<wd-button type="primary" block @click="copyUrl">复制链接</wd-button>
<wd-button type="default" block @click="visible = false">关闭</wd-button>
</view>
</view>
</wd-popup>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
show: { type: Boolean, default: false },
fullLink: { type: String, default: '' },
title: { type: String, default: '推广链接' },
})
const emit = defineEmits(['update:show'])
const visible = computed({
get: () => props.show,
set: (v) => emit('update:show', v),
})
function copyUrl() {
if (!props.fullLink) {
uni.showToast({ title: '暂无链接', icon: 'none' })
return
}
uni.setClipboardData({
data: props.fullLink,
success: () => uni.showToast({ title: '已复制到剪贴板', icon: 'success' }),
fail: () => uni.showToast({ title: '复制失败', icon: 'none' }),
})
}
</script>

View File

@@ -0,0 +1,26 @@
<template>
<view v-if="features && features.length > 0" class="rounded-xl bg-white p-4 shadow">
<view class="mb-3 text-base font-semibold flex items-center text-gray-800">
<view class="w-1 h-5 rounded-full mr-2 bg-primary" />
报告包含内容
</view>
<view class="grid grid-cols-4 gap-2">
<view
v-for="(feature, index) in features"
:key="feature.id || index"
class="aspect-square rounded-xl text-center text-sm text-gray-700 font-medium flex flex-col items-center justify-center p-2 bg-gray-50"
>
<text class="text-xs leading-tight break-all">{{ feature.name }}</text>
</view>
</view>
</view>
</template>
<script setup>
defineProps({
features: {
type: Array,
default: () => [],
},
})
</script>

View File

@@ -0,0 +1,17 @@
<template>
<view class="flex items-center">
<view class="flex items-center gap-2">
<view class="w-1.5 h-5 bg-primary rounded-xl" />
<view class="text-lg text-gray-800">{{ title }}</view>
</view>
</view>
</template>
<script setup>
defineProps({
title: {
type: String,
required: true,
},
})
</script>

View File

@@ -0,0 +1,71 @@
import { ref } from 'vue'
const baseUrl = `${import.meta.env?.VITE_API_URL || ''}${import.meta.env?.VITE_API_PREFIX || '/api'}`
function getPlatform() {
// #ifdef MP-WEIXIN
return 'wxmini'
// #endif
// #ifdef H5
return 'h5'
// #endif
return 'h5'
}
function request(path, options = {}) {
const data = ref(null)
const error = ref(null)
const url = path.startsWith('http') ? path : `${baseUrl}${path.startsWith('/') ? path : '/' + path}`
const separator = url.includes('?') ? '&' : '?'
const finalUrl = `${url}${separator}t=${Date.now()}`
const token = uni.getStorageSync?.('token') || ''
const header = {
'Content-Type': 'application/json',
'X-Platform': getPlatform(),
...(token ? { Authorization: token } : {}),
...options.header,
}
const doRequest = () =>
new Promise((resolve) => {
uni.request({
url: finalUrl,
method: options.method || 'GET',
data: options.body || options.data,
header,
success: (res) => {
if (res.statusCode === 401) {
// 清除 tokenApp.vue 会自动重新登录
uni.removeStorageSync('token')
uni.removeStorageSync('refreshAfter')
uni.removeStorageSync('accessExpire')
uni.removeStorageSync('userInfo')
uni.removeStorageSync('agentInfo')
}
data.value = res.data
error.value = res.statusCode >= 400 ? (res.data?.msg || '请求失败') : null
resolve({ data, error })
},
fail: (err) => {
error.value = err.errMsg || '网络错误'
resolve({ data, error })
},
})
})
return {
json: () => doRequest().then(() => ({ data, error })),
}
}
export default function useApiFetch(path) {
return {
get() {
return request(path, { method: 'GET' })
},
post(body) {
return request(path, { method: 'POST', body })
},
}
}

15
src/composables/useEnv.js Normal file
View File

@@ -0,0 +1,15 @@
import { ref, onMounted } from 'vue'
export function useEnv() {
const isWeChat = ref(false)
onMounted(() => {
// #ifdef H5
const ua = typeof navigator !== 'undefined' ? navigator.userAgent.toLowerCase() : ''
isWeChat.value = /micromessenger/.test(ua)
// #endif
// #ifdef MP-WEIXIN
isWeChat.value = true
// #endif
})
return { isWeChat }
}

View File

@@ -0,0 +1,41 @@
/**
* 微信小程序分享配置(分享给好友、分享到朋友圈)
* 用于 onShareAppMessage / onShareTimeline 的默认文案与图片
*/
const APP_NAME = import.meta.env?.VITE_APP_NAME || '全能查'
/** 默认分享标题 */
export const defaultShareTitle = `${APP_NAME} - 专业大数据风控与查询服务`
/** 默认分享图5:4 比例,建议 500*400不传则使用当前页截图 */
export const defaultShareImageUrl = '/static/index/banner.png'
/**
* 获取「分享给好友」的配置
* @param {Object} options
* @param {string} [options.title] 标题
* @param {string} [options.path] 打开路径,如 'pages/index'
* @param {string} [options.imageUrl] 自定义图片
*/
export function getShareAppMessageOptions({ title = defaultShareTitle, path, imageUrl } = {}) {
return {
title,
path: path || 'pages/index',
imageUrl: imageUrl || defaultShareImageUrl,
}
}
/**
* 获取「分享到朋友圈」的配置
* @param {Object} options
* @param {string} [options.title] 标题
* @param {string} [options.query] 打开页面的 query如 'from=timeline'
* @param {string} [options.imageUrl] 自定义图片
*/
export function getShareTimelineOptions({ title = defaultShareTitle, query = '', imageUrl } = {}) {
return {
title,
query: query || undefined,
imageUrl: imageUrl || defaultShareImageUrl,
}
}

View File

@@ -0,0 +1,11 @@
<template>
<!-- 隐式引用 wd-navbar避免 mp-weixin 编译时因缺少 wd-navbar.wxss ENOENT -->
<wd-navbar v-if="false" title="" />
<!-- 隐式引用 wd-input-number避免 mp-weixin 编译时因缺少 wd-input-number.js ENOENT -->
<wd-input-number v-if="false" :model-value="0" />
<slot />
</template>
<script setup>
// 全局布局占位,后续可在此增加全局通知弹窗等
</script>

View File

@@ -0,0 +1,91 @@
<template>
<view class="home-layout min-h-screen flex flex-col">
<view class="content flex flex-col flex-1">
<slot />
</view>
<!-- 投诉按钮 -->
<view class="complaint-button" @click="toComplaint">
<image
class="w-4 h-4 mr-1 inline-block"
src="/static/homelayout/ts.png"
mode="aspectFit"
/>
<text>投诉</text>
</view>
<view class="disclaimer">
<view class="flex flex-col items-center">
<view v-if="showPublicSecurityRecord" class="flex items-center">
<image
class="w-4 h-4 mr-2"
src="/static/public_security_record_icon.png"
mode="aspectFit"
/>
<text>{{ publicSecurityRecord }}</text>
</view>
<view>
<text class="text-blue-500" @click="openIcp">{{ icpRecord }}</text>
</view>
</view>
<text>{{ companyName }}版权所有</text>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
const companyName = ref(import.meta.env?.VITE_COMPANY_NAME || '')
const icpRecord = ref(import.meta.env?.VITE_ICP_RECORD || '')
const publicSecurityRecord = ref(import.meta.env?.VITE_PUBLIC_SECURITY_RECORD || '')
const showPublicSecurityRecord = ref(import.meta.env?.VITE_SHOW_PUBLIC_SECURITY_RECORD === 'true')
const serviceUrl = ref(import.meta.env?.VITE_SERVICE_URL || '')
const toComplaint = () => {
if (serviceUrl.value) {
// #ifdef H5
window.location.href = serviceUrl.value
// #endif
// #ifndef H5
uni.navigateTo({ url: '/pages/webview/index?url=' + encodeURIComponent(serviceUrl.value) })
// #endif
}
}
const openIcp = () => {
// #ifdef H5
window.open('https://beian.miit.gov.cn', '_blank')
// #endif
}
</script>
<style scoped>
.home-layout {
background: linear-gradient(to bottom, #cfe0fa, #f4f8ff);
}
.complaint-button {
position: fixed;
bottom: 6rem;
right: 1rem;
background: #ff6b6b;
border-radius: 1.5rem;
padding: 0.25rem 1rem;
color: white;
display: flex;
align-items: center;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
z-index: 2000;
}
.disclaimer {
padding: 10px;
font-size: 12px;
color: #999;
text-align: center;
border-top: 1px solid #e0e0e0;
padding-bottom: 60px;
background: #fff;
margin-bottom: 50px;
}
</style>

View File

@@ -0,0 +1,15 @@
<template>
<view class="page-layout min-h-screen">
<slot />
</view>
</template>
<script setup>
// 子页面标题与返回按钮由 pages.json 的 navigationBar 控制
</script>
<style scoped>
.page-layout {
background: linear-gradient(to bottom, rgba(224, 242, 254, 0.2), #fff);
}
</style>

7
src/layouts/default.vue Normal file
View File

@@ -0,0 +1,7 @@
<template>
<slot />
</template>
<script setup>
// 默认布局,仅作为占位
</script>

44
src/main.js Normal file
View File

@@ -0,0 +1,44 @@
import { createSSRApp } from 'vue'
import 'uno.css'
import * as Pinia from 'pinia'
import App from './App.vue'
import { getShareAppMessageOptions, getShareTimelineOptions } from './composables/useWechatShare'
export function createApp() {
const app = createSSRApp(App)
app.use(Pinia.createPinia())
// 微信小程序:全局启用右上角「分享给朋友」「分享到朋友圈」
// #ifdef MP-WEIXIN
app.mixin({
onShareAppMessage() {
const pages = getCurrentPages()
const page = pages[pages.length - 1]
if (!page) return getShareAppMessageOptions()
const route = page.route || 'pages/index'
const options = page.options || {}
const query = Object.keys(options)
.map((k) => `${k}=${encodeURIComponent(options[k])}`)
.join('&')
const path = query ? `${route}?${query}` : route
return getShareAppMessageOptions({ path })
},
onShareTimeline() {
const pages = getCurrentPages()
const page = pages[pages.length - 1]
if (!page) return getShareTimelineOptions()
const options = page.options || {}
const query = Object.keys(options)
.map((k) => `${k}=${encodeURIComponent(options[k])}`)
.join('&')
return getShareTimelineOptions({ query })
},
})
// #endif
return {
app,
Pinia,
}
}

76
src/manifest.json Normal file
View File

@@ -0,0 +1,76 @@
{
"name": "",
"appid": "",
"description": "",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
"app-plus": {
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
},
"modules": {},
"distribute": {
"android": {
"permissions": [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
"ios": {},
"sdkConfigs": {}
}
},
"app-harmony": {
"distribute": {}
},
"mp-harmony": {
"distribute": {}
},
"quickapp": {},
"mp-weixin": {
"appid": "",
"setting": {
"urlCheck": false
},
"usingComponents": true,
"darkmode": true,
"themeLocation": "theme.json"
},
"mp-alipay": {
"usingComponents": true
},
"mp-baidu": {
"usingComponents": true
},
"mp-toutiao": {
"usingComponents": true
},
"uniStatistics": {
"enable": false
},
"vueVersion": "3",
"h5": {
"darkmode": true,
"themeLocation": "theme.json"
}
}

258
src/pages.json Normal file
View File

@@ -0,0 +1,258 @@
{
"globalStyle": {
"backgroundColor": "@bgColor",
"backgroundColorBottom": "@bgColorBottom",
"backgroundColorTop": "@bgColorTop",
"backgroundTextStyle": "@bgTxtStyle",
"navigationBarBackgroundColor": "#000000",
"navigationBarTextStyle": "@navTxtStyle",
"navigationBarTitleText": "全能查",
"navigationStyle": "custom"
},
"pages": [
// GENERATED BY UNI-PAGES, PLATFORM: H5 || MP-WEIXIN
{
"path": "pages/index",
"type": "home",
"layout": "HomeLayout",
"style": {
"navigationBarTitleText": "首页",
"navigationStyle": "custom"
}
},
{
"path": "pages/agent/index",
"type": "page",
"layout": "HomeLayout",
"style": {
"navigationBarTitleText": "数据",
"navigationStyle": "custom"
}
},
{
"path": "pages/agentManageAgreement/index",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "代理管理协议",
"navigationStyle": "default"
}
},
{
"path": "pages/agentSystemGuide/index",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "代理系统指南",
"navigationStyle": "default"
}
},
{
"path": "pages/agentUpgrade/index",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "升级代理",
"navigationStyle": "default"
}
},
{
"path": "pages/help/detail",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "帮助详情",
"navigationStyle": "default"
}
},
{
"path": "pages/help/guide",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "引导指南",
"navigationStyle": "default"
}
},
{
"path": "pages/help/index",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "帮助中心",
"navigationStyle": "default"
}
},
{
"path": "pages/historyQuery/index",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "历史查询",
"navigationStyle": "default"
}
},
{
"path": "pages/invitation/index",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "邀请下级代理",
"navigationStyle": "default"
}
},
{
"path": "pages/me/index",
"type": "page",
"layout": "HomeLayout",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom"
}
},
{
"path": "pages/privacyPolicy/index",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "隐私政策",
"navigationStyle": "default"
}
},
{
"path": "pages/promote/index",
"type": "page",
"layout": "HomeLayout",
"style": {
"navigationBarTitleText": "推广",
"navigationStyle": "custom"
}
},
{
"path": "pages/promote/report",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "推广报告",
"navigationStyle": "default"
}
},
{
"path": "pages/promoteDetails/index",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "我的推广收益",
"navigationStyle": "default"
}
},
{
"path": "pages/register/index",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "注册成为代理",
"navigationStyle": "default"
}
},
{
"path": "pages/report/index",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "报告详情",
"navigationStyle": "default"
}
},
{
"path": "pages/rewardsDetails/index",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "下级推广收益",
"navigationStyle": "default"
}
},
{
"path": "pages/service/index",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "客服",
"navigationStyle": "default"
}
},
{
"path": "pages/teamList/index",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "我的团队",
"navigationStyle": "default"
}
},
{
"path": "pages/userAgreement/index",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "用户协议",
"navigationStyle": "default"
}
},
{
"path": "pages/withdrawDetails/index",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "提现记录",
"navigationStyle": "default"
}
},
// #ifdef MP-WEIXIN
{
"path": "pages/upgradeSubordinate/index",
"type": "page",
"layout": "PageLayout",
"style": {
"navigationBarTitleText": "调整下级级别",
"navigationStyle": "default"
}
}
// #endif
],
"subPackages": [],
"tabBar": {
"color": "#999",
"selectedColor": "#1989fa",
"backgroundColor": "#fff",
"borderStyle": "black",
"list": [
// GENERATED BY UNI-PAGES, PLATFORM: H5 || MP-WEIXIN
{
"pagePath": "pages/index",
"text": "首页",
"iconPath": "static/homelayout/index.png",
"selectedIconPath": "static/homelayout/index_active.png"
},
{
"pagePath": "pages/promote/index",
"text": "推广",
"iconPath": "static/homelayout/promote.png",
"selectedIconPath": "static/homelayout/promote_active.png"
},
{
"pagePath": "pages/agent/index",
"text": "数据",
"iconPath": "static/homelayout/promote.png",
"selectedIconPath": "static/homelayout/promote_active.png"
},
{
"pagePath": "pages/me/index",
"text": "我的",
"iconPath": "static/homelayout/me.png",
"selectedIconPath": "static/homelayout/me_active.png"
}
]
}
}

315
src/pages/agent/index.vue Normal file
View File

@@ -0,0 +1,315 @@
<script setup>
definePage({
layout: 'HomeLayout',
style: {
navigationBarTitleText: '数据',
navigationStyle: 'custom',
},
})
import { ref, onMounted, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { useAgentStore } from '@/stores/agentStore'
import { useUserStore } from '@/stores/userStore'
import { getTeamStatistics, getConversionRate } from '@/api/agent'
const agentStore = useAgentStore()
const userStore = useUserStore()
const { isAgent } = storeToRefs(agentStore)
const { isLoggedIn, mobile } = storeToRefs(userStore)
const teamStats = ref(null)
const conversionData = ref(null)
const myConversionActiveTab = ref('daily')
const subordinateConversionActiveTab = ref('daily')
const isLoadingConversion = ref(true)
const hasLoaded = ref(false)
async function getData() {
try {
const { data: teamData, error: teamError } = await getTeamStatistics()
if (teamData.value?.code === 200 && !teamError.value) teamStats.value = teamData.value.data
const { data: conversionRateData, error: conversionError } = await getConversionRate()
if (conversionRateData.value?.code === 200 && !conversionError.value) conversionData.value = conversionRateData.value.data
} finally {
isLoadingConversion.value = false
}
}
onMounted(() => {
if (isAgent.value && !hasLoaded.value) {
isLoadingConversion.value = true
hasLoaded.value = true
getData()
} else if (!isAgent.value) {
isLoadingConversion.value = false
}
})
watch(isAgent, (val) => {
if (val && !hasLoaded.value) {
isLoadingConversion.value = true
hasLoaded.value = true
getData()
}
})
function toTeamList() {
uni.navigateTo({ url: '/pages/teamList/index' })
}
function toRegister() {
const url = mobile.value
? `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}`
: '/pages/register/index'
uni.navigateTo({ url })
}
</script>
<template>
<view class="p-4 bg-gradient-to-b from-gray-50/50 to-gray-100/30 min-h-screen">
<view v-if="isLoggedIn && !isAgent" class="rounded-xl shadow-lg mb-4 bg-white p-6 text-center">
<view class="text-lg font-bold mb-2 text-gray-800">您还不是代理</view>
<view class="text-sm mb-4 text-gray-500">注册成为代理后即可查看团队统计和转化率数据</view>
<button class="px-6 py-2 rounded-full text-white" style="background: linear-gradient(135deg, #8b5cf6, #7c3aed);" @click="toRegister">
注册成为代理
</button>
</view>
<view v-if="isAgent" class="rounded-xl shadow-lg mb-4 bg-gradient-to-r from-purple-50/60 to-purple-100/50 p-6">
<view class="flex justify-between items-center mb-4">
<view class="flex items-center">
<wd-icon name="user-circle" class="text-xl mr-2" color="#8b5cf6" />
<text class="text-lg font-bold text-gray-800">团队统计</text>
</view>
</view>
<view class="grid grid-cols-3 gap-3 mb-4">
<view class="text-center p-2 rounded-lg bg-white/50">
<view class="text-lg font-bold text-purple-600">{{ teamStats?.total_count || 0 }}</view>
<view class="text-sm mt-1 text-gray-600">团队人数</view>
</view>
<view class="text-center p-2 rounded-lg bg-white/50">
<view class="text-lg font-bold text-purple-600">{{ teamStats?.today_new_members || 0 }}</view>
<view class="text-sm mt-1 text-gray-600">今日新增</view>
</view>
<view class="text-center p-2 rounded-lg bg-white/50">
<view class="text-lg font-bold text-purple-600">{{ teamStats?.month_new_members || 0 }}</view>
<view class="text-sm mt-1 text-gray-600">本月新增</view>
</view>
</view>
<view class="grid grid-cols-4 gap-3 mb-4">
<view class="text-center p-2 rounded-lg bg-white/50">
<view class="text-lg font-bold text-purple-600">{{ teamStats?.direct_count || 0 }}</view>
<view class="text-sm mt-1 text-gray-600">直接下级</view>
</view>
<view class="text-center p-2 rounded-lg bg-white/50">
<view class="text-lg font-bold text-purple-600">{{ teamStats?.indirect_count || 0 }}</view>
<view class="text-sm mt-1 text-gray-600">间接下级</view>
</view>
<view class="text-center p-2 rounded-lg bg-white/50">
<view class="text-lg font-bold text-amber-600">{{ teamStats?.gold_count || 0 }}</view>
<view class="text-sm mt-1 text-gray-600">黄金下级</view>
</view>
<view class="text-center p-2 rounded-lg bg-white/50">
<view class="text-lg font-bold text-gray-600">{{ teamStats?.normal_count || 0 }}</view>
<view class="text-sm mt-1 text-gray-600">普通下级</view>
</view>
</view>
<view class="mt-4">
<button
class="w-full text-white rounded-full py-2 px-4 shadow-md flex items-center justify-center"
style="background: linear-gradient(135deg, #8b5cf6, #7c3aed);"
@click="toTeamList"
>
<wd-icon name="user-circle" class="mr-1" />
查看我的团队
</button>
</view>
</view>
<view v-if="isAgent" class="relative rounded-xl shadow-lg mb-3 bg-white overflow-hidden">
<view class="relative p-5 pb-0">
<view class="flex items-center mb-4">
<wd-icon name="chart-trending" class="text-lg mr-2" color="#1989fa" />
<text class="text-lg font-bold text-gray-800">我的转化率</text>
</view>
<view class="absolute top-3 right-3 flex p-1 rounded-2xl bg-blue-500">
<button
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', myConversionActiveTab === 'daily' ? 'bg-white text-gray-800' : 'text-white']"
@click="myConversionActiveTab = 'daily'"
>
</button>
<button
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', myConversionActiveTab === 'weekly' ? 'bg-white text-gray-800' : 'text-white']"
@click="myConversionActiveTab = 'weekly'"
>
</button>
<button
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', myConversionActiveTab === 'monthly' ? 'bg-white text-gray-800' : 'text-white']"
@click="myConversionActiveTab = 'monthly'"
>
</button>
</view>
</view>
<view class="p-5 pt-0">
<view v-if="!isLoggedIn" class="p-4 rounded-lg bg-gray-50 border border-gray-200 text-center">
<view class="text-sm text-gray-700">正在登录中请稍候...</view>
</view>
<template v-else>
<view v-if="isLoadingConversion" class="p-3">
<wd-skeleton :row="3" />
</view>
<template v-else>
<view v-if="myConversionActiveTab === 'daily'" class="">
<view
v-for="item in (conversionData?.my_conversion_rate?.daily || [])"
:key="item.period_label"
class="p-4 flex items-center border-b border-gray-200"
>
<view class="text-lg font-semibold mr-4 text-gray-800">{{ item.period_label }}</view>
<view class="flex items-center gap-6 text-base text-blue-600">
<text>{{ item.query_user_count || 0 }}人查询</text>
<text>{{ item.paid_user_count || 0 }}人付费</text>
<text>总金额: {{ (item.total_amount || 0).toFixed(2) }}</text>
</view>
</view>
<view v-if="!(conversionData?.my_conversion_rate?.daily?.length)" class="p-6 text-center text-gray-500 text-sm">
暂无数据
</view>
</view>
<view v-if="myConversionActiveTab === 'weekly'" class="">
<view
v-for="item in (conversionData?.my_conversion_rate?.weekly || [])"
:key="item.period_label"
class="p-4 flex items-center border-b border-gray-200"
>
<view class="text-lg font-semibold mr-4 text-gray-800">{{ item.period_label }}</view>
<view class="flex items-center gap-6 text-base text-blue-600">
<text>{{ item.query_user_count || 0 }}人查询</text>
<text>{{ item.paid_user_count || 0 }}人付费</text>
<text>总金额: {{ (item.total_amount || 0).toFixed(2) }}</text>
</view>
</view>
<view v-if="!(conversionData?.my_conversion_rate?.weekly?.length)" class="p-6 text-center text-gray-500 text-sm">
暂无数据
</view>
</view>
<view v-if="myConversionActiveTab === 'monthly'" class="">
<view
v-for="item in (conversionData?.my_conversion_rate?.monthly || [])"
:key="item.period_label"
class="p-4 flex items-center border-b border-gray-200"
>
<view class="text-lg font-semibold mr-4 text-gray-800">{{ item.period_label }}</view>
<view class="flex items-center gap-6 text-base text-blue-600">
<text>{{ item.query_user_count || 0 }}人查询</text>
<text>{{ item.paid_user_count || 0 }}人付费</text>
<text>总金额: {{ (item.total_amount || 0).toFixed(2) }}</text>
</view>
</view>
<view v-if="!(conversionData?.my_conversion_rate?.monthly?.length)" class="p-6 text-center text-gray-500 text-sm">
暂无数据
</view>
</view>
</template>
</template>
</view>
</view>
<view v-if="isAgent" class="relative rounded-xl shadow-lg mb-3 bg-white overflow-hidden">
<view class="relative p-5 pb-0">
<view class="flex items-center mb-4">
<wd-icon name="chart-bar" class="text-lg mr-2" color="#07c160" />
<text class="text-lg font-bold text-gray-800">我的下级转化率</text>
</view>
<view class="absolute top-3 right-3 flex p-1 rounded-2xl bg-blue-500">
<button
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', subordinateConversionActiveTab === 'daily' ? 'bg-white text-gray-800' : 'text-white']"
@click="subordinateConversionActiveTab = 'daily'"
>
</button>
<button
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', subordinateConversionActiveTab === 'weekly' ? 'bg-white text-gray-800' : 'text-white']"
@click="subordinateConversionActiveTab = 'weekly'"
>
</button>
<button
:class="['px-6 py-2 text-sm font-medium rounded-lg mx-1', subordinateConversionActiveTab === 'monthly' ? 'bg-white text-gray-800' : 'text-white']"
@click="subordinateConversionActiveTab = 'monthly'"
>
</button>
</view>
</view>
<view class="p-5 pt-0">
<view v-if="!isLoggedIn" class="p-4 rounded-lg bg-gray-50 border border-gray-200 text-center">
<view class="text-sm text-gray-700">正在登录中请稍候...</view>
</view>
<template v-else>
<view v-if="isLoadingConversion" class="p-3">
<wd-skeleton :row="3" />
</view>
<template v-else>
<view v-if="subordinateConversionActiveTab === 'daily'" class="">
<view
v-for="item in (conversionData?.subordinate_conversion_rate?.daily || [])"
:key="item.period_label"
class="p-4 flex items-center border-b border-gray-200"
>
<view class="text-lg font-semibold mr-4 text-gray-800">{{ item.period_label }}</view>
<view class="flex items-center gap-6 text-base text-blue-600">
<text>{{ item.query_user_count || 0 }}人查询</text>
<text>{{ item.paid_user_count || 0 }}人付费</text>
<text>总金额: {{ (item.total_amount || 0).toFixed(2) }}</text>
</view>
</view>
<view v-if="!(conversionData?.subordinate_conversion_rate?.daily?.length)" class="p-6 text-center text-gray-500 text-sm">
暂无数据
</view>
</view>
<view v-if="subordinateConversionActiveTab === 'weekly'" class="">
<view
v-for="item in (conversionData?.subordinate_conversion_rate?.weekly || [])"
:key="item.period_label"
class="p-4 flex items-center border-b border-gray-200"
>
<view class="text-lg font-semibold mr-4 text-gray-800">{{ item.period_label }}</view>
<view class="flex items-center gap-6 text-base text-blue-600">
<text>{{ item.query_user_count || 0 }}人查询</text>
<text>{{ item.paid_user_count || 0 }}人付费</text>
<text>总金额: {{ (item.total_amount || 0).toFixed(2) }}</text>
</view>
</view>
<view v-if="!(conversionData?.subordinate_conversion_rate?.weekly?.length)" class="p-6 text-center text-gray-500 text-sm">
暂无数据
</view>
</view>
<view v-if="subordinateConversionActiveTab === 'monthly'" class="">
<view
v-for="item in (conversionData?.subordinate_conversion_rate?.monthly || [])"
:key="item.period_label"
class="p-4 flex items-center border-b border-gray-200"
>
<view class="text-lg font-semibold mr-4 text-gray-800">{{ item.period_label }}</view>
<view class="flex items-center gap-6 text-base text-blue-600">
<text>{{ item.query_user_count || 0 }}人查询</text>
<text>{{ item.paid_user_count || 0 }}人付费</text>
<text>总金额: {{ (item.total_amount || 0).toFixed(2) }}</text>
</view>
</view>
<view v-if="!(conversionData?.subordinate_conversion_rate?.monthly?.length)" class="p-6 text-center text-gray-500 text-sm">
暂无数据
</view>
</view>
</template>
</template>
</view>
</view>
</view>
</template>

View File

@@ -0,0 +1,479 @@
<script setup>
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '代理管理协议',
navigationStyle: 'default',
},
})
import { ref } from 'vue'
const companyName = import.meta.env?.VITE_COMPANY_NAME || ''
const appName = import.meta.env?.VITE_APP_NAME || '全能查'
function getFormattedDate() {
const date = new Date()
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
return `${year}${month}${day}`
}
const effectiveDate = ref(getFormattedDate())
</script>
<template>
<view class="agreement-page page-wrap bg-white text-gray-800">
<scroll-view scroll-y class="agreement-scroll" :show-scrollbar="false" enhanced>
<view class="agreement-content box">
<view class="text-center font-bold text-xl mb-4">代理管理制度</view>
<view class="indent-8 mb-2"><text></text><text class="font-bold">前言</text></view>
<view class="indent-8 mb-2">{{ companyName }}为加强对全国代理的统一管理规范各代理行为确保"{{ appName }}"的顺利推广特依据如下原则制定代理管理制度望各级代理认真贯彻严格遵守</view>
<view class="indent-8 mb-2">1.谨慎性原则</view>
<view class="indent-8 mb-2">本着对双方负责的态度请各级代理务必认真贯彻执行本管理制度的工作程序不可草率行事</view>
<view class="indent-8 mb-2">2.用心协助原则</view>
<view class="indent-8 mb-2">{{ companyName }}配合各代理的工作对于代理在推广工作中遇到的问题用心配合解决</view>
<view class="indent-8 mb-2">3.诚信的原则</view>
<view class="indent-8 mb-2">双方务必诚实有信用决不提供虚假信息</view>
<view class="indent-8 mb-2">4.严格管理原则</view>
<view class="indent-8 mb-2">认真贯彻执行各项管理制度对违反管理制度的代理坚决按制度规定予以处罚直至取消代理资格决不姑息迁就</view>
<view class="indent-8 mb-2">5.双方共赢原则</view>
<view class="indent-8 mb-2">{{ companyName }}的目标是与代理共赢共同发展</view>
<view class="indent-8 mb-2">6.长期性原则</view>
<view class="indent-8 mb-2">立足市场与代理长期协作确保代理用心放心地进行市场推广工作</view>
<view class="indent-8 mb-2"><text class="font-bold">总则</text></view>
<view class="indent-8 mb-2">第一条 代理期限为一年代理协议实行一年一签制</view>
<view class="indent-8 mb-2">第二条 本制度规定{{ companyName }}代理(以下称代理)权限运作及业务处理等相关事项旨在使{{ companyName }}与各代理之间持续良好合作关系促进双方共同发展;</view>
<view class="indent-8 mb-2">第三条 代理经{{ companyName }}授权并自代理协议书生效之日起应严格依照代理协议及本制度的规定履行义务享受权利</view>
<view class="indent-8 mb-2">第四条 {{ companyName }}确定的代理应遵循{{ companyName }}的规定从事代理活动不得做出损害{{ companyName }}利益和形象的行为;</view>
<view class="indent-8 mb-2">第五条 代理在代理推广过程中应妥善处理做好售前售中售后的咨询维护工作</view>
<view class="indent-8 mb-2"><text class="font-bold">开通代理账户要求</text></view>
<view class="indent-8 mb-2"><text class="font-bold">个人类</text></view>
<view class="indent-8 mb-2">1完全民事行为能力人</view>
<view class="indent-8 mb-2">2本人实名认证的手机号</view>
<view class="indent-8 mb-2">3首次提现时必须进行本人实名认证并进行人脸识别</view>
<view class="indent-8 mb-2">4全面赞同{{ appName }}的各项制度并能积极参加{{ appName }}为各代理所举办的各种活动;</view>
<view class="indent-8 mb-2">企业类</view>
<view class="indent-8 mb-2">1具有独立法人资格并能提供有效营业执照组织代码证等相关文件复印件经审查合格签定代理协议后即成为{{ companyName }}认证代理</view>
<view class="indent-8 mb-2">2应具备良好的经营规模办公条件设备及人员有固定的营业场所良好的资信潜力和商业信誉并提供以下资料</view>
<view class="indent-8 mb-2">营业执照复印件</view>
<view class="indent-8 mb-2">身份证复印件</view>
<view class="indent-8 mb-2">代理合作协议</view>
<view class="indent-8 mb-2">业务场景展示</view>
<view class="indent-8 mb-2">3全面赞同{{ appName }}的各项制度并能积极参加{{ appName }}为各代理所举办的各种活动;</view>
<view class="indent-8 mb-4"><text class="font-bold">代理权利和义务</text></view>
<view class="indent-8 mb-2">在成为{{ companyName }}的认证代理后可享有如下权利并承担相应的义务:</view>
<view class="indent-8 mb-2">1使用{{ appName }}开展广告宣传市场推广活动;</view>
<view class="indent-8 mb-2">2维护{{ companyName }}及其产品的良好形象;</view>
<view class="indent-8 mb-2">3开拓下级业务推广并负责对其定期进行业务培训;</view>
<view class="indent-8 mb-2">4推广过程中做好售前售中售后工作</view>
<view class="indent-8 mb-2">5如用户需要开具发票代理则需向用户开具咨询费发票如代理未开具发票{{ appName }}有义务配合税务机关采取相关措施</view>
<view class="indent-8 mb-2">6代理业务推广过程中未经{{ companyName }}授权不得使用"{{ appName }}官方"词汇用于广告宣传</view>
<view class="font-bold mb-2">推广管理</view>
<view class="indent-8 mb-2">1{{ appName }}负责建立与代理之间的沟通与联系渠道不定期地向代理提供宣传资料信息政策以及推广方案与管理制度等方面的支持</view>
<view class="indent-8 mb-2"><text class="font-bold">5.1 如何推广报告</text></view>
<view class="indent-8 mb-2">1登录代理账户后进入"推广"页面选择要推广的报告类型如个人大数据婚恋风险入职背调等</view>
<view class="indent-8 mb-2">2系统将自动生成专属推广链接您可以将该链接分享给潜在用户用户通过您的推广链接购买查询服务后您即可获得推广佣金</view>
<view class="indent-8 mb-2">3您可以在推广页面设置查询服务的售价需在平台设定的价格范围内售价与成本价的差额即为您的推广收益</view>
<view class="indent-8 mb-2">4推广链接支持多种分享方式包括复制链接生成二维码生成短链等方便您在不同渠道进行推广</view>
<view class="indent-8 mb-2">5您可以在"推广查询记录"中查看所有通过您推广链接产生的订单详情包括订单金额收益金额订单状态等信息</view>
<view class="indent-8 mb-2">6推广佣金将在用户完成支付后自动结算到您的账户余额中您可以在"我的"页面查看收益统计</view>
<view class="indent-8 mb-2"><text class="font-bold">5.2 如何邀请下级代理</text></view>
<view class="indent-8 mb-2">1登录代理账户后进入"邀请码管理"页面系统会为您生成专属邀请码</view>
<view class="indent-8 mb-2">2您可以将邀请码分享给想要成为代理的用户用户通过您的邀请码注册成为代理后将自动成为您的下级代理</view>
<view class="indent-8 mb-2">3当您的下级代理推广产品产生订单时您将获得一定比例的下级推广返佣返佣比例根据您的代理等级而定等级越高返佣比例越高</view>
<view class="indent-8 mb-2">4您可以在"我的团队"页面查看所有下级代理的信息包括直接下级和间接下级以及团队统计数据</view>
<view class="indent-8 mb-2">5当您的下级代理付费升级为更高等级时如普通代理升级为黄金代理您将获得下级升级返佣只有黄金代理和钻石代理才能获得下级升级返佣</view>
<view class="indent-8 mb-2">6钻石代理拥有特殊权限可以将普通下级代理直接升级为黄金代理无需下级代理付费</view>
<view class="indent-8 mb-2">7您可以在"下级推广收益"中查看所有来自下级代理的返佣明细包括推广返佣和升级返佣</view>
<view class="indent-8 mb-2">8邀请下级代理是扩大团队规模增加收益的重要方式建议您积极发展下级代理建立稳定的推广团队</view>
<view class="indent-8 mb-2">2{{ companyName }}充分尊重代理代理推广权但有下列状况之一时{{ companyName }}将保留或者取消该代理的权利:</view>
<view class="indent-8 mb-2">a代理经营管理不善造成工作无法正常开展的;</view>
<view class="indent-8 mb-2">b国家政策变化等不可抗力发生时;</view>
<view class="indent-8 mb-2">c遇有客户投诉经确认属代理操作不当的;</view>
<view class="indent-8 mb-2">d其他严重损害{{ companyName }}形象与产品形象的行为发生时;</view>
<view class="indent-8 mb-2">e违反国家法律法规时;</view>
<view class="indent-8 mb-2">3当代理名下发生投诉时代理需配合相关的协调否则{{ companyName }}有权无条件取消其代理资格终止其代理协议</view>
<view class="indent-8 mb-2">4代理应合规宣传{{ companyName }}产品形象</view>
<view class="indent-8 mb-2">5市场运作过程中各代理在接到市场投诉时应及时做好记录并报{{ companyName }}相关部门妥善处理</view>
<view class="indent-8 mb-2"><text class="font-bold">违规处罚</text></view>
<view class="indent-8 mb-2">1各代理在推广{{ companyName }}过程中有损害{{ companyName }}产品信誉行为时视情节轻重{{ companyName }}将对其提出书面警告直至取消其代理资格;</view>
<view class="indent-8 mb-2">2未按{{ companyName }}有关规定和本制度开展工作的{{ companyName }}将提出书面警告并限期整改;</view>
<view class="indent-8 mb-2">3不遵守{{ companyName }}的相关规章制度造成与其他推广代理纠纷时{{ companyName }}将视其情节轻重处以20000元以上50000元以下的罚款并取消其代理资格</view>
<view class="indent-8 mb-2">4违反保密义务导致{{ companyName }}重大损失的{{ companyName }}将对其处以5000-20000元罚款情节严重者将直接取消其代理资格</view>
<view class="indent-8 mb-2">5代理如严重违反{{ companyName }}相关规章制度{{ companyName }}可随时解除双方约定的部分或全部协议</view>
<view class="indent-8 mb-2"><text class="font-bold">推广收益及提现</text></view>
<view class="indent-8 mb-2">1用户通过平台推广产品/服务所获得的佣金收益须在平台规定的条件下申请提现</view>
<view class="indent-8 mb-2">2平台有权根据国家税收法律法规对用户佣金收入依法代扣代缴个人所得税</view>
<view class="indent-8 mb-4">3若用户未通过实名认证或未完成相关信息认证平台有权暂缓或拒绝佣金发放</view>
<view class="indent-8 mb-2"><text class="font-bold">税务处理说明</text></view>
<view class="indent-8 mb-2">1用户确认并同意其通过本平台获得的推广佣金奖励分润等收入依法属于"个人所得税"征收范畴</view>
<view class="indent-8 mb-2">2用户同意授权平台代为完成相关税务申报及代扣代缴义务平台有权依据国家相关税收标准在佣金发放前先行扣除应缴税款</view>
<view class="indent-8 mb-2">3用户理解因未能完成实名认证税务资料提交或不配合税务处理流程所导致的提现延迟失败或法律后果平台不承担任何责任</view>
<view class="indent-8 mb-4">4在法律允许的范围内平台有权委托第三方如税务服务平台灵活用工平台等代为完成税务申报发放结算等合规流程</view>
<view class="indent-8 mb-2"><text class="font-bold">信息收集与使用说明</text></view>
<view class="indent-8 mb-2">1用户在申请提现实名认证或佣金结算过程中需向平台提供包括但不限于姓名身份证号银行卡号手机号税务身份信息等个人资料</view>
<view class="indent-8 mb-2">2用户同意平台为履行合同义务税务申报身份核验财务结算等必要目的收集使用存储并在必要范围内共享该等信息</view>
<view class="indent-8 mb-2">3平台承诺遵守国家相关法律法规在取得用户同意的前提下对用户个人信息进行合理保护和使用</view>
<view class="indent-8 mb-2">4在进行税务代扣代缴结算服务时平台有权将必要信息提供给依法合作的第三方税务服务商结算服务商前提是该第三方承担同等信息保护义务</view>
<view class="indent-8 mb-4">5用户有权查询更正其个人信息也可以根据平台流程申请注销账户或停止使用相关服务平台将根据法律要求妥善处理相关信息</view>
<view class="indent-8 mb-4"><text class="font-bold">投诉类处罚</text></view>
<view class="indent-8 mb-2">1代理账户累计投诉率处罚措施</view>
<view class="indent-8 mb-2">a.月查询报告数量200</view>
<!-- 表1 -->
<view class="mb-4 overflow-x-auto">
<view class="agreement-table">
<view class="agreement-table-row bg-gray-100">
<view class="agreement-table-cell w-40 border border-gray-300 p-2">处理类型</view>
<view class="agreement-table-cell border border-gray-300 p-2">提高底价</view>
<view class="agreement-table-cell border border-gray-300 p-2">限制修改查询售价</view>
<view class="agreement-table-cell border border-gray-300 p-2">罚款</view>
<view class="agreement-table-cell border border-gray-300 p-2">禁止提现</view>
<view class="agreement-table-cell border border-gray-300 p-2">封号</view>
<view class="agreement-table-cell border border-gray-300 p-2">黑名单</view>
</view>
<view class="agreement-table-row">
<view class="agreement-table-cell w-40 border border-gray-300 p-2">投诉率5%8%</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">+1</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
</view>
<view class="agreement-table-row">
<view class="agreement-table-cell w-40 border border-gray-300 p-2">投诉率8%10%</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">+3</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
</view>
<view class="agreement-table-row">
<view class="agreement-table-cell w-40 border border-gray-300 p-2">投诉率10%</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">+5</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
</view>
</view>
</view>
<view class="indent-8 mb-2">b.月查询报告数量100</view>
<view class="mb-4 overflow-x-auto">
<view class="agreement-table">
<view class="agreement-table-row bg-gray-100">
<view class="agreement-table-cell w-40 border border-gray-300 p-2">处理类型</view>
<view class="agreement-table-cell border border-gray-300 p-2">提高底价</view>
<view class="agreement-table-cell border border-gray-300 p-2">限制修改查询售价</view>
<view class="agreement-table-cell border border-gray-300 p-2">罚款</view>
<view class="agreement-table-cell border border-gray-300 p-2">禁止提现</view>
<view class="agreement-table-cell border border-gray-300 p-2">封号</view>
<view class="agreement-table-cell border border-gray-300 p-2">黑名单</view>
</view>
<view class="agreement-table-row">
<view class="agreement-table-cell w-40 border border-gray-300 p-2">投诉率6%8%</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">+1</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
</view>
<view class="agreement-table-row">
<view class="agreement-table-cell w-40 border border-gray-300 p-2">投诉率8%15%</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">+5</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">49</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
</view>
<view class="agreement-table-row">
<view class="agreement-table-cell w-40 border border-gray-300 p-2">投诉率15%</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
</view>
</view>
</view>
<view class="indent-8 mb-2">c.月查询报告数量50</view>
<view class="mb-4 overflow-x-auto">
<view class="agreement-table">
<view class="agreement-table-row bg-gray-100">
<view class="agreement-table-cell w-40 border border-gray-300 p-2">处理类型</view>
<view class="agreement-table-cell border border-gray-300 p-2">提高底价</view>
<view class="agreement-table-cell border border-gray-300 p-2">限制修改查询售价</view>
<view class="agreement-table-cell border border-gray-300 p-2">罚款</view>
<view class="agreement-table-cell border border-gray-300 p-2">禁止提现</view>
<view class="agreement-table-cell border border-gray-300 p-2">封号</view>
<view class="agreement-table-cell border border-gray-300 p-2">黑名单</view>
</view>
<view class="agreement-table-row">
<view class="agreement-table-cell w-40 border border-gray-300 p-2">投诉率15%20%</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">+3</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
</view>
<view class="agreement-table-row">
<view class="agreement-table-cell w-40 border border-gray-300 p-2">投诉率20%50%</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">+5</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">39</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
</view>
<view class="agreement-table-row">
<view class="agreement-table-cell w-40 border border-gray-300 p-2">投诉率50%</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
</view>
</view>
</view>
<view class="indent-8 mb-2">备注针对客户自身原因投诉对于产生投诉的代理账户只有三次加底价机会底价只加不减到第四次时直接封号</view>
<view class="indent-8 mb-4">执行时间每月1号出数据统计2号执行</view>
<view class="indent-8 mb-2">2代理单笔投诉处罚措施</view>
<view class="mb-4 overflow-x-auto">
<view class="agreement-table agreement-table-wide">
<view class="agreement-table-row bg-gray-100">
<view class="agreement-table-cell w-36 border border-gray-300 p-2">处理类型</view>
<view class="agreement-table-cell border border-gray-300 p-2">提高底价</view>
<view class="agreement-table-cell border border-gray-300 p-2">冻结推广收益</view>
<view class="agreement-table-cell border border-gray-300 p-2">单笔风险资金冻结</view>
<view class="agreement-table-cell border border-gray-300 p-2">罚款</view>
<view class="agreement-table-cell border border-gray-300 p-2">禁止提现</view>
<view class="agreement-table-cell border border-gray-300 p-2">封号</view>
<view class="agreement-table-cell border border-gray-300 p-2">黑名单</view>
</view>
<view class="agreement-table-row">
<view class="agreement-table-cell w-36 border border-gray-300 p-2">网络公开恶意投诉非欺诈类可解</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
</view>
<view class="agreement-table-row">
<view class="agreement-table-cell w-36 border border-gray-300 p-2">支付宝投诉</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
</view>
<view class="agreement-table-row">
<view class="agreement-table-cell w-36 border border-gray-300 p-2">政务部门投诉</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
</view>
<view class="agreement-table-row">
<view class="agreement-table-cell w-36 border border-gray-300 p-2">非产品质量类客户一般退款</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">1+退款次数最高10元</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
</view>
<view class="agreement-table-row">
<view class="agreement-table-cell w-36 border border-gray-300 p-2">受代理教唆客户恶意退款</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">5+退款次数</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center"></view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
<view class="agreement-table-cell border border-gray-300 p-2 text-center">×</view>
</view>
</view>
</view>
<view class="indent-8 mb-2">3扬言给客户做"数据修复"类投诉措施</view>
<view class="indent-8 mb-2">第一步每接到此类投诉一次所属代理底价+10/并警告</view>
<view class="indent-8 mb-2">第二步警告无效后依旧发生则采取直接封号并加入黑名单</view>
<view class="indent-8 mb-2 text-red-600">备注</view>
<view class="indent-8 mb-2 text-red-600">1为执行项 ×为不执行项</view>
<view class="indent-8 mb-2 text-red-600">2一般投诉罚款投诉金额1倍除退还投诉金额外另行按投诉金额1倍的标准进行罚款</view>
<view class="indent-8 mb-2 text-red-600">投诉金额为45元则退还用户投诉金额45代理罚款45*1=45</view>
<view class="indent-8 mb-2 text-red-600">3公开投诉代理罚款2倍/封号依据具体严重情况而定除退还投诉金额外另行按投诉金额2倍的标准进行罚款</view>
<view class="indent-8 mb-2 text-red-600">投诉金额为45元退还用户金额45则代理罚款45*2=90</view>
<view class="indent-8 mb-2 text-red-600">4如代理发生单笔投诉涉及到本制度第六条所规定的事由将按第六条第七条处罚规则合并执行</view>
<view class="indent-8 mb-2">4冻结封禁类代理处罚措施</view>
<view class="indent-8 mb-2">a.自冻结封禁之日起3个月之内无任何新的投诉舆情升级违法犯罪情形代理可向平台提交"提现申请函"平台根据处罚规则先行处罚后对可提现余额再追加10%的罚款代理可在7个工作日内完成相关的提现操作但该代理账户的推广功能则进入到审核期</view>
<view class="indent-8 mb-2">b.自冻结封禁之日起3个月之内有新的投诉舆情升级违法犯罪情形则平台对该账户的审查期将会延长审查期间无法提现</view>
<view class="indent-8 mb-4"><text class="font-bold">十一封号规则</text></view>
<view class="mb-2">1同一个设备频繁更换账号登录或同一个账号频繁在多个设备登陆系统自动自动检测手机登录IP和设备信息有封号风险一机一号不要频繁切换设备或者账户;</view>
<view class="mb-2">2欺诈用户诱导用户;</view>
<view class="mb-2">3先付款后退款等承诺;</view>
<view class="mb-2">4保证高额下款;</view>
<view class="mb-2">5使用数据优化征信优化等骗取用户钱财</view>
<view class="mb-2">6发布涉嫌性骚扰的文字图片;</view>
<view class="mb-2">7使用含色情淫秽意味或其他令人不适的头像或资料;</view>
<view class="mb-2">8触犯新广告法;</view>
<view class="mb-2">9在朋友圈中使用辱骂恐吓威胁等言论;</view>
<view class="mb-2">10发布各类垃圾广告恶意信息诱骗信息;</view>
<view class="mb-2">11盗用他人头像或资料伪装他人身份;</view>
<view class="mb-2">12多人举报的账号并涉及恶意诈骗;</view>
<view class="mb-2">13频繁被举报每月超过20次以上的代理账户</view>
<view class="mb-2">14恶意投诉比如没有异议非说有异议且无法提供有效证明材料各种奇葩投诉</view>
<view class="mb-2">15租用账号发布不良言论诈骗信息</view>
<view class="mb-4">16发布不当政治言论或者任何违反国家法规政策的言论</view>
<view class="mb-4">更多详细内容请认真阅读{{ appName }}代理协议</view>
<view class="font-bold mb-2">退款的规则及途径</view>
<view class="font-bold mb-2">退款规则</view>
<view class="mb-2">1自订单支付完成后3天内为有效期在3天内可申请退款</view>
<view class="mb-2">2超过报告有效期3天则无法办理退款</view>
<view class="mb-2">3符合相关退款条件的用户退款时仅退还实付金额</view>
<view class="mb-2">4用户购买报告成功后因不可抗力等法定原因或平台原因导致平台无法提供服务用户可联系客服发起退款</view>
<view class="mb-2">5若因用户的失误重复付款则支持退款重复金额</view>
<view class="mb-2">6服务已发生且不符合退款情形的费用不予退款</view>
<view class="mb-2">7如代理在市场推广中存在欺诈等相关行为用户可提供有效的凭证办理退款事宜</view>
<view class="mb-4">8产品呈现的情况与用户本人实际情况不符用户可提供有效的凭证发起退款申请</view>
<view class="font-bold mb-2">用户发起投诉</view>
<view class="mb-2">1.当代理拒绝退款用户与代理双方线下也未达成一致时用户可联系客服发起投诉</view>
<view class="mb-2">2.用户提交投诉后请用户和代理按照相关提示举证完成举证后客服将介入处理纠纷</view>
<view class="mb-2">3.平台客服介入前若用户与代理双方已对退款协商一致商家可直接联系平台客服说明情况或者用户联系平台提供撤销投诉函并说明情况即可同时投诉会关闭</view>
<view class="mb-2">4.平台客服介入后若需要用户与代理提供举证信息可发送相关材料至邮箱方便客服及时处理</view>
<view class="mb-4">5.平台客服会根据举证信息联系用户与代理双方处理投诉</view>
<view class="indent-8 font-bold mb-2">十二退款服务以及流程</view>
<view class="indent-8 mb-4">自用户购买查询报告成功之日起无论由于何种原因用户均可向平台申请退款不适用退款服务的情况除外</view>
<view class="flex flex-col items-center mb-4">
<view class="mb-2">用户发起退款申请</view>
<view class="mb-2"></view>
<view class="mb-2">提供有效证明凭证</view>
<view class="mb-2"></view>
<view class="mb-2">平台审核是否符合退款标准</view>
<view class="mb-2"></view>
<view class="mb-2">确认无误后退款完成</view>
<view class="mb-2">预计1-7个工作日内完成</view>
</view>
<view class="font-bold mb-2">退款流程说明</view>
<view class="indent-8 mb-2">原路返回----直接把金额退回到用户付款的来源方包括但不限于支付宝帐户暂不收取手续费</view>
<view class="indent-8 mb-4">具体操作流程在发生退款时用户可在查询页面点击"联系客服"发起"申请退款"申请并提供有效的证明凭证客服提交至平台系统系统通过审核后相关的退款金额将在1-7个工作日内原路返回对应的付款账户中通过网银或支付宝等第三方支付平台进行支付的费用将直接退到原账户</view>
<view class="font-bold mb-2">不适用退款服务的情况</view>
<view class="indent-8 mb-2">1已超过退款期限</view>
<view class="indent-8 mb-2">2恶意投诉</view>
<view class="indent-8 mb-4">3违反用户使用协议相关规则</view>
<view class="font-bold mb-2">补充说明</view>
<view class="indent-8 mb-4">如您需要退款的产品类型不在以上3天或者超出了30天限制则无法办理退款如您有产品使用方面的疑问您可以通过联系客服进行反馈</view>
<view class="indent-8 mb-2"><text class="font-bold">十三附则</text></view>
<view class="indent-8 mb-2">1本制度作为代理协议之附件与代理协议具有同等法律效力</view>
<view class="indent-8 mb-2">2{{ companyName }}将本着"诚信为本、长期服务"的宗旨和"公平合理"的原则对代理进行合理布局和调整以实现互利互惠共同快速发展的目的</view>
<view class="indent-8 mb-2">3因其他原因需终止代理关系需向{{ companyName }}提出书面申请</view>
<view class="indent-8 mb-2">4代理之间发生业务竞争和冲突{{ companyName }}将依据公平公正公开的原则按相关制度予以调解处理</view>
<view class="indent-8 mb-2">5{{ companyName }}与各代理之间出现协议上的纠纷{{ companyName }}所在地法院裁决</view>
<view class="indent-8 mb-2">6本制度的制定修改与废止皆经由{{ companyName }}讨论决定解释权归{{ companyName }}所有</view>
<view class="indent-8 mb-4">7本制度于2022年1月1日起实施公司将根据实施情况对本制度进行修正和调整</view>
<view class="indent-8 mb-8">本制度一经网上点击/勾选即代表理解并同意勾选遵守</view>
<view class="text-right pb-8 safe-area-bottom">本制度于 {{ effectiveDate }} 生效</view>
</view>
</scroll-view>
</view>
</template>
<style lang="scss" scoped>
.page-wrap {
width: 100%;
height: 100vh;
box-sizing: border-box;
}
.agreement-scroll {
width: 100%;
height: 100%;
box-sizing: border-box;
}
.agreement-content {
padding-left: 48rpx;
padding-right: 48rpx;
padding-top: 24rpx;
padding-bottom: 24rpx;
box-sizing: border-box;
}
.safe-area-bottom {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.indent-8 {
text-indent: 2em;
}
.box {
max-width: 100%;
}
.agreement-table {
min-width: 640px;
display: flex;
flex-direction: column;
}
.agreement-table-wide {
min-width: 800px;
}
.agreement-table-row {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
}
.agreement-table-cell {
flex: 1;
min-width: 0;
font-size: 12px;
}
.agreement-table-cell.w-40 {
flex: 0 0 100px;
min-width: 100px;
}
.agreement-table-cell.w-36 {
flex: 0 0 90px;
min-width: 90px;
}
</style>

View File

@@ -0,0 +1,387 @@
<script setup>
import { storeToRefs } from 'pinia'
import { useAgentStore } from '@/stores/agentStore'
import { useUserStore } from '@/stores/userStore'
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '代理系统指南',
navigationStyle: 'default',
},
})
const agentStore = useAgentStore()
const userStore = useUserStore()
const { isAgent, level } = storeToRefs(agentStore)
const { isLoggedIn } = storeToRefs(userStore)
function toRegister() {
uni.navigateTo({ url: '/pages/register/index' })
}
function toUpgrade() {
uni.navigateTo({ url: '/pages/agentUpgrade/index' })
}
function toAgreement() {
uni.navigateTo({ url: '/pages/agentManageAgreement/index' })
}
</script>
<template>
<view class="agent-system-guide page-wrap bg-[#f7f8fa]">
<scroll-view scroll-y class="guide-scroll" :show-scrollbar="false" enhanced>
<view class="guide-content pt-6 pb-4">
<view class="text-2xl font-bold mb-1 text-gray-800">代理系统指南</view>
<view class="text-sm text-gray-500">了解代理政策等级特权与收益计算</view>
</view>
<view class="guide-content pb-6 flex flex-col gap-4">
<!-- 代理政策概述 -->
<view class="bg-white rounded-lg p-4 shadow-sm">
<view class="flex items-center mb-3">
<view class="w-1 h-5 rounded-full mr-2 bg-primary" />
<view class="text-lg font-semibold text-gray-800">代理政策概述</view>
</view>
<view class="flex flex-col gap-2.5 text-sm leading-relaxed text-gray-600">
<view>
全能查代理系统采用三级代理体系为合作伙伴提供多层级多收益的推广模式
通过推广平台产品和服务代理可获得推广佣金下级返佣升级返佣等多种收益
</view>
<view>
系统分为<text class="font-semibold">普通代理</text><text class="font-semibold">黄金代理</text><text class="font-semibold">钻石代理</text>三个等级
不同等级享有不同的权限和收益比例
</view>
<view class="text-sm mt-2 text-gray-400">
详细政策请查看代理管理协议
</view>
</view>
</view>
<!-- 代理等级说明 -->
<view class="bg-white rounded-lg p-4 shadow-sm">
<view class="flex items-center mb-4">
<view class="w-1 h-5 rounded-full mr-2 bg-primary" />
<view class="text-lg font-semibold text-gray-800">代理等级体系</view>
</view>
<view class="flex flex-col gap-4">
<!-- 普通代理 -->
<view class="border rounded-lg p-4 border-gray-200 bg-gray-50">
<view class="flex items-center justify-between mb-3">
<view class="flex items-center">
<view class="w-10 h-10 rounded-full bg-gray-400 flex items-center justify-center text-white font-semibold text-base mr-3">1</view>
<view>
<view class="text-base font-semibold text-gray-800">普通代理</view>
<view class="text-sm text-gray-500">基础代理特权</view>
</view>
</view>
<view class="px-2.5 py-1 rounded-full text-sm font-medium bg-gray-100 text-gray-600">入门级</view>
</view>
<view class="flex flex-col gap-1.5 text-sm text-gray-600">
<view class="flex items-start"><text class="text-green-500 mr-1.5 mt-0.5"></text><text>推广产品获得推广佣金</text></view>
<view class="flex items-start"><text class="text-green-500 mr-1.5 mt-0.5"></text><text>邀请下级代理获得下级推广返佣</text></view>
<view class="flex items-start"><text class="text-green-500 mr-1.5 mt-0.5"></text><text>查看团队统计和转化率数据</text></view>
<view class="flex items-start"><text class="text-green-500 mr-1.5 mt-0.5"></text><text>生成和管理邀请码</text></view>
<view class="flex items-start"><text class="text-green-500 mr-1.5 mt-0.5"></text><text>申请升级为黄金或钻石代理</text></view>
</view>
</view>
<!-- 黄金代理 -->
<view class="border rounded-lg p-4 border-amber-300 bg-amber-50">
<view class="flex items-center justify-between mb-3">
<view class="flex items-center">
<view class="w-10 h-10 rounded-full flex items-center justify-center text-white font-semibold text-base mr-3 bg-amber-500">2</view>
<view>
<view class="text-base font-semibold text-amber-800">黄金代理</view>
<view class="text-sm text-amber-700">高级代理特权</view>
</view>
</view>
<view class="px-2.5 py-1 rounded-full text-sm font-medium bg-amber-100 text-amber-800">进阶级</view>
</view>
<view class="flex flex-col gap-1.5 text-sm text-gray-600">
<view class="flex items-start"><text class="text-green-500 mr-1.5 mt-0.5"></text><text class="font-medium">享有普通代理所有权限</text></view>
<view class="flex items-start"><text class="text-amber-500 mr-1.5 mt-0.5"></text><text>更高的推广佣金比例</text></view>
<view class="flex items-start"><text class="text-amber-500 mr-1.5 mt-0.5"></text><text>更高的下级推广返佣比例</text></view>
<view class="flex items-start"><text class="text-amber-500 mr-1.5 mt-0.5"></text><text>获得下级升级返佣当普通下级升级为黄金时</text></view>
<view class="flex items-start"><text class="text-amber-500 mr-1.5 mt-0.5"></text><text>可申请升级为钻石代理</text></view>
</view>
</view>
<!-- 钻石代理 -->
<view class="border rounded-lg p-4 border-purple-400 bg-purple-50">
<view class="flex items-center justify-between mb-3">
<view class="flex items-center">
<view class="w-10 h-10 rounded-full flex items-center justify-center text-white font-semibold text-base mr-3 bg-purple-600">💎</view>
<view>
<view class="text-base font-semibold text-purple-800">钻石代理</view>
<view class="text-sm text-purple-600">尊享代理特权</view>
</view>
</view>
<view class="px-2.5 py-1 rounded-full text-sm font-medium bg-purple-200 text-purple-800">最高级</view>
</view>
<view class="flex flex-col gap-1.5 text-sm text-gray-600">
<view class="flex items-start"><text class="text-green-500 mr-1.5 mt-0.5"></text><text class="font-medium">享有黄金代理所有权限</text></view>
<view class="flex items-start"><text class="text-purple-500 mr-1.5 mt-0.5"></text><text>最高推广佣金比例</text></view>
<view class="flex items-start"><text class="text-purple-500 mr-1.5 mt-0.5"></text><text>最高下级推广返佣比例</text></view>
<view class="flex items-start"><text class="text-purple-500 mr-1.5 mt-0.5"></text><text>获得下级升级返佣当普通/黄金下级升级时</text></view>
<view class="flex items-start"><text class="text-purple-500 mr-1.5 mt-0.5"></text><text class="font-medium">可调整下级代理等级将普通代理升级为黄金代理</text></view>
<view class="flex items-start"><text class="text-purple-500 mr-1.5 mt-0.5"></text><text>专属客服支持</text></view>
</view>
</view>
</view>
</view>
<!-- 收益计算说明 -->
<view class="bg-white rounded-lg p-4 shadow-sm">
<view class="flex items-center mb-4">
<view class="w-1 h-5 rounded-full mr-2 bg-primary" />
<view class="text-lg font-semibold text-gray-800">收益计算方式</view>
</view>
<view class="flex flex-col gap-3">
<!-- 推广佣金 -->
<view class="rounded-lg p-3 border-l-4 border-blue-500 bg-blue-50">
<view class="flex items-center mb-2">
<view class="w-5 h-5 rounded mr-2 bg-blue-500 flex items-center justify-center text-white text-xs">💰</view>
<view class="text-base font-semibold text-blue-800">推广佣金</view>
</view>
<view class="text-sm mb-2.5 text-gray-600">
当您推广的产品被用户购买并支付成功后您将获得推广佣金
</view>
<view class="bg-white rounded p-2.5 text-sm">
<view class="font-medium mb-1.5 text-gray-800">计算公式</view>
<view class="mb-1.5 text-gray-600">推广佣金 = 订单金额 × 佣金比例</view>
<view class="text-sm text-gray-400">佣金比例根据您的代理等级和产品类型而定等级越高佣金比例越高</view>
</view>
</view>
<!-- 下级推广返佣 -->
<view class="rounded-lg p-3 border-l-4 border-green-500 bg-green-50">
<view class="flex items-center mb-2">
<view class="w-5 h-5 rounded mr-2 bg-green-500 flex items-center justify-center text-white text-xs">🎁</view>
<view class="text-base font-semibold text-green-800">下级推广返佣</view>
</view>
<view class="text-sm mb-2.5 text-gray-600">
当您的下级代理推广产品产生订单时您将获得一定比例的返佣
</view>
<view class="bg-white rounded p-2.5 text-sm">
<view class="font-medium mb-1.5 text-gray-800">返佣规则</view>
<view class="mb-1.5 text-gray-600"> 直接下级获得直接下级推广订单的返佣</view>
<view class="mb-1.5 text-gray-600"> 间接下级获得间接下级推广订单的返佣</view>
<view class="mb-1.5 text-gray-600"> 返佣比例根据您的代理等级而定</view>
<view class="text-sm text-gray-400">返佣金额 = 下级订单金额 × 返佣比例</view>
</view>
</view>
<!-- 下级升级返佣 -->
<view class="rounded-lg p-3 border-l-4 border-purple-500 bg-purple-50">
<view class="flex items-center mb-2">
<view class="w-5 h-5 rounded mr-2 bg-purple-500 flex items-center justify-center text-white text-xs"></view>
<view class="text-base font-semibold text-purple-800">下级升级返佣</view>
</view>
<view class="text-sm mb-2.5 text-gray-600">
当您的下级代理付费升级为更高等级时您将获得升级返佣
</view>
<view class="bg-white rounded p-2.5 text-sm">
<view class="font-medium mb-1.5 text-gray-800">返佣规则</view>
<view class="mb-1.5 text-gray-600"> 普通代理升级为黄金代理上级获得返佣</view>
<view class="mb-1.5 text-gray-600"> 普通/黄金代理升级为钻石代理上级获得返佣</view>
<view class="mb-1.5 text-gray-600"> 返佣金额由系统配置在升级时显示</view>
<view class="text-sm text-gray-400">只有黄金代理和钻石代理才能获得下级升级返佣</view>
</view>
</view>
<!-- 收益统计 -->
<view class="rounded-lg p-3 border-l-4 border-orange-500 bg-orange-50">
<view class="flex items-center mb-2">
<view class="w-5 h-5 rounded mr-2 bg-orange-500 flex items-center justify-center text-white text-xs">📋</view>
<view class="text-base font-semibold text-orange-800">收益统计</view>
</view>
<view class="bg-white rounded p-2.5 text-sm text-gray-600">
<view class="mb-1.5">您可以在"我的"页面查看详细的收益统计</view>
<view> <text class="font-medium">累计总收益</text>所有收益的总和</view>
<view> <text class="font-medium">今日收益</text>当天的收益金额</view>
<view> <text class="font-medium">本月收益</text>本月的收益金额</view>
<view> <text class="font-medium">余额</text>可提现的金额</view>
<view> <text class="font-medium">风险保障金</text>冻结的保障金</view>
</view>
</view>
</view>
</view>
<!-- 功能玩法 -->
<view class="bg-white rounded-lg p-4 shadow-sm">
<view class="flex items-center mb-4">
<view class="w-1 h-5 rounded-full mr-2 bg-primary" />
<view class="text-lg font-semibold text-gray-800">功能玩法</view>
</view>
<view class="flex flex-row flex-wrap gap-3">
<view class="rounded-lg p-3 border border-blue-200 bg-blue-50 flex-1 min-w-[45%]">
<view class="flex items-center mb-1.5">
<view class="text-blue-500 mr-1.5 text-sm">分享</view>
<view class="font-semibold text-sm text-blue-800">推广查询</view>
</view>
<view class="text-sm leading-relaxed text-gray-600">生成推广链接分享给用户购买查询服务获得推广佣金</view>
</view>
<view class="rounded-lg p-3 border border-green-200 bg-green-50 flex-1 min-w-[45%]">
<view class="flex items-center mb-1.5">
<view class="text-green-500 mr-1.5 text-sm">邀请</view>
<view class="font-semibold text-sm text-green-800">邀请下级</view>
</view>
<view class="text-sm leading-relaxed text-gray-600">通过邀请码邀请他人成为您的下级代理获得下级推广返佣</view>
</view>
<view class="rounded-lg p-3 border border-purple-200 bg-purple-50 flex-1 min-w-[45%]">
<view class="flex items-center mb-1.5">
<view class="text-purple-500 mr-1.5 text-sm">团队</view>
<view class="font-semibold text-sm text-purple-800">团队管理</view>
</view>
<view class="text-sm leading-relaxed text-gray-600">查看团队统计转化率数据了解团队发展情况</view>
</view>
<view class="rounded-lg p-3 border border-amber-200 bg-amber-50 flex-1 min-w-[45%]">
<view class="flex items-center mb-1.5">
<view class="text-amber-500 mr-1.5 text-sm">升级</view>
<view class="font-semibold text-sm text-amber-800">升级代理</view>
</view>
<view class="text-sm leading-relaxed text-gray-600">付费升级为更高等级享受更高佣金和返佣比例</view>
</view>
<view class="rounded-lg p-3 border border-indigo-200 bg-indigo-50 flex-1 min-w-[45%]">
<view class="flex items-center mb-1.5">
<view class="text-indigo-500 mr-1.5 text-sm"></view>
<view class="font-semibold text-sm text-indigo-800">邀请码管理</view>
</view>
<view class="text-sm leading-relaxed text-gray-600">生成查看和管理邀请码用于邀请下级代理</view>
</view>
<view class="rounded-lg p-3 border border-red-200 bg-red-50 flex-1 min-w-[45%]">
<view class="flex items-center mb-1.5">
<view class="text-red-500 mr-1.5 text-sm">认证</view>
<view class="font-semibold text-sm text-red-800">实名认证</view>
</view>
<view class="text-sm leading-relaxed text-gray-600">完成实名认证后才能申请提现收益</view>
</view>
<view class="rounded-lg p-3 border border-teal-200 bg-teal-50 flex-1 min-w-[45%]">
<view class="flex items-center mb-1.5">
<view class="text-teal-500 mr-1.5 text-sm">提现</view>
<view class="font-semibold text-sm text-teal-800">提现功能</view>
</view>
<view class="text-sm leading-relaxed text-gray-600">将收益提现到银行卡需要完成实名认证</view>
</view>
<view class="rounded-lg p-3 border border-violet-200 bg-violet-50 flex-1 min-w-[45%]">
<view class="flex items-center mb-1.5">
<view class="text-violet-500 mr-1.5 text-sm">设置</view>
<view class="font-semibold text-sm text-violet-800">调整级别</view>
</view>
<view class="text-sm leading-relaxed text-gray-600"><text class="font-medium text-purple-600">钻石代理专属</text>可将普通下级升级为黄金代理</view>
</view>
</view>
</view>
<!-- 操作指南 -->
<view class="bg-white rounded-lg p-4 shadow-sm">
<view class="flex items-center mb-4">
<view class="w-1 h-5 rounded-full mr-2 bg-primary" />
<view class="text-lg font-semibold text-gray-800">操作指南</view>
</view>
<view class="mb-4">
<view class="flex items-center mb-2.5">
<view class="w-5 h-5 rounded mr-2 bg-blue-500 flex items-center justify-center text-white text-xs">1</view>
<view class="text-base font-semibold text-gray-800">如何推广报告</view>
</view>
<view class="flex flex-col gap-2 text-sm leading-relaxed text-gray-600">
<view class="flex items-start"><text class="text-blue-500 mr-1.5 mt-0.5 font-medium">1.</text><text>登录代理账户后进入"推广"页面选择要推广的报告类型如个人大数据婚恋风险入职背调等</text></view>
<view class="flex items-start"><text class="text-blue-500 mr-1.5 mt-0.5 font-medium">2.</text><text>系统将自动生成专属推广链接您可以将该链接分享给潜在用户用户通过您的推广链接购买查询服务后您即可获得推广佣金</text></view>
<view class="flex items-start"><text class="text-blue-500 mr-1.5 mt-0.5 font-medium">3.</text><text>您可以在推广页面设置查询服务的售价需在平台设定的价格范围内售价与成本价的差额即为您的推广收益</text></view>
<view class="flex items-start"><text class="text-blue-500 mr-1.5 mt-0.5 font-medium">4.</text><text>推广链接支持多种分享方式包括复制链接生成二维码生成短链等方便您在不同渠道进行推广</text></view>
<view class="flex items-start"><text class="text-blue-500 mr-1.5 mt-0.5 font-medium">5.</text><text>您可以在"推广查询记录"中查看所有通过您推广链接产生的订单详情包括订单金额收益金额订单状态等信息</text></view>
<view class="flex items-start"><text class="text-blue-500 mr-1.5 mt-0.5 font-medium">6.</text><text>推广佣金将在用户完成支付后自动结算到您的账户余额中您可以在"我的"页面查看收益统计</text></view>
</view>
</view>
<view class="border-t border-gray-200 pt-4">
<view class="flex items-center mb-2.5">
<view class="w-5 h-5 rounded mr-2 bg-green-500 flex items-center justify-center text-white text-xs">2</view>
<view class="text-base font-semibold text-gray-800">如何邀请下级代理</view>
</view>
<view class="flex flex-col gap-2 text-sm leading-relaxed text-gray-600">
<view class="flex items-start"><text class="text-green-500 mr-1.5 mt-0.5 font-medium">1.</text><text>登录代理账户后进入"邀请码管理"页面系统会为您生成专属邀请码</text></view>
<view class="flex items-start"><text class="text-green-500 mr-1.5 mt-0.5 font-medium">2.</text><text>您可以将邀请码分享给想要成为代理的用户用户通过您的邀请码注册成为代理后将自动成为您的下级代理</text></view>
<view class="flex items-start"><text class="text-green-500 mr-1.5 mt-0.5 font-medium">3.</text><text>当您的下级代理推广产品产生订单时您将获得一定比例的下级推广返佣返佣比例根据您的代理等级而定等级越高返佣比例越高</text></view>
<view class="flex items-start"><text class="text-green-500 mr-1.5 mt-0.5 font-medium">4.</text><text>您可以在"我的团队"页面查看所有下级代理的信息包括直接下级和间接下级以及团队统计数据</text></view>
<view class="flex items-start"><text class="text-green-500 mr-1.5 mt-0.5 font-medium">5.</text><text>当您的下级代理付费升级为更高等级时如普通代理升级为黄金代理您将获得下级升级返佣只有黄金代理和钻石代理才能获得下级升级返佣</text></view>
<view class="flex items-start"><text class="text-green-500 mr-1.5 mt-0.5 font-medium">6.</text><text>钻石代理拥有特殊权限可以将普通下级代理直接升级为黄金代理无需下级代理付费</text></view>
<view class="flex items-start"><text class="text-green-500 mr-1.5 mt-0.5 font-medium">7.</text><text>您可以在"下级推广收益"中查看所有来自下级代理的返佣明细包括推广返佣和升级返佣</text></view>
<view class="flex items-start"><text class="text-green-500 mr-1.5 mt-0.5 font-medium">8.</text><text>邀请下级代理是扩大团队规模增加收益的重要方式建议您积极发展下级代理建立稳定的推广团队</text></view>
</view>
</view>
</view>
<!-- 提现说明 -->
<view class="bg-white rounded-lg p-4 shadow-sm">
<view class="flex items-center mb-3">
<view class="w-1 h-5 rounded-full mr-2 bg-primary" />
<view class="text-lg font-semibold text-gray-800">提现说明</view>
</view>
<view class="flex flex-col gap-2.5 text-sm leading-relaxed">
<view class="rounded p-2.5 border-l-4 border-amber-400 bg-amber-50">
<view class="font-medium mb-1 text-amber-800">提现条件</view>
<view class="text-amber-900">1. 必须完成实名认证三要素核验</view>
<view class="text-amber-900">2. 单笔提现金额需达到 50 元及以上</view>
<view class="text-amber-900">3. 风险保障金需满足平台要求</view>
</view>
<view class="rounded p-2.5 border-l-4 border-blue-400 bg-blue-50">
<view class="font-medium mb-1 text-blue-800">注意事项</view>
<view class="text-blue-900"> 提现到账账户的实名信息必须与实名认证信息完全一致同一身份证/同一持卡人否则可能提现失败或原路退回</view>
<view class="text-blue-900"> 实名认证时请务必确认使用的证件信息与后续用于收款的账户为同一人</view>
<view class="text-blue-900"> 平台会根据国家税收法律法规代扣代缴个人所得税</view>
<view class="text-blue-900"> 提现申请提交后平台会在规定时间内处理</view>
<view class="text-blue-900"> 可在"提现记录"中查看提现状态</view>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="flex flex-row flex-wrap gap-2.5 pb-8 safe-area-bottom">
<button v-if="!isAgent && isLoggedIn" class="flex-1 py-2.5 rounded-lg text-white text-sm font-medium bg-primary" @click="toRegister">立即注册成为代理</button>
<view v-if="!isLoggedIn" class="flex-1 py-2.5 rounded-lg text-gray-500 text-sm font-medium bg-gray-200 text-center flex items-center justify-center">正在登录中请稍候...</view>
<button v-if="isAgent && level < 3" class="flex-1 py-2.5 rounded-lg text-white text-sm font-medium bg-purple-500" @click="toUpgrade">升级代理等级</button>
<button class="px-4 py-2.5 rounded-lg border border-primary text-sm font-medium text-primary" @click="toAgreement">查看协议</button>
</view>
</view>
</scroll-view>
</view>
</template>
<style lang="scss" scoped>
.page-wrap {
width: 100%;
height: 100vh;
box-sizing: border-box;
}
.guide-scroll {
width: 100%;
height: 100%;
box-sizing: border-box;
}
/* 左右留白,避免文字贴边(小程序 rpx 自适应) */
.guide-content {
padding-left: 48rpx;
padding-right: 48rpx;
box-sizing: border-box;
}
.safe-area-bottom {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
/* 小程序 button 默认样式覆盖 */
.agent-system-guide button {
margin: 0;
line-height: 1.4;
}
.agent-system-guide button::after {
border: none;
}
</style>

View File

@@ -0,0 +1,622 @@
<script setup>
/**
* 升级代理:功能与 webview AgentUpgrade 一致(等级卡片、权益、确认开通),
* 支付方式为微信支付(小程序内 uni.requestPayment + wxpay
*/
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '升级代理',
navigationStyle: 'default',
},
})
import { ref, computed, onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { useAgentStore } from '@/stores/agentStore'
import { useUserStore } from '@/stores/userStore'
import { getLevelPrivilege, applyUpgrade } from '@/api/agent'
import { payment, paymentCheck } from '@/api/pay'
import { debugLog, debugError, showDebugInfo } from '@/utils/debug'
const agentStore = useAgentStore()
const userStore = useUserStore()
const { isAgent, level } = storeToRefs(agentStore)
const { isLoggedIn } = storeToRefs(userStore)
const goldPrice = ref(0)
const diamondPrice = ref(0)
const goldRebate = ref(0)
const diamondRebate = ref(0)
const levelPrivileges = ref([])
const loading = ref(true)
const isSubmitting = ref(false)
const selectedToLevel = ref(0)
const levelNames = { 1: '普通代理', 2: '黄金代理', 3: '钻石代理' }
const levelDescs = { 1: '基础代理特权', 2: '高级代理特权', 3: '尊享代理特权' }
const canUpgradeToGold = computed(() => level.value === 1)
const canUpgradeToDiamond = computed(() => level.value === 1 || level.value === 2)
const isMaxLevel = computed(() => (level.value || 1) >= 3)
function getLevelName(lvl) {
return levelNames[lvl] || '普通代理'
}
function getLevelDesc(lvl) {
return levelDescs[lvl] || '基础代理特权'
}
function getLevelBadgeClass(lvl) {
const classes = { 1: 'badge-normal', 2: 'badge-gold', 3: 'badge-diamond' }
return classes[lvl] || 'badge-normal'
}
function getPrivilegeInfo(targetLevel) {
return levelPrivileges.value.find((p) => p.level === targetLevel)
}
function getUpgradeInfo(toLevel) {
let upgradeFee = 0
let rebateAmount = 0
const cur = level.value || 1
if (cur === 1) {
if (toLevel === 2) {
upgradeFee = goldPrice.value
rebateAmount = goldRebate.value
} else if (toLevel === 3) {
upgradeFee = diamondPrice.value
rebateAmount = diamondRebate.value
}
} else if (cur === 2 && toLevel === 3) {
upgradeFee = diamondPrice.value
rebateAmount = diamondRebate.value
}
return { upgradeFee, rebateAmount }
}
function formatAmount(value) {
const num = Number(value ?? 0)
if (Number.isNaN(num)) return '0'
return num.toFixed(0)
}
function selectUpgrade(toLevel) {
selectedToLevel.value = selectedToLevel.value === toLevel ? 0 : toLevel
}
async function loadPrivilegeInfo() {
loading.value = true
try {
const { data, error } = await getLevelPrivilege()
if (data.value?.code === 200 && !error.value) {
const res = data.value.data
levelPrivileges.value = res?.levels || []
goldPrice.value = Number(res?.upgrade_to_gold_fee) || 0
diamondPrice.value = Number(res?.upgrade_to_diamond_fee) || 0
goldRebate.value = Number(res?.upgrade_to_gold_rebate) || 0
diamondRebate.value = Number(res?.upgrade_to_diamond_rebate) || 0
}
} finally {
loading.value = false
}
}
async function pollPaymentStatus(orderNo, maxAttempts = 10) {
for (let i = 0; i < maxAttempts; i++) {
const { data } = await paymentCheck(orderNo)
if (data.value?.code === 200 && data.value.data?.status === 'paid') return true
await new Promise((r) => setTimeout(r, 1500))
}
return false
}
async function confirmUpgrade() {
if (!selectedToLevel.value) {
uni.showToast({ title: '请选择要升级的等级', icon: 'none' })
return
}
const { upgradeFee } = getUpgradeInfo(selectedToLevel.value)
try {
await new Promise((resolve, reject) => {
uni.showModal({
title: '确认升级',
content: `确定要升级为${getLevelName(selectedToLevel.value)}吗?\n升级费用¥${Number(upgradeFee).toFixed(2)}`,
success: (res) => (res.confirm ? resolve() : reject()),
})
})
} catch {
return
}
isSubmitting.value = true
try {
const { data: applyData, error: applyError } = await applyUpgrade({ to_level: selectedToLevel.value })
if (applyData.value?.code !== 200 || applyError.value) {
uni.showToast({ title: applyData.value?.msg || '申请升级失败', icon: 'none' })
return
}
const applyRes = applyData.value.data
const upgradeId = applyRes?.upgrade_id
if (!upgradeId) {
uni.showToast({ title: '提交成功', icon: 'success' })
await agentStore.fetchAgentStatus()
setTimeout(() => uni.navigateBack(), 1500)
return
}
// 订单号由支付接口生成并返回,有 upgrade_id 即调起微信支付
// 参考 qnc-mini 的实现:直接调用支付接口,后端会处理 openid 获取
// 如果用户未绑定微信账号,后端会返回错误,此时再获取 code 重试
const paymentParams = {
id: upgradeId,
pay_type: 'agent_upgrade',
pay_method: 'wechat',
}
debugLog('支付请求参数:', paymentParams)
let { data: payData, error: payError } = await payment(paymentParams)
// 如果支付失败且提示需要 code则获取 code 重试
// #ifdef MP-WEIXIN
if (payError.value || (payData.value?.code !== 200 && payData.value?.msg?.includes('未绑定'))) {
debugLog('首次支付请求失败,尝试获取 code 重试:', payData.value?.msg || payError.value)
try {
const loginRes = await new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
success: resolve,
fail: reject,
})
})
if (loginRes.code) {
paymentParams.code = loginRes.code
debugLog('重新获取微信登录 code 并重试:', loginRes.code)
// 重试支付请求
const retryResult = await payment(paymentParams)
payData = retryResult.data
payError = retryResult.error
}
} catch (e) {
debugError('获取微信登录 code 失败:', e)
// 继续使用原始错误
}
}
// #endif
// 检查请求错误
if (payError.value) {
debugError('支付请求错误:', payError.value)
uni.showToast({ title: payError.value || '获取支付参数失败', icon: 'none' })
return
}
// 检查响应状态码
if (payData.value?.code !== 200) {
debugError('支付响应错误:', payData.value)
const errorMsg = payData.value?.msg || '获取支付参数失败'
debugError('错误详情:', {
code: payData.value?.code,
msg: errorMsg,
fullResponse: payData.value,
})
uni.showToast({ title: errorMsg, icon: 'none' })
return
}
// 检查响应数据结构
const payRes = payData.value?.data
debugLog('支付响应数据:', payRes)
if (!payRes) {
debugError('支付响应数据为空:', payData.value)
uni.showToast({ title: '支付响应数据异常', icon: 'none' })
return
}
// 获取支付参数和订单号
// 后端返回字段prepay_data, prepay_id, order_no
const prepayData = payRes.prepay_data
const prepayId = payRes.prepay_id
const payOrderNo = payRes.order_no
debugLog('支付参数解析:', { prepayData, prepayId, payOrderNo })
// 验证订单号(后端必须返回)
if (!payOrderNo) {
debugError('支付响应缺少订单号:', payRes)
uni.showToast({ title: '支付参数异常,缺少订单号', icon: 'none' })
return
}
// 检查支付参数
if (!prepayData) {
debugError('支付参数为空:', {
prepayData,
prepayId,
payOrderNo,
fullResponse: payRes,
responseMsg: payData.value?.msg,
})
// 检查错误信息,如果是 code 相关错误,提示用户
const errorMsg = payRes?.msg || payData.value?.msg || ''
if (errorMsg.includes('invalid code') || errorMsg.includes('code') || errorMsg.includes('OpenID')) {
uni.showToast({
title: '微信授权已过期,请重新打开页面',
icon: 'none',
duration: 3000,
})
} else if (prepayId) {
// 如果返回了 prepay_id说明可能是 APP 支付或其他支付方式
uni.showToast({
title: '当前环境不支持此支付方式,请使用微信小程序打开',
icon: 'none',
})
} else {
uni.showToast({
title: errorMsg || '支付参数异常,请重试或联系客服',
icon: 'none',
duration: 3000,
})
}
return
}
// 参考 qnc-mini 的实现方式,使用条件编译区分不同平台
// prepay_data 可能是对象或直接就是支付参数
const paymentData = prepayData.prepay_data || prepayData
// 支付成功回调处理
const handlePaymentSuccess = async () => {
const paid = await pollPaymentStatus(payOrderNo)
if (paid) {
uni.showToast({ title: '支付成功', icon: 'success' })
await agentStore.fetchAgentStatus()
setTimeout(() => uni.navigateBack(), 1500)
} else {
uni.showToast({ title: '支付处理中,请稍后查看', icon: 'none' })
await agentStore.fetchAgentStatus()
uni.navigateBack()
}
}
// 支付失败回调处理
const handlePaymentFail = (err) => {
if (err.errMsg?.includes('cancel')) {
uni.showToast({ title: '已取消支付', icon: 'none' })
} else {
uni.showToast({ title: err.errMsg || '支付失败', icon: 'none' })
}
}
// #ifdef MP-WEIXIN
// 微信小程序支付:使用顶层参数
uni.requestPayment({
provider: 'wxpay',
timeStamp: String(paymentData.timeStamp || paymentData.timestamp || ''),
nonceStr: paymentData.nonceStr || paymentData.nonce_str || '',
package: paymentData.package || '',
signType: paymentData.signType || paymentData.sign_type || 'MD5',
paySign: paymentData.paySign || paymentData.pay_sign || '',
success: handlePaymentSuccess,
fail: handlePaymentFail,
})
// #endif
// #ifdef APP-PLUS
// APP 支付:使用 orderInfo 对象
uni.requestPayment({
provider: 'wxpay',
orderInfo: {
appid: paymentData.appid,
noncestr: paymentData.nonceStr || paymentData.nonce_str,
package: paymentData.package,
partnerid: paymentData.partnerid,
prepayid: paymentData.prepayid,
timestamp: paymentData.timeStamp || paymentData.timestamp,
sign: paymentData.paySign || paymentData.sign,
},
success: handlePaymentSuccess,
fail: handlePaymentFail,
})
// #endif
// #ifdef H5
// H5 微信支付:使用 WeixinJSBridge
if (typeof WeixinJSBridge !== 'undefined') {
WeixinJSBridge.invoke('getBrandWCPayRequest', paymentData, function (result) {
if (result.err_msg === 'get_brand_wcpay_request:ok') {
handlePaymentSuccess()
} else {
handlePaymentFail({ errMsg: result.err_msg || '支付失败' })
}
})
} else {
uni.showToast({ title: '当前环境不支持微信支付', icon: 'none' })
}
// #endif
} catch (e) {
console.error(e)
uni.showToast({ title: '申请升级失败,请重试', icon: 'none' })
} finally {
isSubmitting.value = false
}
}
const serviceUrl = import.meta.env?.VITE_SERVICE_URL || ''
function toService() {
if (serviceUrl) {
// #ifdef H5
window.location.href = serviceUrl
// #endif
// #ifndef H5
uni.navigateTo({ url: '/pages/service/index' })
// #endif
} else {
uni.navigateTo({ url: '/pages/service/index' })
}
}
function toRegister() {
uni.navigateTo({ url: '/pages/register/index' })
}
onMounted(() => {
if (!isLoggedIn.value) return
if (!isAgent.value) return
loadPrivilegeInfo()
if (level.value === 1) selectedToLevel.value = 2
else if (level.value === 2) selectedToLevel.value = 3
})
</script>
<template>
<view class="page-wrap">
<!-- 未登录 / 非代理 -->
<view v-if="!isLoggedIn" class="rounded-xl shadow-lg bg-white p-6 text-center mx-4 mt-4">
<view class="text-gray-600">正在登录中请稍候...</view>
</view>
<view v-else-if="!isAgent" class="rounded-xl shadow-lg bg-white p-6 text-center mx-4 mt-4">
<view class="text-gray-600 mb-4">请先注册成为代理</view>
<wd-button type="primary" block @click="toRegister">注册成为代理</wd-button>
</view>
<template v-else>
<view v-if="loading" class="p-4">
<view class="rounded-xl shadow-lg bg-white p-6">
<wd-skeleton :row="4" />
</view>
</view>
<template v-else>
<!-- 当前等级卡片 -->
<view class="px-4 pt-4">
<view class="rounded-xl shadow-lg bg-white p-6">
<view class="flex items-center gap-4">
<view class="level-badge" :class="getLevelBadgeClass(level)">
{{ getLevelName(level) }}
</view>
<view class="flex-1">
<view class="flex justify-between items-start gap-2">
<view>
<view class="text-lg font-bold text-gray-800">当前等级</view>
<view class="text-sm mt-1 text-gray-500">{{ getLevelDesc(level) }}</view>
</view>
<view v-if="getPrivilegeInfo(level)" class="space-y-1 text-sm text-right text-gray-700">
<view class="flex items-center gap-1 justify-end">
<text>设置查询价最高{{ getPrivilegeInfo(level).max_set_price }}</text>
<wd-icon name="check" size="14" color="#f59e0b" />
</view>
<view class="flex items-center gap-1 justify-end">
<text>邀请黄金代理奖励{{ getPrivilegeInfo(level).invite_gold_reward }}</text>
<wd-icon name="check" size="14" color="#f59e0b" />
</view>
<view class="flex items-center gap-1 justify-end">
<text>邀请钻石代理奖励{{ getPrivilegeInfo(level).invite_diamond_reward }}</text>
<wd-icon name="check" size="14" color="#f59e0b" />
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 升级选项 -->
<view class="px-4 mt-4 mb-32">
<view class="text-lg font-bold mb-4 text-amber-800">选择升级等级</view>
<!-- 黄金代理选项 -->
<view
v-if="canUpgradeToGold"
class="rounded-xl shadow-lg mb-4 bg-white p-6 border-2"
:class="selectedToLevel === 2 ? 'border-amber-400' : 'border-transparent'"
@click="selectUpgrade(2)"
>
<view class="flex justify-between items-center mb-4">
<view class="flex items-center gap-2">
<wd-icon name="medal" size="20" color="#f59e0b" />
<text class="text-lg font-bold text-gray-800">黄金代理</text>
<wd-icon v-if="selectedToLevel === 2" name="check" size="20" color="#f59e0b" />
</view>
<view class="text-right">
<view class="text-xs mb-1 text-gray-500">升级费用</view>
<view class="text-2xl font-bold text-amber-500">¥{{ goldPrice }}</view>
</view>
</view>
<view v-if="getPrivilegeInfo(2)" class="space-y-2">
<view class="flex items-center gap-2 text-sm text-gray-700">
<wd-icon name="check" size="14" color="#f59e0b" />
<text>设置查询价最高{{ getPrivilegeInfo(2).max_set_price }}</text>
</view>
<view class="flex items-center gap-2 text-sm text-gray-700">
<wd-icon name="check" size="14" color="#f59e0b" />
<text>查询底价降低{{ formatAmount(getPrivilegeInfo(2).price_reduction) }}元每单</text>
</view>
<view class="flex items-center gap-2 text-sm text-gray-700">
<wd-icon name="check" size="14" color="#f59e0b" />
<text>下级代理查询奖励最高{{ getPrivilegeInfo(2).subordinate_reward_max }}元每单</text>
</view>
<view class="flex items-center gap-2 text-sm text-gray-700">
<wd-icon name="check" size="14" color="#f59e0b" />
<text>邀请黄金代理奖励{{ getPrivilegeInfo(2).invite_gold_reward }}</text>
</view>
<view class="flex items-center gap-2 text-sm text-gray-700">
<wd-icon name="check" size="14" color="#f59e0b" />
<text>邀请钻石代理奖励{{ getPrivilegeInfo(2).invite_diamond_reward }}</text>
</view>
<view class="flex items-center gap-2 text-sm text-gray-700">
<wd-icon name="check" size="14" color="#f59e0b" />
<text>更多权益敬请期待</text>
</view>
</view>
</view>
<!-- 钻石代理选项 -->
<view
v-if="canUpgradeToDiamond"
class="rounded-xl shadow-lg mb-4 bg-white p-6 border-2"
:class="selectedToLevel === 3 ? 'border-amber-400' : 'border-transparent'"
@click="selectUpgrade(3)"
>
<view class="flex justify-between items-center mb-4">
<view class="flex items-center gap-2">
<wd-icon name="medal" size="20" color="#f59e0b" />
<text class="text-lg font-bold text-gray-800">钻石代理</text>
<wd-icon v-if="selectedToLevel === 3" name="check" size="20" color="#f59e0b" />
</view>
<view class="text-right">
<view class="text-xs mb-1 text-gray-500">升级费用</view>
<view class="text-2xl font-bold text-amber-500">¥{{ diamondPrice }}</view>
</view>
</view>
<view v-if="getPrivilegeInfo(3)" class="space-y-2">
<view class="flex items-center gap-2 text-sm text-gray-700">
<wd-icon name="check" size="14" color="#f59e0b" />
<text>升级费用¥{{ formatAmount(getUpgradeInfo(3).upgradeFee) }}</text>
</view>
<view class="flex items-center gap-2 text-sm text-gray-700">
<wd-icon name="check" size="14" color="#f59e0b" />
<text>设置查询价最高{{ getPrivilegeInfo(3).max_set_price }}</text>
</view>
<view class="flex items-center gap-2 text-sm text-gray-700">
<wd-icon name="check" size="14" color="#f59e0b" />
<text>查询底价降低{{ formatAmount(getPrivilegeInfo(3).price_reduction) }}元每单</text>
</view>
<view class="flex items-center gap-2 text-sm text-gray-700">
<wd-icon name="check" size="14" color="#f59e0b" />
<text>下级代理查询奖励最高{{ getPrivilegeInfo(3).subordinate_reward_max }}元每单</text>
</view>
<view class="flex items-center gap-2 text-sm text-gray-700">
<wd-icon name="check" size="14" color="#f59e0b" />
<text>邀请黄金代理奖励{{ getPrivilegeInfo(3).invite_gold_reward }}</text>
</view>
<view class="flex items-center gap-2 text-sm text-gray-700">
<wd-icon name="check" size="14" color="#f59e0b" />
<text>邀请钻石代理奖励{{ getPrivilegeInfo(3).invite_diamond_reward }}</text>
</view>
<view class="flex items-center gap-2 text-sm text-gray-700">
<wd-icon name="check" size="14" color="#f59e0b" />
<text>调整下级代理级别权限</text>
</view>
<view class="flex items-center gap-2 text-sm text-gray-700">
<wd-icon name="check" size="14" color="#f59e0b" />
<text>系统内白名单权限</text>
</view>
<view class="flex items-center gap-2 text-sm text-gray-700">
<wd-icon name="check" size="14" color="#f59e0b" />
<text>更多权益敬请期待</text>
</view>
</view>
</view>
<!-- 已是最高等级 -->
<view
v-if="isMaxLevel"
class="rounded-xl shadow-lg bg-white p-8 text-center"
>
<wd-icon name="medal" size="48" color="#f59e0b" />
<view class="text-lg font-bold mt-4 text-gray-800">您已是最高等级</view>
<view class="text-sm mt-2 text-gray-500">钻石代理享有最高权益</view>
</view>
</view>
<!-- 底部固定按钮 -->
<view
v-if="!isMaxLevel && !loading"
class="fixed bottom-0 left-0 right-0 bg-white border-t border-amber-100 p-4 safe-area-bottom"
>
<view class="flex items-center gap-3">
<wd-button
v-if="serviceUrl"
size="small"
custom-class="flex-shrink-0"
custom-style="background-color: #fef3c7; color: #92400e; border: 1px solid #fde68a;"
@click="toService"
>
<wd-icon name="service" class="mr-1" />
联系客服
</wd-button>
<wd-button
type="primary"
block
:disabled="!selectedToLevel"
:loading="isSubmitting"
custom-style="background: linear-gradient(135deg, #f59e0b, #d97706); border: none;"
@click="confirmUpgrade"
>
确认开通
</wd-button>
</view>
</view>
</template>
</template>
</view>
</template>
<style scoped>
.page-wrap {
min-height: 100vh;
background: linear-gradient(to bottom, #fef3c7, #fef9e7);
padding-bottom: env(safe-area-inset-bottom);
}
.level-badge {
width: 56px;
height: 56px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
color: white;
flex-shrink: 0;
}
.badge-normal {
background-color: #6b7280;
}
.badge-gold {
background-color: #f59e0b;
}
.badge-diamond {
background-color: #f59e0b;
}
.space-y-1 > view + view,
.space-y-2 > view + view {
margin-top: 4px;
}
.space-y-2 > view + view {
margin-top: 8px;
}
.safe-area-bottom {
padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
}
</style>

58
src/pages/help/detail.vue Normal file
View File

@@ -0,0 +1,58 @@
<script setup>
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '帮助详情',
navigationStyle: 'default',
},
})
import { ref, onMounted } from 'vue'
const currentHelp = ref({ title: '', image: '', images: null })
const titleMap = {
report_calculation: '推广报告的收益是如何计算的?',
report_efficiency: '报告推广效率飙升指南',
report_cost: '推广报告的成本是如何计算的?',
report_types: '全能查有哪些大数据报告类型',
report_push: '如何推广报告',
report_secret: '报告推广秘籍大公开',
invite_earnings: '如何邀请下级成为代理',
direct_earnings: '如何成为全能查代理',
}
const imageMap = {
report_calculation: '/static/help/report-calculation.jpg',
report_efficiency: '/static/help/report-efficiency.jpg',
report_cost: '/static/help/report-cost.jpg',
report_types: '/static/help/report-types.jpg',
report_push: '/static/help/report-push.jpg',
report_secret: ['/static/help/report-secret-1.jpg', '/static/help/report-secret-2.jpg'],
invite_earnings: '/static/help/invite-earnings.jpg',
direct_earnings: '/static/help/direct-earnings.jpg',
}
onMounted(() => {
const pages = getCurrentPages()
const page = pages[pages.length - 1]
const id = page?.options?.id || ''
if (id && titleMap[id]) {
currentHelp.value = {
title: titleMap[id],
image: Array.isArray(imageMap[id]) ? null : imageMap[id],
images: Array.isArray(imageMap[id]) ? imageMap[id] : null,
}
}
})
</script>
<template>
<view class="help-detail p-5 min-h-screen bg-white">
<view class="text-xl font-semibold text-gray-800 mb-5">{{ currentHelp.title }}</view>
<template v-if="Array.isArray(currentHelp.images)">
<image v-for="(img, index) in currentHelp.images" :key="index" :src="img" class="w-full rounded-lg mb-3 block" mode="widthFix" />
</template>
<image v-else-if="currentHelp.image" :src="currentHelp.image" class="w-full rounded-lg block" mode="widthFix" />
</view>
</template>

56
src/pages/help/guide.vue Normal file
View File

@@ -0,0 +1,56 @@
<script setup>
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '引导指南',
navigationStyle: 'default',
},
})
import { ref, computed, onMounted } from 'vue'
const currentStepIndex = ref(0)
const guideSteps = {
report_guide: [
{ title: '第一步:进入直推报告页面', image: '/static/help/report-step1.jpg' },
{ title: '第二步:选择报告类型', image: '/static/help/report-step2.jpg' },
{ title: '第三步:填写推广信息', image: '/static/help/report-step3.jpg' },
{ title: '第四步:完成推广', image: '/static/help/report-step4.jpg' },
],
invite_guide: [
{ title: '第一步:进入邀请页面', image: '/static/help/invite-step1.jpg' },
{ title: '第二步:获取邀请码', image: '/static/help/invite-step2.jpg' },
{ title: '第三步:分享邀请链接', image: '/static/help/invite-step3.jpg' },
],
}
const guideId = ref('')
const currentGuide = computed(() => guideSteps[guideId.value] || [])
const totalSteps = computed(() => currentGuide.value.length)
const currentStep = computed(() => currentGuide.value[currentStepIndex.value] || {})
function handleImageClick() {
if (currentStepIndex.value < totalSteps.value - 1) {
currentStepIndex.value++
} else {
uni.navigateBack()
}
}
onMounted(() => {
const pages = getCurrentPages()
const page = pages[pages.length - 1]
guideId.value = page?.options?.id || ''
if (!guideSteps[guideId.value]) uni.navigateBack()
})
</script>
<template>
<view class="help-guide min-h-screen bg-white p-4">
<view class="guide-content" @click="handleImageClick">
<image v-if="currentStep.image" :src="currentStep.image" class="w-full rounded-lg block" mode="widthFix" />
<view v-if="currentStep.title" class="text-center mt-4 text-gray-700">{{ currentStep.title }}</view>
</view>
</view>
</template>

80
src/pages/help/index.vue Normal file
View File

@@ -0,0 +1,80 @@
<script setup>
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '帮助中心',
navigationStyle: 'default',
},
})
import { ref } from 'vue'
const activeTab = ref(0)
const categories = [
{
title: '推广报告',
name: 'report',
items: [
{ id: 'report_guide', title: '直推报告页面引导', type: 'guide' },
{ id: 'invite_guide', title: '邀请下级页面引导', type: 'guide' },
{ id: 'direct_earnings', title: '如何成为全能查代理' },
{ id: 'report_push', title: '如何推广报告' },
{ id: 'report_calculation', title: '推广报告的收益是如何计算的?' },
{ id: 'report_cost', title: '推广报告的成本是如何计算的?' },
{ id: 'report_efficiency', title: '报告推广效率飙升指南' },
{ id: 'report_secret', title: '报告推广秘籍大公开' },
{ id: 'report_types', title: '全能查有哪些大数据报告类型' },
],
},
{
title: '邀请下级',
name: 'invite',
items: [
{ id: 'invite_earnings', title: '邀请下级赚取收益' },
],
},
{
title: '其他',
name: 'other',
items: [],
},
]
function goToDetail(id, type) {
if (type === 'guide') {
uni.navigateTo({ url: `/pages/help/guide?id=${encodeURIComponent(id)}` })
} else {
uni.navigateTo({ url: `/pages/help/detail?id=${encodeURIComponent(id)}` })
}
}
</script>
<template>
<view class="help-center min-h-screen bg-gray-100">
<wd-tabs v-model="activeTab" sticky>
<wd-tab v-for="(category, index) in categories" :key="index" :title="category.title">
<view class="help-list p-4">
<view
v-for="item in category.items"
:key="item.id"
class="help-item flex items-center justify-between py-4 px-4 bg-white rounded-lg mb-2 shadow-sm"
@click="goToDetail(item.id, item.type)"
>
<view class="flex items-center">
<text class="text-base text-gray-800">{{ item.title }}</text>
<wd-tag v-if="item.type === 'guide'" type="primary" size="small" class="ml-2">引导指南</wd-tag>
</view>
<wd-icon name="arrow-right" />
</view>
</view>
</wd-tab>
</wd-tabs>
</view>
</template>
<style scoped>
.help-center {
padding-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,194 @@
<script setup>
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '历史查询',
navigationStyle: 'default',
},
})
import { ref, onMounted, computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/userStore'
import useApiFetch from '@/composables/useApiFetch'
const userStore = useUserStore()
const { isLoggedIn, mobile } = storeToRefs(userStore)
const page = ref(1)
const pageSize = 10
const total = ref(0)
const reportList = ref([])
const loading = ref(false)
const finished = ref(false)
const queryRetentionDays = 30
const showBindNotice = computed(
() => isLoggedIn.value && !mobile.value && reportList.value.length > 0
)
const hasNoRecords = computed(() => reportList.value.length === 0)
async function fetchData() {
if (loading.value || finished.value) return
loading.value = true
try {
const { data, error } = await useApiFetch(
`query/list?page=${page.value}&page_size=${pageSize}`
)
.get()
.json()
if (data.value && !error.value) {
if (data.value.code === 200) {
total.value = data.value.data.total
if (data.value.data.list && data.value.data.list.length > 0) {
reportList.value.push(...data.value.data.list)
page.value += 1
}
if (reportList.value.length >= total.value) {
finished.value = true
}
} else {
finished.value = true
}
} else {
finished.value = true
}
} catch (err) {
finished.value = true
} finally {
loading.value = false
}
}
onMounted(() => {
fetchData()
})
function onLoadMore() {
if (!finished.value && !loading.value) fetchData()
}
function toDetail(item) {
if (item.query_state !== 'success') return
uni.navigateTo({
url: `/pages/report/index?orderId=${item.order_id || ''}`,
})
}
function toBindPhone() {
uni.navigateTo({ url: '/pages/me/index' })
}
function stateText(state) {
switch (state) {
case 'pending':
return '查询中'
case 'success':
return '查询成功'
case 'failed':
return '查询失败'
case 'refunded':
return '已退款'
default:
return '未知状态'
}
}
function statusClass(state) {
switch (state) {
case 'pending':
return 'status-pending'
case 'success':
return 'status-success'
case 'failed':
return 'status-failed'
case 'refunded':
return 'status-refunded'
default:
return ''
}
}
</script>
<template>
<view class="flex flex-col p-4 min-h-screen">
<view
v-if="showBindNotice"
class="bg-yellow-50 border border-yellow-200 text-yellow-700 rounded-lg p-3 flex items-center justify-between mb-4"
>
<text class="text-sm flex-1">为防止报告丢失请绑定手机号</text>
<button
class="px-3 py-1 text-sm font-medium bg-blue-500 text-white rounded"
@click="toBindPhone"
>
绑定手机号
</button>
</view>
<view class="text-xs text-gray-500 mb-4">
为保障用户隐私及数据安全报告保留{{ queryRetentionDays }}过期自动清理
</view>
<view
v-if="hasNoRecords"
class="bg-white rounded-lg shadow-sm p-6 flex flex-col items-center justify-center gap-3"
>
<text class="text-gray-600 text-sm">暂无历史报告</text>
</view>
<scroll-view
v-else
scroll-y
class="flex-1"
style="height: 70vh"
:lower-threshold="80"
@scrolltolower="onLoadMore"
>
<view
v-for="item in reportList"
:key="item.id"
class="bg-white rounded-lg shadow-sm p-4 mb-4 relative"
@click="toDetail(item)"
>
<view class="flex flex-col">
<text class="text-xl text-black mb-1">{{ item.product_name }}</text>
<text class="text-sm text-[#999999]">{{ item.create_time }}</text>
</view>
<view
class="absolute top-0 right-0 rounded-bl-lg rounded-tr-lg px-2 py-0.5 text-white text-sm font-medium"
:class="[statusClass(item.query_state)]"
>
{{ stateText(item.query_state) }}
</view>
</view>
<view
v-if="reportList.length > 0"
class="py-4 text-center text-gray-400 text-sm"
>
<text v-if="loading">加载中...</text>
<text v-else-if="finished">没有更多了</text>
<text v-else>上拉加载更多</text>
</view>
</scroll-view>
</view>
</template>
<style scoped>
.status-pending {
background-color: #1976d2;
color: white;
}
.status-success {
background-color: #1fbe5d;
color: white;
}
.status-failed {
background-color: #eb3c3c;
color: white;
}
.status-refunded {
background-color: #999999;
color: white;
}
</style>

120
src/pages/index.vue Normal file
View File

@@ -0,0 +1,120 @@
<script setup>
definePage({
type: 'home',
layout: 'HomeLayout',
style: {
navigationBarTitleText: '首页',
navigationStyle: 'custom',
},
})
import { storeToRefs } from 'pinia'
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
import { useAgentStore } from '@/stores/agentStore'
import { useUserStore } from '@/stores/userStore'
import { getShareAppMessageOptions, getShareTimelineOptions } from '@/composables/useWechatShare'
const agentStore = useAgentStore()
const userStore = useUserStore()
const { isAgent } = storeToRefs(agentStore)
const { isLoggedIn, mobile } = storeToRefs(userStore)
function toInvitation() {
if (!isAgent.value) {
if (!isLoggedIn.value) {
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
return
} else {
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
}
return
}
uni.navigateTo({ url: '/pages/invitation/index' })
}
// 直推报告:进入推广报告页 pages/promote/report
function toDirectReport() {
if (!isAgent.value) {
if (!isLoggedIn.value) {
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
return
} else {
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
}
return
}
uni.navigateTo({ url: '/pages/promote/report' })
}
function toPromote() {
if (!isAgent.value) {
if (!isLoggedIn.value) {
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
return
} else {
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
}
return
}
uni.switchTab({ url: '/pages/promote/index' })
}
// 微信分享:分享给好友
onShareAppMessage(() => {
return getShareAppMessageOptions({ path: 'pages/index' })
})
// 微信分享:分享到朋友圈(基础库 2.11.3+
onShareTimeline(() => {
return getShareTimelineOptions({ query: 'from=timeline' })
})
</script>
<template>
<view class="box-border min-h-screen from-blue-100 to-white bg-gradient-to-b">
<!-- 头图保留 -->
<view class="relative">
<image class="h-full w-full block" mode="widthFix" src="/static/index/banner.png" />
</view>
<!-- 卡片一行一个文字在左图标在右 -->
<view class="p-4 flex flex-col gap-4">
<view
class="flex flex-row items-center rounded-xl shadow-lg bg-white py-5 px-4 w-full"
@click="toDirectReport"
>
<view class="flex-1 min-w-0 mr-4">
<view class="flex items-center gap-2 flex-wrap">
<text class="text-gray-800 font-bold text-lg">直推报告</text>
<text class="text-xs px-2 py-0.5 rounded-full bg-amber-100 text-amber-700">推广收益</text>
</view>
<view class="text-sm text-gray-500 mt-2 leading-relaxed">查看推广数据转化率与收益明细一键生成推广链接</view>
<view class="inline-block mt-3 px-4 py-2 rounded-lg bg-primary/10 text-primary border border-primary/30 text-xs font-medium shadow-sm active:opacity-90">推广报告 </view>
</view>
<view class="flex items-center flex-shrink-0">
<image class="w-16 h-16" src="/static/promote/tgbg.png" mode="aspectFit" />
<text class="text-gray-300 text-xl ml-1"></text>
</view>
</view>
<view
class="flex flex-row items-center rounded-xl shadow-lg bg-white py-5 px-4 w-full"
@click="toInvitation"
>
<view class="flex-1 min-w-0 mr-4">
<view class="flex items-center gap-2 flex-wrap">
<text class="text-gray-800 font-bold text-lg">邀请下级代理</text>
<text class="text-xs px-2 py-0.5 rounded-full bg-blue-100 text-blue-700">邀请码·团队</text>
</view>
<view class="text-sm text-gray-500 mt-2 leading-relaxed">获取专属邀请码与邀请链接管理下级团队邀请即享奖励</view>
<view class="inline-block mt-3 px-4 py-2 rounded-lg bg-primary/10 text-primary border border-primary/30 text-xs font-medium shadow-sm active:opacity-90">邀请下级 </view>
</view>
<view class="flex items-center flex-shrink-0">
<image class="w-16 h-16" src="/static/promote/yqxj.png" mode="aspectFit" />
<text class="text-gray-300 text-xl ml-1"></text>
</view>
</view>
</view>
</view>
</template>

View File

@@ -0,0 +1,98 @@
<script setup>
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '邀请下级代理',
navigationStyle: 'default',
},
})
import { ref, onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { useAgentStore } from '@/stores/agentStore'
import { getInviteLink } from '@/api/agent'
import InvitePoster from '@/components/InvitePoster.vue'
const inviteLink = ref('')
const agentStore = useAgentStore()
const { agentCode } = storeToRefs(agentStore)
function copyInviteCode() {
if (!agentCode.value) {
uni.showToast({ title: '未获取到邀请码', icon: 'none' })
return
}
uni.setClipboardData({
data: String(agentCode.value),
success: () => uni.showToast({ title: '邀请码已复制', icon: 'success' }),
fail: () => uni.showToast({ title: '复制失败', icon: 'none' }),
})
}
function copyInviteLink() {
if (!inviteLink.value) {
uni.showToast({ title: '暂无邀请链接', icon: 'none' })
return
}
uni.setClipboardData({
data: inviteLink.value,
success: () => uni.showToast({ title: '邀请链接已复制', icon: 'success' }),
fail: () => uni.showToast({ title: '复制失败', icon: 'none' }),
})
}
onMounted(async () => {
const token = typeof uni !== 'undefined' && uni.getStorageSync ? uni.getStorageSync('token') : ''
if (token && !agentStore.isLoaded) {
await agentStore.fetchAgentStatus()
}
if (!agentStore.isAgent || !agentCode.value) {
uni.showToast({ title: '请注册成为代理后再邀请', icon: 'none' })
return
}
// H5 注册页路径:短链重定向后打开 H5 网站注册页
const targetPath = `/register?invite_code=${encodeURIComponent(agentCode.value)}`
const { data, error } = await getInviteLink({
invite_code: agentCode.value,
target_path: targetPath,
})
if (data.value && !error.value && data.value.code === 200) {
inviteLink.value = data.value.data?.invite_link || ''
} else {
uni.showToast({ title: data.value?.msg || '获取邀请链接失败', icon: 'none' })
}
})
</script>
<template>
<view class="p-4">
<view class="mb-4 flex items-center justify-between">
<view>
<view class="text-sm text-gray-500">我的邀请码</view>
<view class="text-2xl font-bold text-primary mt-1">{{ agentCode || '—' }}</view>
</view>
<wd-button type="primary" size="small" @click="copyInviteCode">复制邀请码</wd-button>
</view>
<view class="p-3">
<view v-if="inviteLink" class="rounded-xl bg-white shadow p-4">
<view class="text-sm text-gray-500 mb-2">邀请链接</view>
<view class="rounded-lg bg-gray-100 p-3 mb-3 break-all text-sm text-blue-600">{{ inviteLink }}</view>
<wd-button type="primary" block @click="copyInviteLink">复制链接</wd-button>
</view>
<view v-else class="rounded-xl bg-gray-100 p-4 text-center text-gray-500 text-sm">
加载邀请链接中
</view>
</view>
<!-- 邀请海报背景图 + 二维码 webview 邀请模式一致 -->
<view v-if="inviteLink" class="mt-4">
<view class="mb-2">
<view class="text-base font-semibold text-gray-800">邀请海报</view>
<view class="text-xs text-gray-500 mt-1">邀请码{{ agentCode }}</view>
</view>
<InvitePoster :invite-link="inviteLink" />
<wd-button type="primary" block class="mt-3" @click="copyInviteLink">复制邀请链接</wd-button>
</view>
</view>
</template>

770
src/pages/me/index.vue Normal file
View File

@@ -0,0 +1,770 @@
<script setup>
definePage({
layout: 'HomeLayout',
style: {
navigationBarTitleText: '我的',
navigationStyle: 'custom',
},
})
import { ref, computed, onBeforeMount, onMounted, onUnmounted } from 'vue'
import { storeToRefs } from 'pinia'
import { useAgentStore } from '@/stores/agentStore'
import { useUserStore } from '@/stores/userStore'
import { useEnv } from '@/composables/useEnv'
import { getRevenueInfo, realNameAuth } from '@/api/agent'
import { bindMobile } from '@/api/user'
import useApiFetch from '@/composables/useApiFetch'
const agentStore = useAgentStore()
const userStore = useUserStore()
const { isAgent, level, levelName, agentCode, isRealName } = storeToRefs(agentStore)
const { userName, userAvatar, isLoggedIn, mobile } = storeToRefs(userStore)
const { isWeChat } = useEnv()
const revenueData = ref(null)
const showWithdrawQrPopup = ref(false)
const showBindMobilePopup = ref(false)
const showRealNameAuthPopup = ref(false)
const phoneNumber = ref('')
const verificationCode = ref('')
const isCountingDown = ref(false)
const countdown = ref(60)
let countdownTimer = null
// 实名认证相关
const realName = ref('')
const idCard = ref('')
const realNamePhoneNumber = ref('')
const realNameVerificationCode = ref('')
const isRealNameCountingDown = ref(false)
const realNameCountdown = ref(60)
const isAgreed = ref(false)
let realNameCountdownTimer = null
const levelNamesMap = { 1: '普通代理', 2: '黄金代理', 3: '钻石代理' }
const levelGradient = computed(() => {
const currentLevel = level.value || 1
const gradients = {
1: { border: 'bg-gradient-to-r from-gray-300 to-gray-400', badge: 'bg-gradient-to-r from-gray-500 to-gray-600', text: 'text-gray-600', cardBorder: 'bg-gradient-to-r from-gray-300 to-gray-400' },
2: { border: 'bg-gradient-to-r from-yellow-400 to-amber-500', badge: 'bg-gradient-to-r from-yellow-500 to-amber-600', text: 'text-amber-600', cardBorder: 'bg-gradient-to-r from-yellow-400 to-amber-500' },
3: { border: 'bg-gradient-to-r from-purple-400 to-pink-400', badge: 'bg-gradient-to-r from-purple-500 to-pink-500', text: 'text-purple-600', cardBorder: 'bg-gradient-to-r from-purple-400 to-pink-400' },
}
return gradients[currentLevel] || gradients[1]
})
function getDefaultAvatar() {
return '/static/me/user.png'
}
const serviceUrl = import.meta.env?.VITE_SERVICE_URL || ''
function toService() {
if (serviceUrl) {
// #ifdef H5
window.location.href = serviceUrl
// #endif
// #ifndef H5
uni.navigateTo({ url: '/pages/webview/index?url=' + encodeURIComponent(serviceUrl) })
// #endif
}
}
function toUserAgreement() {
uni.navigateTo({ url: '/pages/userAgreement/index' })
}
function toPrivacyPolicy() {
uni.navigateTo({ url: '/pages/privacyPolicy/index' })
}
function toAgentSystemGuide() {
uni.navigateTo({ url: '/pages/agentSystemGuide/index' })
}
function toUpgrade() {
uni.navigateTo({ url: '/pages/agentUpgrade/index' })
}
function toUpgradeSubordinate() {
uni.navigateTo({ url: '/pages/upgradeSubordinate/index' })
}
function toInviteCodeManage() {
uni.navigateTo({ url: '/pages/invitation/index' })
}
function toRealNameAuth() {
if (!isAgent.value) {
uni.showToast({ title: '请先成为代理', icon: 'none' })
return
}
// 如果已实名认证,提示用户
if (isRealName.value) {
uni.showToast({ title: '您已完成实名认证', icon: 'success' })
return
}
// 初始化表单数据,使用代理的手机号
realNamePhoneNumber.value = agentStore.mobile || mobile.value || ''
realName.value = ''
idCard.value = ''
realNameVerificationCode.value = ''
isAgreed.value = false
showRealNameAuthPopup.value = true
}
function closeRealNameAuthPopup() {
showRealNameAuthPopup.value = false
realName.value = ''
idCard.value = ''
realNamePhoneNumber.value = ''
realNameVerificationCode.value = ''
isAgreed.value = false
if (realNameCountdownTimer) {
clearInterval(realNameCountdownTimer)
realNameCountdownTimer = null
}
isRealNameCountingDown.value = false
realNameCountdown.value = 60
}
function toAgentReport() {
uni.navigateTo({ url: '/pages/agentPromotionQueryList/index' })
}
function toWithdraw() {
showWithdrawQrPopup.value = true
}
function closeWithdrawQrPopup() {
showWithdrawQrPopup.value = false
}
function toWithdrawDetails() {
uni.navigateTo({ url: '/pages/withdrawDetails/index' })
}
function goToPromoteDetail() {
uni.navigateTo({ url: '/pages/promoteDetails/index' })
}
function goToRebateDetail() {
uni.navigateTo({ url: '/pages/rewardsDetails/index' })
}
function toRegister() {
uni.navigateTo({ url: '/pages/register/index' })
}
function openBindMobilePopup() {
showBindMobilePopup.value = true
phoneNumber.value = ''
verificationCode.value = ''
}
function closeBindMobilePopup() {
showBindMobilePopup.value = false
phoneNumber.value = ''
verificationCode.value = ''
if (countdownTimer) {
clearInterval(countdownTimer)
countdownTimer = null
}
isCountingDown.value = false
countdown.value = 60
}
const isPhoneNumberValid = computed(() => /^1[3-9]\d{9}$/.test(phoneNumber.value))
// 实名认证表单验证
const isRealNamePhoneValid = computed(() => /^1[3-9]\d{9}$/.test(realNamePhoneNumber.value))
const isIdCardValid = computed(() => /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(idCard.value))
const isRealNameValid = computed(() => /^[\u4e00-\u9fa5]{2,}$/.test(realName.value))
const canSubmitRealName = computed(() =>
isRealNamePhoneValid.value &&
isIdCardValid.value &&
isRealNameValid.value &&
realNameVerificationCode.value.length === 6 &&
isAgreed.value
)
async function sendVerificationCode() {
if (isCountingDown.value || !isPhoneNumberValid.value) return
if (!isPhoneNumberValid.value) {
uni.showToast({ title: '请输入有效的手机号', icon: 'none' })
return
}
try {
const { data, error } = await useApiFetch('/auth/sendSms')
.post({ mobile: phoneNumber.value, actionType: 'bindMobile' })
.json()
if (data.value && !error.value) {
if (data.value.code === 200) {
uni.showToast({ title: '验证码已发送', icon: 'success' })
startCountdown()
} else {
uni.showToast({ title: data.value.msg || '发送失败', icon: 'none' })
}
} else {
uni.showToast({ title: error.value || '发送失败', icon: 'none' })
}
} catch (err) {
console.error('发送验证码失败:', err)
uni.showToast({ title: '发送失败,请重试', icon: 'none' })
}
}
function startCountdown() {
isCountingDown.value = true
countdown.value = 60
countdownTimer = setInterval(() => {
if (countdown.value > 0) {
countdown.value--
} else {
clearInterval(countdownTimer)
countdownTimer = null
isCountingDown.value = false
}
}, 1000)
}
async function handleBindMobile() {
if (!isPhoneNumberValid.value) {
uni.showToast({ title: '请输入有效的手机号', icon: 'none' })
return
}
if (verificationCode.value.length !== 6) {
uni.showToast({ title: '请输入6位验证码', icon: 'none' })
return
}
try {
const { data, error } = await bindMobile({
mobile: phoneNumber.value,
code: verificationCode.value,
})
if (data.value && !error.value) {
if (data.value.code === 200) {
// 更新 token
uni.setStorageSync('token', data.value.data.accessToken)
uni.setStorageSync('refreshAfter', data.value.data.refreshAfter)
uni.setStorageSync('accessExpire', data.value.data.accessExpire)
// 刷新用户信息
await userStore.fetchUserInfo()
uni.showToast({ title: '绑定成功', icon: 'success' })
closeBindMobilePopup()
} else {
uni.showToast({ title: data.value.msg || '绑定失败', icon: 'none' })
}
} else {
uni.showToast({ title: error.value || '绑定失败,请重试', icon: 'none' })
}
} catch (err) {
console.error('绑定手机号失败:', err)
uni.showToast({ title: '绑定失败,请重试', icon: 'none' })
}
}
// 实名认证 - 发送验证码
async function sendRealNameVerificationCode() {
if (isRealNameCountingDown.value || !isRealNamePhoneValid.value) return
if (!isRealNamePhoneValid.value) {
uni.showToast({ title: '请输入有效的手机号', icon: 'none' })
return
}
try {
const { data, error } = await useApiFetch('/auth/sendSms')
.post({ mobile: realNamePhoneNumber.value, actionType: 'realName' })
.json()
if (data.value && !error.value) {
if (data.value.code === 200) {
uni.showToast({ title: '验证码已发送', icon: 'success' })
startRealNameCountdown()
} else {
uni.showToast({ title: data.value.msg || '发送失败', icon: 'none' })
}
} else {
uni.showToast({ title: error.value || '发送失败', icon: 'none' })
}
} catch (err) {
console.error('发送验证码失败:', err)
uni.showToast({ title: '发送失败,请重试', icon: 'none' })
}
}
function startRealNameCountdown() {
isRealNameCountingDown.value = true
realNameCountdown.value = 60
realNameCountdownTimer = setInterval(() => {
if (realNameCountdown.value > 0) {
realNameCountdown.value--
} else {
clearInterval(realNameCountdownTimer)
realNameCountdownTimer = null
isRealNameCountingDown.value = false
}
}, 1000)
}
// 实名认证 - 提交
async function handleRealNameAuth() {
if (!isRealNameValid.value) {
uni.showToast({ title: '请输入有效的姓名', icon: 'none' })
return
}
if (!isIdCardValid.value) {
uni.showToast({ title: '请输入有效的身份证号', icon: 'none' })
return
}
if (!isRealNamePhoneValid.value) {
uni.showToast({ title: '请输入有效的手机号', icon: 'none' })
return
}
if (realNameVerificationCode.value.length !== 6) {
uni.showToast({ title: '请输入6位验证码', icon: 'none' })
return
}
if (!isAgreed.value) {
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
return
}
try {
const { data, error } = await realNameAuth({
name: realName.value,
id_card: idCard.value,
mobile: realNamePhoneNumber.value,
code: realNameVerificationCode.value,
})
if (data.value && !error.value) {
if (data.value.code === 200) {
uni.showToast({ title: '认证成功', icon: 'success' })
// 刷新代理状态信息
await agentStore.fetchAgentStatus()
closeRealNameAuthPopup()
} else {
uni.showToast({ title: data.value.msg || '认证失败', icon: 'none' })
}
} else {
uni.showToast({ title: error.value || '认证失败,请重试', icon: 'none' })
}
} catch (err) {
console.error('实名认证失败:', err)
uni.showToast({ title: '认证失败,请重试', icon: 'none' })
}
}
async function getRevenueData() {
if (!isAgent.value) return
try {
const { data, error } = await getRevenueInfo()
if (data.value?.code === 200 && !error.value) revenueData.value = data.value.data
} catch (e) {
console.error(e)
}
}
onBeforeMount(async () => {
try {
const userInfo = uni.getStorageSync('userInfo')
if (userInfo) userStore.updateUserInfo(typeof userInfo === 'string' ? JSON.parse(userInfo) : userInfo)
const agentInfo = uni.getStorageSync('agentInfo')
if (agentInfo) agentStore.updateAgentInfo(typeof agentInfo === 'string' ? JSON.parse(agentInfo) : agentInfo)
const token = uni.getStorageSync('token')
if (token && isLoggedIn.value) await agentStore.fetchAgentStatus()
} catch (e) {
console.error(e)
}
})
onMounted(() => {
if (isAgent.value) getRevenueData()
})
onUnmounted(() => {
if (countdownTimer) {
clearInterval(countdownTimer)
countdownTimer = null
}
if (realNameCountdownTimer) {
clearInterval(realNameCountdownTimer)
realNameCountdownTimer = null
}
})
</script>
<template>
<view class="box-border min-h-screen">
<view class="flex flex-col p-4 space-y-6">
<view
class="profile-section rounded-xl p-0.5"
:class="isAgent ? levelGradient.cardBorder : 'bg-gray-200'"
>
<view class="rounded-xl bg-white p-6">
<view class="flex items-center gap-4 mb-4">
<view class="relative">
<view class="overflow-hidden rounded-full p-0.5" :class="levelGradient.border">
<image
:src="userAvatar || getDefaultAvatar()"
class="h-20 w-20 rounded-full border-4 border-white block"
mode="aspectFill"
/>
</view>
<view v-if="isAgent" class="absolute -bottom-2 -right-2">
<view
class="flex items-center justify-center rounded-full px-3 py-1 text-xs font-bold text-white shadow-sm"
:class="levelGradient.badge"
>
{{ levelNamesMap[level] || levelNamesMap[1] }}
</view>
</view>
</view>
<view class="flex-1 space-y-1">
<view class="text-2xl font-bold text-gray-800">
{{ !isLoggedIn ? '点击登录' : mobile ? mobile : isWeChat ? '微信用户' : '未绑定手机号' }}
</view>
<view v-if="isLoggedIn && !mobile && isWeChat" class="text-sm text-blue-500" @click.stop="openBindMobilePopup">
绑定手机号
</view>
<view v-if="isLoggedIn && mobile && !isAgent" class="text-sm text-blue-500" @click.stop="toRegister">
点击申请成为代理
</view>
<view v-if="isAgent" class="font-bold" :class="levelGradient.text">ID: {{ agentCode }}</view>
</view>
<view v-if="isAgent" class="text-right">
<view class="text-sm mb-1 text-gray-500">余额</view>
<view class="text-2xl font-bold text-blue-500">¥ {{ (revenueData?.balance || 0).toFixed(2) }}</view>
</view>
</view>
<template v-if="isAgent">
<view class="grid grid-cols-3 gap-3 mb-4 pt-4 border-t border-gray-200">
<view class="text-center">
<view class="text-sm mb-1 text-gray-500">累计收益</view>
<view class="text-base font-semibold text-gray-800">¥ {{ (revenueData?.total_earnings || 0).toFixed(2) }}</view>
</view>
<view class="text-center">
<view class="text-sm mb-1 text-gray-500">风险保障金</view>
<view class="text-base font-semibold text-gray-800">¥ {{ (revenueData?.frozen_balance || 0).toFixed(2) }}</view>
</view>
<view class="text-center">
<view class="text-sm mb-1 text-gray-500">累计提现</view>
<view class="text-base font-semibold text-gray-800">¥ {{ (revenueData?.withdrawn_amount || 0).toFixed(2) }}</view>
</view>
</view>
<view class="grid grid-cols-2 gap-3">
<button
class="text-white rounded-full py-1.5 px-4 shadow-md flex items-center justify-center text-base bg-blue-500"
@click.stop="toWithdraw"
>
<wd-icon name="gold-coin" class="mr-1" />
提现
</button>
<button
class="bg-white border border-gray-200 rounded-full py-1.5 px-4 shadow-sm flex items-center justify-center text-base text-gray-500"
@click.stop="toWithdrawDetails"
>
<wd-icon name="notes" class="mr-1" />
提现记录
</button>
</view>
</template>
</view>
</view>
<view v-if="isAgent" class="rounded-xl shadow-lg bg-white p-5">
<view class="grid grid-cols-2 gap-4">
<view class="pr-4 border-r border-gray-200">
<view class="flex items-center mb-3">
<wd-icon name="balance-list" class="text-lg mr-2" color="#e6a23c" />
<text class="text-base font-bold text-gray-800">我的推广收益</text>
</view>
<view class="text-center mb-3">
<view class="text-xl font-bold text-amber-500">¥ {{ (revenueData?.commission_total || 0).toFixed(2) }}</view>
<view class="text-sm mt-0.5 text-gray-500">累计总收益</view>
</view>
<view class="grid grid-cols-2 gap-2 mb-3">
<view class="text-center">
<view class="text-sm mb-1 text-gray-500">今日</view>
<view class="text-base font-semibold text-amber-500">¥ {{ (revenueData?.commission_today || 0).toFixed(2) }}</view>
</view>
<view class="text-center">
<view class="text-sm mb-1 text-gray-500">本月</view>
<view class="text-base font-semibold text-amber-500">¥ {{ (revenueData?.commission_month || 0).toFixed(2) }}</view>
</view>
</view>
<view class="flex items-center justify-center text-sm font-semibold text-amber-500" @click="goToPromoteDetail">
<text>查看明细</text>
<text class="ml-1 text-base"></text>
</view>
</view>
<view class="pl-4">
<view class="flex items-center mb-3">
<wd-icon name="gift" class="text-lg mr-2" color="#07c160" />
<text class="text-base font-bold text-gray-800">下级推广收益</text>
</view>
<view class="text-center mb-3">
<view class="text-xl font-bold text-green-500">¥ {{ (revenueData?.rebate_total || 0).toFixed(2) }}</view>
<view class="text-sm mt-0.5 text-gray-500">累计总收益</view>
</view>
<view class="grid grid-cols-2 gap-2 mb-3">
<view class="text-center">
<view class="text-sm mb-1 text-gray-500">今日</view>
<view class="text-base font-semibold text-green-500">¥ {{ (revenueData?.rebate_today || 0).toFixed(2) }}</view>
</view>
<view class="text-center">
<view class="text-sm mb-1 text-gray-500">本月</view>
<view class="text-base font-semibold text-green-500">¥ {{ (revenueData?.rebate_month || 0).toFixed(2) }}</view>
</view>
</view>
<view class="flex items-center justify-center text-sm font-semibold text-green-500" @click="goToRebateDetail">
<text>查看明细</text>
<text class="ml-1 text-base"></text>
</view>
</view>
</view>
</view>
<view class="bg-white rounded-xl shadow-sm p-4">
<view class="grid grid-cols-4 gap-4">
<view v-if="isAgent && level !== 3" class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg" @click="toUpgrade">
<image src="/static/me/sjdl.svg" class="w-8 h-8" mode="aspectFit" />
<text class="text-xs text-gray-700 font-medium text-center">升级代理</text>
</view>
<view v-if="isAgent && level === 3" class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg" @click="toUpgradeSubordinate">
<image src="/static/me/sjxj.svg" class="w-8 h-8" mode="aspectFit" />
<text class="text-xs text-gray-700 font-medium text-center">调整级别</text>
</view>
<view v-if="isAgent" class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg" @click="toInviteCodeManage">
<image src="/static/me/yqmgl.svg" class="w-8 h-8" mode="aspectFit" />
<text class="text-xs text-gray-700 font-medium text-center">邀请码管理</text>
</view>
<view v-if="isAgent" class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg" @click="toRealNameAuth">
<image src="/static/me/smrz.svg" class="w-8 h-8" mode="aspectFit" />
<text class="text-xs text-gray-700 font-medium text-center">实名认证</text>
<text v-if="isRealName" class="text-xs text-green-500">已认证</text>
</view>
<view v-if="isAgent" class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg" @click="toAgentReport">
<image src="/static/me/tgcxjl.svg" class="w-8 h-8" mode="aspectFit" />
<text class="text-xs text-gray-700 font-medium text-center">推广查询记录</text>
</view>
<view class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg" @click="toUserAgreement">
<image src="/static/me/yhxy.svg" class="w-8 h-8" mode="aspectFit" />
<text class="text-xs text-gray-700 font-medium text-center">用户协议</text>
</view>
<view class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg" @click="toPrivacyPolicy">
<image src="/static/me/yszc.svg" class="w-8 h-8" mode="aspectFit" />
<text class="text-xs text-gray-700 font-medium text-center">隐私政策</text>
</view>
<!-- 小程序内使用原生客服按钮其他端跳转客服页 -->
<!-- #ifdef MP-WEIXIN -->
<button
open-type="contact"
class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg w-full min-h-0 leading-none border-0 bg-transparent p-0 m-0 after:border-0"
>
<image src="/static/me/lxkf.svg" class="w-8 h-8" mode="aspectFit" />
<text class="text-xs text-gray-700 font-medium text-center">联系客服</text>
</button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg" @click="toService">
<image src="/static/me/lxkf.svg" class="w-8 h-8" mode="aspectFit" />
<text class="text-xs text-gray-700 font-medium text-center">联系客服</text>
</view>
<!-- #endif -->
<view class="flex flex-col items-center justify-center gap-2 py-4 rounded-lg" @click="toAgentSystemGuide">
<image src="/static/me/yhxy.svg" class="w-8 h-8" mode="aspectFit" />
<text class="text-xs text-gray-700 font-medium text-center">代理系统指南</text>
</view>
</view>
</view>
</view>
<!-- 提现 - 公众号二维码弹窗 -->
<view v-if="showWithdrawQrPopup" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50" @click.self="closeWithdrawQrPopup">
<view class="mx-4 rounded-2xl bg-white p-6 shadow-xl" @click.stop>
<view class="text-center text-lg font-bold text-gray-800 mb-2">提现须知</view>
<view class="text-center text-sm text-gray-500 mb-4">请长按识别下方公众号二维码联系客服办理提现</view>
<image
src="/static/me/official_qrcode.jpg"
class="w-56 h-56 mx-auto block rounded-lg bg-gray-100"
mode="aspectFit"
show-menu-by-longpress
/>
<button
class="mt-4 w-full rounded-full py-2.5 text-base font-medium text-white bg-blue-500"
@click="closeWithdrawQrPopup"
>
关闭
</button>
</view>
</view>
<!-- 绑定手机号弹窗 -->
<view v-if="showBindMobilePopup" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50" @click.self="closeBindMobilePopup">
<view class="mx-4 w-full max-w-sm rounded-2xl bg-white p-6 shadow-xl" @click.stop>
<view class="text-center text-lg font-bold text-gray-800 mb-4">绑定手机号</view>
<view class="mb-4">
<view class="text-sm text-gray-600 mb-2">手机号</view>
<input
v-model="phoneNumber"
type="number"
placeholder="请输入手机号"
maxlength="11"
class="w-full px-4 py-3 border border-gray-300 rounded-lg text-base focus:border-blue-500 focus:outline-none"
/>
</view>
<view class="mb-4">
<view class="text-sm text-gray-600 mb-2">验证码</view>
<view class="flex gap-2">
<input
v-model="verificationCode"
type="number"
placeholder="请输入验证码"
maxlength="6"
class="flex-1 px-4 py-3 border border-gray-300 rounded-lg text-base focus:border-blue-500 focus:outline-none"
/>
<button
:disabled="isCountingDown || !isPhoneNumberValid"
:class="[
'px-4 py-3 rounded-lg text-sm font-medium whitespace-nowrap',
isCountingDown || !isPhoneNumberValid
? 'bg-gray-200 text-gray-400'
: 'bg-blue-500 text-white'
]"
@click="sendVerificationCode"
>
{{ isCountingDown ? `${countdown}` : '获取验证码' }}
</button>
</view>
</view>
<view class="flex gap-3">
<button
class="flex-1 rounded-lg py-3 text-base font-medium text-gray-700 bg-gray-100"
@click="closeBindMobilePopup"
>
取消
</button>
<button
class="flex-1 rounded-lg py-3 text-base font-medium text-white bg-blue-500"
@click="handleBindMobile"
>
确定
</button>
</view>
</view>
</view>
<!-- 实名认证弹窗 -->
<view v-if="showRealNameAuthPopup" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50" @click.self="closeRealNameAuthPopup">
<view class="mx-4 w-full max-w-sm rounded-2xl bg-white p-6 shadow-xl max-h-[90vh] overflow-y-auto" @click.stop>
<view class="text-center text-lg font-bold text-gray-800 mb-4">实名认证</view>
<view class="mb-4">
<view class="text-sm text-gray-600 mb-2">姓名</view>
<input
v-model="realName"
type="text"
placeholder="请输入真实姓名"
maxlength="20"
class="w-full px-4 py-3 border border-gray-300 rounded-lg text-base focus:border-blue-500 focus:outline-none"
/>
</view>
<view class="mb-4">
<view class="text-sm text-gray-600 mb-2">身份证号</view>
<input
v-model="idCard"
type="text"
placeholder="请输入身份证号"
maxlength="18"
class="w-full px-4 py-3 border border-gray-300 rounded-lg text-base focus:border-blue-500 focus:outline-none"
/>
</view>
<view class="mb-4">
<view class="text-sm text-gray-600 mb-2">手机号</view>
<input
v-model="realNamePhoneNumber"
type="number"
placeholder="请输入手机号"
maxlength="11"
class="w-full px-4 py-3 border border-gray-300 rounded-lg text-base focus:border-blue-500 focus:outline-none"
/>
</view>
<view class="mb-4">
<view class="text-sm text-gray-600 mb-2">验证码</view>
<view class="flex gap-2">
<input
v-model="realNameVerificationCode"
type="number"
placeholder="请输入验证码"
maxlength="6"
class="flex-1 px-4 py-3 border border-gray-300 rounded-lg text-base focus:border-blue-500 focus:outline-none"
/>
<button
:disabled="isRealNameCountingDown || !isRealNamePhoneValid"
:class="[
'px-4 py-3 rounded-lg text-sm font-medium whitespace-nowrap',
isRealNameCountingDown || !isRealNamePhoneValid
? 'bg-gray-200 text-gray-400'
: 'bg-blue-500 text-white'
]"
@click="sendRealNameVerificationCode"
>
{{ isRealNameCountingDown ? `${realNameCountdown}` : '获取验证码' }}
</button>
</view>
</view>
<view class="mb-4">
<view class="flex items-start gap-2">
<wd-checkbox v-model="isAgreed" />
<view class="text-xs text-gray-600 flex-1">
我已阅读并同意
<text class="text-blue-500" @click.stop="toUserAgreement">用户协议</text>
<text class="text-blue-500" @click.stop="toPrivacyPolicy">隐私政策</text>
</view>
</view>
</view>
<view class="flex gap-3">
<button
class="flex-1 rounded-lg py-3 text-base font-medium text-gray-700 bg-gray-100"
@click="closeRealNameAuthPopup"
>
取消
</button>
<button
class="flex-1 rounded-lg py-3 text-base font-medium text-white bg-blue-500"
:class="{ 'opacity-50': !canSubmitRealName }"
:disabled="!canSubmitRealName"
@click="handleRealNameAuth"
>
提交认证
</button>
</view>
</view>
</view>
</view>
</template>
<style scoped>
/* 小程序客服按钮:去除默认边框与背景,与菜单项一致 */
/* #ifdef MP-WEIXIN */
button[open-type='contact']::after {
border: none;
}
/* #endif */
</style>

View File

@@ -0,0 +1,346 @@
<script setup>
const companyName = import.meta.env?.VITE_COMPANY_NAME || ''
const appName = import.meta.env?.VITE_APP_NAME || '全能查'
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '隐私政策',
navigationStyle: 'default',
},
})
</script>
<template>
<view class="p-4 min-h-screen bg-white">
<view class="mb-4 text-center text-lg font-bold">隐私政策</view>
<view class="content indent-2em">
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">您的信任对我们非常重要</view>
<view class="leading-relaxed">
我们深知个人信息对您的重要性我们将按法律法规要求采取相应安全保护措施尽力保护您的个人信息安全可控
有鉴于此{{ companyName }}以下简称"我们""{{ appName }}"作为{{ appName }}产品及服务的提供者制定本隐私政策下称"本政策"并提醒您
</view>
<view class="leading-relaxed">
本政策适用于全部{{ appName }}产品及服务如我们关联公司的产品或服务中使用了{{ appName }}提供的产品或服务但未设独立的隐私政策的
该部分{{ appName }}提供的产品或服务同样适用于本政策
</view>
<view class="leading-relaxed">
需要特别说明的是本政策不适用于其他第三方通过网页或{{ appName }}客户端直接向您提供的服务统称"第三方服务"
您向该第三方服务提供者提供的信息不适用于本政策您在选择使用第三方服务前应充分了解第三方服务的产品功能及隐私保护政策再选择是否开通功能
</view>
<view class="leading-relaxed">
在使用{{ appName }}产品或服务前请您务必仔细阅读并透彻理解本政策在确认充分理解使用相关产品或服务
一旦您开始使用{{ appName }}产品或服务即表示您已充分理解并同意本政策
</view>
</view>
<view class="mb-2 font-bold leading-relaxed">第一部分 定义</view>
<view class="mb-4">
<view class="leading-relaxed">
<view>1{{ appName }}服务提供者是指研发并提供{{ appName }}产品和服务法律主体{{ companyName }}下称"我们""{{ appName }}"</view>
<view>2{{ appName }}用户是指注册{{ appName }}账户的用户以下称"您"</view>
<view>3个人信息指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息</view>
<view>4个人信息删除指在实现日常业务功能所涉及的系统中去除个人信息的行为使其保持不可被检索访问的状态具体指产品内的账号注销功能</view>
<view>5个人信息匿名化通过对个人信息的加密技术处理使得个人信息主体无法被识别且处理后的信息不能被复原的过程</view>
</view>
</view>
<view class="mb-2 font-bold leading-relaxed">第二部分 隐私政策</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">我们如何收集您的个人信息</view>
<view class="leading-relaxed">
为了向您及{{ appName }}企业用户提供{{ appName }}服务维护{{ appName }}服务的正常运行改进及优化我们的服务体验并保障您的账号安全
我们会出于本政策下述目的及方式收集您在注册使用{{ appName }}服务时主动提供授权提供或基于您使用{{ appName }}服务时产生的信息
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">注册{{ appName }}用户信息</view>
<view class="leading-relaxed">
为注册成为{{ appName }}用户以便我们为您提供{{ appName }}服务诸如数据查询视频查看等功能
您需要提供您的手机号码及短信验证码以注册并创建{{ appName }}账号否则您将不能使用{{ appName }}服务
</view>
<view class="leading-relaxed">
如果您仅需使用浏览搜索{{ appName }}网页展示的产品功能及服务介绍您不需要注册成为{{ appName }}用户并提供上述信息
</view>
<view class="leading-relaxed">
如您的账号是注册在企业下的关联账号当您所在企业用户注销{{ appName }}账户时我们将会匿名化处理或删除您在该组织的相关个人信息
但您作为{{ appName }}个人用户的个人信息仍将保留除非您主动注销{{ appName }}账户
</view>
<view class="leading-relaxed">
在经过用户授权同意的情况下我司需要获取用户的手机号码以便开展相应业务
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">使用{{ appName }}服务过程中收集信息</view>
<view class="leading-relaxed">
当您在使用{{ appName }}服务过程中为向您提供您需求的{{ appName }}软件服务交互展示搜索结果识别账号异常状态维护{{ appName }}服务的正常运行改进及优化您对{{ appName }}服务的体验并保障您的账号安全包括您使用{{ appName }}服务以及使用方式的信息并将这些信息进行关联
</view>
<view class="leading-relaxed">
<view>1日志信息</view>
<view>
当您使用我们的网站或客户端提供的产品或服务时我们会自动收集您对我们服务的详细使用情况作为有关网络日志保存例如您的搜索查询内容IP地址使用的语言访问日期和时间您访问的网页记录日志信息
</view>
<view>
请注意单独的设备信息日志信息是无法识别特定自然人身份的信息如果我们将这类非个人信息与其他信息结合用于识别特定自然人身份或者将其与个人信息结合使用则在结合使用期间这类非个人信息将有可能被视为个人信息除取得您授权或法律法规另有规定外我们会将该类个人信息做匿名化去标识化处理
</view>
</view>
<view class="leading-relaxed">
<view>2您向我们提供的信息</view>
<view>
在服务使用过程中特别是在申请提现实名认证或佣金结算时您需要提供包括但不限于姓名身份证号银行卡号手机号税务身份信息等个人资料
您同意我们为履行合同义务税务申报身份核验财务结算等必要目的收集使用存储并在必要范围内共享该等信息
在进行税务代扣代缴结算服务时我们有权将必要信息提供给依法合作的第三方税务服务商结算服务商前提是该第三方承担同等信息保护义务
</view>
<view>
您可以对{{ appName }}产品及服务的体验问题反馈帮助我们更好地了解您使用我们产品或服务的体验和需求改善我们产品或服务,为此我们会记录您的联系信息反馈的问题或建议以便我们进一步联系您反馈您我们的处理意见
为向您提供更好的服务例如在不同的服务端或设备上提供体验一致的服务和您需求的客服接待了解产品适配性识别账号异常状态
</view>
</view>
<view class="leading-relaxed">
<view>3为您提供安全保障收集信息</view>
<view>
为预防发现调查欺诈侵权危害安全非法或违反与我们或与我们关联公司的协议政策或规则的行为我们可能收集或整合您的用户个人信息服务使用信息设备信息日志信息以及我们关联公司合作伙伴取得您授权或依据法律共享的信息
您理解并同意我们向您提供的功能和服务场景是不断迭代升级的如我们未在上述场景中明示您需要收集的个人信息我们将会通过页面提示交互设计等方式另行向您明示信息收集的内容范围和目的并征得您同意
</view>
<view>
如我们停止运营{{ appName }}产品或服务我们将及时停止继续收集您个人信息的活动将停止运营的通知以公告或短信的形式通知您并依照所适用的法律对所持有的您的个人信息进行删除或匿名化处理
</view>
</view>
<view class="leading-relaxed">
<view>4手机号码收集及其用途</view>
<view>
在您使用{{ appName }}服务的过程中我们可能会要求您提供手机号码我们收集您的手机号码主要是为了向您发送重要的通知服务更新账户安全信息促销活动服务相关的短信等为了确保您能及时获得关于您账号安全产品更新和优化系统维护等信息我们可能会向您发送有关服务变更功能更新版本升级等通知确保您能够持续享受我们的产品和服务
</view>
<view>
此外您的手机号码还可能用于为您提供个性化的短信推广内容帮助您了解我们新推出的服务产品或活动优惠我们承诺不会在未经您明确同意的情况下将您的手机号码用于任何与服务相关以外的用途且不会将您的信息出售或租赁给第三方为了保障您的权益您可以随时通过设置页面或联系客户服务停止接收短信通知或推广信息如果您选择取消订阅短信通知或推广您仍将继续收到与账户安全系统通知等相关的重要信息
</view>
<view>
我们会采取严格的措施保护您的手机号码不被滥用包括采用加密存储定期审查访问权限等技术和管理手段以确保您的个人信息安全同时我们也会根据适用的法律法规在您停止使用我们的服务或终止您的账户时删除或匿名化处理您的手机号码及其他相关信息
</view>
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">我们如何使用信息</view>
<view class="leading-relaxed">
收集您的信息是为了向您提供服务及提升服务质量为了实现这一目的我们会把您的信息用于下列用途
</view>
<view class="leading-relaxed">
<view>1向您提供您使用的{{ appName }}产品或服务并维护改进优化这些服务及服务体验</view>
<view>2为预防发现调查欺诈侵权危害安全非法或违反与我们或与我们关联公司的协议政策或规则的行为保护您其他用户或公众以及我们或我们关联公司的合法权益我们会使用或整合您的个人信息服务使用信息设备信息日志信息以及我们关联公司合作伙伴取得您授权或依据法律共享的信息来综合判断您的操作风险检测及防范安全事件并依法采取必要的记录审计分析处置措施</view>
<view>3经您许可的其他用途</view>
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">我们如何使用Cookie 和同类技术</view>
<view class="leading-relaxed">
为使您获得更轻松的访问体验您使用{{ appName }}产品或服务时我们可能会通过采用各种技术收集和存储您访问{{ appName }}服务的相关数据
在您访问或再次访问{{ appName }}服务时我们能识别您的身份并通过分析数据为您提供更好更多的服务
</view>
<view class="leading-relaxed">
包括使用小型数据文件识别您的身份这么做是为了解您的使用习惯帮您省去重复输入账户信息的步骤或者帮助判断您的账户安全
这些数据文件可能是CookieFlash Cookie或您的浏览器或关联应用程序提供的其他本地存储统称"Cookie"
</view>
<view class="leading-relaxed">
请您理解我们的某些服务只能通过使用Cookie才可得到实现如果您的浏览器或浏览器附加服务允许
您可以修改对Cookie的接受程度或者拒绝{{ appName }}的Cookie但拒绝{{ appName }}的Cookie在某些情况下您可能无法使用依赖于cookies的{{ appName }}服务的部分功能
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">我们如何共享转让公开披露您的信息</view>
<view class="mb-2 font-semibold">() 共享</view>
<view class="leading-relaxed">
我们不会和其他公司组织和个人共享您的个人信息但以下情况除外
</view>
<view class="leading-relaxed">
<view>1在获取您同意的情况下共享获得您的明确同意后我们会与其他方共享您的个人信息</view>
<view>2在法定情形下的共享我们可能会根据法律法规规定诉讼争议解决需要或按行政司法机关依法提出的要求对外共享您的个人信息</view>
<view>3只有透露您的资料才能提供您所要求的第三方产品和服务在您通过{{ appName }}客户端购买查询服务的您同意{{ appName }}向实际产品提供者提供您的身份信息包括真实姓名和身份证号等为了提升实人认证的准确性您同意第三方公司仅限于个人信息进行验证相关服务将您提供的个人信息与法律法规允许的机构或政府机关授权的机构的数据进行校验</view>
<view>4在您被他人投诉侵犯知识产权或其他合法权利时需要向投诉人披露您的必要资料以便进行投诉处理的</view>
<view>5{{ appName }}服务可能含有其他网站的链接除法律另有规定外{{ appName }}对其他网站的隐私保护措施不负相应法律责任我们可能在需要的时候增加商业伙伴但是提供给他们的将仅是综合信息我们将不会公开您的个人信息</view>
</view>
<view class="mb-2 font-semibold">() 转让</view>
<view class="leading-relaxed">
我们不会将您的个人信息转让给任何公司组织和个人但以下情况除外
</view>
<view class="leading-relaxed">
<view>1在获取明确同意的情况下转让获得您的明确同意后我们会向其他方转让您的个人信息</view>
<view>2{{ appName }}发生合并收购或破产清算情形或其他涉及合并收购或破产清算情形时如涉及到个人信息转让我们会要求新的持有您个人信息的公司组织继续受本政策的约束否则我们将要求该公司组织和个人重新向您征求授权同意</view>
</view>
<view class="mb-2 font-semibold">() 公开披露</view>
<view class="leading-relaxed">
我们仅会在以下情况下公开披露您的个人信息
</view>
<view class="leading-relaxed">
<view>1获得您明确同意或基于您的主动选择我们可能会公开披露您的个人信息</view>
<view>2如果我们确定您出现违反法律法规或严重违反{{ appName }}相关协议规则的情况或为保护{{ appName }}及其关联公司用户或公众的人身财产安全免遭侵害我们可能依据法律法规或{{ appName }}相关协议规则征得您同意的情况下披露关于您的个人信息包括相关违规行为以及{{ appName }}已对您采取的措施</view>
</view>
<view class="mb-2 font-semibold">() 共享转让公开披露个人信息时事先征得授权同意的例外</view>
<view class="leading-relaxed">
以下情形中共享转让公开披露您的个人信息无需事先征得您的授权同意
</view>
<view class="leading-relaxed">
<view>1与国家安全国防安全有关的</view>
<view>2与公共安全公共卫生重大公共利益有关的</view>
<view>3与犯罪侦查起诉审判和判决执行等有关的</view>
<view>4出于维护您或其他个人的生命财产等重大合法权益但又很难得到本人同意的</view>
<view>5您自行向社会公众公开的个人信息</view>
<view>6从合法公开披露的信息中收集个人信息的如合法的新闻报道政府信息公开等渠道请您注意根据法律规定共享转让经匿名化处理的个人信息且确保数据接收方无法复原并重新识别个人信息主体的不属于个人信息的对外共享转让及公开披露行为对此类数据的保存及处理将无需另行向您通知并征得您的同意</view>
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">我们如何保护您的信息</view>
<view class="leading-relaxed">
我们会采取各种预防措施来保护您的个人信息以保障您的个人信息免遭丢失盗用和误用以及被擅自取阅披露更改或销毁
为确保您个人信息的安全我们有严格的信息安全规定和流程并严格执行上述措施
</view>
<view class="leading-relaxed">
{{ appName }}建立了全方位多维度的数据安全管理体系保证整个{{ appName }}各个平台的安全性
我们会采取合理可行的措施尽力避免收集无关的个人信息
并在限于达成本政策所述目的所需的期限以及所适用法律法规所要求的期限内对您的个人信息进行脱敏处理
在您使用查询过程中所涉及的用户姓名身份证号手机号/账号密码信息均采用的是AES加密方式
所有二次输出信息均经过脱敏处理数据库文件不存储用户明文数据
</view>
<view class="leading-relaxed">
在不幸发生个人信息安全事件后我们将按照法律法规的要求最迟不迟于30个自然日内向您告知
安全事件的基本情况和可能的影响我们已采取或将要采取的处置措施您可自主防范和降低风险的建议对您的补救措施等
事件相关情况我们将以邮件信函电话通知等方式告知您
难以逐一告知个人信息主体时我们会采取合理有效的方式发布公告
同时我们还将按照监管部门要求上报个人信息安全事件的处置情况
</view>
<view class="leading-relaxed">
互联网环境并非百分之百安全尽管我们有这些安全措施但仍然无法完全避免互联网中存在的各种风险我们将尽力确保您的信息的安全性
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">未成年人保护</view>
<view class="leading-relaxed">
我们重视未成年人的信息保护如您为未成年人的建议您请您的父母或监护人仔细阅读本隐私权政策
并在征得您的父母或监护人同意的前提下使用我们的服务或向我们提供信息
</view>
<view class="leading-relaxed">
对于经父母或监护人同意使用我们的产品或服务而收集未成年人个人信息的情况
我们只会在法律法规允许父母或监护人明确同意或者保护未成年人所必要的情况下使用共享转让或披露此信息
</view>
<view class="leading-relaxed">
我们将根据国家相关法律法规及本政策的规定保护未成年人的个人信息
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">您的个人信息存储</view>
<view class="mb-2 font-semibold">() 存储地区</view>
<view class="leading-relaxed">
我们将在中华人民共和国境内运营{{ appName }}服务中收集和产生的个人信息存储在中华人民共和国境内
目前我们不会将上述信息传输至境外如果我们向境外传输我们将会遵循相关国家规定或者征求您的同意
</view>
<view class="mb-2 font-semibold">() 存储期限</view>
<view class="leading-relaxed">
您在使用本平台期间我们将保存您的个人脱敏加密信息保存期限将以不超过为您提供服务所必须的期间为原则
在您终止使用本平台后除法律法规对于特定信息保留期限另有规定外我们会对您的信息进行删除或做匿名化处理
如我们停止运营本平台服务我们将在合理期限内依照所适用的法律对所持有的您的个人信息进行删除或匿名化处理
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">您享有的权利及权利行使路径</view>
<view class="mb-2 font-semibold">() 访问查询权</view>
<view class="leading-relaxed">
您对您的{{ appName }}账号内的信息含个人信息依法享有访问查询权包括
</view>
<view class="leading-relaxed">
<view><text class="font-semibold">账户信息</text>您可以登录手机客户端通过我的-点击名字或头像可以访问您的头像信息姓名绑定手机号</view>
<view><text class="font-semibold">使用信息</text>您可以在{{ appName }}手机客户端相关页面访问查询您的使用信息包括订单信息可以通过报告列表-查看详情进行访问查看</view>
<view><text class="font-semibold">其他信息</text>如您在此前述过程中遇到操作问题的或如需获取其他前述无法获知的个人信息内容您可通过在线客服或邮箱联系我们我们将在核实您的身份后在合理期限内向您提供但法律法规另有规定的或本政策另有约定的除外</view>
</view>
<view class="mb-2 font-semibold">() 同意的撤回与变更</view>
<view class="leading-relaxed">
若您需要更改相关权限的授权例如相机相册麦克风您可以通过您的硬件设备进行修改
您也可以通过注销{{ appName }}账户的方式永久撤回我们继续收集您个人信息的全部授权
如您在此过程中遇到操作问题的可以通过本政策"帮助中心"方式联系我们
</view>
<view class="mb-2 font-semibold">() 帮助反馈权</view>
<view class="leading-relaxed">
我们为您提供了多种反馈渠道具体请见设置帮助中心
</view>
<view class="mb-2 font-semibold">() 提前获知产品与/或服务停止运营权</view>
<view class="leading-relaxed">
我们将持续为您提供优质服务若因特殊原因导致我们的部分或全部产品与/或服务被迫停止运营
我们将提前在显著位置或通知您并将停止对您个人信息的收集
同时在超出法律法规规定的必需且最短期限后我们将会对所持有的您的个人信息进行删除或匿名化处理
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">本政策如何更新</view>
<view class="leading-relaxed">
我们的隐私政策可能变更
未经您明确同意我们不会限制您按照本隐私政策所应享有的权利
我们会在{{ appName }}各个平台包括客户端相关网页上以首页弹窗形式发布对本隐私政策所做的任何变更并以交互设计提醒您阅读并完整理解
对于重大变更我们还会提供更为显著的通知可能包括公告通知甚至向您提供弹窗提示
</view>
<view class="leading-relaxed">
本政策所指的重大变更包括但不限于
<view>1我们的服务模式发生重大变化如处理用户信息的目的用户信息的使用方式等</view>
<view>2我们在控制权组织架构等方面发生重大变化如业务调整破产并购等引起的所有者变更等</view>
<view>3用户信息共享转让或公开披露的主要对象发生变化</view>
<view>4我们负责处理用户信息安全的责任部门联络方式及投诉渠道发生变化时</view>
<view>5用户信息安全影响评估报告表明存在高风险时</view>
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">如何联系我们</view>
<view class="leading-relaxed">
如果您对本政策或数据处理有任何疑问意见或建议可以通过{{ appName }}产品内的"联系客服"或邮箱
<text class="text-blue-500"> admin@iieeii.com </text>
与我们联系我们将在收到您发送的响应请求或相关信息之日起十五15天内回复您
</view>
<view class="leading-relaxed">
您理解并同意当涉及以下任一情形时我们无法响应您的请求
<view>1与国家安全国防安全有关的</view>
<view>2与公共安全公共卫生重大公共利益有关的</view>
<view>3与犯罪侦查起诉和审判等有关的</view>
<view>4有充分证据表明您存在主观恶意或滥用权利的</view>
<view>5响应您的请求将导致您或其他个人组织的合法权益受到严重损害的</view>
<view>6涉及{{ appName }}或任何第三方主体商业秘密的</view>
<view>7法律法规规定的其他情形</view>
</view>
<view class="leading-relaxed">
如果您对我们的回复不满意特别是您认为我们的个人信息处理行为损害了您的合法权益
您还可以通过向有管辖权的法院提起诉讼来寻求解决方案
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">十一其他</view>
<view class="leading-relaxed">
隐私政策的解释及争议解决均应适用中华人民共和国大陆地区法律
与本隐私政策相关的任何纠纷双方应协商友好解决若不能协商解决
应将争议提交至{{ companyName }}注册地有管辖权的人民法院解决
</view>
<view class="leading-relaxed">
隐私政策的标题仅为方便及阅读而设并不影响正文其中任何规定的含义或解释
</view>
</view>
<view class="mt-4 text-right text-sm text-gray-600">
<text>2024年11月19日</text>
</view>
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
.content {
text-indent: 2em;
}
</style>

288
src/pages/promote/index.vue Normal file
View File

@@ -0,0 +1,288 @@
<script setup>
definePage({
layout: 'HomeLayout',
style: {
navigationBarTitleText: '推广',
navigationStyle: 'custom',
},
})
import { ref, computed, onMounted, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
import SectionTitle from '@/components/SectionTitle.vue'
import { useAgentStore } from '@/stores/agentStore'
import { useUserStore } from '@/stores/userStore'
import { getProductConfig } from '@/api/agent'
import { getShareAppMessageOptions, getShareTimelineOptions } from '@/composables/useWechatShare'
const agentStore = useAgentStore()
const userStore = useUserStore()
const { isAgent } = storeToRefs(agentStore)
const { isLoggedIn, mobile } = storeToRefs(userStore)
function toInquire(name) {
if (!isAgent.value) {
if (!isLoggedIn.value) {
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
return
} else {
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
}
return
}
uni.navigateTo({ url: `/pages/promote/report?feature=${encodeURIComponent(name)}` })
}
function toInvitation() {
if (!isAgent.value) {
if (!isLoggedIn.value) {
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
return
} else {
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
}
return
}
uni.navigateTo({ url: '/pages/invitation/index' })
}
function toPromote() {
if (!isAgent.value) {
if (!isLoggedIn.value) {
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
return
} else {
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
}
return
}
uni.navigateTo({ url: '/pages/promote/report' })
}
function toHelp() {
uni.navigateTo({ url: '/pages/agentSystemGuide/index' })
}
function toHistory() {
uni.navigateTo({ url: '/pages/historyQuery/index' })
}
function toAgent() {
if (!isAgent.value) {
if (!isLoggedIn.value) {
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
return
} else {
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
}
return
}
uni.switchTab({ url: '/pages/agent/index' })
}
function toWithdraw() {
if (!isAgent.value) {
if (!isLoggedIn.value) {
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
return
} else {
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
}
return
}
uni.navigateTo({ url: '/pages/withdraw/index' })
}
function toTeamList() {
if (!isAgent.value) {
if (!isLoggedIn.value) {
uni.showToast({ title: '正在登录中,请稍候', icon: 'none' })
return
} else {
uni.showToast({ title: '请先注册成为代理', icon: 'none' })
uni.navigateTo({ url: `/pages/register/index?mobile=${encodeURIComponent(mobile.value)}` })
}
return
}
uni.navigateTo({ url: '/pages/teamList/index' })
}
function toService() {
uni.navigateTo({ url: '/pages/service/index' })
}
const services = ref([
{ name: 'riskassessment', title: '个人大数据', subtitle: '个人信用 精准查询', bg: '/static/promote/personal_data_bg.png', goColor: '#6699ff', costPrice: null },
{ name: 'marriage', title: '情侣报告', subtitle: '相信才能相依 相爱才能永久', bg: '/static/promote/marriage_risk_bg.png', goColor: '#ff99cc', costPrice: null },
{ name: 'backgroundcheck', title: '入职背调', subtitle: '查询便可 慧眼识英雄', bg: '/static/promote/backgroundcheck_bg.png', goColor: '#7db3ff', costPrice: null },
{ name: 'companyinfo', title: '企业大数据', subtitle: '信任是合作 永恒的基石', bg: '/static/promote/company_bg.png', goColor: '#ffaa66', costPrice: null },
{ name: 'homeservice', title: '家政报告', subtitle: '口碑与能力 一查便知', bg: '/static/promote/housekeeping_risk_bg.png', goColor: '#66cccc', costPrice: null },
{ name: 'consumerFinanceReport', title: '消金报告', subtitle: '', bg: '/static/promote/consumer_finance_report_bg.png', goColor: '#a259ff', costPrice: null },
])
const allServices = computed(() => services.value)
function getCostPriceText(service) {
if (isLoggedIn.value && isAgent.value && service.costPrice) return `成本价 ${service.costPrice}`
if (!isLoggedIn.value) return '登录查看'
if (!isAgent.value) return '成为代理查看'
return '成本价 --'
}
async function fetchProductConfig() {
const token = typeof uni !== 'undefined' && uni.getStorageSync ? uni.getStorageSync('token') : ''
if (!token) return
try {
const { data, error } = await getProductConfig()
if (data.value && !error.value && data.value.code === 200) {
const list = data.value.data?.list || []
services.value.forEach((s) => {
const config = list.find((item) => item.product_en === s.name)
if (config?.actual_base_price != null) s.costPrice = parseFloat(config.actual_base_price).toFixed(2)
})
}
} catch (e) {
console.error(e)
}
}
onMounted(() => {})
watch([isLoggedIn, isAgent], ([loggedIn, agent]) => {
if (loggedIn && agent) fetchProductConfig()
}, { immediate: true })
// 推广页头图(单图,已去掉轮播)
const bannerImage = '/static/promote/banner_1.png'
// 微信分享:分享给好友
onShareAppMessage(() => {
return getShareAppMessageOptions({ path: 'pages/promote/index' })
})
// 微信分享:分享到朋友圈
onShareTimeline(() => {
return getShareTimelineOptions({ query: 'from=timeline' })
})
</script>
<template>
<view class="box-border from-blue-100 to-white bg-gradient-to-b">
<view class="relative" @click="toPromote">
<image :src="bannerImage" class="w-full block" mode="widthFix" />
</view>
<view class="px-6 mt-4">
<view class="grid grid-cols-4 gap-3">
<view class="text-center flex flex-col justify-center items-center" @click="toPromote">
<view class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
<image src="/static/promote/tgbg.png" class="h-14 w-14" mode="aspectFit" />
</view>
<view class="text-center mt-1 font-bold text-sm">推广报告</view>
</view>
<view class="text-center flex flex-col justify-center items-center" @click="toInvitation">
<view class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
<image src="/static/promote/yqxj.png" class="h-14 w-14" mode="aspectFit" />
</view>
<view class="text-center mt-1 font-bold text-sm">邀请下级</view>
</view>
<view class="text-center flex flex-col justify-center items-center" @click="toHelp">
<view class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
<image src="/static/promote/bzzx.png" class="h-14 w-14" mode="aspectFit" />
</view>
<view class="text-center mt-1 font-bold text-sm">帮助中心</view>
</view>
<view class="text-center flex flex-col justify-center items-center" @click="toHistory">
<view class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
<image src="/static/promote/wdbg.png" class="h-14 w-14" mode="aspectFit" />
</view>
<view class="text-center mt-1 font-bold text-sm">我的报告</view>
</view>
<view class="text-center flex flex-col justify-center items-center" @click="toAgent">
<view class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
<image src="/static/promote/zc.png" class="h-14 w-14" mode="aspectFit" />
</view>
<view class="text-center mt-1 font-bold text-sm">资产</view>
</view>
<view class="text-center flex flex-col justify-center items-center" @click="toWithdraw">
<view class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
<image src="/static/promote/wytx.png" class="h-14 w-14" mode="aspectFit" />
</view>
<view class="text-center mt-1 font-bold text-sm">我要提现</view>
</view>
<view class="text-center flex flex-col justify-center items-center" @click="toTeamList">
<view class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
<image src="/static/promote/wdxj.png" class="h-14 w-14" mode="aspectFit" />
</view>
<view class="text-center mt-1 font-bold text-sm">我的团队</view>
</view>
<view class="text-center flex flex-col justify-center items-center" @click="toService">
<view class="h-14 w-14 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
<image src="/static/promote/zxkf.png" class="h-14 w-14" mode="aspectFit" />
</view>
<view class="text-center mt-1 font-bold text-sm">在线客服</view>
</view>
</view>
</view>
<view class="my-2 mx-4 rounded-xl overflow-hidden shadow-xl" @click="toInvitation">
<image src="/static/promote/tghb.png" class="w-full block" mode="widthFix" />
</view>
<view class="flex items-center justify-between mx-4 mb-2">
<SectionTitle title="推广服务" />
<view class="text-xs text-gray-500 flex items-center gap-1.5">
<text></text>
<text>滑动查看更多</text>
<text></text>
</view>
</view>
<view class="relative p-4 pt-0">
<scroll-view scroll-x class="services-scroll-container" :show-scrollbar="false">
<view class="services-scroll-wrapper">
<view
v-for="(service, index) in allServices"
:key="index"
class="relative flex flex-col px-4 py-2 rounded-xl shadow service-card"
:style="`background: url(${service.bg}) no-repeat; background-size: contain; background-position: center;`"
@click="toInquire(service.name)"
>
<view class="flex flex-col items-start flex-1">
<view class="mt-1 text-left text-gray-600 font-bold">{{ service.title }}</view>
<view
class="mt-2 rounded-lg px-2 py-1 text-xs text-white w-max flex items-center"
:style="`background-color: ${service.goColor}`"
>
立即推广
<image src="/static/index/go_icon.png" class="ml-0.5 h-3 w-3 inline-block" mode="aspectFit" />
</view>
</view>
<view class="absolute bottom-0 left-0 right-0 rounded-b-xl px-2 py-1 text-xs text-white text-center">
{{ getCostPriceText(service) }}
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<style scoped>
.services-scroll-container {
white-space: nowrap;
padding-bottom: 0.5rem;
}
.services-scroll-wrapper {
display: inline-flex;
gap: 1rem;
padding-right: 1rem;
}
.service-card {
width: 120px;
height: 120px;
flex-shrink: 0;
display: inline-block;
}
</style>

View File

@@ -0,0 +1,370 @@
<script setup>
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '推广报告',
navigationStyle: 'default',
},
})
import { ref, computed, watch, onMounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import useApiFetch from '@/composables/useApiFetch'
import { getProductConfig, generateLink } from '@/api/agent'
import PriceInputPopup from '@/components/PriceInputPopup.vue'
import QRcode from '@/components/QRcode.vue'
import ReportFeatures from '@/components/ReportFeatures.vue'
const currentFeature = ref('')
const reportTypes = ref([])
const showPricePicker = ref(false)
const pickerFieldText = ref('')
const selectedReportTypeIndex = ref(0)
const pickerProductConfig = ref(null)
const pickerFieldVal = ref(null)
const clientPrice = ref(null)
const productConfig = ref(null)
const fullLink = ref('')
const featureData = ref({})
const showQRcode = ref(false)
const logoMap = {
riskassessment: '/static/promote/personal_data_logo.png',
companyinfo: '/static/promote/company_logo.png',
preloanbackgroundcheck: '/static/promote/preloan_background_check_logo.png',
marriage: '/static/promote/marriage_risk_logo.png',
homeservice: '/static/promote/housekeeping_risk_logo.png',
backgroundcheck: '/static/promote/backgroundcheck_logo.png',
consumerFinanceReport: '/static/promote/consumer_finance_report_logo.png',
}
const currentLogo = computed(() => (currentFeature.value ? logoMap[currentFeature.value] || null : null))
function safeTruncate(num, decimals = 2) {
if (isNaN(num) || !isFinite(num)) return '0.00'
const factor = 10 ** decimals
const scaled = Math.trunc(num * factor)
return (scaled / factor).toFixed(decimals)
}
const baseCost = computed(() => {
if (!pickerProductConfig.value) return '0.00'
const v = Number(pickerProductConfig.value.actual_base_price) || 0
return safeTruncate(v)
})
const raiseCost = computed(() => {
if (!pickerProductConfig.value) return '0.00'
const priceNum = Number(clientPrice.value) || 0
const threshold = Number(pickerProductConfig.value.price_threshold) || 0
const rate = Number(pickerProductConfig.value.price_fee_rate) || 0
let cost = 0
if (priceNum > threshold) cost = (priceNum - threshold) * rate
return safeTruncate(cost)
})
const promotionRevenue = computed(() => {
const priceNum = Number(clientPrice.value) || 0
const base = parseFloat(baseCost.value) || 0
const raise = parseFloat(raiseCost.value) || 0
const revenue = priceNum - base - raise
return safeTruncate(revenue >= 0 ? revenue : 0)
})
const pickerProductConfigForPopup = computed(() => pickerProductConfig.value || {})
async function getProductInfo() {
if (!currentFeature.value) return
try {
const { data, error } = await useApiFetch(`/product/en/${currentFeature.value}`).get().json()
if (data.value && !error.value && data.value.data) {
featureData.value = data.value.data
if (featureData.value.features?.length) {
featureData.value.features.sort((a, b) => {
if (a.api_id === 'FLXG0V4B') return -1
if (b.api_id === 'FLXG0V4B') return 1
return 0
})
}
}
} catch (e) {
console.error(e)
}
}
function findReportTypeByFeature(feature) {
return reportTypes.value.find((t) => t.value === feature)
}
function selectTypePicker(reportType) {
if (!reportType) return
pickerFieldVal.value = reportType.value
pickerFieldText.value = reportType.text
const idx = reportTypes.value.findIndex((t) => t.value === reportType.value)
selectedReportTypeIndex.value = idx >= 0 ? idx : 0
if (productConfig.value) {
for (const i of productConfig.value) {
if (i.product_en === reportType.value) {
pickerProductConfig.value = i
clientPrice.value = Number(i.actual_base_price) || Number(i.price_range_min) || 0
break
}
}
}
currentFeature.value = reportType.value
getProductInfo()
}
async function getPromoteConfig() {
const token = typeof uni !== 'undefined' && uni.getStorageSync ? uni.getStorageSync('token') : ''
if (!token) return
const { data, error } = await getProductConfig()
if (!data.value || error.value || data.value.code !== 200) return
const list = data.value.data?.list || []
productConfig.value = list
const types = list.filter((c) => c.product_en).map((c) => ({ text: c.product_name, value: c.product_en, id: c.product_id }))
reportTypes.value = types
let reportType = currentFeature.value ? findReportTypeByFeature(currentFeature.value) : null
if (!reportType && types.length) reportType = types[0]
if (reportType) selectTypePicker(reportType)
}
function onTypePickerChange(e) {
const idx = Number(e.detail?.value ?? e.detail?.value?.[0] ?? 0)
selectedReportTypeIndex.value = idx
const reportType = reportTypes.value[idx]
if (reportType) selectTypePicker(reportType)
}
async function generatePromotionCode() {
if (!pickerFieldVal.value || !pickerProductConfig.value) {
uni.showToast({ title: '请选择报告类型', icon: 'none' })
return
}
const priceNum = Number(clientPrice.value)
if (isNaN(priceNum) || priceNum <= 0) {
uni.showToast({ title: '请输入有效的查询价格', icon: 'none' })
return
}
const minPrice = Number(pickerProductConfig.value.price_range_min) || 0
const maxPrice = Number(pickerProductConfig.value.price_range_max) ?? Infinity
if (priceNum < minPrice) {
uni.showToast({ title: `价格不能低于 ${minPrice.toFixed(2)}`, icon: 'none' })
return
}
if (priceNum > maxPrice) {
uni.showToast({ title: `价格不能高于 ${maxPrice.toFixed(2)}`, icon: 'none' })
return
}
try {
const targetPath = '/agent/promotionInquire/'
const { data, error } = await generateLink({
product_id: pickerProductConfig.value.product_id,
set_price: priceNum,
target_path: targetPath,
})
if (data.value && !error.value && data.value.code === 200) {
fullLink.value = data.value.data?.full_link || ''
showQRcode.value = true
} else {
uni.showToast({ title: data.value?.msg || '生成推广链接失败,请重试', icon: 'none' })
}
} catch (err) {
console.error(err)
uni.showToast({ title: '生成推广链接失败,请重试', icon: 'none' })
}
}
function onPriceChange(price) {
const num = Number(price)
if (!isNaN(num) && isFinite(num)) clientPrice.value = num
}
function openPricePicker() {
showPricePicker.value = true
}
function onUpdateShowPricePicker(v) {
showPricePicker.value = !!v
}
function onUpdateShowQRcode(v) {
showQRcode.value = !!v
}
function toExample() {
if (!currentFeature.value) return
uni.showToast({ title: '示例报告功能敬请期待', icon: 'none' })
}
onLoad((options) => {
currentFeature.value = options?.feature || ''
})
watch(
() => currentFeature.value,
() => {
if (currentFeature.value) getPromoteConfig()
},
{ immediate: false },
)
onMounted(() => {
getPromoteConfig()
})
</script>
<template>
<view class="min-h-screen promote bg-gray-100">
<view class="promote-header-bg">
<image src="/static/promote/promote_bg.jpg" class="bg-image" mode="aspectFill" />
</view>
<view class="px-4 pb-6">
<view class="card card-with-header rounded-2xl bg-white shadow-lg p-4 -mt-12 relative">
<view class="report-header-elegant">
<view class="report-header-content">
<view v-if="currentLogo" class="report-logo-wrapper">
<image :src="currentLogo" class="report-logo-elegant" mode="aspectFit" />
</view>
<view class="report-info-elegant">
<view class="report-label-elegant">推广报告</view>
<view class="report-title-elegant">{{ pickerFieldText || '请选择报告类型' }}</view>
</view>
</view>
<picker
v-if="reportTypes.length"
mode="selector"
:range="reportTypes"
range-key="text"
:value="selectedReportTypeIndex"
@change="onTypePickerChange"
>
<view class="report-action-elegant">
<text class="report-action-text">切换</text>
</view>
</picker>
</view>
<view class="pt-6 space-y-4">
<view class="flex items-center justify-between py-3 border-b border-gray-100">
<text class="font-medium text-gray-700">客户查询价</text>
<text class="text-lg font-semibold text-orange-500">¥{{ clientPrice != null ? clientPrice : '0.00' }}</text>
</view>
<view class="rounded-xl bg-gray-50 p-3 space-y-2">
<view class="flex justify-between">
<text class="text-sm text-gray-600">底价成本</text>
<text class="text-sm font-semibold text-orange-500">¥{{ baseCost }}</text>
</view>
<view class="flex justify-between">
<text class="text-sm text-gray-600">提价成本</text>
<text class="text-sm font-semibold text-orange-500">¥{{ raiseCost }}</text>
</view>
<view class="flex justify-between">
<text class="text-sm text-gray-600">推广收益</text>
<text class="text-sm font-semibold text-orange-500">¥{{ promotionRevenue }}</text>
</view>
</view>
<view class="grid grid-cols-3 gap-3 mt-4">
<wd-button type="primary" size="large" @click="openPricePicker">设置价格</wd-button>
<wd-button
type="primary"
size="large"
:disabled="!clientPrice || !currentFeature"
@click="generatePromotionCode"
>
推广报告
</wd-button>
<wd-button type="default" size="large" :disabled="!currentFeature" @click="toExample">示例报告</wd-button>
</view>
</view>
</view>
<view v-if="featureData.product_name" class="card mt-4 rounded-2xl bg-white shadow-lg p-4">
<ReportFeatures :features="featureData.features || []" />
</view>
</view>
<PriceInputPopup
:show="showPricePicker"
:default-price="clientPrice"
:product-config="pickerProductConfigForPopup"
@update:show="onUpdateShowPricePicker"
@change="onPriceChange"
/>
<QRcode :show="showQRcode" :full-link="fullLink" @update:show="onUpdateShowQRcode" />
</view>
</template>
<style scoped>
.promote-header-bg {
width: 100%;
height: 200rpx;
overflow: hidden;
}
.bg-image {
width: 100%;
height: 100%;
}
.report-header-elegant {
position: absolute;
top: -2.5rem;
left: 1rem;
right: 1rem;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.98), rgba(249, 250, 251, 0.98));
border-radius: 1.25rem;
padding: 1rem 1.5rem;
display: flex;
align-items: center;
justify-content: space-between;
z-index: 10;
}
.report-header-content {
display: flex;
align-items: center;
gap: 1rem;
flex: 1;
min-width: 0;
}
.report-logo-wrapper {
width: 72rpx;
height: 72rpx;
border-radius: 1rem;
overflow: hidden;
flex-shrink: 0;
}
.report-logo-elegant {
width: 100%;
height: 100%;
}
.report-info-elegant {
flex: 1;
min-width: 0;
}
.report-label-elegant {
font-size: 24rpx;
font-weight: 600;
color: #6b7280;
margin-bottom: 4rpx;
}
.report-title-elegant {
font-size: 28rpx;
font-weight: 700;
color: #111827;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.report-action-elegant {
padding: 0 14rpx;
height: 72rpx;
border-radius: 12rpx;
background: linear-gradient(135deg, #64b5f6, #42a5f5);
display: flex;
align-items: center;
justify-content: center;
}
.report-action-text {
font-size: 28rpx;
font-weight: 600;
color: #fff;
}
</style>

View File

@@ -0,0 +1,129 @@
<script setup>
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '我的推广收益',
navigationStyle: 'default',
},
})
import { ref, onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { useAgentStore } from '@/stores/agentStore'
import { getRevenueInfo, getCommissionList } from '@/api/agent'
const agentStore = useAgentStore()
const { isAgent } = storeToRefs(agentStore)
const summary = ref(null)
const list = ref([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(20)
const loading = ref(false)
const hasMore = ref(true)
async function loadSummary() {
if (!isAgent.value) return
const { data, error } = await getRevenueInfo()
if (data.value?.code === 200 && !error.value) summary.value = data.value.data
}
async function loadList(isRefresh = false) {
if (!isAgent.value) return
if (loading.value) return
const nextPage = isRefresh ? 1 : page.value
if (!isRefresh && !hasMore.value) return
loading.value = true
try {
const { data, error } = await getCommissionList({ page: nextPage, page_size: pageSize.value })
if (data.value?.code === 200 && !error.value) {
const res = data.value.data
total.value = res?.total ?? 0
const items = res?.list || []
if (nextPage === 1) {
list.value = items
page.value = 1
} else {
list.value = list.value.concat(items)
}
page.value = nextPage
hasMore.value = list.value.length < total.value
}
} finally {
loading.value = false
}
}
function loadMore() {
if (!hasMore.value || loading.value) return
page.value += 1
loadList(false)
}
function statusText(status) {
const map = { 1: '已发放', 2: '已冻结', 3: '已取消' }
return map[status] || '—'
}
onMounted(() => {
if (isAgent.value) {
loadSummary()
loadList(true)
}
})
</script>
<template>
<view class="p-4 min-h-screen bg-gray-50">
<view v-if="!isAgent" class="rounded-xl bg-white shadow p-6 text-center">
<view class="text-gray-500 text-sm">请先注册成为代理</view>
</view>
<template v-else>
<view v-if="summary" class="rounded-xl bg-white shadow p-4 mb-4">
<view class="text-sm text-gray-500 mb-1">累计推广收益</view>
<view class="text-2xl font-bold text-amber-500">¥ {{ (summary.commission_total || 0).toFixed(2) }}</view>
<view class="grid grid-cols-2 gap-3 mt-3 text-center">
<view class="p-2 rounded-lg bg-gray-50">
<view class="text-xs text-gray-500">今日</view>
<view class="text-base font-semibold text-amber-500">¥ {{ (summary.commission_today || 0).toFixed(2) }}</view>
</view>
<view class="p-2 rounded-lg bg-gray-50">
<view class="text-xs text-gray-500">本月</view>
<view class="text-base font-semibold text-amber-500">¥ {{ (summary.commission_month || 0).toFixed(2) }}</view>
</view>
</view>
</view>
<view class="rounded-xl bg-white shadow overflow-hidden">
<view class="p-4 border-b border-gray-100">
<view class="text-base font-semibold text-gray-800">收益明细</view>
<view class="text-xs text-gray-500 mt-1">推广订单获得的佣金</view>
</view>
<view v-if="loading && list.length === 0" class="p-6">
<wd-skeleton :row="4" />
</view>
<view v-else-if="list.length === 0" class="p-8 text-center text-gray-500 text-sm">
暂无收益记录
</view>
<view v-else class="divide-y divide-gray-100">
<view
v-for="item in list"
:key="item.id"
class="p-4 flex items-center justify-between"
>
<view class="flex-1 min-w-0">
<view class="font-medium text-gray-800">{{ item.product_name || '推广订单' }}</view>
<view class="text-xs text-gray-500 mt-0.5">{{ item.order_no || item.create_time }}</view>
<view class="text-xs text-gray-500 mt-0.5">{{ item.create_time }} · {{ statusText(item.status) }}</view>
</view>
<view class="text-right flex-shrink-0 ml-3">
<view class="text-lg font-bold text-amber-500">+¥{{ (item.amount || 0).toFixed(2) }}</view>
</view>
</view>
</view>
<view v-if="loading && list.length > 0" class="p-3 text-center text-gray-400 text-sm">加载中</view>
<view v-else-if="hasMore && list.length > 0" class="p-3 text-center text-primary text-sm" @click="loadMore">加载更多</view>
</view>
</template>
</view>
</template>

View File

@@ -0,0 +1,424 @@
<script setup>
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '注册成为代理',
navigationStyle: 'default',
},
})
import { ref, computed, onUnmounted, onMounted } from 'vue'
import { registerByInviteCode, applyForAgent } from '@/api/agent'
import { useAgentStore } from '@/stores/agentStore'
import { useUserStore } from '@/stores/userStore'
import useApiFetch from '@/composables/useApiFetch'
const agentStore = useAgentStore()
const userStore = useUserStore()
const appName = import.meta.env?.VITE_APP_NAME || '全能查'
const phoneNumber = ref('')
const verificationCode = ref('')
const inviteCode = ref('')
const isAgreed = ref(false)
const isCountingDown = ref(false)
const countdown = ref(60)
const isPhoneDisabled = ref(false)
let timer = null
function fillInviteCode() {
inviteCode.value = '16800'
}
onMounted(async () => {
const pages = getCurrentPages()
const page = pages[pages.length - 1]
const options = page?.options || {}
if (options.invite_code) {
inviteCode.value = decodeURIComponent(options.invite_code)
}
if (options.mobile) {
phoneNumber.value = decodeURIComponent(options.mobile)
isPhoneDisabled.value = true
} else {
const token = uni.getStorageSync('token')
if (token && userStore.mobile) {
phoneNumber.value = userStore.mobile
isPhoneDisabled.value = true
} else if (token) {
try {
await userStore.fetchUserInfo()
if (userStore.mobile) {
phoneNumber.value = userStore.mobile
isPhoneDisabled.value = true
}
} catch (e) {
console.error(e)
}
}
}
})
const isPhoneNumberValid = computed(() => /^1[3-9]\d{9}$/.test(phoneNumber.value))
const isInviteCodeValid = computed(() => inviteCode.value.trim().length > 0)
const canRegister = computed(() =>
isPhoneNumberValid.value &&
verificationCode.value.length === 6 &&
isInviteCodeValid.value &&
isAgreed.value
)
async function sendVerificationCode() {
if (isCountingDown.value || !isPhoneNumberValid.value) return
if (!isPhoneNumberValid.value) {
uni.showToast({ title: '请输入有效的手机号', icon: 'none' })
return
}
if (!isInviteCodeValid.value) {
uni.showToast({ title: '请先输入邀请码', icon: 'none' })
return
}
const { data, error } = await useApiFetch('auth/sendSms')
.post({ mobile: phoneNumber.value, actionType: 'agentApply' })
.json()
if (data.value && !error.value) {
if (data.value.code === 200) {
uni.showToast({ title: '获取成功', icon: 'success' })
startCountdown()
} else {
uni.showToast({ title: data.value.msg || '获取失败', icon: 'none' })
}
} else {
uni.showToast({ title: error.value || '获取失败', icon: 'none' })
}
}
function startCountdown() {
isCountingDown.value = true
countdown.value = 60
timer = setInterval(() => {
if (countdown.value > 0) {
countdown.value--
} else {
clearInterval(timer)
isCountingDown.value = false
}
}, 1000)
}
async function handleRegister() {
if (!isPhoneNumberValid.value) {
uni.showToast({ title: '请输入有效的手机号', icon: 'none' })
return
}
if (!isInviteCodeValid.value) {
uni.showToast({ title: '请输入邀请码', icon: 'none' })
return
}
if (verificationCode.value.length !== 6) {
uni.showToast({ title: '请输入6位验证码', icon: 'none' })
return
}
if (!isAgreed.value) {
uni.showToast({ title: '请先同意用户协议、隐私政策和代理管理协议', icon: 'none' })
return
}
await performRegister()
}
async function performRegister() {
try {
const { data, error } = await registerByInviteCode({
mobile: phoneNumber.value,
code: verificationCode.value,
referrer: inviteCode.value.trim(),
})
if (data.value && !error.value) {
if (data.value.code === 200) {
uni.setStorageSync('token', data.value.data.accessToken)
uni.setStorageSync('refreshAfter', data.value.data.refreshAfter)
uni.setStorageSync('accessExpire', data.value.data.accessExpire)
if (data.value.data.agent_id) {
agentStore.updateAgentInfo({
isAgent: true,
agentID: data.value.data.agent_id,
level: data.value.data.level || 1,
levelName: data.value.data.level_name || '普通代理',
})
}
uni.showToast({ title: '注册成功', icon: 'success' })
setTimeout(() => {
uni.reLaunch({ url: '/pages/index' })
}, 500)
} else {
uni.showToast({ title: data.value.msg || '注册失败', icon: 'none' })
}
} else {
uni.showToast({ title: error.value || '注册失败,请重试', icon: 'none' })
}
} catch (err) {
console.error('注册失败:', err)
uni.showToast({ title: '注册失败,请重试', icon: 'none' })
}
}
function toUserAgreement() {
uni.navigateTo({ url: '/pages/userAgreement/index' })
}
function toPrivacyPolicy() {
uni.navigateTo({ url: '/pages/privacyPolicy/index' })
}
function toAgentManageAgreement() {
uni.navigateTo({ url: '/pages/agentManageAgreement/index' })
}
onUnmounted(() => {
if (timer) clearInterval(timer)
})
function onClickLeft() {
uni.navigateBack()
}
</script>
<template>
<view class="login-layout">
<image
class="login-bg-image"
src="/static/login/login_bg.png"
mode="aspectFill"
/>
<view class="login px-4 relative z-10" style="padding-top: 44px;">
<view class="mb-8 pt-20 text-left">
<view class="flex flex-col items-center">
<image class="h-16 w-16 rounded-full shadow block" src="/static/login/logo.png" mode="aspectFit" />
<image class="h-12 mt-4 block" src="/static/login/logo_title.png" mode="widthFix" />
</view>
</view>
<view class="login-form">
<view class="form-item">
<view class="form-label">邀请码</view>
<view class="input-with-btn flex-1">
<input
v-model="inviteCode"
class="form-input"
type="text"
placeholder="请输入邀请码"
/>
<view class="get-invite-code-btn" @click="fillInviteCode">获取邀请码</view>
</view>
</view>
<view class="form-item">
<view class="form-label">手机号</view>
<input
v-model="phoneNumber"
class="form-input flex-1"
type="number"
placeholder="请输入手机号"
maxlength="11"
:disabled="isPhoneDisabled"
/>
</view>
<view class="form-item">
<view class="form-label">验证码</view>
<view class="verification-input-wrapper flex-1">
<input
v-model="verificationCode"
class="form-input verification-input"
type="number"
placeholder="请输入验证码"
maxlength="6"
/>
<view
class="get-code-btn"
:class="{ disabled: isCountingDown || !isPhoneNumberValid || !isInviteCodeValid }"
@click="sendVerificationCode"
>
{{ isCountingDown ? `${countdown}s` : '获取验证码' }}
</view>
</view>
</view>
<view class="agreement-wrapper">
<wd-checkbox v-model="isAgreed" />
<view class="agreement-text">
我已阅读并同意
<text class="agreement-link" @click.stop="toUserAgreement">用户协议</text>
<text class="agreement-link" @click.stop="toPrivacyPolicy">隐私政策</text>
<text class="agreement-link" @click.stop="toAgentManageAgreement">代理管理协议</text>
</view>
</view>
<view class="notice-text">
未注册手机号注册后将自动生成账号并成为代理并且代表您已阅读并同意
</view>
<button
class="login-btn"
:class="{ disabled: !canRegister }"
:disabled="!canRegister"
@click="handleRegister"
>
注册成为代理
</button>
</view>
</view>
</view>
</template>
<style scoped>
.login-layout {
min-height: 100vh;
position: relative;
overflow: hidden;
}
.login-bg-image {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.login-form {
background-color: #fff;
padding: 2rem;
margin-top: 0.5rem;
box-shadow: 0 0 24px rgba(63, 63, 63, 0.06);
border-radius: 8px;
}
.form-item {
margin-bottom: 1.5rem;
display: flex;
align-items: center;
border: none;
border-bottom: 1px solid #ebedf0;
}
.input-with-btn {
position: relative;
display: flex;
align-items: center;
}
.input-with-btn .form-input {
padding-right: 5rem;
}
.get-invite-code-btn {
position: absolute;
right: 0;
color: #1989fa;
font-size: 14px;
padding: 0.5rem;
font-weight: 500;
}
.form-label {
font-size: 15px;
color: #323233;
margin-right: 1rem;
font-weight: 500;
min-width: 4rem;
flex-shrink: 0;
}
.form-input {
width: 100%;
padding: 14px 0;
font-size: 15px;
color: #323233;
background-color: transparent;
}
.form-input:disabled {
color: #969799;
background-color: transparent;
}
.verification-input-wrapper {
position: relative;
display: flex;
align-items: center;
}
.verification-input {
flex: 1;
padding-right: 5rem;
}
.get-code-btn {
position: absolute;
right: 0;
color: #1989fa;
font-size: 14px;
padding: 0.5rem;
font-weight: 500;
}
.get-code-btn.disabled {
color: #c8c9cc;
}
.agreement-wrapper {
display: flex;
align-items: flex-start;
margin-top: 1.5rem;
margin-bottom: 1rem;
}
.agreement-wrapper :deep(.wd-checkbox) {
margin-right: 8px;
flex-shrink: 0;
}
.agreement-text {
font-size: 12px;
color: #646566;
line-height: 1.4;
}
.agreement-link {
color: #1989fa;
}
.notice-text {
font-size: 11px;
color: #969799;
line-height: 1.5;
margin-bottom: 2rem;
}
.login-btn {
width: 100%;
padding: 14px;
background-color: #1989fa;
color: #fff;
border: none;
border-radius: 24px;
font-size: 16px;
font-weight: 500;
letter-spacing: 0.25em;
}
.login-btn.disabled {
background-color: #c8c9cc;
}
</style>

View File

@@ -0,0 +1,26 @@
<script setup>
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '报告详情',
navigationStyle: 'default',
},
})
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
const orderId = ref('')
onLoad((options) => {
orderId.value = options?.orderId || options?.order_id || ''
})
</script>
<template>
<view class="p-4 min-h-screen bg-white">
<view class="text-gray-600 py-20 text-center">
报告详情待开发orderId: {{ orderId }}
</view>
</view>
</template>

View File

@@ -0,0 +1,134 @@
<script setup>
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '下级推广收益',
navigationStyle: 'default',
},
})
import { ref, onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { useAgentStore } from '@/stores/agentStore'
import { getRevenueInfo, getRebateList } from '@/api/agent'
const agentStore = useAgentStore()
const { isAgent } = storeToRefs(agentStore)
const summary = ref(null)
const list = ref([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(20)
const loading = ref(false)
const hasMore = ref(true)
async function loadSummary() {
if (!isAgent.value) return
const { data, error } = await getRevenueInfo()
if (data.value?.code === 200 && !error.value) summary.value = data.value.data
}
async function loadList(isRefresh = false) {
if (!isAgent.value) return
if (loading.value) return
const nextPage = isRefresh ? 1 : page.value
if (!isRefresh && !hasMore.value) return
loading.value = true
try {
const { data, error } = await getRebateList({ page: nextPage, page_size: pageSize.value })
if (data.value?.code === 200 && !error.value) {
const res = data.value.data
total.value = res?.total ?? 0
const items = res?.list || []
if (nextPage === 1) {
list.value = items
page.value = 1
} else {
list.value = list.value.concat(items)
}
page.value = nextPage
hasMore.value = list.value.length < total.value
}
} finally {
loading.value = false
}
}
function loadMore() {
if (!hasMore.value || loading.value) return
page.value += 1
loadList(false)
}
function rebateTypeText(type) {
const map = { 1: '直接上级返佣', 2: '钻石上级返佣', 3: '黄金上级返佣' }
return map[type] || '返佣'
}
function levelName(level) {
const map = { 1: '普通', 2: '黄金', 3: '钻石' }
return map[level] || '—'
}
onMounted(() => {
if (isAgent.value) {
loadSummary()
loadList(true)
}
})
</script>
<template>
<view class="p-4 min-h-screen bg-gray-50">
<view v-if="!isAgent" class="rounded-xl bg-white shadow p-6 text-center">
<view class="text-gray-500 text-sm">请先注册成为代理</view>
</view>
<template v-else>
<view v-if="summary" class="rounded-xl bg-white shadow p-4 mb-4">
<view class="text-sm text-gray-500 mb-1">累计下级返佣收益</view>
<view class="text-2xl font-bold text-green-500">¥ {{ (summary.rebate_total || 0).toFixed(2) }}</view>
<view class="grid grid-cols-2 gap-3 mt-3 text-center">
<view class="p-2 rounded-lg bg-gray-50">
<view class="text-xs text-gray-500">今日</view>
<view class="text-base font-semibold text-green-500">¥ {{ (summary.rebate_today || 0).toFixed(2) }}</view>
</view>
<view class="p-2 rounded-lg bg-gray-50">
<view class="text-xs text-gray-500">本月</view>
<view class="text-base font-semibold text-green-500">¥ {{ (summary.rebate_month || 0).toFixed(2) }}</view>
</view>
</view>
</view>
<view class="rounded-xl bg-white shadow overflow-hidden">
<view class="p-4 border-b border-gray-100">
<view class="text-base font-semibold text-gray-800">返佣明细</view>
<view class="text-xs text-gray-500 mt-1">下级推广与升级产生的返佣</view>
</view>
<view v-if="loading && list.length === 0" class="p-6">
<wd-skeleton :row="4" />
</view>
<view v-else-if="list.length === 0" class="p-8 text-center text-gray-500 text-sm">
暂无返佣记录
</view>
<view v-else class="divide-y divide-gray-100">
<view
v-for="item in list"
:key="item.id"
class="p-4 flex items-center justify-between"
>
<view class="flex-1 min-w-0">
<view class="font-medium text-gray-800">{{ item.source_agent_mobile || '下级代理' }}</view>
<view class="text-xs text-gray-500 mt-0.5">{{ rebateTypeText(item.rebate_type) }} · {{ levelName(item.source_agent_level) }}代理</view>
<view class="text-xs text-gray-500 mt-0.5">{{ item.create_time }}</view>
</view>
<view class="text-right flex-shrink-0 ml-3">
<view class="text-lg font-bold text-green-500">+¥{{ (item.amount || 0).toFixed(2) }}</view>
</view>
</view>
</view>
<view v-if="loading && list.length > 0" class="p-3 text-center text-gray-400 text-sm">加载中</view>
<view v-else-if="hasMore && list.length > 0" class="p-3 text-center text-primary text-sm" @click="loadMore">加载更多</view>
</view>
</template>
</view>
</template>

View File

@@ -0,0 +1,97 @@
<script setup>
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '客服',
navigationStyle: 'default',
},
})
import { ref, onMounted } from 'vue'
// 与 webview Service.vue 一致:可选环境变量,未配置则用默认 Chatwoot
const chatwootBaseUrl = import.meta.env?.VITE_CHATWOOT_BASE_URL || 'https://service.quannengcha.com'
const chatwootToken = import.meta.env?.VITE_CHATWOOT_WEBSITE_TOKEN || 'XJqwnEWKVNte8iJW8DLroEzd'
const serviceUrl = import.meta.env?.VITE_SERVICE_URL || ''
const chatwootReady = ref(false)
// #ifdef H5
onMounted(() => {
const d = document
const t = 'script'
const g = d.createElement(t)
const s = d.getElementsByTagName(t)[0]
g.src = chatwootBaseUrl + '/packs/js/sdk.js'
g.defer = true
g.async = true
s.parentNode.insertBefore(g, s)
g.onload = function () {
if (typeof window.chatwootSDK !== 'undefined') {
window.chatwootSDK.run({
websiteToken: chatwootToken,
baseUrl: chatwootBaseUrl,
type: 'expanded_bubble',
})
chatwootReady.value = true
}
}
})
// #endif
function openChatwoot() {
// #ifdef H5
if (chatwootReady.value && typeof window.chatwootSDK !== 'undefined' && window.chatwootSDK.open) {
window.chatwootSDK.open()
} else {
const launcher = document.querySelector('[data-chatwoot-widget]') || document.querySelector('.woot--bubble-holder') || document.querySelector('[class*="woot"]')
if (launcher) launcher.click()
}
// #endif
}
function openServiceUrl() {
if (serviceUrl) {
// #ifdef H5
window.location.href = serviceUrl
// #endif
// #ifndef H5
uni.navigateTo({ url: '/pages/webview/index?url=' + encodeURIComponent(serviceUrl) })
// #endif
} else {
uni.showToast({ title: '暂未配置客服链接', icon: 'none' })
}
}
</script>
<template>
<view class="service-page p-4 min-h-screen bg-gray-50">
<view class="support-chat rounded-xl bg-white shadow-sm p-6 text-center">
<view class="text-xl font-bold text-primary mb-2">在线客服</view>
<view class="text-gray-600 text-sm mb-4">如果您有任何问题请通过在线客服联系我们</view>
<!-- H5本页已加载 Chatwoot点击打开气泡 -->
<!-- #ifdef H5 -->
<wd-button type="primary" block class="mb-3" @click="openChatwoot">
打开在线客服
</wd-button>
<view class="text-xs text-gray-400 mb-2">或请留意页面右下角客服气泡</view>
<!-- #endif -->
<!-- 外链客服VITE_SERVICE_URL 配置后可用小程序端主要依赖此方式 -->
<wd-button v-if="serviceUrl" type="default" block outline @click="openServiceUrl">
联系客服
</wd-button>
<view v-else class="text-gray-500 text-sm">可在 .env 中配置 VITE_SERVICE_URL 作为客服链接</view>
</view>
</view>
</template>
<style scoped>
.service-page {
box-sizing: border-box;
}
.support-chat view {
word-break: break-all;
}
</style>

View File

@@ -0,0 +1,187 @@
<script setup>
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '我的团队',
navigationStyle: 'default',
},
})
import { ref, computed, onMounted, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { useAgentStore } from '@/stores/agentStore'
import { useUserStore } from '@/stores/userStore'
import { getTeamList } from '@/api/agent'
const agentStore = useAgentStore()
const userStore = useUserStore()
const { isAgent } = storeToRefs(agentStore)
const { isLoggedIn } = storeToRefs(userStore)
const list = ref([])
const total = ref(0)
const statistics = ref(null)
const page = ref(1)
const pageSize = ref(20)
const loading = ref(false)
const keyword = ref('')
const hasMore = computed(() => list.value.length < total.value)
async function loadList(isRefresh = false) {
if (!isAgent.value) return
if (loading.value) return
const nextPage = isRefresh ? 1 : page.value
if (!isRefresh && nextPage > 1 && list.value.length >= total.value && total.value > 0) return
loading.value = true
try {
const params = { page: nextPage, page_size: pageSize.value }
if (keyword.value.trim()) params.mobile = keyword.value.trim()
const { data, error } = await getTeamList(params)
if (data.value && !error.value && data.value.code === 200) {
const res = data.value.data
statistics.value = res?.statistics || null
total.value = res?.total ?? 0
const items = res?.list || []
if (nextPage === 1) {
list.value = items
page.value = 1
} else {
list.value = list.value.concat(items)
}
page.value = nextPage
} else {
if (isRefresh) list.value = []
}
} finally {
loading.value = false
}
}
function onRefresh() {
page.value = 1
loadList(true)
}
function loadMore() {
if (!hasMore.value || loading.value) return
page.value += 1
loadList(false)
}
function toRegister() {
uni.navigateTo({ url: '/pages/register/index' })
}
function levelName(level) {
const map = { 1: '普通代理', 2: '黄金代理', 3: '钻石代理' }
return map[level] || '代理'
}
onMounted(() => {
if (isAgent.value) loadList(true)
})
watch(isAgent, (val) => {
if (val) loadList(true)
})
</script>
<template>
<view class="p-4 min-h-screen bg-gray-50">
<view v-if="!isLoggedIn" class="rounded-xl bg-white shadow p-6 text-center">
<view class="text-gray-600">正在登录中请稍候...</view>
</view>
<view v-else-if="!isAgent" class="rounded-xl bg-white shadow p-6 text-center">
<view class="text-gray-600 mb-4">注册成为代理后即可查看团队列表</view>
<wd-button type="primary" block @click="toRegister">注册成为代理</wd-button>
</view>
<template v-else>
<!-- 统计 -->
<view v-if="statistics" class="rounded-xl bg-white shadow p-4 mb-4">
<view class="text-base font-semibold text-gray-800 mb-3">团队概览</view>
<view class="grid grid-cols-3 gap-3">
<view class="text-center p-2 rounded-lg bg-gray-50">
<view class="text-lg font-bold text-primary">{{ statistics.total_members || 0 }}</view>
<view class="text-xs text-gray-500 mt-1">团队人数</view>
</view>
<view class="text-center p-2 rounded-lg bg-gray-50">
<view class="text-lg font-bold text-primary">{{ statistics.today_new_members || 0 }}</view>
<view class="text-xs text-gray-500 mt-1">今日新增</view>
</view>
<view class="text-center p-2 rounded-lg bg-gray-50">
<view class="text-lg font-bold text-primary">{{ statistics.month_new_members || 0 }}</view>
<view class="text-xs text-gray-500 mt-1">本月新增</view>
</view>
</view>
<view class="mt-3 flex gap-3">
<view class="flex-1 text-center p-2 rounded-lg bg-gray-50">
<view class="text-sm font-semibold text-gray-700">总收益</view>
<view class="text-sm text-primary">¥{{ (statistics.total_earnings ?? 0).toFixed(2) }}</view>
</view>
<view class="flex-1 text-center p-2 rounded-lg bg-gray-50">
<view class="text-sm font-semibold text-gray-700">今日收益</view>
<view class="text-sm text-primary">¥{{ (statistics.today_earnings ?? 0).toFixed(2) }}</view>
</view>
</view>
</view>
<!-- 搜索 -->
<view class="mb-3">
<wd-search
v-model="keyword"
placeholder="手机号搜索"
shape="round"
@search="onRefresh"
@clear="onRefresh"
/>
</view>
<!-- 列表 -->
<view class="rounded-xl bg-white shadow overflow-hidden">
<view v-if="loading && list.length === 0" class="p-6">
<wd-skeleton :row="5" />
</view>
<view v-else-if="list.length === 0" class="p-8 text-center text-gray-500 text-sm">
暂无团队成员
</view>
<view v-else class="divide-y divide-gray-100">
<view
v-for="item in list"
:key="item.agent_id"
class="p-4 flex items-center justify-between"
>
<view class="flex-1 min-w-0">
<view class="flex items-center gap-2">
<text class="font-medium text-gray-800">{{ item.mobile || '—' }}</text>
<text
v-if="item.is_direct"
class="text-xs px-1.5 py-0.5 rounded bg-primary/10 text-primary"
>
直推
</text>
</view>
<view class="text-xs text-gray-500 mt-1">
{{ levelName(item.level) }} · 加入 {{ item.create_time || '—' }}
</view>
<view class="text-xs text-gray-500 mt-0.5">
返佣 ¥{{ (item.total_rebate_amount ?? 0).toFixed(2) }} · 查询 {{ item.total_queries ?? 0 }}
</view>
</view>
</view>
</view>
<view v-if="loading && list.length > 0" class="p-3 text-center text-gray-400 text-sm">
加载中
</view>
<view
v-else-if="hasMore && list.length > 0"
class="p-3 text-center text-primary text-sm"
@click="loadMore"
>
加载更多
</view>
</view>
</template>
</view>
</template>

View File

@@ -0,0 +1,363 @@
<script setup>
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '调整下级级别',
navigationStyle: 'default',
},
})
import { ref, computed, onMounted, onBeforeMount } from 'vue'
import { storeToRefs } from 'pinia'
import { useAgentStore } from '@/stores/agentStore'
import { getTeamList, upgradeSubordinate } from '@/api/agent'
const agentStore = useAgentStore()
const { isAgent, level } = storeToRefs(agentStore)
const teamMembers = ref([])
const loading = ref(false)
const finished = ref(false)
const page = ref(1)
const pageSize = 10
const refreshing = ref(false)
const searchMobile = ref('')
const showConfirmDialog = ref(false)
const currentUpgradeItem = ref(null)
const isUpgrading = ref(false)
// 检查是否是钻石代理
const isDiamondAgent = computed(() => {
return isAgent.value && level.value === 3
})
onBeforeMount(() => {
page.value = 1
finished.value = false
})
// 获取团队列表(仅普通代理)
async function fetchTeamMembers() {
if (loading.value || finished.value) return
loading.value = true
try {
const params = {
page: page.value,
page_size: pageSize,
}
if (searchMobile.value && searchMobile.value.trim()) {
params.mobile = searchMobile.value.trim()
}
const { data, error } = await getTeamList(params)
if (data.value && !error.value) {
if (data.value.code === 200) {
let list = data.value.data.list || []
// 过滤出普通代理level === 1
list = list.filter((item) => {
const agentLevel = item.level || (item.level_name === '普通' ? 1 : item.level_name === '黄金' ? 2 : item.level_name === '钻石' ? 3 : 1)
return agentLevel === 1
})
if (page.value === 1) {
teamMembers.value = list
} else {
teamMembers.value.push(...list)
}
// 判断是否加载完成
if (list.length < pageSize) {
finished.value = true
} else if (teamMembers.value.length >= data.value.data.total) {
finished.value = true
} else {
page.value++
}
} else {
finished.value = true
uni.showToast({ title: data.value.msg || '获取团队列表失败', icon: 'none' })
}
} else {
finished.value = true
uni.showToast({ title: '获取团队列表失败', icon: 'none' })
console.error('获取团队列表失败:', error.value || '请求失败')
}
} catch (err) {
finished.value = true
console.error('获取团队列表失败:', err)
uni.showToast({ title: '获取团队列表失败', icon: 'none' })
} finally {
loading.value = false
refreshing.value = false
}
}
// 下拉刷新
function onRefresh() {
finished.value = false
page.value = 1
fetchTeamMembers()
}
// 搜索功能
function handleSearch() {
finished.value = false
page.value = 1
fetchTeamMembers()
}
// 清空搜索
function handleClear() {
searchMobile.value = ''
finished.value = false
page.value = 1
fetchTeamMembers()
}
// 处理升级
function handleUpgrade(item) {
currentUpgradeItem.value = item
showConfirmDialog.value = true
}
// 确认升级
async function confirmUpgrade() {
if (!currentUpgradeItem.value) return
isUpgrading.value = true
try {
const { data, error } = await upgradeSubordinate({
subordinate_id: currentUpgradeItem.value.agent_id || currentUpgradeItem.value.id,
to_level: 2, // 只能升级为黄金代理
})
if (data.value && !error.value) {
if (data.value.code === 200) {
uni.showToast({ title: '升级成功', icon: 'success' })
showConfirmDialog.value = false
currentUpgradeItem.value = null
// 刷新列表
finished.value = false
page.value = 1
await fetchTeamMembers()
// 刷新代理状态
await agentStore.fetchAgentStatus()
} else {
uni.showToast({ title: data.value.msg || '升级失败', icon: 'none' })
}
} else {
uni.showToast({ title: '升级失败,请重试', icon: 'none' })
}
} catch (err) {
console.error('升级失败:', err)
uni.showToast({ title: '升级失败,请重试', icon: 'none' })
} finally {
isUpgrading.value = false
}
}
// 格式化时间
function formatTime(timeStr) {
if (!timeStr) return '-'
return timeStr.split(' ')[0]
}
// 格式化金额
function formatNumber(num) {
if (!num) return '0.00'
return Number(num).toFixed(2)
}
// 格式化数字
function formatCount(num) {
if (!num) return '0'
return Number(num).toLocaleString()
}
// 上拉加载更多
function onLoadMore() {
if (!finished.value && !loading.value) {
fetchTeamMembers()
}
}
onMounted(() => {
// 检查权限
if (!isDiamondAgent.value) {
uni.showToast({ title: '只有钻石代理可以升级下级', icon: 'none' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
return
}
fetchTeamMembers()
})
</script>
<template>
<view class="min-h-screen bg-gray-50 pb-4">
<!-- 说明卡片 -->
<view class="bg-white mx-4 mt-4 rounded-xl p-4 shadow-sm">
<view class="flex gap-3">
<wd-icon name="info-o" class="text-lg text-blue-500 flex-shrink-0 mt-0.5" />
<view class="flex-1">
<view class="text-base font-semibold text-gray-800 mb-2">调整说明</view>
<view class="text-sm text-gray-600 space-y-1">
<view> 仅可升级普通代理为黄金代理</view>
<view> 调整操作免费无需支付费用</view>
</view>
</view>
</view>
</view>
<!-- 搜索框 -->
<view class="mx-4 mt-4">
<view class="flex gap-2">
<input
v-model="searchMobile"
type="text"
placeholder="请输入手机号搜索"
class="flex-1 px-4 py-2.5 border border-gray-300 rounded-lg text-base bg-white"
@confirm="handleSearch"
/>
<button
class="px-4 py-2.5 bg-blue-500 text-white rounded-lg text-base whitespace-nowrap"
@click="handleSearch"
>
搜索
</button>
</view>
</view>
<!-- 代理列表 -->
<scroll-view
scroll-y
class="flex-1 mt-4"
style="height: calc(100vh - 280px)"
:refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
:lower-threshold="80"
@scrolltolower="onLoadMore"
>
<view class="px-4">
<!-- 空状态 -->
<view
v-if="teamMembers.length === 0 && !loading"
class="bg-white rounded-xl p-12 flex flex-col items-center justify-center"
>
<wd-icon name="user-o" class="text-6xl text-gray-300 mb-4" />
<text class="text-base text-gray-500">暂无可调整的普通代理</text>
</view>
<!-- 成员卡片 -->
<view
v-for="(item, index) in teamMembers"
:key="item.agent_id || item.id"
class="bg-white rounded-xl p-4 mb-3 shadow-sm"
>
<view class="flex items-start gap-3 mb-3">
<view
class="w-7 h-7 flex items-center justify-center bg-gray-200 text-gray-600 rounded-md text-sm font-semibold flex-shrink-0"
>
{{ index + 1 }}
</view>
<view class="flex-1 min-w-0">
<view class="text-base font-semibold text-gray-800 mb-1">
{{ item.mobile || '未绑定手机' }}
</view>
<view class="text-xs text-gray-500">加入时间{{ formatTime(item.create_time) }}</view>
</view>
<view class="flex-shrink-0">
<view
class="px-3 py-1 bg-gray-200 text-gray-600 rounded-xl text-xs font-medium"
>
普通代理
</view>
</view>
</view>
<view class="grid grid-cols-3 gap-3 mb-4 p-3 bg-gray-50 rounded-lg">
<view class="text-center">
<view class="text-xs text-gray-500 mb-1">总查询量</view>
<view class="text-sm font-semibold text-gray-800">{{ formatCount(item.total_queries || 0) }}</view>
</view>
<view class="text-center">
<view class="text-xs text-gray-500 mb-1">返佣总额</view>
<view class="text-sm font-semibold text-gray-800">¥{{ formatNumber(item.total_rebate_amount || 0) }}</view>
</view>
<view class="text-center">
<view class="text-xs text-gray-500 mb-1">邀请人数</view>
<view class="text-sm font-semibold text-gray-800">{{ formatCount(item.total_invites || 0) }}</view>
</view>
</view>
<view class="flex justify-end">
<button
class="px-4 py-2 bg-blue-500 text-white rounded-lg text-sm font-medium flex items-center justify-center"
@click="handleUpgrade(item)"
>
<text>调整为黄金代理</text>
</button>
</view>
</view>
<!-- 加载状态 -->
<view v-if="teamMembers.length > 0" class="py-4 text-center text-gray-400 text-sm">
<text v-if="loading">加载中...</text>
<text v-else-if="finished">没有更多了</text>
<text v-else>上拉加载更多</text>
</view>
</view>
</scroll-view>
<!-- 确认升级弹窗 -->
<view
v-if="showConfirmDialog"
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
@click.self="showConfirmDialog = false"
>
<view class="mx-4 w-full max-w-sm rounded-2xl bg-white p-6 shadow-xl" @click.stop>
<view class="flex items-center gap-3 mb-4">
<wd-icon name="warning-o" class="text-2xl text-amber-500" />
<view class="text-lg font-semibold text-gray-800">确认升级</view>
</view>
<view class="mb-6">
<view class="text-base text-gray-700 mb-4 leading-relaxed">
确定要将
<text class="text-blue-500 font-semibold">{{ currentUpgradeItem?.mobile }}</text>
升级为黄金代理吗
</view>
<view class="bg-amber-50 rounded-lg p-3 text-sm text-amber-800 space-y-1">
<view> 升级操作不可撤销</view>
<view> 升级操作免费</view>
</view>
</view>
<view class="flex gap-3">
<button
class="flex-1 py-3 rounded-lg text-base font-medium text-gray-700 bg-gray-100"
@click="showConfirmDialog = false"
>
取消
</button>
<button
class="flex-1 py-3 rounded-lg text-base font-medium text-white bg-blue-500"
:disabled="isUpgrading"
@click="confirmUpgrade"
>
{{ isUpgrading ? '升级中...' : '确认升级' }}
</button>
</view>
</view>
</view>
</view>
</template>
<style scoped>
/* 确保样式正确应用 */
</style>

View File

@@ -0,0 +1,223 @@
<script setup>
const companyName = import.meta.env?.VITE_COMPANY_NAME || ''
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '用户协议',
navigationStyle: 'default',
},
})
</script>
<template>
<view class="p-4 min-h-screen bg-white">
<view class="mb-4 text-center text-lg font-bold">用户协议</view>
<view class="content indent-2em">
<view class="mb-4 leading-relaxed">
本协议是您以下又称"用户"在使用本服务时约定您和{{ companyName }}之间权利义务关系的有效协议
</view>
<view class="mb-4 leading-relaxed">
在您使用本服务前请您务必仔细阅读本协议特别是隐私权保护及授权条款免除或者限制{{ companyName }}责任的条款争议解决和法律适用条款一旦您有对本服务的任何部分或全部的注册查看定制使用等任何使用行为即视为您已充分阅读理解并接受本协议的全部内容并与{{ companyName }}达成本协议如您对本协议有任何疑问应向{{ companyName }}客服咨询如果您不同意本协议的部分或全部约定您应立即停止使用本服务
</view>
<view class="mb-4 leading-relaxed">
您与{{ companyName }}达成本协议后您承诺接受并遵守本协议的约定并不得以未阅读本协议的内容或者未获得{{ companyName }}对您问询的解答等理由主张本协议无效或要求撤销本协议在本协议履行过程中{{ companyName }}可以依其单独判断暂时停止提供限制或改变本服务并有权根据自身业务需要修订本协议一旦本协议的内容发生变动{{ companyName }}将通过平台公布最新的服务协议不再向您作个别通知如果您不同意{{ companyName }}对本服务协议所做的修改您应立即停止使用本服务或通过{{ companyName }}客服与{{ companyName }}联系如果您继续使用本服务则视为您接受{{ companyName }}对本协议所做的修改并应遵照修改后的协议执行
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">服务内容</view>
<view class="leading-relaxed">
本服务向您提供多项个人信息整理服务您知悉并认可如您需使用该类服务必须满足如下所述条件且您承诺您向{{ companyName }}提请服务申请时已经满足如下所述条件
</view>
<view class="leading-relaxed">
<view>A.您已注册成为本服务的会员</view>
<view>B.您已在服务页面对应框中填写被查询主体的姓名身份证号手机号银行卡号和被查询主体的手机号收到的动态验证码以下称"被查询主体信息"</view>
<view>C.您确保被查询主体信息是您本人的信息或者被查询主体已授权您本人使用被查询主体信息进行查询授权内容应包括本条D项所述内容并且被查询主体已知悉该授权的风险</view>
<view>D.被查询主体不可撤销地授权{{ companyName }}为查询评估被查询主体的信息状况a.可以委托合法存续的第三方机构收集查询验证使用并提供您或被查询主体的个人信息b.可以向数据源机构采集您或被查询主体的个人信息c.可以整理保存加工使用您或被查询主体的个人信息并向您提供数据报告d.可以向为您提供服务的第三方商户提供脱敏后的个人信息或数据报告本条所述的个人信息包括但不限于身份信息联系方式职业和居住地址等个人基本信息个人社保公积金收入及在商业活动中形成的各类交易记录个人公共费用缴纳违法违规信息财产状况等</view>
<view>E.被查询主体已被明确告知提供被查询主体信息并作出D项授权可能给被查询主体带来的各类损失以及其他可能的不利后果包括采集上述个人信息对被查询主体信用方面可能产生不良影响以及上述信息被信息使用者依法提供给第三方后被他人不当利用的风险</view>
<view>F.您已全额支付相应的查询服务费用</view>
<view>G.验证码请不要轻易提供给他人一旦填入手机号对应验证码视为手机号机主本人操作</view>
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">服务中断或故障</view>
<view class="leading-relaxed">
您同意因下列原因导致{{ companyName }}无法正常提供本服务的{{ companyName }}不承担责任
</view>
<view class="leading-relaxed">
<view>1承载本服务的系统停机维护期间</view>
<view>2您的电脑手机软硬件和通信线路供电线路出现故障的</view>
<view>3您操作不当或通过非{{ companyName }}授权或认可的方式使用本服务的</view>
<view>4因病毒木马恶意程序攻击网络拥堵系统不稳定系统或设备故障通讯故障电力故障或政府行为等原因</view>
<view>5由于黑客攻击网络供应商技术调整或故障网站升级手机运营商系统方面的问题等原因而造成的本服务中断或延迟</view>
<view>6因台风地震海啸洪水停电战争恐怖袭击等不可抗力之因素造成本服务系统障碍不能执行业务的</view>
</view>
<view class="leading-relaxed">
{{ companyName }}不对因使用本服务而对用户造成的间接的附带的特殊的后果性的损失承担任何法律责任尽管有前款约定{{ companyName }}将采取合理行动积极促使本服务恢复正常
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">信息的使用和保护</view>
<view class="leading-relaxed">
{{ companyName }}深知您注重个人信息安全和保护并理解保护被查询主体个人信息的重要性
{{ companyName }}会严格遵守中国关于收集使用保存用户个人信息的相关法律法规
尽最大努力采用相应安全技术和管理手段保护您或被查询主体的个人信息
防止您或被查询主体个人信息遭受未经授权的访问适用或泄露毁损篡改或者丢失
未经您或被查询主体的授权不会向任何第三方提供
</view>
<view class="leading-relaxed">
您使用本服务即表示您已授权{{ companyName }}将您相关信息披露给{{ companyName }}关联公司
关联公司是指直接或间接控制于本协议一方的任何法律实体或者与本协议一方共同于另一法律实体的任何法律实体使用
{{ companyName }}关联公司仅为了向您提供服务而使用您的相关信息
{{ companyName }}关联公司使用您的相关信息则受本协议约束且会按照与{{ companyName }}同等谨慎程度保护您的相关信息
</view>
<view class="leading-relaxed">
在您使用本服务过程中特别是在申请提现实名认证或佣金结算时您需要提供包括但不限于姓名身份证号银行卡号手机号税务身份信息等个人资料
您同意我们为履行合同义务税务申报身份核验财务结算等必要目的收集使用存储并在必要范围内共享该等信息
在进行税务代扣代缴结算服务时我们有权将必要信息提供给依法合作的第三方税务服务商结算服务商前提是该第三方承担同等信息保护义务
</view>
<view class="leading-relaxed">
您有权查询更正您的个人信息也可以根据平台流程申请注销账户或停止使用相关服务我们将根据法律要求妥善处理相关信息
</view>
<view class="leading-relaxed">
{{ companyName }}就下列原因导致的您或被查询主体个人信息的泄露不承担任何法律责任
</view>
<view class="leading-relaxed">
<view>1由于您个人原因将本服务的会员账号和密码告知他人或与他人共享{{ companyName }}服务账户由此导致的与您相关的信息的泄露</view>
<view>2您使用第三方提供的服务包括您向第三方提供的任何个人信息须受第三方自己的服务条款及个人信息保护协议而非本协议约束您需要仔细阅读其条款本协议仅适用于{{ companyName }}所提供的服务并不适用于任何第三方提供的服务或第三方的信息使用规则{{ companyName }}对任何第三方使用由您提供的信息不承担任何责任</view>
<view>3根据相关的法律法规相关政府主管部门或相关证券交易所的要求提供公布与您相关的信息</view>
<view>4或其他非因{{ companyName }}原因导致的与您相关的信息的泄露</view>
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">用户声明与保证</view>
<view class="leading-relaxed">
<view>1您使用本服务的前提是您依照适用的法律是具有完全民事权利和民事行为能力能够独立承担民事责任的自然人</view>
<view>2您如违反本协议第一条款中的承诺您可能会对他人造成侵权如由此给{{ companyName }}或他人造成损失的您需依照法律法规规定承担相应的法律责任</view>
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">知识产权保护</view>
<view class="leading-relaxed">
本服务涉及的文档资料软件商标图案排版设计等以下简称"{{ companyName }}产品"的著作权商标以及其他知识产权或权益均为{{ companyName }}享有或{{ companyName }}获得授权使用
用户不得出租出借拷贝仿冒复制或修改{{ companyName }}产品任何部分或用于其他任何商业目的
也不得将{{ companyName }}产品做反向工程反编译或反汇编或以其他方式或工具取得{{ companyName }}产品之目标程序或源代码
如果用户违反此约定造成{{ companyName }}及其他任何第三方任何损失的甲方应予以全额赔偿
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">隐私保护</view>
<view class="leading-relaxed">
全能查保证不对外公开或向第三方提供单个用户的注册资料及存储在全能查的非公开内容但下列情况下除外:
</view>
<view class="leading-relaxed">
<view>1. 事先获得用户的明确授权;</view>
<view>2. 根据有关的法律法规要求;</view>
<view>3. 按照有关政府部门的要求;</view>
<view>4. 为维护社会公众的利益;</view>
<view>5. 为维护全能查的合法利益</view>
</view>
<view class="leading-relaxed">
在不透露单个用户隐私资料的前提下全能查有权利对整个用户数据库进行分析并对用户数据库进行商业上的利用
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">免责条款</view>
<view class="leading-relaxed">
<view>不管基于任何直接的间接的特殊的惩罚性的惩戒性的附带的或结果性的损害损失或费用我们均不对其承担责任即使有人告知我们或我们的员工存在出现这些损害损失或费用的可能性这些损害损失或费用由以下这些情况引起或与这些情况有关</view>
<view>1. 使用我们网站上或其他链接网站上的信息</view>
<view>2. 无法使用这些信息</view>
<view>3. 任何在操作或传输中出现的操作失败错误遗漏中断缺陷延迟计算机病毒断线或系统运行失败</view>
</view>
<view class="leading-relaxed">
<view>我们可以在不事先通知的情况下更改信息并且不承担更新这些信息的义务不经任何种类的授权不做任何专门或暗指或法定的不侵犯第三方权利名称可出售性出于某种特殊目的适当措施或不携带计算机病毒的保证</view>
</view>
<view class="leading-relaxed">
<view>我们不对您查询信息内容的正确性适当性完整性准确性可靠性或适时性做出任何证明声明和保证我们不对任何因个人平台产生的错误遗漏及失准承担任何责任</view>
</view>
<view class="leading-relaxed">
<view>对于由于您违反本协议导致任何第三方针对我们及或我们的员工提出的任何申诉起诉要求或者诉讼或者其他法律程序您同意自费作出赔偿并令其免受上述损害</view>
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">违约</view>
<view class="leading-relaxed">
用户不得利用本服务进行任何损害{{ companyName }}及其他第三方权益的行为否则{{ companyName }}有权立即终止为该用户提供本服务并要求用户赔偿损失由此产生的任何后果由用户自行承担{{ companyName }}无关
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">数据来源及准确性说明</view>
<view class="leading-relaxed">
本产品数据来源于第三方可能因数据未公开更新延迟或信息受到限制因此不一定能完全返回不同数据格式及记录详细程度会有所差异这是行业正常现象本报告仅供参考请结合实际情况做出决策
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">退款协议</view>
<view class="leading-relaxed">
除非由于本程序的技术性问题导致用户无法正常使用本产品否则我们不提供任何退款服务
用户在购买前应仔细阅读本用户协议及相关使用条款确保对本产品有充分了解
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">十一协议的变更和终止</view>
<view class="leading-relaxed">
鉴于网络服务的特殊性我们变更本协议及其附件的若干条款时将提前通过我们平台公告有关变更事项
修订后的条款或将来可能发布或更新的各类规则一经在我们平台公布后立即自动生效
如您不同意相关修订应当立即停止使用该项服务
如您在发布上述协议变更的有关公告后继续使用互联网查询的视为您已接受协议的有关变更并受其约束
本协议中的相关条款根据该变更而自动做相应修改双方无须另行签订书面协议
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">十二适用法律</view>
<view class="leading-relaxed">
本协议条款的解释效力及纠纷的解决适用中华人民共和国大陆地区法律法规
如用户和{{ companyName }}之间发生任何争议首先应友好协商解决协商不成的应将争议提交至{{ companyName }}注册地有管辖权的人民法院解决
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">十三问题咨询</view>
<view class="leading-relaxed">
如您对本协议及本服务有任何问题请通过邮箱
<text class="text-blue-500"> admin@iieeii.com </text>
通过"联系客服"联系{{ companyName }}进行咨询
{{ companyName }}会尽最大努力解决您的问题
</view>
</view>
<view class="mb-4">
<view class="mb-2 font-bold leading-relaxed">十四附则</view>
<view class="leading-relaxed">
<view>本协议的某一条款被确认无效均不影响本协议其他条款的效力</view>
<view>本协议未尽事宜根据我国相关法律法规及我们相关业务规定办理如需制定补充协议其法律效力同本协议</view>
</view>
<view class="mt-2 leading-relaxed">
本协议通过点击同意/勾选的方式签署自签署之日生效
</view>
<view class="text-right text-sm">
<text>本协议于 2024 11 17 日生效</text>
</view>
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
.content {
text-indent: 2em;
}
</style>

View File

@@ -0,0 +1,146 @@
<script setup>
definePage({
layout: 'PageLayout',
style: {
navigationBarTitleText: '提现记录',
navigationStyle: 'default',
},
})
import { ref, onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { useAgentStore } from '@/stores/agentStore'
import { getWithdrawalList } from '@/api/agent'
const agentStore = useAgentStore()
const { isAgent } = storeToRefs(agentStore)
// 状态映射1=待审核2=审核通过3=审核拒绝4=提现中5=提现成功6=提现失败
const statusConfig = {
1: { chinese: '待审核', color: { bg: 'bg-yellow-100', text: 'text-yellow-800', dot: 'bg-yellow-500', amount: 'text-yellow-500' } },
2: { chinese: '审核通过', color: { bg: 'bg-blue-100', text: 'text-blue-800', dot: 'bg-blue-500', amount: 'text-blue-500' } },
3: { chinese: '审核拒绝', color: { bg: 'bg-red-100', text: 'text-red-800', dot: 'bg-red-500', amount: 'text-red-500' } },
4: { chinese: '提现中', color: { bg: 'bg-purple-100', text: 'text-purple-800', dot: 'bg-purple-500', amount: 'text-purple-500' } },
5: { chinese: '提现成功', color: { bg: 'bg-green-100', text: 'text-green-800', dot: 'bg-green-500', amount: 'text-green-500' } },
6: { chinese: '提现失败', color: { bg: 'bg-red-100', text: 'text-red-800', dot: 'bg-red-500', amount: 'text-red-500' } },
}
const list = ref([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(10)
const loading = ref(false)
const hasMore = ref(true)
function maskName(name) {
if (!name || typeof name !== 'string') return ''
if (name.length <= 7) return name
return name.substring(0, 3) + '****' + name.substring(7)
}
function statusToChinese(status) {
return statusConfig[status]?.chinese || '未知状态'
}
function getStatusStyle(status) {
const config = statusConfig[status] || {}
return `${config.color?.bg || 'bg-gray-100'} ${config.color?.text || 'text-gray-800'}`
}
function getDotColor(status) {
return statusConfig[status]?.color?.dot || 'bg-gray-500'
}
function getAmountColor(status) {
return statusConfig[status]?.color?.amount || 'text-gray-500'
}
async function loadList(isRefresh = false) {
if (!isAgent.value) return
if (loading.value) return
const nextPage = isRefresh ? 1 : page.value
if (!isRefresh && !hasMore.value) return
loading.value = true
try {
const { data, error } = await getWithdrawalList({ page: nextPage, page_size: pageSize.value })
if (data.value?.code === 200 && !error.value) {
const res = data.value.data
total.value = res?.total ?? 0
const items = res?.list || []
if (nextPage === 1) {
list.value = items
page.value = 1
} else {
list.value = list.value.concat(items)
}
page.value = nextPage
hasMore.value = list.value.length < total.value
} else {
hasMore.value = false
}
} catch (e) {
hasMore.value = false
console.error(e)
} finally {
loading.value = false
}
}
function loadMore() {
if (!hasMore.value || loading.value) return
page.value += 1
loadList(false)
}
onMounted(() => {
if (isAgent.value) loadList(true)
})
</script>
<template>
<view class="min-h-screen bg-gray-50 p-4">
<view v-if="!isAgent" class="rounded-xl bg-white shadow p-6 text-center">
<view class="text-gray-500 text-sm">请先注册成为代理</view>
</view>
<template v-else>
<view v-if="loading && list.length === 0" class="rounded-xl bg-white shadow p-6">
<wd-skeleton :row="4" />
</view>
<view v-else-if="list.length === 0" class="rounded-xl bg-white shadow p-8 text-center text-gray-500 text-sm">
暂无提现记录
</view>
<view v-else class="space-y-3">
<view
v-for="(item, index) in list"
:key="item.id || index"
class="bg-white rounded-xl p-4 shadow-sm"
>
<view class="flex justify-between items-center mb-2">
<text class="text-gray-500 text-sm">{{ item.create_time || '—' }}</text>
<text class="font-bold" :class="getAmountColor(item.status)">{{ (item.amount || 0).toFixed(2) }}</text>
</view>
<view class="flex items-center mb-2">
<view
class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium"
:class="getStatusStyle(item.status)"
>
<view class="w-2 h-2 rounded-full mr-1" :class="getDotColor(item.status)" />
<text>{{ statusToChinese(item.status) }}</text>
</view>
</view>
<view class="text-xs text-gray-500 space-y-1">
<view v-if="item.payee_account">收款账户{{ maskName(item.payee_account) }}</view>
<view v-if="item.payee_name">收款人{{ item.payee_name }}</view>
<view v-if="item.tax_amount > 0">税费-¥{{ (item.tax_amount || 0).toFixed(2) }}</view>
<view v-if="item.actual_amount > 0" class="text-green-600 font-medium">实际到账¥{{ (item.actual_amount || 0).toFixed(2) }}</view>
<view v-if="item.withdrawal_no">提现单号{{ item.withdrawal_no }}</view>
<view v-if="item.remark">备注{{ item.remark }}</view>
</view>
</view>
</view>
<view v-if="loading && list.length > 0" class="py-4 text-center text-gray-400 text-sm">加载中</view>
<view v-else-if="hasMore && list.length > 0" class="py-4 text-center text-blue-500 text-sm" @click="loadMore">加载更多</view>
<view v-else-if="list.length > 0" class="py-4 text-center text-gray-400 text-sm">没有更多了</view>
</template>
</view>
</template>

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

BIN
src/static/index/banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
src/static/login/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

1
src/static/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="512" height="512" viewBox="0 0 512 512"><defs><clipPath id="master_svg0_25_97"><rect x="0" y="0" width="512" height="512" rx="0"/></clipPath><clipPath id="master_svg1_25_11"><rect x="11" y="39" width="490" height="435" rx="0"/></clipPath></defs><g style="mix-blend-mode:passthrough" clip-path="url(#master_svg0_25_97)"><g clip-path="url(#master_svg1_25_11)"><g><path d="M51.4931,294.222767578125L205.214,437.551767578125C211.594,443.498767578125,220.016,446.812767578125,228.778,446.812767578125C237.54,446.812767578125,245.962,443.498767578125,252.342,437.551767578125L254.554,435.512767578125C238.306,411.638767578125,228.778,382.751767578125,228.778,351.655767578125C228.778,269.073767578125,295.812,202.124767578125,378.5,202.124767578125C402.575,202.124767578125,425.288,207.817767578125,445.45,217.842767578125C446.215,212.320767578125,446.556,206.797767578125,446.556,201.19076757812502L446.556,196.262767578125C446.556,136.874967578125,403.595,86.238267578125,344.983,76.467747578125C306.191,70.010717578125,266.719,82.669897578125,238.986,110.36716757812499L228.778,120.562467578125L218.569,110.36716757812499C190.837,82.669897578125,151.365,70.010717578125,112.573,76.467747578125C53.9601,86.238267578125,11,136.874967578125,11,196.262767578125L11,201.19076757812502C11,236.448767578125,25.6319,270.17876757812496,51.4931,294.222767578125ZM378.5,473.999767578125C446.13,473.999767578125,501,419.199767578125,501,351.655767578125C501,284.112767578125,446.13,229.312767578125,378.5,229.312767578125C310.87,229.312767578125,256,284.112767578125,256,351.655767578125C256,419.199767578125,310.87,473.999767578125,378.5,473.999767578125Z" fill="#2B9939" fill-opacity="1"/></g><g style="mix-blend-mode:passthrough"><path d="M322,415L441,415L441,293.5L419,293.5L419,393L344.5,393L344.5,293.5L322,293.5L322,415Z" fill="#FFFFFF" fill-opacity="1"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

1
src/static/me/lxkf.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764650175158" class="icon" viewBox="0 0 1052 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14882" xmlns:xlink="http://www.w3.org/1999/xlink" width="205.46875" height="200"><path d="M516.920889 741.745778c4.010667 0 8.106667-0.227556 12.231111-0.711111 39.395556-4.551111 72.874667-31.260444 85.248-68.010667a5.575111 5.575111 0 0 0-3.128889-6.912 5.973333 5.973333 0 0 0-7.480889 2.275556c-0.142222 0.284444-15.815111 24.746667-76.8 32.711111-9.102222 1.194667-17.976889 1.820444-26.396444 1.820444-42.723556 0-61.781333-15.616-61.952-15.758222a5.973333 5.973333 0 0 0-7.623111-0.085333 5.575111 5.575111 0 0 0-1.223111 7.338666 103.964444 103.964444 0 0 0 87.125333 47.331556zM844.8 375.808c-0.540444 0-1.024 0.113778-1.536 0.113778C807.537778 226.133333 671.004444 114.346667 507.335111 114.346667c-168.163556 0-307.797333 117.930667-338.773333 273.92-30.094222 5.233778-52.992 30.833778-52.992 61.809777v124.017778c0 34.702222 28.785778 62.919111 64.284444 62.919111 20.053333 0 37.745778-9.187556 49.521778-23.239111 28.444444 72.817778 86.328889 131.527111 159.573333 162.503111a32.995556 32.995556 0 0 1 3.242667-5.034666c1.137778-1.450667 2.446222-2.645333 3.555556-2.645334 1.137778 0 2.190222 0.398222 3.100444 1.024-16.952889-12.231111-78.222222-75.064889-91.477333-162.759111-5.802667-38.570667 24.035556-76.458667 58.823111-82.744889 55.808-10.069333 111.331556-21.532444 167.139555-31.402666 35.470222-6.257778 59.733333-25.116444 74.524445-56.490667 3.498667-7.338667 8.533333-22.186667 10.808889-43.548444a6.826667 6.826667 0 0 1 6.769777-5.632c2.275556 0 4.266667 1.137778 5.546667 2.759111l1.536-0.910223c21.987556 30.919111 65.621333 99.413333 71.879111 171.633778 7.196444 82.545778 3.185778 139.093333-62.065778 196.949334l-0.284444 0.227555a5.12 5.12 0 0 0-1.422222 3.555556c0 1.763556 0.967111 3.299556 2.389333 4.209777 0.540444 0.227556 1.080889 0.512 1.621333 0.711112 0.426667 0.085333 0.853333 0.227556 1.28 0.227555 0.455111 0 0.853333-0.142222 1.223112-0.227555 0.938667-0.483556 1.820444-1.024 2.730666-1.479112 65.991111-35.214222 116.622222-94.008889 140.003556-164.721777 9.472 13.624889 24.291556 23.409778 41.528889 26.851555-27.619556 121.656889-139.690667 197.831111-278.528 209.379556-8.305778-19.996444-28.785778-34.190222-52.849778-34.190222-31.431111 0-56.888889 24.092444-56.888889 53.788444s25.457778 53.76 56.888889 53.76c25.315556 0 46.535111-15.758222 53.902222-37.404444 160.711111-12.629333 289.536-105.159111 316.416-248.888889 23.779556-9.614222 40.448-32.170667 40.448-58.538667v-125.44c0-35.072-29.553778-63.516444-65.991111-63.516444z m-59.278222 36.039111c-41.927111-109.966222-150.528-188.501333-278.300445-188.501333-127.232 0-235.463111 77.880889-277.76 187.136-2.104889-2.503111-4.579556-4.636444-6.997333-6.798222 23.950222-133.12 142.250667-234.353778 284.899556-234.353778 141.966222 0 259.896889 100.238222 284.643555 232.419555a62.094222 62.094222 0 0 0-6.485333 10.097778zM389.091556 776.334222h-0.028445c-0.170667 0.341333-0.170667 0.398222 0.028445 0z" fill="#1daaf4" p-id="14883"></path></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

1
src/static/me/sjdl.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764650020736" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10374" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M700.02368 966.144H324.85568a140.8 140.8 0 0 1-130.24-90.368L57.46368 516.928a70.784 70.784 0 0 1 12.8-140.288 70.4 70.4 0 0 1 69.632 65.792 605.696 605.696 0 0 0 108.096 13.184 168.448 168.448 0 0 0 79.936-16.64c64-33.792 105.472-121.984 135.936-186.368l2.368-5.056 0.384-0.896a94.784 94.784 0 0 1-47.296-82.112 93.12 93.12 0 1 1 139.072 81.92v0.512a410.816 410.816 0 0 0 138.496 182.208 199.168 199.168 0 0 0 94.208 19.2 724.992 724.992 0 0 0 94.016-8.064 69.888 69.888 0 1 1 82.24 76.8l-137.152 358.784a140.8 140.8 0 0 1-130.176 90.24z m-212.8-312.192v178.24a33.28 33.28 0 1 0 66.56 0v-178.304l29.568 29.952a38.4 38.4 0 0 0 55.104 0 39.936 39.936 0 0 0 0-55.808L548.02368 536.448a38.4 38.4 0 0 0-24.704-11.392h-5.696a38.976 38.976 0 0 0-24.768 11.456l-90.304 91.52a39.936 39.936 0 0 0 0 55.808 38.4 38.4 0 0 0 55.168 0l29.44-29.824z" p-id="10375" fill="#1daaf4"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

1
src/static/me/sjxj.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764650035745" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11504" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M733.6448 947.2c-118.1184 0-213.6064-101.504-213.6064-226.5344 0-125.2608 95.6928-226.5856 213.6064-226.5856C851.456 494.08 947.2 595.584 947.2 720.6656 947.2 845.8752 851.6352 947.2 733.6448 947.2z m51.0976-316.8l-81.8432 101.6576v-101.6576h-66.3296v17.536h16.5376v193.3824h49.7152l164.7616-210.7392-82.7648-0.2048h-0.0768z m-184.5504-146.9952l-6.4512 4.224a217.216 217.216 0 0 1-115.9424 33.536c-124.4416 0-225.152-105.216-225.152-235.008C252.6464 156.3392 353.408 51.2 477.7472 51.2c124.4416 0 225.2032 105.216 225.2032 234.9312a238.2848 238.2848 0 0 1-26.9056 111.616 235.8784 235.8784 0 0 1-75.8528 85.6576z m-75.52 72.2944a288.3072 288.3072 0 0 0-51.456 164.9664c0 37.1968 6.9376 73.3952 20.5312 107.4688A274.4832 274.4832 0 0 0 584.4736 947.2h-191.1296C232.704 947.2 102.4 947.2 102.4 878.6688v-19.456c0-167.552 130.304-303.5136 290.944-303.5136h131.328z" p-id="11505" fill="#1daaf4"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

1
src/static/me/smrz.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764649808201" class="icon" viewBox="0 0 1169 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3433" xmlns:xlink="http://www.w3.org/1999/xlink" width="228.3203125" height="200"><path d="M72.227662 0h996.553234c17.168557 1.082587 33.980498 7.794627 46.780497 19.359204 16.162388 14.239204 25.7401 35.547065 25.6 57.084179 0.012736 167.915622 0.025473 335.831244 0 503.734129-25.803781-31.751642-58.192239-58.141294-94.605373-76.812736-37.686766-19.435622-79.627463-30.47801-121.988458-32.261094-49.620697-2.228856-99.776318 8.227662-144.238806 30.401592-37.393831 18.531343-70.737512 45.11204-97.216318 77.360398-29.497313 35.827264-50.384876 78.697711-60.255523 124.039005-14.277413 64.751443-6.075224 134.240796 23.294727 193.719403-189.911244 0.025473-379.809751 0.012736-569.708259 0.012736-21.549851 0.127363-42.883184-9.450348-57.096915-25.625473C7.8201 858.249552 1.146269 841.501294 0 824.409154V72.40597C1.120796 54.345871 8.54607 36.680597 21.167761 23.638607 34.38806 9.628657 53.046766 1.235423 72.227662 0m240.487164 132.190249c-28.64398 3.362388-56.07801 16.162388-77.054726 35.954627-22.530547 20.96398-37.623085 49.735323-41.927961 80.213333-4.444975 30.274229 1.630249 61.974925 17.104876 88.39005 16.862886 29.153433 44.857313 51.671244 77.029254 61.707462 31.369552 9.99801 66.330746 8.138507 96.41393-5.22189 30.121393-13.169353 55.071841-37.635821 68.890746-67.451542 14.442985-30.681791 16.71005-66.827463 6.393632-99.126767-9.488557-30.363383-30.108657-56.995025-56.918607-74.061691-26.415124-16.926567-58.790846-24.275423-89.931144-20.403582m333.475025 31.471443c-6.457313 2.088756-11.984876 6.852139-14.965174 12.940099-3.960995 7.845572-3.413333 17.741692 1.426467 25.077811 4.63602 7.374328 13.271244 11.895721 21.97015 11.679204 109.978109 0.025473 219.968955 0.025473 329.947064 0.012736 8.698905 0.216517 17.346866-4.304876 22.021095-11.666467 5.196418-7.883781 5.412935-18.684179 0.522189-26.771742-4.534129-7.832836-13.475025-12.774527-22.517811-12.507064-107.851144 0.012736-215.689552 0-323.52796 0-4.9799 0.038209-10.0999-0.38209-14.87602 1.235423m0.063681 110.793233c-6.775721 2.152438-12.545274 7.29791-15.436418 13.80617-3.642587 7.947463-2.763781 17.754428 2.279801 24.899502 4.725174 6.966766 13.080199 11.195224 21.498906 11.004179 110.436617 0 220.873234 0.076418 331.309851-0.038209 14.035423 0.012736 26.134925-13.334925 24.670248-27.319403-0.598607-9.832438-7.501692-18.837015-16.773731-22.084776-5.005373-1.897711-10.443781-1.464677-15.678408-1.490149H661.027662c-4.941692 0.038209-10.010746-0.38209-14.77413 1.222686M214.199403 476.605771c-30.516219 23.460299-53.925572 55.976119-66.636418 92.3001-12.507065 35.648955-14.557612 74.851343-6.024279 111.63383 130.101493 0.025473 260.190249-0.025473 390.291742 0.025473 9.552239-40.794428 5.896915-84.581891-10.418309-123.172935-16.95204-40.450547-47.646567-74.940498-85.804577-96.515821-32.286567-18.505871-69.858706-27.53592-107.010547-25.931144-41.252935 1.464677-81.843582 16.315224-114.397612 41.660497z" p-id="3434" fill="#1daaf4"></path><path d="M882.028259 511.694328c50.066468-5.84597 101.788657 3.247761 146.658706 26.300498 39.75005 20.3399 74.061692 51.2 98.413533 88.619303 21.231443 32.464876 34.922985 69.795025 39.60995 108.296916 5.514826 44.296915-0.611343 90.045771-17.970946 131.196816-21.129552 50.512239-58.943682 93.85393-106.144477 121.63184-32.65592 19.410149-69.74408 31.433234-107.609154 34.668259-44.742687 4.126567-90.542488-3.782687-131.273234-22.772537-37.228259-17.346866-70.202587-43.711045-95.254926-76.277811-27.29393-35.317811-45.137512-77.882587-50.983482-122.141294-6.355423-47.022488 0.458507-95.789851 19.906866-139.093333 18.505871-41.58408 48.359801-77.971741 85.384278-104.399602 35.012139-25.090547 76.456119-41.125572 119.262886-46.029055m144.633632 157.051543c-5.056318 1.69393-8.877214 5.553035-12.481592 9.297512-41.813333 41.813333-83.61393 83.626667-125.44 125.414527-26.020299-25.994826-52.027861-52.015124-78.035423-78.022686-3.222289-3.209552-6.317214-6.724776-10.469254-8.737115-7.705473-3.973731-17.512438-3.324179-24.606567 1.642986-5.553035 4.024677-10.341891 9.666866-11.704677 16.557213-1.872239 8.100299 0.904279 16.977512 6.915821 22.708856 33.52199 33.509254 67.018507 67.056716 100.578706 100.540498 6.406368 6.457313 16.56995 8.660697 25.103284 5.553035 6.266269-2.037811 10.405572-7.412537 15.385473-11.386269 47.009751-46.882388 93.904876-93.904876 140.889154-140.812736 3.515224-3.375124 6.32995-7.578109 7.463483-12.366966 5.234627-18.467662-15.652935-37.686766-33.598408-30.388855z" p-id="3435" fill="#1daaf4"></path></svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
src/static/me/tcdl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 B

1
src/static/me/tgcxjl.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1765182453848" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2537" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M0 0h1024v1024H0z" fill="#1daaf4" fill-opacity="0" p-id="2538"></path><path d="M668 484c121.503 0 220 98.497 220 220 0 42.679-12.153 82.52-33.187 116.25l54.531 54.532c19.527 19.526 19.527 51.184 0 70.71-19.33 19.331-50.552 19.525-70.12 0.58l-0.59-0.58-54.567-54.565C750.376 911.89 710.602 924 668 924c-121.503 0-220-98.497-220-220s98.497-220 220-220zM768 64c17.673 0 32.004 14.327 32.004 32v356.477C760.566 431.737 715.654 420 668 420c-156.849 0-284 127.151-284 284 0 112.742 65.694 210.14 160.891 256.003H192c-17.673 0-32-14.33-32-32.003V428h268c53.02 0 96-42.98 96-96V64h244zM668 584c-66.274 0-120 53.726-120 120s53.726 120 120 120 120-53.726 120-120-53.726-120-120-120zM460 64v268c0 17.673-14.327 32-32 32H160L460 64z" fill="#1daaf4" p-id="2539"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src/static/me/user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

1
src/static/me/yhxy.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764650051703" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12625" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M775.912727 105.192727H234.356364a69.818182 69.818182 0 0 0-68.654546 68.654546V837.818182a69.818182 69.818182 0 0 0 68.654546 69.818182h384a226.210909 226.210909 0 0 0 226.210909-226.210909V173.847273a69.818182 69.818182 0 0 0-68.654546-68.654546zM541.789091 577.396364H302.545455a18.385455 18.385455 0 0 1 0-36.770909h239.243636a18.385455 18.385455 0 1 1 0 36.770909z m165.236364-128.465455H302.545455a18.385455 18.385455 0 0 1 0-36.770909h404.48a18.385455 18.385455 0 1 1 0 36.770909z m0-128.465454H302.545455a18.385455 18.385455 0 0 1 0-36.77091h404.48a18.385455 18.385455 0 1 1 0 36.77091z" fill="#1daaf4" p-id="12626"></path></svg>

After

Width:  |  Height:  |  Size: 971 B

1
src/static/me/yqmgl.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764649988299" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6624" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M714.5 501.2c0 19.5-16 35.4-35.4 35.4H345.7c-19.4 0-35.4-16-35.4-35.4 0-19.5 16-35.4 35.4-35.4h333.4c19.5 0 35.4 15.8 35.4 35.4z m244.7-133.4V261.7c0-29.7-11.7-58.2-32.7-79.2-21-21-49.5-32.9-79.2-32.7H176.8c-29.7 0-58.2 11.8-79.1 32.7-21 21-32.7 49.5-32.7 79.2v106.1c82.3 0 149 65.1 149 145.3S147.3 658.4 65 658.4v106c0 29.7 11.7 58.2 32.7 79.2 21 21 49.5 32.9 79.2 32.7h670.6c29.7 0 58.2-11.8 79.1-32.7 21-21 32.7-49.5 32.7-79.2v-106c-82.3 0-149-65.1-149-145.3-0.1-80.4 66.7-145.3 148.9-145.3z" fill="#1daaf4" p-id="6625"></path></svg>

After

Width:  |  Height:  |  Size: 869 B

1
src/static/me/yszc.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764650162537" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13739" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 384m-85.333333 0a85.333333 85.333333 0 1 0 170.666666 0 85.333333 85.333333 0 1 0-170.666666 0Z" p-id="13740" fill="#1daaf4"></path><path d="M768 56.888889H256a113.777778 113.777778 0 0 0-113.777778 113.777778v682.666666a113.777778 113.777778 0 0 0 113.777778 113.777778h369.777778a256 256 0 0 0 256-256V170.666667a113.777778 113.777778 0 0 0-113.777778-113.777778z m-142.222222 554.666667a42.666667 42.666667 0 0 1 0 85.333333h-71.111111V768a42.666667 42.666667 0 0 1-85.333334 0v-219.022222a170.666667 170.666667 0 1 1 85.333334 0v62.577778z" p-id="13741" fill="#1daaf4"></path></svg>

After

Width:  |  Height:  |  Size: 925 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
src/static/promote/bzzx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Some files were not shown because too many files have changed in this diff Show More