diff --git a/.env b/.env index 320d6e8..abdd69f 100644 --- a/.env +++ b/.env @@ -1,7 +1,7 @@ VITE_API_URL= VITE_API_PREFIX=/api/v1 -VITE_COMPANY_NAME=海南省学宇思网络科技有限公司 +VITE_COMPANY_NAME=海南海宇大数据有限公司 VITE_INQUIRE_AES_KEY=ff83609b2b24fc73196aac3d3dfb874f @@ -13,4 +13,5 @@ VITE_CHAT_AES_IV=345GDFED433223DF VITE_SHARE_TITLE=一查查|大数据风险报告查询与代理平台,支持个人和企业多场景风控应用 VITE_SHARE_DESC=提供个人信用评估、入职背调、信贷风控、企业风险监测等服务 -VITE_SHARE_IMG=https://www.tianyuandb.com/logo.png \ No newline at end of file +VITE_SHARE_IMG=https://www.onecha.cn/logo.png +VITE_TOKEN_VERSION=1.5 \ No newline at end of file diff --git a/public/logo.jpg b/public/logo.jpg deleted file mode 100644 index 517584e..0000000 Binary files a/public/logo.jpg and /dev/null differ diff --git a/public/logo.png b/public/logo.png index 19ab382..af12e51 100644 Binary files a/public/logo.png and b/public/logo.png differ diff --git a/src/App.vue b/src/App.vue index 8eb69b1..6aedb96 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,211 +1,324 @@ diff --git a/src/api/agent.js b/src/api/agent.js new file mode 100644 index 0000000..14391a4 --- /dev/null +++ b/src/api/agent.js @@ -0,0 +1,280 @@ +import useApiFetch from "@/composables/useApiFetch"; + +/** + * 代理相关API调用统一管理 + */ + +/** + * 构建查询字符串的辅助函数 + * @param {object} params - 查询参数对象 + * @returns {string} 查询字符串 + */ +function buildQueryString(params) { + const queryParams = new URLSearchParams(); + Object.keys(params).forEach((key) => { + if ( + params[key] !== undefined && + params[key] !== null && + params[key] !== "" + ) { + queryParams.append(key, params[key]); + } + }); + const queryString = queryParams.toString(); + return queryString ? `?${queryString}` : ""; +} + +// ==================== 公开接口(无需登录) ==================== + +/** + * 获取推广链接数据 + * @param {string} linkIdentifier - 推广链接标识 + */ +export function getLinkData(linkIdentifier) { + return useApiFetch( + `/agent/link?link_identifier=${encodeURIComponent(linkIdentifier)}` + ) + .get() + .json(); +} + +/** + * 通过邀请码申请成为代理(已注册用户) + * @param {object} params - 申请参数 + * @param {string} params.mobile - 手机号 + * @param {string} params.code - 验证码 + * @param {string} params.invite_code - 邀请码(必填) + * @param {string} params.region - 区域(可选) + */ +export function applyForAgent(params) { + return useApiFetch("/agent/apply").post(params).json(); +} + +/** + * 通过邀请码注册(同时注册用户和代理) + * @param {object} params - 注册参数 + * @param {string} params.mobile - 手机号 + * @param {string} params.code - 验证码 + * @param {string} params.invite_code - 邀请码(必填) + * @param {string} params.region - 区域(可选) + * @param {string} params.wechat_id - 微信号(可选) + */ +export function registerByInviteCode(params) { + return useApiFetch("/agent/register/invite").post(params).json(); +} + +// ==================== 需要登录的接口 ==================== + +/** + * 获取代理信息 + */ +export function getAgentInfo() { + return useApiFetch("/agent/info").get().json(); +} + +/** + * 获取代理等级特权信息 + */ +export function getLevelPrivilege() { + return useApiFetch("/agent/level/privilege").get().json(); +} + +/** + * 生成推广链接 + * @param {object} params - 生成参数 + * @param {number} params.product_id - 产品ID + * @param {number} params.set_price - 设定价格 + */ +export function generateLink(params) { + return useApiFetch("/agent/generating_link").post(params).json(); +} + +/** + * 获取产品配置 + */ +export function getProductConfig() { + return useApiFetch("/agent/product_config").get().json(); +} + +/** + * 获取团队统计 + */ +export function getTeamStatistics() { + return useApiFetch("/agent/team/statistics").get().json(); +} + +/** + * 获取团队列表 + * @param {object} params - 查询参数 + * @param {number} params.page - 页码 + * @param {number} params.page_size - 每页数量 + */ +export function getTeamList(params) { + const queryString = buildQueryString(params || {}); + return useApiFetch(`/agent/team/list${queryString}`).get().json(); +} + +/** + * 获取下级列表 + * @param {object} params - 查询参数 + * @param {number} params.page - 页码 + * @param {number} params.page_size - 每页数量 + */ +export function getSubordinateList(params) { + const queryString = buildQueryString(params || {}); + return useApiFetch(`/agent/subordinate/list${queryString}`).get().json(); +} + +/** + * 获取收益信息 + */ +export function getRevenueInfo() { + return useApiFetch("/agent/revenue").get().json(); +} + +/** + * 获取转化率统计 + */ +export function getConversionRate() { + return useApiFetch("/agent/conversion/rate").get().json(); +} + +/** + * 获取佣金记录 + * @param {object} params - 查询参数 + * @param {number} params.page - 页码 + * @param {number} params.page_size - 每页数量 + */ +export function getCommissionList(params) { + const queryString = buildQueryString(params || {}); + return useApiFetch(`/agent/commission/list${queryString}`).get().json(); +} + +/** + * 获取返佣记录(推广返佣) + * @param {object} params - 查询参数 + * @param {number} params.page - 页码 + * @param {number} params.page_size - 每页数量 + * @param {number} params.rebate_type - 返佣类型(可选):1=直接上级返佣,2=钻石上级返佣,3=黄金上级返佣 + */ +export function getRebateList(params) { + const queryString = buildQueryString(params || {}); + return useApiFetch(`/agent/rebate/list${queryString}`).get().json(); +} + +/** + * 获取升级返佣记录 + * @param {object} params - 查询参数 + * @param {number} params.page - 页码 + * @param {number} params.page_size - 每页数量 + */ +export function getUpgradeRebateList(params) { + const queryString = buildQueryString(params || {}); + return useApiFetch(`/agent/rebate/upgrade/list${queryString}`).get().json(); +} + +/** + * 获取升级记录 + * @param {object} params - 查询参数 + * @param {number} params.page - 页码 + * @param {number} params.page_size - 每页数量 + */ +export function getUpgradeList(params) { + const queryString = buildQueryString(params || {}); + return useApiFetch(`/agent/upgrade/list${queryString}`).get().json(); +} + +/** + * 申请升级 + * @param {object} params - 升级参数 + * @param {number} params.to_level - 目标等级:2=黄金,3=钻石 + */ +export function applyUpgrade(params) { + return useApiFetch("/agent/upgrade/apply").post(params).json(); +} + +/** + * 钻石代理升级下级 + * @param {object} params - 升级参数 + * @param {number} params.subordinate_id - 下级代理ID + * @param {number} params.to_level - 目标等级(只能是2=黄金) + */ +export function upgradeSubordinate(params) { + return useApiFetch("/agent/upgrade/subordinate").post(params).json(); +} + +/** + * 获取提现列表 + * @param {object} params - 查询参数 + * @param {number} params.page - 页码 + * @param {number} params.page_size - 每页数量 + */ +export function getWithdrawalList(params) { + const queryString = buildQueryString(params || {}); + return useApiFetch(`/agent/withdrawal/list${queryString}`).get().json(); +} + +/** + * 申请提现 + * @param {object} params - 提现参数 + * @param {number} params.amount - 提现金额 + * @param {string} params.payee_account - 收款账户 + * @param {string} params.payee_name - 收款人姓名 + */ +export function applyWithdrawal(params) { + return useApiFetch("/agent/withdrawal/apply").post(params).json(); +} + +/** + * 实名认证 + * @param {object} params - 实名认证参数 + * @param {string} params.name - 姓名 + * @param {string} params.id_card - 身份证号 + * @param {string} params.mobile - 手机号 + * @param {string} params.code - 验证码 + */ +export function realNameAuth(params) { + return useApiFetch("/agent/real_name").post(params).json(); +} + +/** + * 生成邀请码 + * @param {object} params - 生成参数 + * @param {number} params.count - 生成数量 + * @param {number} params.expire_days - 过期天数(可选,0表示不过期) + * @param {string} params.remark - 备注(可选) + */ +export function generateInviteCode(params) { + return useApiFetch("/agent/invite_code/generate").post(params).json(); +} + +/** + * 获取邀请码列表 + * @param {object} params - 查询参数 + * @param {number} params.page - 页码 + * @param {number} params.page_size - 每页数量 + * @param {number} params.status - 状态(可选):0=未使用,1=已使用,2=已失效 + */ +export function getInviteCodeList(params) { + const queryString = buildQueryString(params || {}); + return useApiFetch(`/agent/invite_code/list${queryString}`).get().json(); +} + +/** + * 删除邀请码 + * @param {object} params - 删除参数 + * @param {number} params.id - 邀请码ID + */ +export function deleteInviteCode(params) { + return useApiFetch("/agent/invite_code/delete").post(params).json(); +} + +/** + * 获取邀请链接 + * @param {object} params - 请求参数 + * @param {string} params.invite_code - 邀请码 + */ +export function getInviteLink(params) { + const queryString = buildQueryString(params || {}); + return useApiFetch(`/agent/invite_link${queryString}`).get().json(); +} diff --git a/src/api/user.js b/src/api/user.js index 75d3ff8..1a03833 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -1,8 +1,24 @@ import axios from "axios"; +import useApiFetch from "@/composables/useApiFetch"; // 获取API基础URL(与生产规则一致:VITE_API_URL) const baseURL = import.meta.env.VITE_API_URL; +// 手机号验证码登录 +export function mobileCodeLogin(params) { + return useApiFetch("/user/mobileCodeLogin").post(params).json(); +} + +// 统一认证 +export function unifiedAuth(params) { + return useApiFetch("/user/auth").post(params).json(); +} + +// 绑定手机号 +export function bindMobile(params) { + return useApiFetch("/user/bindMobile").post(params).json(); +} + // 注销账号API export function cancelAccount() { return axios({ diff --git a/src/assets/images/index/n/01.jpg b/src/assets/images/index/n/01.jpg new file mode 100644 index 0000000..974ddea Binary files /dev/null and b/src/assets/images/index/n/01.jpg differ diff --git a/src/assets/images/index/n/02.png b/src/assets/images/index/n/02.png new file mode 100644 index 0000000..af4e512 Binary files /dev/null and b/src/assets/images/index/n/02.png differ diff --git a/src/assets/images/index/n/03.png b/src/assets/images/index/n/03.png new file mode 100644 index 0000000..0bc5912 Binary files /dev/null and b/src/assets/images/index/n/03.png differ diff --git a/src/assets/images/index/n/04.png b/src/assets/images/index/n/04.png new file mode 100644 index 0000000..f2de5ce Binary files /dev/null and b/src/assets/images/index/n/04.png differ diff --git a/src/assets/images/index/n/05.png b/src/assets/images/index/n/05.png new file mode 100644 index 0000000..cba34b3 Binary files /dev/null and b/src/assets/images/index/n/05.png differ diff --git a/src/assets/images/index/n/06.png b/src/assets/images/index/n/06.png new file mode 100644 index 0000000..c364b30 Binary files /dev/null and b/src/assets/images/index/n/06.png differ diff --git a/src/assets/images/index/n/07.png b/src/assets/images/index/n/07.png new file mode 100644 index 0000000..d8c5e7f Binary files /dev/null and b/src/assets/images/index/n/07.png differ diff --git a/src/assets/images/index/n/08.png b/src/assets/images/index/n/08.png new file mode 100644 index 0000000..9da8480 Binary files /dev/null and b/src/assets/images/index/n/08.png differ diff --git a/src/assets/images/index/n/09.png b/src/assets/images/index/n/09.png new file mode 100644 index 0000000..b3437fe Binary files /dev/null and b/src/assets/images/index/n/09.png differ diff --git a/src/assets/images/index/n/10.png b/src/assets/images/index/n/10.png new file mode 100644 index 0000000..698631d Binary files /dev/null and b/src/assets/images/index/n/10.png differ diff --git a/src/assets/images/index/n/11.png b/src/assets/images/index/n/11.png new file mode 100644 index 0000000..d2fe757 Binary files /dev/null and b/src/assets/images/index/n/11.png differ diff --git a/src/assets/images/index/n/样板.png b/src/assets/images/index/n/样板.png new file mode 100644 index 0000000..8b36fd8 Binary files /dev/null and b/src/assets/images/index/n/样板.png differ diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png index 19ab382..af12e51 100644 Binary files a/src/assets/images/logo.png and b/src/assets/images/logo.png differ diff --git a/src/assets/images/me/lxkf.svg b/src/assets/images/me/lxkf.svg new file mode 100644 index 0000000..dca9623 --- /dev/null +++ b/src/assets/images/me/lxkf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/me/sjdl.svg b/src/assets/images/me/sjdl.svg new file mode 100644 index 0000000..3295f51 --- /dev/null +++ b/src/assets/images/me/sjdl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/me/sjxj.svg b/src/assets/images/me/sjxj.svg new file mode 100644 index 0000000..cd5fc1a --- /dev/null +++ b/src/assets/images/me/sjxj.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/me/smrz.svg b/src/assets/images/me/smrz.svg new file mode 100644 index 0000000..c719c4b --- /dev/null +++ b/src/assets/images/me/smrz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/me/tgcxjl.svg b/src/assets/images/me/tgcxjl.svg new file mode 100644 index 0000000..9cd4ab8 --- /dev/null +++ b/src/assets/images/me/tgcxjl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/me/tx.svg b/src/assets/images/me/tx.svg new file mode 100644 index 0000000..2b185e1 --- /dev/null +++ b/src/assets/images/me/tx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/me/yhxy.svg b/src/assets/images/me/yhxy.svg new file mode 100644 index 0000000..32d5c21 --- /dev/null +++ b/src/assets/images/me/yhxy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/me/yqmgl.svg b/src/assets/images/me/yqmgl.svg new file mode 100644 index 0000000..30727ed --- /dev/null +++ b/src/assets/images/me/yqmgl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/me/yszc.svg b/src/assets/images/me/yszc.svg new file mode 100644 index 0000000..4b8a455 --- /dev/null +++ b/src/assets/images/me/yszc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts index 3a7330e..a4ca6de 100644 --- a/src/auto-imports.d.ts +++ b/src/auto-imports.d.ts @@ -119,6 +119,7 @@ declare global { const useAgentStore: typeof import('./stores/agentStore.js')['useAgentStore'] const useAnimate: typeof import('@vueuse/core')['useAnimate'] const useApiFetch: typeof import('./composables/useApiFetch.js')['default'] + const useAppStore: typeof import('./stores/appStore.js')['useAppStore'] const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference'] const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery'] const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter'] diff --git a/src/components/AgentApplicationForm.vue b/src/components/AgentApplicationForm.vue index 2da4b0d..96b1d4f 100644 --- a/src/components/AgentApplicationForm.vue +++ b/src/components/AgentApplicationForm.vue @@ -6,17 +6,22 @@ > 成为代理 -
- {{ maskName(ancestor) }}邀您成为一查查代理方 -
+ @@ -124,10 +129,6 @@ import { useCascaderAreaData } from "@vant/area-data"; import { showToast } from "vant"; // 引入 showToast 方法 const emit = defineEmits(); // 确保 emit 可以正确使用 const props = defineProps({ - ancestor: { - type: String, - required: true, - }, isSelf: { type: Boolean, default: false, @@ -137,11 +138,12 @@ const props = defineProps({ default: "", }, }); -const { ancestor, isSelf, userName } = toRefs(props); +const { isSelf, userName } = toRefs(props); const form = ref({ + referrer: "", region: "", mobile: "", - code: "", // 增加验证码字段 + code: "", // 验证码字段 }); const showCascader = ref(false); const cascaderValue = ref(""); @@ -207,10 +209,11 @@ onUnmounted(() => { }); const submit = () => { // 校验表单字段 - if (!form.value.region) { - showToast({ message: "请选择地区" }); + if (!form.value.referrer || !form.value.referrer.trim()) { + showToast({ message: "请输入邀请信息" }); return; } + if (!form.value.mobile) { showToast({ message: "请输入手机号" }); return; @@ -231,15 +234,13 @@ const submit = () => { showToast({ message: "请先阅读并同意用户协议及相关条款" }); return; } - console.log("form", form.value); + // 触发父组件提交申请 - emit("submit", form.value); + emit("submit", { + ...form.value, + referrer: form.value.referrer.trim() + }); }; -const maskName = computed(() => { - return (name) => { - return name.substring(0, 3) + "****" + name.substring(7); - }; -}); const closePopup = () => { emit("close"); }; diff --git a/src/components/BaseReport.vue b/src/components/BaseReport.vue index ad656e3..6362f0c 100644 --- a/src/components/BaseReport.vue +++ b/src/components/BaseReport.vue @@ -8,6 +8,7 @@ import { splitDWBG6A2CForTabs } from '@/ui/DWBG6A2C/utils/simpleSplitter.js'; import { splitJRZQ7F1AForTabs } from '@/ui/JRZQ7F1A/utils/simpleSplitter.js'; import { splitCJRZQ5E9FForTabs } from '@/ui/CJRZQ5E9F/utils/simpleSplitter.js'; import { splitCQYGL3F8EForTabs } from '@/ui/CQYGL3F8E/utils/simpleSplitter.js'; +import { useAppStore } from "@/stores/appStore"; // 动态导入产品背景图片的函数 const loadProductBackground = async (productType) => { @@ -17,7 +18,7 @@ const loadProductBackground = async (productType) => { return (await import("@/assets/images/report/xwqy_inquire_bg.png")).default; case 'preloanbackgroundcheck': return (await import("@/assets/images/report/dqfx_inquire_bg.png")).default; - case 'personalData': + case 'riskassessment': return (await import("@/assets/images/report/grdsj_inquire_bg.png")).default; case 'marriage': return (await import("@/assets/images/report/marriage_inquire_bg.png")).default; @@ -36,6 +37,8 @@ const loadProductBackground = async (productType) => { } }; +const appStore = useAppStore(); + const props = defineProps({ isShare: { type: Boolean, @@ -879,7 +882,7 @@ watch([reportData, componentRiskScores], () => { 1、本份报告是在取得您个人授权后,我们才向合法存有您以上个人信息的机构去调取相关内容,我们不会以任何形式对您的报告进行存储,除您和您授权的人外不会提供给任何人和机构进行查看。

-     2、本报告自生成之日起,有效期 30 +     2、本报告自生成之日起,有效期 {{ useAppStore().queryRetentionDays || 30 }} 天,过期自动删除。如果您对本份报告存有异议,可能是合作机构数据有延迟或未能获取到您的相关数据,出于合作平台数据隐私的保护,本平台将不做任何解释。

@@ -894,17 +897,17 @@ watch([reportData, componentRiskScores], () => {

- -
海南省学宇思网络科技有限公司版权所有
+
海南海宇大数据有限公司版权所有
diff --git a/src/components/BindPhoneDialog.vue b/src/components/BindPhoneDialog.vue index 411d107..e80dad7 100644 --- a/src/components/BindPhoneDialog.vue +++ b/src/components/BindPhoneDialog.vue @@ -1,33 +1,72 @@ + diff --git a/src/components/ClickCaptcha.vue b/src/components/ClickCaptcha.vue index 6ca2eb6..aa3364f 100644 --- a/src/components/ClickCaptcha.vue +++ b/src/components/ClickCaptcha.vue @@ -47,8 +47,8 @@ const canvasWidth = 300 const canvasHeight = 180 const bgImgUrl = '/image/clickCaptcha.jpg' // 可替换为任意背景图 -const allChars = ['大', '数', '据', '全', '能', '查', '风', '险', '报', '告'] -const targetChars = ref(['全', '能', '查']) // 目标点击顺序固定 +const allChars = ['大', '数', '据', '一', '查', '风', '险', '报', '告'] +const targetChars = ref(['一', '查', '查']) // 目标点击顺序固定 const charPositions = ref([]) // [{char, x, y, w, h}] const clickedIndex = ref(0) const errorMessage = ref('') @@ -134,7 +134,7 @@ function generateCaptcha() { ;[chars[i], chars[j]] = [chars[j], chars[i]] } currentChars = chars - targetChars.value = ['全', '能', '查'] + targetChars.value = ['一', '查', '查'] clickedIndex.value = 0 errorMessage.value = '' successMessage.value = '' @@ -238,284 +238,284 @@ watch( z-index: 9999; padding: 1rem; backdrop-filter: blur(4px); - animation: fadeIn 0.2s ease-out; + animation: fadeIn 0.2s ease-out; +} + +@keyframes fadeIn { + from { + opacity: 0; } - - @keyframes fadeIn { - from { - opacity: 0; - } - - to { - opacity: 1; - } + + to { + opacity: 1; } - +} + +.captcha-modal { + background: #fff; + border-radius: 1rem; + width: 100%; + max-width: 360px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05); + overflow: hidden; + animation: slideUp 0.3s ease-out; +} + +@keyframes slideUp { + from { + transform: translateY(20px); + opacity: 0; + } + + to { + transform: translateY(0); + opacity: 1; + } +} + +.captcha-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1.25rem 1.5rem; + border-bottom: 1px solid var(--color-border-primary, #ebedf0); + background: linear-gradient(135deg, var(--color-primary-light, rgba(140, 198, 247, 0.1)), #ffffff); +} + +.captcha-title { + font-size: 1.125rem; + font-weight: 600; + color: var(--color-text-primary, #323233); + margin: 0; + background: linear-gradient(135deg, var(--color-primary, #8CC6F7), var(--color-primary-600, #709ec6)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.close-btn { + background: rgba(0, 0, 0, 0.05); + border: none; + font-size: 1.5rem; + color: var(--color-text-secondary, #646566); + cursor: pointer; + padding: 0.375rem; + width: 32px; + height: 32px; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + line-height: 1; +} + +.close-btn:hover { + color: var(--color-text-primary, #323233); + background: rgba(0, 0, 0, 0.1); + transform: rotate(90deg); +} + +.captcha-content { + padding: 1.5rem 1.5rem 1rem 1.5rem; + background: #ffffff; +} + +.captcha-canvas { + width: 100%; + border-radius: 0.75rem; + background: var(--color-bg-tertiary, #f8f8f8); + display: block; + margin: 0 auto; + border: 2px solid var(--color-border-primary, #ebedf0); + transition: all 0.2s ease; + cursor: pointer; +} + +.captcha-canvas:hover { + border-color: var(--color-primary, #8CC6F7); + box-shadow: 0 0 0 3px var(--color-primary-light, rgba(140, 198, 247, 0.1)); +} + +.captcha-instruction { + margin: 1.25rem 0 0.75rem 0; + text-align: center; +} + +.captcha-instruction p { + font-size: 0.95rem; + color: var(--color-text-secondary, #646566); + margin: 0; + line-height: 1.5; +} + +.target-list { + color: var(--color-primary, #8CC6F7); + font-weight: 600; + font-size: 1.05rem; + padding: 0.25rem 0.5rem; + background: var(--color-primary-light, rgba(140, 198, 247, 0.1)); + border-radius: 0.375rem; + display: inline-block; +} + +.captcha-status { + text-align: center; + min-height: 1.75rem; + margin-top: 0.5rem; +} + +.error-message { + color: var(--color-danger, #ee0a24); + font-size: 0.875rem; + margin: 0; + padding: 0.5rem; + background: rgba(238, 10, 36, 0.1); + border-radius: 0.5rem; + animation: shake 0.3s ease-in-out; +} + +@keyframes shake { + + 0%, + 100% { + transform: translateX(0); + } + + 25% { + transform: translateX(-5px); + } + + 75% { + transform: translateX(5px); + } +} + +.success-message { + color: var(--color-success, #07c160); + font-size: 0.875rem; + margin: 0; + padding: 0.5rem; + background: rgba(7, 193, 96, 0.1); + border-radius: 0.5rem; + animation: bounce 0.4s ease-out; +} + +@keyframes bounce { + + 0%, + 100% { + transform: translateY(0); + } + + 50% { + transform: translateY(-3px); + } +} + +.status-text { + color: var(--color-text-tertiary, #969799); + font-size: 0.875rem; + margin: 0; +} + +.captcha-footer { + padding: 1.25rem 1.5rem; + border-top: 1px solid var(--color-border-primary, #ebedf0); + background: var(--color-bg-secondary, #fafafa); + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.refresh-btn { + width: 100%; + padding: 0.875rem; + background: var(--color-primary, #8CC6F7); + color: white; + border: none; + border-radius: 0.625rem; + font-size: 0.95rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 2px 8px rgba(140, 198, 247, 0.3); +} + +.refresh-btn:hover:not(:disabled) { + background: var(--color-primary-dark, rgba(140, 198, 247, 0.8)); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(140, 198, 247, 0.4); +} + +.refresh-btn:active:not(:disabled) { + transform: translateY(0); +} + +.refresh-btn:disabled { + background: var(--color-text-disabled, #c8c9cc); + cursor: not-allowed; + box-shadow: none; +} + +.confirm-btn { + width: 100%; + padding: 0.875rem; + background: var(--color-success, #07c160); + color: white; + border: none; + border-radius: 0.625rem; + font-size: 0.95rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 2px 8px rgba(7, 193, 96, 0.3); +} + +.confirm-btn:hover:not(:disabled) { + background: rgba(7, 193, 96, 0.9); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(7, 193, 96, 0.4); +} + +.confirm-btn:active:not(:disabled) { + transform: translateY(0); +} + +.confirm-btn:disabled { + background: var(--color-text-disabled, #c8c9cc); + cursor: not-allowed; + box-shadow: none; +} + +@media (max-width: 480px) { + .captcha-overlay { + padding: 0.75rem; + } + .captcha-modal { - background: #fff; - border-radius: 1rem; - width: 100%; - max-width: 360px; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05); - overflow: hidden; - animation: slideUp 0.3s ease-out; + max-width: 100%; + border-radius: 0.875rem; } - - @keyframes slideUp { - from { - transform: translateY(20px); - opacity: 0; - } - - to { - transform: translateY(0); - opacity: 1; - } - } - + .captcha-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1.25rem 1.5rem; - border-bottom: 1px solid var(--color-border-primary, #ebedf0); - background: linear-gradient(135deg, var(--color-primary-light, rgba(140, 198, 247, 0.1)), #ffffff); + padding: 1rem 1.25rem; } - - .captcha-title { - font-size: 1.125rem; - font-weight: 600; - color: var(--color-text-primary, #323233); - margin: 0; - background: linear-gradient(135deg, var(--color-primary, #8CC6F7), var(--color-primary-600, #709ec6)); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - } - - .close-btn { - background: rgba(0, 0, 0, 0.05); - border: none; - font-size: 1.5rem; - color: var(--color-text-secondary, #646566); - cursor: pointer; - padding: 0.375rem; - width: 32px; - height: 32px; - border-radius: 0.5rem; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s ease; - line-height: 1; - } - - .close-btn:hover { - color: var(--color-text-primary, #323233); - background: rgba(0, 0, 0, 0.1); - transform: rotate(90deg); - } - + .captcha-content { - padding: 1.5rem 1.5rem 1rem 1.5rem; - background: #ffffff; + padding: 1.25rem 1.25rem 0.875rem 1.25rem; } - + .captcha-canvas { - width: 100%; - border-radius: 0.75rem; - background: var(--color-bg-tertiary, #f8f8f8); - display: block; - margin: 0 auto; - border: 2px solid var(--color-border-primary, #ebedf0); - transition: all 0.2s ease; - cursor: pointer; + min-height: 140px; } - - .captcha-canvas:hover { - border-color: var(--color-primary, #8CC6F7); - box-shadow: 0 0 0 3px var(--color-primary-light, rgba(140, 198, 247, 0.1)); - } - - .captcha-instruction { - margin: 1.25rem 0 0.75rem 0; - text-align: center; - } - - .captcha-instruction p { - font-size: 0.95rem; - color: var(--color-text-secondary, #646566); - margin: 0; - line-height: 1.5; - } - - .target-list { - color: var(--color-primary, #8CC6F7); - font-weight: 600; - font-size: 1.05rem; - padding: 0.25rem 0.5rem; - background: var(--color-primary-light, rgba(140, 198, 247, 0.1)); - border-radius: 0.375rem; - display: inline-block; - } - - .captcha-status { - text-align: center; - min-height: 1.75rem; - margin-top: 0.5rem; - } - - .error-message { - color: var(--color-danger, #ee0a24); - font-size: 0.875rem; - margin: 0; - padding: 0.5rem; - background: rgba(238, 10, 36, 0.1); - border-radius: 0.5rem; - animation: shake 0.3s ease-in-out; - } - - @keyframes shake { - - 0%, - 100% { - transform: translateX(0); - } - - 25% { - transform: translateX(-5px); - } - - 75% { - transform: translateX(5px); - } - } - - .success-message { - color: var(--color-success, #07c160); - font-size: 0.875rem; - margin: 0; - padding: 0.5rem; - background: rgba(7, 193, 96, 0.1); - border-radius: 0.5rem; - animation: bounce 0.4s ease-out; - } - - @keyframes bounce { - - 0%, - 100% { - transform: translateY(0); - } - - 50% { - transform: translateY(-3px); - } - } - - .status-text { - color: var(--color-text-tertiary, #969799); - font-size: 0.875rem; - margin: 0; - } - + .captcha-footer { - padding: 1.25rem 1.5rem; - border-top: 1px solid var(--color-border-primary, #ebedf0); - background: var(--color-bg-secondary, #fafafa); - display: flex; - flex-direction: column; - gap: 0.75rem; + padding: 1rem 1.25rem; } - - .refresh-btn { - width: 100%; - padding: 0.875rem; - background: var(--color-primary, #8CC6F7); - color: white; - border: none; - border-radius: 0.625rem; - font-size: 0.95rem; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - box-shadow: 0 2px 8px rgba(140, 198, 247, 0.3); - } - - .refresh-btn:hover:not(:disabled) { - background: var(--color-primary-dark, rgba(140, 198, 247, 0.8)); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(140, 198, 247, 0.4); - } - - .refresh-btn:active:not(:disabled) { - transform: translateY(0); - } - - .refresh-btn:disabled { - background: var(--color-text-disabled, #c8c9cc); - cursor: not-allowed; - box-shadow: none; - } - - .confirm-btn { - width: 100%; - padding: 0.875rem; - background: var(--color-success, #07c160); - color: white; - border: none; - border-radius: 0.625rem; - font-size: 0.95rem; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - box-shadow: 0 2px 8px rgba(7, 193, 96, 0.3); - } - - .confirm-btn:hover:not(:disabled) { - background: rgba(7, 193, 96, 0.9); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(7, 193, 96, 0.4); - } - - .confirm-btn:active:not(:disabled) { - transform: translateY(0); - } - - .confirm-btn:disabled { - background: var(--color-text-disabled, #c8c9cc); - cursor: not-allowed; - box-shadow: none; - } - - @media (max-width: 480px) { - .captcha-overlay { - padding: 0.75rem; - } - - .captcha-modal { - max-width: 100%; - border-radius: 0.875rem; - } - - .captcha-header { - padding: 1rem 1.25rem; - } - - .captcha-content { - padding: 1.25rem 1.25rem 0.875rem 1.25rem; - } - - .captcha-canvas { - min-height: 140px; - } - - .captcha-footer { - padding: 1rem 1.25rem; - } - - .captcha-instruction p { - font-size: 0.875rem; + + .captcha-instruction p { + font-size: 0.875rem; } } diff --git a/src/components/InquireForm.vue b/src/components/InquireForm.vue index 05c3e05..c013297 100644 --- a/src/components/InquireForm.vue +++ b/src/components/InquireForm.vue @@ -72,7 +72,7 @@
-->
- 为保证用户的隐私及数据安全,查询结果生成30天后将自动删除 + 为保证用户的隐私及数据安全,查询结果生成{{ appStore.queryRetentionDays || 30 }}天后将自动删除
@@ -108,14 +108,14 @@ v-html="featureData.description">
- 为保证用户的隐私以及数据安全,查询的结果生成30天之后将自动清除。 + 为保证用户的隐私以及数据安全,查询的结果生成{{ appStore.queryRetentionDays || 30 }}天之后将自动清除。
- +
- \ No newline at end of file + diff --git a/src/components/QRcode.vue b/src/components/QRcode.vue index 3ee8b60..cb53cdc 100644 --- a/src/components/QRcode.vue +++ b/src/components/QRcode.vue @@ -1,5 +1,5 @@ @@ -43,8 +43,8 @@ const router = useRouter(); const route = useRoute(); const tabbar = ref("promote"); const menu = reactive([ - { title: "推广", icon: "cluster-o", name: "promote" }, - { title: "资产", icon: "gold-coin-o", name: "agent" }, + { title: "首页", icon: "home-o", name: "promote" }, + { title: "数据", icon: "bar-chart-o", name: "agent" }, { title: "我的", icon: "user-o", name: "me" } ]); // 根据当前路由设置 Tabbar 的高亮项 @@ -83,7 +83,7 @@ const tabChange = (name, a, b, c) => { // 跳转到投诉页面 const toComplaint = () => { window.location.href = - "https://work.weixin.qq.com/kfid/kfc8a32720024833f57"; // 跳转到客服页面 + "https://work.weixin.qq.com/kfid/kfc82d4424e4b19e5f3"; // 跳转到客服页面 // router.push({ name: 'complaint' }); // 使用 Vue Router 进行跳转 }; diff --git a/src/router/index.js b/src/router/index.js index ad8852e..904e8d4 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,17 +1,17 @@ -import { createRouter, createWebHistory } from 'vue-router' -import NProgress from 'nprogress' -import GlobalLayout from '@/layouts/GlobalLayout.vue' -import HomeLayout from '@/layouts/HomeLayout.vue' -import PageLayout from '@/layouts/PageLayout.vue' -import index from '@/views/index.vue' -import Promote from '@/views/Promote.vue' -import PromotePage from '@/views/PromotePage.vue' -import { useAgentStore } from '@/stores/agentStore' -import { useUserStore } from '@/stores/userStore' -import { useDialogStore } from '@/stores/dialogStore' -import { useEnv } from '@/composables/useEnv' -import { storeToRefs } from 'pinia' -import { useSEO } from '@/composables/useSEO' +import { createRouter, createWebHistory } from "vue-router"; +import NProgress from "nprogress"; +import GlobalLayout from "@/layouts/GlobalLayout.vue"; +import HomeLayout from "@/layouts/HomeLayout.vue"; +import PageLayout from "@/layouts/PageLayout.vue"; +import index from "@/views/index.vue"; +import Promote from "@/views/Promote.vue"; +import PromotePage from "@/views/PromotePage.vue"; +import { useAgentStore } from "@/stores/agentStore"; +import { useUserStore } from "@/stores/userStore"; +import { useDialogStore } from "@/stores/dialogStore"; +import { useEnv } from "@/composables/useEnv"; +import { storeToRefs } from "pinia"; +import { useSEO } from "@/composables/useSEO"; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -27,262 +27,292 @@ const router = createRouter({ }, routes: [ { - path: '/', + path: "/", component: GlobalLayout, // 使用 Layout 作为父组件 children: [ { - path: '', + path: "", component: HomeLayout, // 使用 Layout 作为父组件 children: [ { - path: '', - name: 'promote', + path: "", + name: "promote", component: PromotePage, }, { - path: 'index', - name: 'index', + path: "index", + name: "index", component: index, }, { - path: '/agent', - name: 'agent', - component: () => import('@/views/Agent.vue'), + path: "/agent", + name: "agent", + component: () => import("@/views/Agent.vue"), + meta: { title: "代理主页", requiresAuth: true }, }, { - path: 'me', - name: 'me', - component: () => import('@/views/Me.vue'), + path: "me", + name: "me", + component: () => import("@/views/Me.vue"), }, ], }, { - path: '', + path: "", component: PageLayout, children: [ { - path: '/historyQuery', - name: 'history', - component: () => import('@/views/HistoryQuery.vue'), - meta: { title: '历史报告', requiresAuth: true }, - }, - { - path: '/help', - name: 'help', - component: () => import('@/views/Help.vue'), - meta: { title: '帮助中心' }, - }, - { - path: '/help/detail', - name: 'helpDetail', - component: () => import('@/views/HelpDetail.vue'), - meta: { title: '帮助中心' }, - }, - { - path: '/help/guide', - name: 'helpGuide', - component: () => import('@/views/HelpGuide.vue'), - meta: { title: '引导指南' }, - }, - { - path: '/withdraw', - name: 'withdraw', - component: () => import('@/views/Withdraw.vue'), - meta: { title: '提现', requiresAuth: true }, - }, - { - path: '/service', - name: 'service', - component: () => import('@/views/Service.vue'), - meta: { title: '客服' }, - }, - { - path: '/complaint', - name: 'complaint', - component: () => import('@/views/Complaint.vue'), - meta: { title: '投诉' }, - }, - { - path: '/report', - name: 'report', - component: () => import('@/views/Report.vue'), + path: "/historyQuery", + name: "history", + component: () => import("@/views/HistoryQuery.vue"), meta: { - title: '报告结果', requiresAuth: true, notNeedBindPhone: true + title: "历史报告", + requiresAuth: true, + notNeedBindPhone: true, }, }, { - path: '/example', - name: 'example', - component: () => import('@/views/Example.vue'), - meta: { title: '示例报告', notNeedBindPhone: true }, + path: "/help", + name: "help", + component: () => import("@/views/Help.vue"), + meta: { title: "帮助中心" }, }, { - path: '/vant-theme-test', - name: 'vantThemeTest', - component: () => import('@/views/VantThemeTest.vue'), - meta: { title: 'Vant主题色测试' }, + path: "/help/detail", + name: "helpDetail", + component: () => import("@/views/HelpDetail.vue"), + meta: { title: "帮助中心" }, }, { - path: '/privacyPolicy', - name: 'privacyPolicy', - component: () => import('@/views/PrivacyPolicy.vue'), - meta: { title: '隐私政策' }, + path: "/help/guide", + name: "helpGuide", + component: () => import("@/views/HelpGuide.vue"), + meta: { title: "引导指南" }, }, { - path: '/userAgreement', - name: 'userAgreement', - component: () => import('@/views/UserAgreement.vue'), - meta: { title: '用户协议' }, + path: "/withdraw", + name: "withdraw", + component: () => import("@/views/Withdraw.vue"), + meta: { title: "提现", requiresAuth: true }, }, { - path: '/agentManageAgreement', - name: 'agentManageAgreement', - component: () => import('@/views/AgentManageAgreement.vue'), - meta: { title: '代理管理协议' }, + path: "/service", + name: "service", + component: () => import("@/views/Service.vue"), + meta: { title: "客服" }, }, { - path: '/agentSerivceAgreement', - name: 'agentSerivceAgreement', - component: () => import('@/views/AgentServiceAgreement.vue'), - meta: { title: '信息技术服务合同' }, + path: "/complaint", + name: "complaint", + component: () => import("@/views/Complaint.vue"), + meta: { title: "投诉" }, }, { - path: '/inquire/:feature', - name: 'inquire', - component: () => import('@/views/Inquire.vue'), - meta: { title: '查询报告' }, + path: "/report", + name: "report", + component: () => import("@/views/Report.vue"), + meta: { + title: "报告结果", + requiresAuth: true, + notNeedBindPhone: true, + }, }, { - path: '/authorization', - name: 'authorization', - component: () => import('@/views/Authorization.vue'), - meta: { title: '授权书' }, + path: "/example", + name: "example", + component: () => import("@/views/Example.vue"), + meta: { title: "示例报告", notNeedBindPhone: true }, }, { - path: '/payment/result', - name: 'paymentResult', - component: () => import('@/views/PaymentResult.vue'), - meta: { title: '支付结果', requiresAuth: true }, + path: "/vant-theme-test", + name: "vantThemeTest", + component: () => + import("@/views/VantThemeTest.vue"), + meta: { title: "Vant主题色测试" }, + }, + { + path: "/privacyPolicy", + name: "privacyPolicy", + component: () => + import("@/views/PrivacyPolicy.vue"), + meta: { title: "隐私政策" }, + }, + { + path: "/userAgreement", + name: "userAgreement", + component: () => + import("@/views/UserAgreement.vue"), + meta: { title: "用户协议" }, + }, + { + path: "/agentManageAgreement", + name: "agentManageAgreement", + component: () => + import("@/views/AgentManageAgreement.vue"), + meta: { title: "代理管理协议" }, + }, + { + path: "/agentSerivceAgreement", + name: "agentSerivceAgreement", + component: () => + import("@/views/AgentServiceAgreement.vue"), + meta: { title: "信息技术服务合同" }, + }, + { + path: "/inquire/:feature", + name: "inquire", + component: () => import("@/views/Inquire.vue"), + meta: { title: "查询报告" }, + }, + { + path: "/authorization", + name: "authorization", + component: () => + import("@/views/Authorization.vue"), + meta: { title: "授权书" }, + }, + { + path: "/payment/result", + name: "paymentResult", + component: () => + import("@/views/PaymentResult.vue"), + meta: { + title: "支付结果", + requiresAuth: true, + notNeedBindPhone: true, + }, }, ], }, { - path: 'agent', + path: "agent", component: PageLayout, children: [ { - path: '/agent/promote', - name: 'agentPromote', + path: "/agent/promote", + name: "agentPromote", component: Promote, meta: { - title: '推广报告', + title: "推广报告", requiresAuth: true, requiresAgent: true, }, }, { - path: 'promoteDetails', - name: 'promoteDetails', - component: () => import('@/views/AgentPromoteDetails.vue'), + path: "/agent/promotion/query/list", + name: "agentPromotionQueryList", + component: () => + import("@/views/AgentPromotionHistory.vue"), meta: { - title: '直推报告收益明细', + title: "推广查询记录", requiresAuth: true, requiresAgent: true, }, }, { - path: 'rewardsDetails', - name: 'rewardsDetails', - component: () => import('@/views/AgentRewardsDetails.vue'), + path: "promoteDetails", + name: "promoteDetails", + component: () => + import("@/views/AgentPromoteDetails.vue"), meta: { - title: '代理奖励收益明细', + title: "我的推广收益", + requiresAuth: true, + requiresAgent: true, + }, + }, + { + path: "rewardsDetails", + name: "rewardsDetails", + component: () => + import("@/views/AgentRewardsDetails.vue"), + meta: { + title: "下级推广收益", requiresAuth: true, requiresAgent: true, }, }, { - path: 'invitation', - name: 'invitation', - component: () => import('@/views/Invitation.vue'), + path: "invitation", + name: "invitation", + component: () => + import("@/views/InvitationPage.vue"), meta: { - title: '邀请下级', + title: "邀请下级", requiresAuth: true, requiresAgent: true, }, }, { - path: 'agentVip', - name: 'agentVip', - component: () => import('@/views/AgentVip.vue'), + path: "withdraw", + name: "withdraw", + component: () => import("@/views/Withdraw.vue"), meta: { - title: '代理会员', + title: "提现", requiresAuth: true, requiresAgent: true, }, }, { - path: 'vipApply', - name: 'agentVipApply', - component: () => import('@/views/AgentVipApply.vue'), + path: "withdrawDetails", + name: "withdrawDetails", + component: () => + import("@/views/WithdrawDetails.vue"), meta: { - title: 'VIP代理申请', + title: "提现记录", requiresAuth: true, requiresAgent: true, }, }, { - path: 'vipConfig', - name: 'agentVipConfig', - component: () => import('@/views/AgentVipConfig.vue'), + path: "teamList", + name: "teamList", + component: () => import("@/views/TeamList.vue"), meta: { - title: '代理会员报告配置', + title: "我的团队", requiresAuth: true, requiresAgent: true, }, }, { - path: 'withdraw', - name: 'withdraw', - component: () => import('@/views/Withdraw.vue'), + path: "upgradeSubordinate", + name: "upgradeSubordinate", + component: () => + import("@/views/UpgradeSubordinate.vue"), meta: { - title: '提现', + title: "调整下级级别", requiresAuth: true, requiresAgent: true, }, }, { - path: 'withdrawDetails', - name: 'withdrawDetails', - component: () => import('@/views/WithdrawDetails.vue'), + path: "upgrade", + name: "agentUpgrade", + component: () => import("@/views/AgentUpgrade.vue"), meta: { - title: '提现记录', + title: "升级代理", requiresAuth: true, requiresAgent: true, }, }, { - path: 'invitationAgentApply/self', - name: 'invitationAgentApplySelf', - component: () => import('@/views/InvitationAgentApply.vue'), - meta: { title: '代理申请', requiresAuth: true }, - }, - { - path: 'subordinateList', - name: 'subordinateList', - component: () => import('@/views/SubordinateList.vue'), + path: "subordinateList", + name: "subordinateList", + component: () => + import("@/views/SubordinateList.vue"), meta: { - title: '我的下级', + title: "我的下级", requiresAuth: true, requiresAgent: true, }, }, { - path: 'subordinateDetail/:id', - name: 'subordinateDetail', - component: () => import('@/views/SubordinateDetail.vue'), + path: "subordinateDetail/:id", + name: "subordinateDetail", + component: () => + import("@/views/SubordinateDetail.vue"), meta: { - title: '下级贡献详情', + title: "下级贡献详情", requiresAuth: true, requiresAgent: true, }, @@ -290,37 +320,42 @@ const router = createRouter({ ], }, { - path: 'app', + path: "app", children: [ { - path: 'authorization', - name: 'appAuthorization', - component: () => import('@/views/Authorization.vue'), - meta: { title: '授权书' }, + path: "authorization", + name: "appAuthorization", + component: () => + import("@/views/Authorization.vue"), + meta: { title: "授权书" }, }, { - path: 'privacyPolicy', - name: 'appPrivacyPolicy', - component: () => import('@/views/PrivacyPolicy.vue'), - meta: { title: '隐私政策' }, + path: "privacyPolicy", + name: "appPrivacyPolicy", + component: () => + import("@/views/PrivacyPolicy.vue"), + meta: { title: "隐私政策" }, }, { - path: 'userAgreement', - name: 'appUserAgreement', - component: () => import('@/views/UserAgreement.vue'), - meta: { title: '用户协议' }, + path: "userAgreement", + name: "appUserAgreement", + component: () => + import("@/views/UserAgreement.vue"), + meta: { title: "用户协议" }, }, { - path: 'agentManageAgreement', - name: 'appAgentManageAgreement', - component: () => import('@/views/AgentManageAgreement.vue'), - meta: { title: '代理管理协议' }, + path: "agentManageAgreement", + name: "appAgentManageAgreement", + component: () => + import("@/views/AgentManageAgreement.vue"), + meta: { title: "代理管理协议" }, }, { - path: 'agentSerivceAgreement', - name: 'appAgentSerivceAgreement', - component: () => import('@/views/AgentServiceAgreement.vue'), - meta: { title: '信息技术服务合同' }, + path: "agentSerivceAgreement", + name: "appAgentSerivceAgreement", + component: () => + import("@/views/AgentServiceAgreement.vue"), + meta: { title: "信息技术服务合同" }, }, ], }, @@ -328,45 +363,50 @@ const router = createRouter({ }, { - path: '/login', - name: 'login', - component: () => import('@/views/Login.vue'), + path: "/login", + name: "login", + component: () => import("@/views/Login.vue"), }, { - path: '/agent/promotionInquire/:linkIdentifier', - name: 'promotionInquire', - component: () => import('@/views/PromotionInquire.vue'), + path: "/register", + name: "register", + component: () => import("@/views/Register.vue"), + }, + { + path: "/agent/promotionInquire/:linkIdentifier", + name: "promotionInquire", + component: () => import("@/views/PromotionInquire.vue"), meta: { notNeedBindPhone: true }, }, { - path: '/agent/invitationAgentApply/:linkIdentifier', - name: 'invitationAgentApply', - component: () => import('@/views/InvitationAgentApply.vue'), - meta: { title: '代理申请' }, + path: "/agent/invitationAgentApply/:linkIdentifier", + name: "invitationAgentApply", + component: () => import("@/views/InvitationAgentApply.vue"), + meta: { title: "代理申请" }, }, { - path: '/report/share/:linkIdentifier', - name: 'reportShare', - component: () => import('@/views/ReportShare.vue'), + path: "/report/share/:linkIdentifier", + name: "reportShare", + component: () => import("@/views/ReportShare.vue"), }, { - path: '/:pathMatch(.*)*', - name: 'NotFound', - component: () => import('@/views/NotFound.vue'), + path: "/:pathMatch(.*)*", + name: "NotFound", + component: () => import("@/views/NotFound.vue"), }, ], -}) +}); NProgress.configure({ - easing: 'ease', // 动画方式 + easing: "ease", // 动画方式 speed: 500, // 递增进度条的速度(毫秒) showSpinner: false, // 是否显示加载的圆圈 trickleSpeed: 200, // 自动递增间隔 minimum: 0.3, // 初始化最小百分比 -}) +}); // 路由导航守卫 router.beforeEach(async (to, from, next) => { - NProgress.start(); // 启动进度条 + NProgress.start(); const isAuthenticated = localStorage.getItem("token"); const agentStore = useAgentStore(); const userStore = useUserStore(); @@ -375,93 +415,89 @@ router.beforeEach(async (to, from, next) => { const { isWeChat } = useEnv(); const { isAgent, isLoaded } = storeToRefs(agentStore); const { mobile, isLoggedIn } = storeToRefs(userStore); - const { isWeixinAuthing, weixinAuthComplete } = storeToRefs(authStore); - - // 微信环境下,如果正在进行授权,等待授权完成 - if (isWeChat.value && isWeixinAuthing.value && !weixinAuthComplete.value) { - // 等待授权完成,使用响应式监听 - await new Promise((resolve) => { - const stopWatcher = watch( - [isWeixinAuthing, weixinAuthComplete], - ([authing, complete]) => { - if (!authing || complete) { - stopWatcher(); - resolve(); - } - }, - { immediate: true } - ); - }); + // 检查 token 是否过期 + const accessExpire = localStorage.getItem("accessExpire"); + const now = Date.now(); + let isTokenExpired = false; + if (accessExpire) { + isTokenExpired = now > parseInt(accessExpire) * 1000; } - - // 处理需要登录的页面 - if (to.meta.requiresAuth && !isAuthenticated) { - if (isWeChat.value) { - // 微信环境下,如果授权失败或超时,重定向到首页 - if (!weixinAuthComplete.value) { - next("/"); - location.reload(); - } else { - // 授权完成但仍无token,可能是授权失败 - next("/"); - location.reload(); - } - } else { - next("/login"); + // ============================================================ + // 场景 2: 需要登录的页面 + 无 token 或 token 过期 → 跳转登录 + // ============================================================ + if (to.meta.requiresAuth && (!isAuthenticated || isTokenExpired)) { + const loginQuery = { + redirect: to.fullPath, + }; + if (from && from.name === "promotionInquire") { + loginQuery.from = "promotionInquire"; } + + next({ path: "/login", query: loginQuery }); return; } - // 已登录状态下的处理 - if (isAuthenticated) { + // ============================================================ + // 场景 3: 已登录状态下的处理 + // ============================================================ + if (isAuthenticated && !isTokenExpired) { // 确保用户信息已加载 if (!isLoggedIn.value) { - await userStore.fetchUserInfo(); - } - - // 检查手机号绑定状态 - // 只有在未绑定手机号,且目标路由需要登录并且没有设置notNeedBindPhone时,才弹出绑定手机号弹窗 - if ( - !mobile.value && - to.meta.requiresAuth && - !to.meta.notNeedBindPhone - ) { - dialogStore.openBindPhone(); - next(false); - return; - } - - // 检查代理权限 - if (to.meta.requiresAgent) { - if (!isLoaded.value) { - await agentStore.fetchAgentStatus(); + try { + await userStore.fetchUserInfo(); + } catch (err) { + console.error("Error loading user info:", err); } + } + + // 检查代理权限(仅在 requiresAgent 为 true 时) + if (to.meta.requiresAgent) { + if (!mobile.value) { + if (to.meta.notNeedBindPhone) { + dialogStore.openBindPhone(); + } else { + next("/register"); + return; + } + } + + // 确保代理信息已加载 + if (!isLoaded.value) { + try { + await agentStore.fetchAgentStatus(); + } catch (err) { + console.error("Error loading agent info:", err); + } + } + + // 如果不是代理,跳转到注册页 if (!isAgent.value) { - next("/agent/invitationAgentApply/self"); + next("/register"); return; } } } + // ============================================================ // 其他情况正常通过 + // ============================================================ next(); -}) - +}); router.afterEach((to) => { - NProgress.done() // 结束进度条 + NProgress.done(); // 结束进度条 // SEO优化:更新页面标题和meta信息 - const { updateSEO } = useSEO() + const { updateSEO } = useSEO(); // 根据路由meta信息更新SEO if (to.meta.title) { const seoConfig = { title: `${to.meta.title} - 一查查`, description: `一查查${to.meta.title}页面,提供专业的大数据风险管控服务。`, - url: `https://www.zhinengcha.cn${to.path}` - } - updateSEO(seoConfig) + url: `https://www.zhinengcha.cn${to.path}`, + }; + updateSEO(seoConfig); } -}) +}); -export default router +export default router; diff --git a/src/services/authService.js b/src/services/authService.js new file mode 100644 index 0000000..e377492 --- /dev/null +++ b/src/services/authService.js @@ -0,0 +1,58 @@ +import useApiFetch from "@/composables/useApiFetch"; + +class AuthService { + detectPlatform() { + const ua = navigator.userAgent.toLowerCase(); + if (/micromessenger/.test(ua)) return "wxh5"; + return "h5"; + } + + async authenticate() { + const platform = this.detectPlatform(); + return await this.authByPlatform(platform); + } + + async authByPlatform(platform) { + switch (platform) { + case "h5": + return await this.authBrowser(); + case "wxh5": + return await this.authWechatH5(); + default: + return await this.authBrowser(); + } + } + + async authBrowser() { + let token = localStorage.getItem("token"); + if (!token) { + const { data } = await useApiFetch("/user/auth") + .post({ platform: "h5" }) + .json(); + token = data.accessToken; + localStorage.setItem("token", token); + localStorage.setItem("refreshAfter", data.refreshAfter); + localStorage.setItem("accessExpire", data.accessExpire); + } + return token; + } + + async authWechatH5() { + const params = new URLSearchParams(window.location.search); + const code = params.get("code"); + if (code) { + const { data } = await useApiFetch("/user/auth") + .post({ platform: "wxh5", code }) + .json(); + window.history.replaceState({}, "", window.location.pathname); + localStorage.setItem("token", data.accessToken); + localStorage.setItem("refreshAfter", data.refreshAfter); + localStorage.setItem("accessExpire", data.accessExpire); + return data.accessToken; + } else { + return null; + } + } +} + +export const authService = new AuthService(); diff --git a/src/stores/agentStore.js b/src/stores/agentStore.js index f31a786..2a5587d 100644 --- a/src/stores/agentStore.js +++ b/src/stores/agentStore.js @@ -1,47 +1,97 @@ import { defineStore } from 'pinia' +import { getAgentInfo } from '@/api/agent' export const useAgentStore = defineStore('agent', { state: () => ({ isLoaded: false, - level: '', - status: 3, // 0=待审核,1=审核通过,2=审核未通过,3=未申请 + level: 0, // 1=普通,2=黄金,3=钻石 + levelName: '', // 等级名称 isAgent: false, - ancestorID: null, agentID: null, + agentCode: 0, mobile: '', - ExpiryTime: '', + region: '', + wechatId: '', + teamLeaderId: null, isRealName: false, }), + getters: { + // 是否是代理 + isAgentUser: (state) => state.isAgent && state.agentID !== null, + // 是否是钻石代理 + isDiamond: (state) => state.level === 3, + // 是否是黄金代理 + isGold: (state) => state.level === 2, + // 是否是普通代理 + isNormal: (state) => state.level === 1, + // 获取等级显示名称 + levelDisplayName: (state) => { + const levelMap = { + 1: '普通代理', + 2: '黄金代理', + 3: '钻石代理' + } + return levelMap[state.level] || '普通代理' + } + }, actions: { async fetchAgentStatus() { - const { data, error } = await useApiFetch('/agent/info').get().json() + const { data, error } = await getAgentInfo() if (data.value && !error.value) { if (data.value.code === 200) { - this.level = data.value.data.level - this.isAgent = data.value.data.is_agent // 判断是否是代理 - this.status = data.value.data.status // 获取代理状态 0=待审核,1=审核通过,2=审核未通过,3=未申请 - this.agentID = data.value.data.agent_id - this.mobile = data.value.data.mobile - this.ExpiryTime = data.value.data.expiry_time - this.isRealName = data.value.data.is_real_name + const agentData = data.value.data + // 如果 agent_id 为 0,说明不是代理 + if (agentData.agent_id === 0) { + this.resetAgent() + } else { + this.level = agentData.level || 0 + this.levelName = agentData.level_name || '' + this.isAgent = true // 如果能获取到信息,说明是代理 + this.agentID = agentData.agent_id + this.agentCode = agentData.agent_code || 0 + this.mobile = agentData.mobile || '' + this.region = agentData.region || '' + this.wechatId = agentData.wechat_id || '' + this.teamLeaderId = agentData.team_leader_id || null + this.isRealName = agentData.is_real_name || false - // 保存到localStorage - localStorage.setItem( - 'agentInfo', - JSON.stringify({ - isAgent: this.isAgent, - level: this.level, - status: this.status, - agentID: this.agentID, - mobile: this.mobile, - ExpiryTime: this.ExpiryTime, - isRealName: this.isRealName, - - }) - ) + // 保存到localStorage + localStorage.setItem( + 'agentInfo', + JSON.stringify({ + isAgent: this.isAgent, + level: this.level, + levelName: this.levelName, + agentID: this.agentID, + agentCode: this.agentCode, + mobile: this.mobile, + region: this.region, + wechatId: this.wechatId, + teamLeaderId: this.teamLeaderId, + isRealName: this.isRealName, + }) + ) + } } else { - console.log('Error fetching agent info', data.value) + // 检查是否是临时用户需要绑定手机号的错误(100010) + if (data.value.code === 100010) { + // 临时用户,不重置状态,静默处理 + console.log('临时用户需要绑定手机号,跳过代理状态检查') + } else { + // 如果不是代理或获取失败,重置状态 + this.resetAgent() + console.log('Error fetching agent info', data.value) + } + } + } else { + // 检查错误码 + if (error.value && error.value.code === 100010) { + // 临时用户需要绑定手机号,静默处理 + console.log('临时用户需要绑定手机号,跳过代理状态检查') + } else { + // 请求失败或未登录,重置状态 + this.resetAgent() } } this.isLoaded = true @@ -51,25 +101,30 @@ export const useAgentStore = defineStore('agent', { updateAgentInfo(agentInfo) { if (agentInfo) { this.isAgent = agentInfo.isAgent || false - this.level = agentInfo.level || '' - this.status = agentInfo.status || 3 + this.level = agentInfo.level || 0 + this.levelName = agentInfo.levelName || '' this.agentID = agentInfo.agentID || null this.mobile = agentInfo.mobile || '' - this.isLoaded = true + this.region = agentInfo.region || '' + this.wechatId = agentInfo.wechatId || '' + this.teamLeaderId = agentInfo.teamLeaderId || null this.isRealName = agentInfo.isRealName || false - + this.isLoaded = true } }, // 重置代理信息 resetAgent() { this.isLoaded = false - this.level = '' - this.status = 3 + this.level = 0 + this.levelName = '' this.isAgent = false - this.ancestorID = null this.agentID = null + this.agentCode = 0 this.mobile = '' + this.region = '' + this.wechatId = '' + this.teamLeaderId = null this.isRealName = false }, }, diff --git a/src/stores/appStore.js b/src/stores/appStore.js new file mode 100644 index 0000000..9cb5230 --- /dev/null +++ b/src/stores/appStore.js @@ -0,0 +1,21 @@ +import { defineStore } from 'pinia' + +export const useAppStore = defineStore('app', { + state: () => ({ + queryRetentionDays: 0, + isLoaded: false, + }), + actions: { + async fetchAppConfig() { + try { + const { data, error } = await useApiFetch('/app/config').get().json() + if (data.value && !error.value && data.value.code === 200) { + const cfg = data.value.data + this.queryRetentionDays = Number(cfg.query_retention_days) || 0 + } + } catch (e) { + } + this.isLoaded = true + }, + }, +}) diff --git a/src/stores/authStore.js b/src/stores/authStore.js index 96a96a2..c1bce2f 100644 --- a/src/stores/authStore.js +++ b/src/stores/authStore.js @@ -6,22 +6,51 @@ export const useAuthStore = defineStore("auth", { isWeixinAuthing: false, // 是否正在进行微信授权 weixinAuthComplete: false, // 微信授权是否完成 pendingRoute: null, // 等待授权完成后跳转的路由 + authStartTime: 0, // 授权开始时间,用于防止超时重复授权 }), actions: { // 开始微信授权 startWeixinAuth(targetRoute = null) { + // 如果已经在授权过程中,不再重复启动 + if (this.isWeixinAuthing) { + console.warn("WeChat auth already in progress"); + return; + } + this.isWeixinAuthing = true; this.weixinAuthComplete = false; this.pendingRoute = targetRoute; + this.authStartTime = Date.now(); // 保存到localStorage,防止页面刷新后状态丢失 localStorage.setItem("weixinAuthing", "true"); + localStorage.setItem( + "authStartTime", + this.authStartTime.toString() + ); + if (targetRoute) { - localStorage.setItem( - "pendingRoute", - JSON.stringify(targetRoute) - ); + const routeData = + typeof targetRoute === "string" + ? targetRoute + : targetRoute.fullPath || targetRoute.path || ""; + if (routeData) { + let sanitized = routeData; + const parts = routeData.split("?"); + if (parts.length > 1) { + const base = parts[0]; + const params = new URLSearchParams(parts[1]); + params.delete("code"); + params.delete("state"); + const qs = params.toString(); + sanitized = qs ? `${base}?${qs}` : base; + } + localStorage.setItem( + "pendingRoute", + JSON.stringify(sanitized) + ); + } } }, @@ -29,16 +58,11 @@ export const useAuthStore = defineStore("auth", { completeWeixinAuth() { this.isWeixinAuthing = false; this.weixinAuthComplete = true; + this.authStartTime = 0; // 清除localStorage中的授权状态 localStorage.removeItem("weixinAuthing"); - localStorage.removeItem("pendingRoute"); - }, - - // 清除待处理路由 - clearPendingRoute() { - this.pendingRoute = null; - localStorage.removeItem("pendingRoute"); + localStorage.removeItem("authStartTime"); }, // 重置授权状态 @@ -46,22 +70,47 @@ export const useAuthStore = defineStore("auth", { this.isWeixinAuthing = false; this.weixinAuthComplete = false; this.pendingRoute = null; + this.authStartTime = 0; localStorage.removeItem("weixinAuthing"); localStorage.removeItem("pendingRoute"); + localStorage.removeItem("authStartTime"); + }, + + // 检查授权是否超时(超过30秒视为超时) + isAuthTimeout() { + if (!this.authStartTime) return false; + const elapsed = Date.now() - this.authStartTime; + return elapsed > 30000; // 30秒超时 }, // 从localStorage恢复状态(页面刷新后调用) restoreFromStorage() { const isAuthing = localStorage.getItem("weixinAuthing") === "true"; const pendingRouteStr = localStorage.getItem("pendingRoute"); + const authStartTime = localStorage.getItem("authStartTime"); if (isAuthing) { + console.log("🔄 Restoring WeChat auth state from storage"); this.isWeixinAuthing = true; this.weixinAuthComplete = false; + this.authStartTime = authStartTime + ? parseInt(authStartTime) + : 0; + + // 检查是否超时,如果超时则重置 + if (this.isAuthTimeout()) { + console.warn("WeChat auth timeout, resetting state"); + this.resetAuthState(); + return; + } if (pendingRouteStr) { try { this.pendingRoute = JSON.parse(pendingRouteStr); + console.log( + "✅ Restored pendingRoute from storage:", + this.pendingRoute + ); } catch (e) { console.error("Failed to parse pending route:", e); this.pendingRoute = null; @@ -69,5 +118,12 @@ export const useAuthStore = defineStore("auth", { } } }, + + // 清除待处理路由时,确保同步清除内存和localStorage + clearPendingRoute() { + this.pendingRoute = null; + localStorage.removeItem("pendingRoute"); + console.log("✅ Cleared pendingRoute from both memory and storage"); + }, }, }); diff --git a/src/stores/dialogStore.js b/src/stores/dialogStore.js index 3f90016..3ea493c 100644 --- a/src/stores/dialogStore.js +++ b/src/stores/dialogStore.js @@ -2,7 +2,8 @@ import { defineStore } from 'pinia' import { ref } from 'vue' export const useDialogStore = defineStore('dialog', () => { - const showBindPhone = ref(false) + const showBindPhone = ref(false) // 推广页面专用的绑定手机号(不要求邀请码) + const showRegisterAgent = ref(false) // 注册成为代理(带邀请码) const showRealNameAuth = ref(false) function openBindPhone() { @@ -13,6 +14,14 @@ export const useDialogStore = defineStore('dialog', () => { showBindPhone.value = false } + function openRegisterAgent() { + showRegisterAgent.value = true + } + + function closeRegisterAgent() { + showRegisterAgent.value = false + } + function openRealNameAuth() { showRealNameAuth.value = true } @@ -25,6 +34,9 @@ export const useDialogStore = defineStore('dialog', () => { showBindPhone, openBindPhone, closeBindPhone, + showRegisterAgent, + openRegisterAgent, + closeRegisterAgent, showRealNameAuth, openRealNameAuth, closeRealNameAuth, diff --git a/src/stores/userStore.js b/src/stores/userStore.js index 0ec9a6c..0d5eeb6 100644 --- a/src/stores/userStore.js +++ b/src/stores/userStore.js @@ -1,59 +1,76 @@ -import { defineStore } from 'pinia' +import { defineStore } from "pinia"; -export const useUserStore = defineStore('user', { - state: () => ({ - userName: '', - mobile: '', - userAvatar: '', - isLoggedIn: false, - }), - actions: { - async fetchUserInfo() { - const { data, error } = await useApiFetch('/user/detail').get().json() - if (data.value && !error.value) { - if (data.value.code === 200) { - const userinfo = data.value.data.userInfo - this.userName = userinfo.mobile || '' - this.mobile = userinfo.mobile || '' - this.userAvatar = userinfo.userAvatar - this.isLoggedIn = true +export const useUserStore = defineStore("user", { + state: () => ({ + userName: "", + mobile: "", + userAvatar: "", + isLoggedIn: false, + }), + actions: { + async fetchUserInfo() { + const { data, error } = await useApiFetch("/user/detail") + .get() + .json(); + if (data.value && !error.value) { + if (data.value.code === 200) { + const userinfo = data.value.data.userInfo; + this.userName = userinfo.mobile || ""; + this.mobile = userinfo.mobile || ""; + this.userAvatar = userinfo.userAvatar; + this.isLoggedIn = true; - // 保存到localStorage - localStorage.setItem( - 'userInfo', - JSON.stringify({ - nickName: this.userName, - avatar: this.userAvatar, - }) - ) - } else if (data.value.code === 100009) { - localStorage.removeItem('token') - localStorage.removeItem('refreshAfter') - localStorage.removeItem('accessExpire') - localStorage.removeItem('userInfo') - localStorage.removeItem('agentInfo') + // 保存到localStorage + localStorage.setItem( + "userInfo", + JSON.stringify({ + nickName: this.userName, + avatar: this.userAvatar, + }) + ); + } else if (data.value.code === 100009) { + // Token 无效或用户不存在,清除数据但不 reload + // reload 会导致无限循环 + console.warn( + "User not found or token invalid (100009), clearing auth data" + ); + localStorage.removeItem("token"); + localStorage.removeItem("refreshAfter"); + localStorage.removeItem("accessExpire"); + localStorage.removeItem("userInfo"); + localStorage.removeItem("agentInfo"); - this.resetUser() - window.location.reload() - } - } else { - } + this.resetUser(); + + // 不要 reload,让调用者处理错误 + throw new Error("User not found or token invalid"); + } else { + // 其他错误 + console.error("Unexpected response code:", data.value.code); + throw new Error( + `Unexpected response code: ${data.value.code}` + ); + } + } else { + console.error("API error:", error.value); + throw error.value || new Error("Unknown error"); + } + }, + + // 更新用户信息 + updateUserInfo(userInfo) { + if (userInfo) { + this.userName = userInfo.mobile || userInfo.nickName || ""; + this.userAvatar = userInfo.avatar || ""; + this.isLoggedIn = true; + } + }, + + // 重置用户信息 + resetUser() { + this.userName = ""; + this.userAvatar = ""; + this.isLoggedIn = false; + }, }, - - // 更新用户信息 - updateUserInfo(userInfo) { - if (userInfo) { - this.userName = userInfo.mobile || userInfo.nickName || '' - this.userAvatar = userInfo.avatar || '' - this.isLoggedIn = true - } - }, - - // 重置用户信息 - resetUser() { - this.userName = '' - this.userAvatar = '' - this.isLoggedIn = false - }, - }, -}) +}); diff --git a/src/views/Agent.vue b/src/views/Agent.vue index 96840c2..f57eb4b 100644 --- a/src/views/Agent.vue +++ b/src/views/Agent.vue @@ -1,292 +1,360 @@ - + diff --git a/src/views/AgentPromoteDetails.vue b/src/views/AgentPromoteDetails.vue index 4a87315..661cc5d 100644 --- a/src/views/AgentPromoteDetails.vue +++ b/src/views/AgentPromoteDetails.vue @@ -7,19 +7,24 @@ {{ item.create_time || '-' }} +{{ item.amount.toFixed(2) }}
-
+
{{ item.product_name }}
+
+ 订单号:{{ item.order_no }} +
+ + + + diff --git a/src/views/AgentRewardsDetails.vue b/src/views/AgentRewardsDetails.vue index aa14113..95fa6ce 100644 --- a/src/views/AgentRewardsDetails.vue +++ b/src/views/AgentRewardsDetails.vue @@ -1,5 +1,11 @@ + + diff --git a/src/views/AgentVip.vue b/src/views/AgentVip.vue deleted file mode 100644 index 357ad38..0000000 --- a/src/views/AgentVip.vue +++ /dev/null @@ -1,39 +0,0 @@ - - - - - diff --git a/src/views/AgentVipApply.vue b/src/views/AgentVipApply.vue deleted file mode 100644 index e9b3cf5..0000000 --- a/src/views/AgentVipApply.vue +++ /dev/null @@ -1,907 +0,0 @@ - - - - - diff --git a/src/views/AgentVipConfig.vue b/src/views/AgentVipConfig.vue deleted file mode 100644 index bb5bf6b..0000000 --- a/src/views/AgentVipConfig.vue +++ /dev/null @@ -1,537 +0,0 @@ - - - - - diff --git a/src/views/Complaint.vue b/src/views/Complaint.vue index 6c51d0c..aa63e07 100644 --- a/src/views/Complaint.vue +++ b/src/views/Complaint.vue @@ -10,7 +10,7 @@ onMounted(() => { // 插入客服脚本 (function (d, t) { - var BASE_URL = "https://service.quannengcha.com"; + var BASE_URL = "https://service.onecha.cn"; var g = d.createElement(t), s = d.getElementsByTagName(t)[0]; g.src = BASE_URL + "/packs/js/sdk.js"; diff --git a/src/views/Help.vue b/src/views/Help.vue index a76ff38..0838ab6 100644 --- a/src/views/Help.vue +++ b/src/views/Help.vue @@ -49,9 +49,7 @@ const categories = [ { title: '其他', name: 'other', - items: [ - { id: 'vip_guide', title: '如何成为VIP代理和SVIP代理?' } - ] + items: [] } ] diff --git a/src/views/HelpDetail.vue b/src/views/HelpDetail.vue index 07f8f74..46b3903 100644 --- a/src/views/HelpDetail.vue +++ b/src/views/HelpDetail.vue @@ -29,8 +29,7 @@ const imageMap = { report_push: '/image/help/report-push.jpg', report_secret: ['/image/help/report-secret-1.jpg', '/image/help/report-secret-2.jpg'], invite_earnings: '/image/help/invite-earnings.jpg', - direct_earnings: '/image/help/direct-earnings.jpg', - vip_guide: '/image/help/vip-guide.jpg' + direct_earnings: '/image/help/direct-earnings.jpg' } // 标题映射 @@ -42,8 +41,7 @@ const titleMap = { report_push: '如何推广报告', report_secret: '报告推广秘籍大公开', invite_earnings: '如何邀请下级成为代理', - direct_earnings: '如何成为一查查代理', - vip_guide: '如何成为VIP代理和SVIP代理?' + direct_earnings: '如何成为一查查代理' } onMounted(() => { diff --git a/src/views/HistoryQuery.vue b/src/views/HistoryQuery.vue index 87ff478..48d5609 100644 --- a/src/views/HistoryQuery.vue +++ b/src/views/HistoryQuery.vue @@ -1,6 +1,16 @@ \ No newline at end of file diff --git a/src/views/Invitation.vue b/src/views/Invitation.vue index 58dd280..3b722d1 100644 --- a/src/views/Invitation.vue +++ b/src/views/Invitation.vue @@ -1,44 +1,398 @@ + - + diff --git a/src/views/InvitationAgentApply.vue b/src/views/InvitationAgentApply.vue index 7ac0eed..1deabb0 100644 --- a/src/views/InvitationAgentApply.vue +++ b/src/views/InvitationAgentApply.vue @@ -3,20 +3,9 @@ 邀请代理申请
- -
- 您的申请正在审核中 -
-
- 审核进行中 -
-
-
- - -
- 您已成为认证代理方 + +
+ 您已是代理,欢迎回来!
@@ -25,19 +14,8 @@
- -
- 审核未通过,请重新提交 -
-
- 重新提交申请 -
-
-
- - -
+ +
{{ isSelf ? "立即申请成为代理人" : "邀您注册代理人" }} @@ -50,8 +28,7 @@
- + diff --git a/src/views/InvitationPage.vue b/src/views/InvitationPage.vue new file mode 100644 index 0000000..eb8289c --- /dev/null +++ b/src/views/InvitationPage.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/src/views/Login.vue b/src/views/Login.vue index 1647029..cf6ed61 100644 --- a/src/views/Login.vue +++ b/src/views/Login.vue @@ -1,39 +1,31 @@ @@ -217,8 +184,6 @@ const onClickLeft = () => { overflow: hidden; } -.login {} - /* 登录表单 */ .login-form { background-color: var(--color-bg-primary); @@ -295,37 +260,25 @@ const onClickLeft = () => { cursor: not-allowed; } -/* 协议同意 */ -.agreement-wrapper { - display: flex; - align-items: center; - margin-top: 1.5rem; - margin-bottom: 1rem; -} - -.agreement-checkbox { - flex-shrink: 0; - margin-right: 0.5rem; -} - -.agreement-text { - font-size: 0.75rem; - color: var(--color-text-secondary); - line-height: 1.4; -} - -.agreement-link { +/* 注册按钮 */ +.register-btn { + width: 100%; + padding: 0.875rem; + background-color: transparent; color: var(--color-primary); + border: 1px solid var(--color-primary); + border-radius: 1.5rem; + font-size: 1rem; + font-weight: 500; cursor: pointer; - text-decoration: none; + transition: all 0.3s; + letter-spacing: 0.25rem; + margin-top: 1rem; } -/* 提示文字 */ -.notice-text { - font-size: 0.6875rem; - color: var(--color-text-tertiary); - line-height: 1.5; - margin-bottom: 2rem; +.register-btn:hover { + background-color: var(--color-primary); + color: var(--color-text-white); } /* 登录按钮 */ diff --git a/src/views/Me.vue b/src/views/Me.vue index 56be6f7..94de3a3 100644 --- a/src/views/Me.vue +++ b/src/views/Me.vue @@ -1,221 +1,326 @@ @@ -87,6 +92,7 @@ import { useRoute, useRouter } from 'vue-router'; import PriceInputPopup from '@/components/PriceInputPopup.vue'; import QRcode from '@/components/QRcode.vue'; import ReportFeatures from '@/components/ReportFeatures.vue'; +import { getProductConfig, generateLink } from '@/api/agent'; // 导入logo图片 import personalDataLogo from '@/assets/images/promote/personal_data_logo.png'; @@ -100,16 +106,8 @@ import consumerFinanceReportLogo from '@/assets/images/promote/consumer_finance_ const route = useRoute(); const router = useRouter(); -// 报告类型配置(保持原来的数据结构) -const reportTypes = [ - { text: "小微企业", value: "companyinfo", id: 2 }, - { text: "贷前风险", value: "preloanbackgroundcheck", id: 5 }, - { text: "个人大数据", value: "personalData", id: 27 }, - { text: '入职背调', value: 'backgroundcheck', id: 1 }, - { text: '家政风险', value: 'homeservice', id: 3 }, - { text: '婚恋风险', value: 'marriage', id: 4 }, - { text: "消金报告", value: "consumerFinanceReport", id: 28 }, -]; +// 报告类型配置(从接口获取) +const reportTypes = ref([]); // 从 query 参数获取报告类型(使用 computed 以响应路由变化) const currentFeature = computed(() => route.query.feature || ''); @@ -121,13 +119,13 @@ const pickerProductConfig = ref(null); const pickerFieldVal = ref(null); // 保持原来的变量名,用于存储报告类型的 value const clientPrice = ref(null); const productConfig = ref(null); -const linkIdentifier = ref(""); +const fullLink = ref(""); // 完整的推广短链 const featureData = ref({}); const showQRcode = ref(false); // Logo映射 const logoMap = { - 'personalData': personalDataLogo, + 'riskassessment': personalDataLogo, 'companyinfo': companyLogo, 'preloanbackgroundcheck': preloanBackgroundCheckLogo, 'marriage': marriageRiskLogo, @@ -145,35 +143,41 @@ const currentLogo = computed(() => { const costPrice = computed(() => { if (!pickerProductConfig.value) return 0.00 - // 确保所有金额值都是数字类型 - const baseCost = Number(pickerProductConfig.value.cost_price) || 0; + // 新系统:成本价 = 实际底价(actual_base_price) + // actual_base_price = base_price + 等级加成 + const actualBasePrice = Number(pickerProductConfig.value.actual_base_price) || 0; const clientPriceNum = Number(clientPrice.value) || 0; - const pStandard = Number(pickerProductConfig.value.p_pricing_standard) || 0; - const pRatio = Number(pickerProductConfig.value.p_overpricing_ratio) || 0; - const aStandard = Number(pickerProductConfig.value.a_pricing_standard) || 0; - const aEnd = Number(pickerProductConfig.value.a_pricing_end) || 0; - const aRatio = Number(pickerProductConfig.value.a_overpricing_ratio) || 0; + const priceThreshold = Number(pickerProductConfig.value.price_threshold) || 0; + const priceFeeRate = Number(pickerProductConfig.value.price_fee_rate) || 0; - // 平台定价成本 - let platformPricing = baseCost; - - // 提价成本计算 - if (clientPriceNum > pStandard) { - platformPricing += (clientPriceNum - pStandard) * pRatio; + // 计算提价成本 + let priceCost = 0; + if (clientPriceNum > priceThreshold) { + priceCost = (clientPriceNum - priceThreshold) * priceFeeRate; } - // 代理提价成本计算 - if (aStandard > platformPricing && aEnd > platformPricing && aRatio > 0) { - if (clientPriceNum > aStandard) { - if (clientPriceNum > aEnd) { - platformPricing += (aEnd - aStandard) * aRatio; - } else { - platformPricing += (clientPriceNum - aStandard) * aRatio; - } - } - } + // 总成本 = 实际底价 + 提价成本 + const totalCost = actualBasePrice + priceCost; - return safeTruncate(platformPricing); + return safeTruncate(totalCost); +}); + +const baseCost = computed(() => { + if (!pickerProductConfig.value) return "0.00"; + const actualBasePrice = Number(pickerProductConfig.value.actual_base_price) || 0; + return safeTruncate(actualBasePrice); +}); + +const raiseCost = computed(() => { + if (!pickerProductConfig.value) return "0.00"; + const clientPriceNum = Number(clientPrice.value) || 0; + const priceThreshold = Number(pickerProductConfig.value.price_threshold) || 0; + const priceFeeRate = Number(pickerProductConfig.value.price_fee_rate) || 0; + let priceCost = 0; + if (clientPriceNum > priceThreshold) { + priceCost = (clientPriceNum - priceThreshold) * priceFeeRate; + } + return safeTruncate(priceCost); }); const promotionRevenue = computed(() => { @@ -222,10 +226,10 @@ const getProductInfo = async () => { // 根据 feature 找到对应的报告类型(保持原来的数据结构匹配方式) const findReportTypeByFeature = (feature) => { - return reportTypes.find(type => type.value === feature); + return reportTypes.value.find(type => type.value === feature); }; -// 选择报告类型并设置配置(保持原来的处理逻辑) +// 选择报告类型并设置配置 const SelectTypePicker = (reportType) => { if (!reportType) return; @@ -236,12 +240,12 @@ const SelectTypePicker = (reportType) => { // 如果产品配置已加载,则设置配置 if (productConfig.value) { - // 遍历产品配置,找到匹配的产品 + // 遍历产品配置,找到匹配的产品(根据 product_en 匹配) for (let i of productConfig.value) { - if (i.product_id === reportType.id) { + if (i.product_en === reportType.value) { pickerProductConfig.value = i; - // 确保初始价格为数字类型 - clientPrice.value = Number(i.cost_price) || 0; + // 新系统:初始价格设置为实际底价(成本价) + clientPrice.value = Number(i.actual_base_price) || Number(i.price_range_min) || 0; break; } } @@ -256,13 +260,25 @@ const SelectTypePicker = (reportType) => { // 获取产品配置 const getPromoteConfig = async () => { - const { data, error } = await useApiFetch("/agent/product_config") - .get() - .json(); + const { data, error } = await getProductConfig(); if (data.value && !error.value) { if (data.value.code === 200) { - productConfig.value = data.value.data.AgentProductConfig; + // 新系统数据结构:data.value.data.list 是数组 + productConfig.value = data.value.data.list || []; + + // 根据接口返回的产品列表,生成报告类型配置 + const types = []; + productConfig.value.forEach(config => { + if (config.product_en) { + types.push({ + text: config.product_name, + value: config.product_en, + id: config.product_id, + }); + } + }); + reportTypes.value = types; // 根据当前 feature 找到对应的报告类型,然后设置配置 // 如果没有 feature 参数,默认选择第一个报告类型 @@ -272,8 +288,8 @@ const getPromoteConfig = async () => { } // 如果没有找到匹配的报告类型或没有feature参数,使用第一个报告类型 - if (!reportType && reportTypes.length > 0) { - reportType = reportTypes[0]; + if (!reportType && reportTypes.value.length > 0) { + reportType = reportTypes.value[0]; } if (reportType) { @@ -286,7 +302,7 @@ const getPromoteConfig = async () => { }; const generatePromotionCode = async () => { - if (!pickerFieldVal.value) { + if (!pickerFieldVal.value || !pickerProductConfig.value) { showToast({ message: '请选择报告类型' }); return; } @@ -298,23 +314,46 @@ const generatePromotionCode = async () => { return; } - // 保持原来的接口调用方式,price 参数需要转换为 string 类型 - // 使用 toFixed(2) 确保精度,避免浮点数精度问题 - const priceStr = priceNum.toFixed(2); + // 验证价格范围 + const minPrice = Number(pickerProductConfig.value.price_range_min) || 0; + const maxPrice = Number(pickerProductConfig.value.price_range_max) || Infinity; - const { data, error } = await useApiFetch("/agent/generating_link") - .post({ product: pickerFieldVal.value, price: priceStr }) - .json(); + if (priceNum < minPrice) { + showToast({ message: `价格不能低于 ${minPrice.toFixed(2)} 元` }); + return; + } - if (data.value && !error.value) { - if (data.value.code === 200) { - linkIdentifier.value = data.value.data.link_identifier; - showQRcode.value = true; + if (priceNum > maxPrice) { + showToast({ message: `价格不能高于 ${maxPrice.toFixed(2)} 元` }); + return; + } + + try { + // 构建目标路径模板:推广报告页面路径(后端会将 linkIdentifier 拼接到路径中) + // 注意:后端会在重定向时自动将 linkIdentifier 拼接到 target_path 后面 + const targetPath = `/agent/promotionInquire/`; + + // 新系统API:使用 product_id、set_price 和 target_path + const { data, error } = await generateLink({ + product_id: pickerProductConfig.value.product_id, + set_price: priceNum, + target_path: targetPath + }); + + if (data.value && !error.value) { + if (data.value.code === 200) { + // 使用后端返回的完整短链 + fullLink.value = data.value.data.full_link || ""; + showQRcode.value = true; + } else { + console.log("Error generating promotion link", data.value); + showToast({ message: data.value.msg || '生成推广链接失败,请重试' }); + } } else { - console.log("Error generating promotion link", data.value); showToast({ message: '生成推广链接失败,请重试' }); } - } else { + } catch (err) { + console.error('生成推广链接失败:', err); showToast({ message: '生成推广链接失败,请重试' }); } }; @@ -562,4 +601,4 @@ onMounted(async () => { opacity: 0.4 !important; color: #bdbdbd !important; } - \ No newline at end of file + diff --git a/src/views/PromotePage.vue b/src/views/PromotePage.vue index 6a5776f..455ca9e 100644 --- a/src/views/PromotePage.vue +++ b/src/views/PromotePage.vue @@ -1,28 +1,34 @@