样式修改完成,待测试
This commit is contained in:
@@ -0,0 +1,334 @@
|
|||||||
|
# 当前项目与参考项目功能差异审计
|
||||||
|
|
||||||
|
审计日期:2026-07-07
|
||||||
|
|
||||||
|
当前项目:`C:\Users\Administrator\Desktop\job\shencaisuan`
|
||||||
|
|
||||||
|
参考项目:`C:\Users\Administrator\Desktop\job\3Dyjs`
|
||||||
|
|
||||||
|
## 审计边界
|
||||||
|
|
||||||
|
本次按 AGENTS.md 的约束执行:只做源码静态对比和差异记录,不改业务代码,不删除现有迁移结果,不把有意外链化的页面误判成缺失。
|
||||||
|
|
||||||
|
已对比范围:
|
||||||
|
|
||||||
|
- `html/*.html` 页面文件清单、页面脚本引用、页面内主要函数和接口调用。
|
||||||
|
- `utils/*.js` 公共请求、公共工具、认证工具、权限工具。
|
||||||
|
- 首页、导航、底部、走势图、专家/付费文章、免费文章、个人中心、钱包、推广、登录注册找回、协议公告下载等核心功能。
|
||||||
|
|
||||||
|
未完成范围:
|
||||||
|
|
||||||
|
- 未逐页浏览器点测。
|
||||||
|
- 未用真实接口账号完整走通购买、提现、微信授权、专家申请审核流。
|
||||||
|
- 未对 CSS 视觉像素级复刻做审计,本文件只记录功能与交互差异。
|
||||||
|
|
||||||
|
## 总体结论
|
||||||
|
|
||||||
|
当前项目不是简单落后于参考项目,而是做过一轮“品牌化 + 新接口兼容 + 公共导航底部 + 移动端适配 + 部分页面外链化”的迁移。主要差异集中在三类:
|
||||||
|
|
||||||
|
1. 明确替换:`duanzuhe.html`、`shazuhe.html` 本地方案页被改为跳转 `https://www.3dyjs.cn/duanzuhe.html` 和 `https://www.3dyjs.cn/shazuhe.html`;专家收藏从参考项目的 `zhuanjiashoucang.html` 合并到当前 `mianfeishoucang.html`。
|
||||||
|
2. 项目裁剪:当前项目明确不需要本地 `zhuanjia.html` 专家推荐页,这属于允许差异;只需要确保公共导航、接口返回入口和页面跳转不会误指向该页面。
|
||||||
|
3. 实现风险:参考项目把趋势图渲染收敛到 `public/js/trend-pages.js`,当前项目是每个走势图页面各自实现,后续维护一致性风险更高。
|
||||||
|
|
||||||
|
## 文件级差异
|
||||||
|
|
||||||
|
### 当前项目独有页面
|
||||||
|
|
||||||
|
| 页面 | 判断 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `html/c.html` | 新增/替代 | 当前项目的免费文章收藏页入口,调用 `mineFreeCollectList` 和 `freeArticleCollect`。参考项目没有同名页面。 |
|
||||||
|
| `html/jine.html` | 新增 | 当前项目的钱包资金明细/余额分类页,覆盖账户余额、销售余额、专家余额等明细查看。参考项目没有同名页面。 |
|
||||||
|
|
||||||
|
### 参考项目独有页面
|
||||||
|
|
||||||
|
| 页面 | 当前状态 | 风险 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `html/duanzuhe.html` | 当前项目已改为外链到 `https://www.3dyjs.cn/duanzuhe.html` | 本地不再承载断组方案购买、预测列表、侧栏文章等功能;依赖外站可用性和登录态跨站体验。 |
|
||||||
|
| `html/shazuhe.html` | 当前项目已改为外链到 `https://www.3dyjs.cn/shazuhe.html` | 同上,杀组合方案详情、购买、钱包余额等能力不在本项目内。 |
|
||||||
|
| `html/zhuanjia.html` | 当前项目明确不需要本地页面 | 允许差异;只需确认公共导航、接口入口和页面跳转不会指向该页面。 |
|
||||||
|
| `html/zhuanjiashoucang.html` | 当前项目未提供本地页面 | 当前 `mianfeishoucang.html` 已包含专家/免费/付费收藏 tab,可视为合并替代;需要确认入口文案是否仍叫“专家收藏”。 |
|
||||||
|
|
||||||
|
### 公共工具差异
|
||||||
|
|
||||||
|
| 模块 | 参考项目 | 当前项目 | 判断 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `utils/ApiClient.js` | 新版 API 集中表,`TENANT_ID = 000000`,包含大量注释说明。 | API 集中表更兼容旧路径,`TENANT_ID = 531923`,有请求去重、旧接口映射、走势图兼容、专家列表兼容。 | 当前更偏兼容层,但要防止旧路径映射掩盖错误接口。 |
|
||||||
|
| `utils/CommonUtil.js` | 公共配置、导航、底部、首页渲染、cai/caiinfo 兼容。 | 公共配置、导航、底部、专家状态判断、移动适配、cai/caiinfo 兼容更重。 | 当前承担职责更多,后续改动需小心回归。 |
|
||||||
|
| `utils/AuthPageUtil.js` | 包含邀请码缓存、绑定、滑块移动端居中、滑块头注入等完整能力。 | 具备验证码/滑块/短信计时,但邀请码持久化和滑块移动端增强比参考项目少。 | 登录注册可用,但邀请关系跨登录/注册页保留能力需要重点验证。 |
|
||||||
|
| `utils/PublishPermission.js` | 独立发布权限判断工具。 | 没有独立文件,权限判断散落在 `fabumianfeiwenzhang.html`、`CommonUtil.js` 等处。 | 功能可覆盖,但契约不集中,后续容易出现免费/付费发布条件不一致。 |
|
||||||
|
|
||||||
|
## 功能模块差异
|
||||||
|
|
||||||
|
### 1. 登录、注册、忘记密码
|
||||||
|
|
||||||
|
已覆盖:
|
||||||
|
|
||||||
|
- 登录:手机号、密码、验证码/滑块验证、协议勾选、微信登录入口。
|
||||||
|
- 注册:手机号、短信验证码、图片验证码/滑块、协议勾选、密码校验。
|
||||||
|
- 找回密码:手机号、短信验证码、新密码、确认密码、验证码/滑块。
|
||||||
|
|
||||||
|
差异与风险:
|
||||||
|
|
||||||
|
- 参考项目 `AuthPageUtil` 有更完整的邀请码捕获、session 缓存、登录后自动绑定推广关系逻辑。当前项目 API 里有 `referralBindPreview/referralBind`,但登录页/注册页需要重点确认是否完整覆盖“带邀请码访问 -> 注册/登录后绑定”的链路。
|
||||||
|
- 参考项目滑块组件有移动端居中 patch;当前项目滑块功能存在,但移动端弹层居中和 viewport resize 回收能力不如参考完整。
|
||||||
|
- 当前项目登录注册页面已经做了视觉重构,字段顺序和 DOM 类名与参考不同;后续复制参考逻辑时不能直接照搬选择器。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 用一个新手机号和邀请链接跑一次:`?inviteCode=xxx` 进入注册、登录后 `referralBind` 是否触发。
|
||||||
|
- 移动端验证滑块弹窗是否居中、是否能关闭、是否能重复打开。
|
||||||
|
|
||||||
|
### 2. 首页
|
||||||
|
|
||||||
|
已覆盖:
|
||||||
|
|
||||||
|
- 公告、APP 下载二维码、走势工具、组合工具、彩票分类导航、最新开奖、免费文章、广告、其它推荐、友情链接、公共底部。
|
||||||
|
- 组合工具当前跳转外站断组/杀组合。
|
||||||
|
|
||||||
|
差异与风险:
|
||||||
|
|
||||||
|
- 参考项目首页保留 3Dyjs 原站专家推荐入口,公共工具里也会把“专家推荐/专家”指向 `zhuanjia.html`。
|
||||||
|
- 当前项目明确不需要本地 `zhuanjia.html`,如果接口 `home-nav` 返回专家推荐项,当前公共导航必须过滤或改写,不能出现 404。
|
||||||
|
- 2026-07-07 任务 1 复查结果:当前代码中未发现 `zhuanjia.html` 作为运行时跳转目标;保留的“专家推荐”主要是 `cai.html` 面包屑文案或发布页菜单过滤规则。
|
||||||
|
- 当前首页底部和友情链接已重新设计,客服二维码使用本地 `public/img/kfwx.jpg`、`public/img/kfqq.jpg`,这属于当前新增要求。
|
||||||
|
- 当前项目 app 下载使用 `appDownloadInfo` 或页面内 `/api/web/app/getDownloadInfo`,参考项目没有在 API 表里集中同名 key。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 明确“专家推荐”入口策略:当前项目不展示本地 `zhuanjia.html`,入口应删除、隐藏或稳定改到已有等价页面。
|
||||||
|
- 断组/杀组合外链需要确认是否新开页、是否要求携带登录态。
|
||||||
|
|
||||||
|
### 3. 走势图
|
||||||
|
|
||||||
|
已覆盖:
|
||||||
|
|
||||||
|
- 当前项目包含 `fc3d/pl3/pl5/kl8/ssq/cjdlt/qlc/qxc` 8 个走势图页面。
|
||||||
|
- 期数按钮已对齐为 `10期/20期/30期/60期`。
|
||||||
|
- 每 10 期有分隔线。
|
||||||
|
- 当前项目已处理 `lotteryTrend` 返回对象包装,避免 `allData.slice is not a function`。
|
||||||
|
- 表格最大宽度已向 1420px 调整过。
|
||||||
|
|
||||||
|
差异与风险:
|
||||||
|
|
||||||
|
- 参考项目所有走势图页面都引用 `public/js/trend-pages.js`,真正渲染逻辑集中在一个文件里。
|
||||||
|
- 当前项目每个走势图页面内都有自己的 `renderData`、`buildHeader`、`calcStats` 等逻辑,维护成本高,且快乐8、双色球、七乐彩、七星彩等特殊玩法更容易出现局部不一致。
|
||||||
|
- 参考项目的 `trend-pages.js` 具备 `lotteryTrend` 优先、`latestLotteryResult` fallback 的统一请求策略;当前项目要逐页确认是否一致。
|
||||||
|
- 参考项目表格区分区背景、开奖号码颜色、蓝球特殊标色、统计行、横向滚动统一。当前项目此前已多次调整颜色和分线,但仍建议按 8 个页面逐页比对。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 后续重构方向:引入当前项目版 `public/js/trend-pages.js`,把 8 个页面收敛成配置驱动。
|
||||||
|
- 回归清单:8 个彩种分别测 10/20/30/60 期、移动端横向滚动、快乐8 20 个号两行展示、双色球/大乐透/七乐彩蓝球标色。
|
||||||
|
|
||||||
|
### 4. 专家排行、专家详情
|
||||||
|
|
||||||
|
已覆盖:
|
||||||
|
|
||||||
|
- `cai.html` 有按彩种/玩法查看专家排行,调用 `payArticleExpertRank`。
|
||||||
|
- `caiinfo.html` 有专家信息、文章列表、购买/预购、收藏专家、收藏文章、余额支付、积分抵扣等复杂功能。
|
||||||
|
|
||||||
|
允许差异:
|
||||||
|
|
||||||
|
- 参考项目有独立 `zhuanjia.html`,支持专家推荐聚合、彩种/方案/类型筛选、期数切换、分页。
|
||||||
|
- 当前项目明确不需要 `zhuanjia.html`,这个页面不作为必须补齐项。
|
||||||
|
|
||||||
|
风险:
|
||||||
|
|
||||||
|
- 如果 `home-nav` 或其它入口仍包含 `zhuanjia.html`,当前项目会跳空。
|
||||||
|
- `CommonUtil` 中需要确认专家推荐路径映射是否已改掉,否则公共导航会受影响。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 按当前项目口径,不恢复 `zhuanjia.html`;只做入口清理或入口改写,确保不会跳空。
|
||||||
|
- 文案层面统一“专家申请/申请审核中/发布文章”的状态。
|
||||||
|
|
||||||
|
### 5. 免费文章
|
||||||
|
|
||||||
|
已覆盖:
|
||||||
|
|
||||||
|
- 列表页 `mianfeilist.html`。
|
||||||
|
- 详情页 `mianfei.html`。
|
||||||
|
- 发布页 `fabumianfeiwenzhang.html`。
|
||||||
|
- 收藏页当前有 `mianfeishoucang.html` 和 `c.html` 两种入口形态。
|
||||||
|
- 首页免费文章推荐、免费文章列表中的头像、昵称 `issuer`、标题省略等已按近期要求调整过。
|
||||||
|
|
||||||
|
差异与风险:
|
||||||
|
|
||||||
|
- 参考项目免费文章发布权限与付费发布权限通过 `PublishPermission.js` 做集中判断;当前免费发布页里存在页面内判断。
|
||||||
|
- 参考项目很多内容页引入 `purify.min.js`,当前项目更多依赖 `CommonUtil.escapeHtml` 和局部渲染,富文本内容页需要确认是否所有 HTML 内容都经过净化。
|
||||||
|
- 当前 `mianfeishoucang.html` 已承担专家/免费/付费收藏,但又新增 `c.html`,入口功能可能重复。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 明确收藏入口只保留一个,另一个作为兼容跳转。
|
||||||
|
- 发布权限建议收敛为 `CommonUtil` 或新增当前项目版 `PublishPermission.js`。
|
||||||
|
|
||||||
|
### 6. 付费文章、购买与预购
|
||||||
|
|
||||||
|
已覆盖:
|
||||||
|
|
||||||
|
- `cai.html` 专家预测列表。
|
||||||
|
- `caiinfo.html` 专家文章详情、购买、预购、收藏、余额支付。
|
||||||
|
- `fufei.html` 发布付费文章。
|
||||||
|
- `usercenter.html` 我的付费文章、购买记录、删除购买记录。
|
||||||
|
|
||||||
|
差异与风险:
|
||||||
|
|
||||||
|
- 参考项目的 `caiinfo.html` 对菜单元数据、购买记录、已购状态、预购状态、专家收藏、文章收藏有大量兼容字段;当前项目也有大量兼容,但两边函数名和数据归一化入口不同。
|
||||||
|
- 当前项目购买记录中断组/杀组合购买详情会跳外站;参考项目可在本地方案页展示。
|
||||||
|
- 当前项目没有本地方案详情页后,`walletPurchaseFc3dScheme` 的本地消费入口减少,但 API 仍存在。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 对 `caiinfo.html` 做一次真实账号回归:未登录、余额不足、购买成功、预购成功、已购复看、专家收藏取消、文章收藏取消。
|
||||||
|
- 购买记录跳转需要明确:付费文章本地打开,方案类外链打开。
|
||||||
|
|
||||||
|
### 7. 断组、杀组合方案
|
||||||
|
|
||||||
|
参考项目能力:
|
||||||
|
|
||||||
|
- 本地 `duanzuhe.html`、`shazuhe.html` 提供方案列表、当前方案、历史预测、侧栏文章、钱包余额、购买弹窗、复制推荐码等。
|
||||||
|
- 调用 `fc3dFreePredictMenus`、`fc3dFreePredictList`、`fc3dSchemeList`、`fc3dSchemeDetail`、`walletPurchaseFc3dScheme`。
|
||||||
|
|
||||||
|
当前项目状态:
|
||||||
|
|
||||||
|
- 首页组合工具和购买记录相关逻辑已改为跳转外站。
|
||||||
|
- 当前 API 仍保留 `fc3dFreePredict*`、`fc3dScheme*`、`walletPurchaseFc3dScheme`,但本地页面入口已删除。
|
||||||
|
|
||||||
|
判断:
|
||||||
|
|
||||||
|
- 这是有意架构差异,不是单纯缺文件。
|
||||||
|
- 风险在于用户登录态、支付状态、购买记录复看是否能在外站无缝完成。
|
||||||
|
|
||||||
|
### 8. 个人中心
|
||||||
|
|
||||||
|
已覆盖:
|
||||||
|
|
||||||
|
- 用户资料、头像、昵称、微信绑定、签到、专家申请状态、发布入口、免费文章、付费文章、购买记录。
|
||||||
|
- 资料编辑弹窗已改为保存 `nickName/password/avatar`。
|
||||||
|
- 专家申请按钮已按 `expertStatus` 等状态显示“专家申请/申请审核中/发布文章”。
|
||||||
|
|
||||||
|
差异与风险:
|
||||||
|
|
||||||
|
- 参考项目个人中心布局更大,付费文章、免费文章、购买记录渲染逻辑更复杂,包含更多空状态与错误状态。
|
||||||
|
- 当前项目有重复的微信绑定卡片 DOM:头部区域和侧栏区域都存在同样 `id="wechat-bind-card"`、`id="wechat-bind-button"`,这会让 jQuery 选择器只操作首个或产生重复 ID 风险。
|
||||||
|
- 当前项目资料编辑是 layer 新弹窗,参考项目是页面内旧弹窗;当前实现更干净,但要真实接口验证头像返回字段。
|
||||||
|
- 当前项目 `pageSize` 全局值复用较多,文章列表、签到、购买记录之间后续改动需避免互相影响。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 立即检查重复 ID:微信绑定卡片应只保留一处,或改成唯一 ID + class 统一更新。
|
||||||
|
- 用真实账号验证:只改昵称、只改头像、只改密码、三者都改、取消弹窗。
|
||||||
|
|
||||||
|
### 9. 专家申请
|
||||||
|
|
||||||
|
已覆盖:
|
||||||
|
|
||||||
|
- 身份证正反面上传、个人简介字数、读取 `expertConfig`、提交 `expertApply`。
|
||||||
|
- 已是专家或审核中时禁用提交。
|
||||||
|
|
||||||
|
差异与风险:
|
||||||
|
|
||||||
|
- 参考项目文案中明确“审核前三天每天发布 3 篇以上免费文章”等说明;当前项目简化为资料和简介说明。
|
||||||
|
- 参考项目 `apply.html` 使用 `mineSummary` 渲染用户信息;当前项目用 `CommonUtil.getCurrentUserDetail` 和 `CommonUtil.getExpertState`。
|
||||||
|
- 当前项目如果后端 `expertStatus: 0` 表示审核中,之前已修过 truthy 丢失问题,但仍需真实数据确认。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 明确后端审核状态字典:`0/pending/audit/applying` 哪些代表审核中,`1/regular/intern` 哪些代表专家。
|
||||||
|
- 文案是否需要恢复参考项目的申请规则说明。
|
||||||
|
|
||||||
|
### 10. 钱包、充值、提现、明细
|
||||||
|
|
||||||
|
已覆盖:
|
||||||
|
|
||||||
|
- `chongzhi.html` 充值套餐、下单、二维码、轮询订单状态。
|
||||||
|
- `tixian.html` 提现、收款账户、二维码上传、默认账户、转入余额。
|
||||||
|
- `tixianlist.html` 提现记录/资金流水筛选、删除流水。
|
||||||
|
- `jine.html` 资金明细为当前新增。
|
||||||
|
|
||||||
|
差异与风险:
|
||||||
|
|
||||||
|
- 参考项目充值页会加载 `walletOverview` 刷新余额;当前充值页主要加载充值配置和订单状态,需确认充值前余额展示是否仍需要。
|
||||||
|
- 当前 `tixianlist.html` 比参考更偏筛选化,功能上覆盖更广。
|
||||||
|
- 收款账户字段兼容较多,真实上传二维码后返回字段需要验证。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 真实回归:充值二维码生成、支付成功轮询、提现账户新增/编辑/设默认/删除、提现提交、提现列表筛选。
|
||||||
|
|
||||||
|
### 11. 推广与分销
|
||||||
|
|
||||||
|
已覆盖:
|
||||||
|
|
||||||
|
- `tuiguang.html` 推广链接、二维码、邀请列表、返佣列表、统计。
|
||||||
|
- `fenxiao.html` 申请推广/专家入口。
|
||||||
|
- `wodefenxiao.html` 我的分销列表。
|
||||||
|
|
||||||
|
差异与风险:
|
||||||
|
|
||||||
|
- 参考项目 `AuthPageUtil` 有邀请码跨页面缓存和登录后自动绑定能力,当前需要确认从推广链接进入注册/登录后的绑定链路是否完整。
|
||||||
|
- 当前 `wodefenxiao.html` 调用 `referralInviteList`,参考项目同名页主要调用 `referralRebateList`。这里可能是业务口径差异:我的下级 vs 我的返佣。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 明确 `wodefenxiao.html` 应展示“邀请用户”还是“返佣流水”。
|
||||||
|
- 用推广链接从匿名态进入注册/登录,验证 `referralBind` 是否发生。
|
||||||
|
|
||||||
|
### 12. 收藏
|
||||||
|
|
||||||
|
已覆盖:
|
||||||
|
|
||||||
|
- 免费文章收藏。
|
||||||
|
- 付费文章收藏。
|
||||||
|
- 专家收藏。
|
||||||
|
|
||||||
|
差异与风险:
|
||||||
|
|
||||||
|
- 参考项目有 `zhuanjiashoucang.html` 独立收藏聚合页。
|
||||||
|
- 当前项目主要合并到 `mianfeishoucang.html`,另有 `c.html`。入口命名和展示口径需要统一。
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 保留一个主入口:建议 `mianfeishoucang.html` 作为三类收藏聚合,`c.html` 改兼容重定向或入口隐藏。
|
||||||
|
|
||||||
|
### 13. 公告、协议、APP 下载
|
||||||
|
|
||||||
|
已覆盖:
|
||||||
|
|
||||||
|
- 公告列表 `gonggaolist.html`、详情 `gonggao.html`。
|
||||||
|
- 用户协议 `yonghuxieyi.html`、隐私协议 `yinsixieyi.html`。
|
||||||
|
- APP 下载 `appdown.html`。
|
||||||
|
|
||||||
|
差异与风险:
|
||||||
|
|
||||||
|
- 当前 `appdown.html` 有独立下载信息接口,参考项目主要是默认信息 + 页面渲染。
|
||||||
|
- 当前协议页使用 `agreement(type)`,覆盖参考项目功能。
|
||||||
|
- 当前项目移除了参考项目的 umami 统计脚本引用,若线上需要访问统计,需要补回或确认由其它系统接入。
|
||||||
|
|
||||||
|
## 高优先级待确认项
|
||||||
|
|
||||||
|
1. 公共导航、首页接口入口和公共路径映射是否已彻底过滤 `zhuanjia.html`,避免跳空。
|
||||||
|
2. `duanzuhe.html`、`shazuhe.html` 外链化是否接受登录态不互通、购买记录跨站打开的体验。
|
||||||
|
3. `mianfeishoucang.html` 和 `c.html` 是否重复,需要保留哪个入口。
|
||||||
|
4. `usercenter.html` 微信绑定卡片重复 ID 是否要立即清理。
|
||||||
|
5. 发布权限是否要恢复成独立 `PublishPermission.js`,避免免费/付费文章权限逻辑分叉。
|
||||||
|
6. 走势图是否要收敛成共享 `trend-pages.js`,避免 8 个页面维护不一致。
|
||||||
|
7. 邀请码/推广绑定链路是否完整覆盖参考项目的跨页缓存和登录后绑定。
|
||||||
|
|
||||||
|
## 建议执行顺序
|
||||||
|
|
||||||
|
1. 先确认“有意差异”:断组/杀组合外链、收藏页是否合并,`zhuanjia.html` 不恢复。
|
||||||
|
2. 修真实风险:重复 ID、公共入口误跳 `zhuanjia.html`、邀请码绑定链路、资料编辑真实接口回归。
|
||||||
|
3. 做结构收敛:发布权限工具化、走势图共享渲染模块化。
|
||||||
|
4. 做完整手工回归:登录注册找回、首页入口、8 个走势图、专家详情购买、个人中心、钱包、推广、收藏。
|
||||||
|
|
||||||
|
## 证据摘录
|
||||||
|
|
||||||
|
- 当前页面数 38,参考页面数 40,共同页面 36。
|
||||||
|
- 当前独有:`c.html`、`jine.html`。
|
||||||
|
- 参考独有:`duanzuhe.html`、`shazuhe.html`、`zhuanjia.html`、`zhuanjiashoucang.html`;其中 `zhuanjia.html` 为本项目明确不需要的允许差异。
|
||||||
|
- 当前工具文件 4 个:`ApiClient.js`、`AuthPageUtil.js`、`CommonUtil.js`、`DateUtil.js`。
|
||||||
|
- 参考工具文件 5 个,多出 `PublishPermission.js`。
|
||||||
|
- 当前首页组合工具已经指向外站 `https://www.3dyjs.cn/duanzuhe.html` 和 `https://www.3dyjs.cn/shazuhe.html`。
|
||||||
|
- 参考走势图页面统一引用 `../public/js/trend-pages.js?v=20260615-limit`;当前走势图页面各自定义 `renderData`。
|
||||||
@@ -0,0 +1,567 @@
|
|||||||
|
# 功能等价修复实施计划
|
||||||
|
|
||||||
|
> **给后续执行者:** 实施本计划时必须使用 `superpowers:subagent-driven-development`(推荐)或 `superpowers:executing-plans`,按任务逐项执行。任务步骤使用复选框格式,方便跟踪进度。
|
||||||
|
|
||||||
|
**目标:** 让 `shencaisuan` 在功能上与参考项目 `3Dyjs` 保持一致,同时保留当前项目的换皮样式、页面组织方式和已经明确允许的差异。
|
||||||
|
|
||||||
|
**架构:** 以 `3Dyjs` 作为功能事实标准。先补齐公共业务规则,再让各页面复用同一套规则,避免每个页面各自判断。视觉样式可以不同,但接口、参数、状态判断、标色规则、购买规则、注册登录、分销、专家申请、文章权限等功能必须和参考项目一致,除非本计划明确列为允许差异。
|
||||||
|
|
||||||
|
**技术栈:** 静态 HTML 页面、Layui、jQuery、WangEditor、`utils/*.js` 浏览器全局工具、`utils/ApiClient.js` 统一接口、`utils/CommonUtil.js` 公共渲染和工具方法。
|
||||||
|
|
||||||
|
## 全局约束
|
||||||
|
|
||||||
|
- 参考项目路径:`C:\Users\Administrator\Desktop\job\3Dyjs`。
|
||||||
|
- 当前项目路径:`C:\Users\Administrator\Desktop\job\shencaisuan`。
|
||||||
|
- 功能行为默认对齐参考项目,除非写在“允许差异”里。
|
||||||
|
- 样式、品牌、公共导航外观、底部外观、移动端视觉表现可以不同。
|
||||||
|
- 当前项目不需要 `zhuanjia.html`,不要恢复这个页面。
|
||||||
|
- 如果公共导航或接口返回入口指向 `zhuanjia.html`,必须过滤或改写到当前项目有效页面。
|
||||||
|
- 以 `docs/reference-project-function-diff-2026-07-07.md` 作为差异对比基线。
|
||||||
|
- 不删除、不回退与本任务无关的用户已有改动。
|
||||||
|
- 每个任务完成后必须做最小可行验证。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 允许差异
|
||||||
|
|
||||||
|
这些差异是当前项目明确允许的,不作为缺陷处理:
|
||||||
|
|
||||||
|
- `html/zhuanjia.html` 不属于当前项目。
|
||||||
|
- 视觉皮肤、间距、渐变、导航底部样式、移动端呈现可以不同于参考项目。
|
||||||
|
- 部分页面可以合并,但合并后必须保留等价功能和有效入口。
|
||||||
|
- 客服二维码使用当前项目本地图片:`public/img/kfwx.jpg`、`public/img/kfqq.jpg`。
|
||||||
|
- 工信部备案链接必须使用 `https://beian.miit.gov.cn/`。
|
||||||
|
|
||||||
|
## 任务 1:锁定导航和允许差异契约
|
||||||
|
|
||||||
|
**涉及文件:**
|
||||||
|
|
||||||
|
- 修改:`docs/reference-project-function-diff-2026-07-07.md`
|
||||||
|
- 检查:`utils/CommonUtil.js`
|
||||||
|
- 检查:`html/index.html`
|
||||||
|
- 检查:所有渲染公共导航的 `html/*.html`
|
||||||
|
|
||||||
|
**输入:**
|
||||||
|
|
||||||
|
- 当前项目明确不需要 `zhuanjia.html`。
|
||||||
|
|
||||||
|
**输出:**
|
||||||
|
|
||||||
|
- 找出当前项目里所有仍可能跳转到 `zhuanjia.html` 的运行时代码路径。
|
||||||
|
|
||||||
|
- [ ] 步骤 1:搜索错误专家页入口。
|
||||||
|
|
||||||
|
执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
rg -n "zhuanjia\.html|专家推荐" html utils public
|
||||||
|
```
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 所有 `zhuanjia.html` 结果都必须被检查。
|
||||||
|
- 只出现在文档里的结果允许存在。
|
||||||
|
- 当前项目运行时导航不能打开缺失的 `zhuanjia.html`。
|
||||||
|
|
||||||
|
- [ ] 步骤 2:检查 `CommonUtil.js` 的导航映射。
|
||||||
|
|
||||||
|
检查菜单名称为“专家推荐”或路径为 `zhuanjia.html` 时,当前公共工具如何处理。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 公共导航不会打开缺失的 `zhuanjia.html`。
|
||||||
|
- 如果接口返回专家推荐入口,当前代码必须过滤它,或改写到一个有效页面。
|
||||||
|
|
||||||
|
- [ ] 步骤 3:如果发现新风险,同步更新差异文档。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 文档继续把 `zhuanjia.html` 标为允许差异。
|
||||||
|
- 文档只记录真实运行时风险。
|
||||||
|
|
||||||
|
- [ ] 步骤 4:验证文档格式。
|
||||||
|
|
||||||
|
执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git diff --check -- docs/reference-project-function-diff-2026-07-07.md
|
||||||
|
```
|
||||||
|
|
||||||
|
期望:无输出。
|
||||||
|
|
||||||
|
## 任务 2:补齐发布权限公共契约
|
||||||
|
|
||||||
|
**涉及文件:**
|
||||||
|
|
||||||
|
- 新增:`utils/PublishPermission.js`
|
||||||
|
- 修改:`html/fabumianfeiwenzhang.html`
|
||||||
|
- 修改:`html/fufei.html`
|
||||||
|
- 参考:`C:\Users\Administrator\Desktop\job\3Dyjs\utils\PublishPermission.js`
|
||||||
|
- 参考:`C:\Users\Administrator\Desktop\job\3Dyjs\html\fabumianfeiwenzhang.html`
|
||||||
|
- 参考:`C:\Users\Administrator\Desktop\job\3Dyjs\html\fufei.html`
|
||||||
|
|
||||||
|
**输入:**
|
||||||
|
|
||||||
|
- `ApiClient.API.mineSummary`
|
||||||
|
|
||||||
|
**输出:**
|
||||||
|
|
||||||
|
- 全局对象:`PublishPermission.parseSummary(userData, options)`
|
||||||
|
- 权限对象字段:`hasExpertIdentity`、`isRegularExpert`、`canFree`、`canPaid`、`quotaExempt`、`todayFreeArticleCount`、`requiredDailyFreeArticleCount`、`paidReason`、`reason`
|
||||||
|
|
||||||
|
- [ ] 步骤 1:按参考项目逻辑新增 `utils/PublishPermission.js`。
|
||||||
|
|
||||||
|
期望行为:
|
||||||
|
|
||||||
|
- 专家身份兼容字段:`is_expert`、`isExpert`、`expert`、`is_regular_expert`、`isRegularExpert`、`regularExpert`、文本型 `expertStatus`。
|
||||||
|
- 付费发布兼容字段:`canPublishPayArticle`、`payArticleFreeQuotaExempt`、`todayApprovedFreeArticleCount`、`requiredDailyApprovedFreeArticleCount`、`payArticlePublishReason`。
|
||||||
|
- 默认每天需要审核通过的免费文章数量为 `2`。
|
||||||
|
|
||||||
|
- [ ] 步骤 2:两个发布页都引入新脚本。
|
||||||
|
|
||||||
|
期望增加:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="../utils/PublishPermission.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] 步骤 3:替换 `fabumianfeiwenzhang.html` 里的本地权限判断。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 免费发布页请求 `mineSummary`。
|
||||||
|
- 使用 `PublishPermission.parseSummary(res.data)`。
|
||||||
|
- `canFree` 为假时禁止发布,并引导到 `apply.html`。
|
||||||
|
- “发布付费文章”tab 使用 `canPaid` 和 `paidReason` 判断。
|
||||||
|
|
||||||
|
- [ ] 步骤 4:给 `fufei.html` 增加付费发布拦截。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 页面加载付费发布数据前先请求 `mineSummary`。
|
||||||
|
- `canPaid` 为假时显示原因,并跳回 `fabumianfeiwenzhang.html`。
|
||||||
|
- 不改变现有视觉表单和选号逻辑。
|
||||||
|
|
||||||
|
- [ ] 步骤 5:静态验证。
|
||||||
|
|
||||||
|
执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
rg -n "PublishPermission|parsePublishPermission|loadTodayPublishedFreeCount|checkPaidPublishPermission" html\fabumianfeiwenzhang.html html\fufei.html utils\PublishPermission.js
|
||||||
|
```
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- `PublishPermission.js` 存在,并暴露 `parseSummary`。
|
||||||
|
- 两个发布页调用 `PublishPermission.parseSummary`。
|
||||||
|
- 旧的重复权限解析函数被删除,或不再被使用。
|
||||||
|
|
||||||
|
- [ ] 步骤 6:语法验证。
|
||||||
|
|
||||||
|
执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
node -c utils\PublishPermission.js
|
||||||
|
```
|
||||||
|
|
||||||
|
期望:没有语法错误。
|
||||||
|
|
||||||
|
## 任务 3:修正个人中心契约和重复 DOM 风险
|
||||||
|
|
||||||
|
**涉及文件:**
|
||||||
|
|
||||||
|
- 修改:`html/usercenter.html`
|
||||||
|
- 参考:`C:\Users\Administrator\Desktop\job\3Dyjs\html\usercenter.html`
|
||||||
|
|
||||||
|
**输入:**
|
||||||
|
|
||||||
|
- `CommonUtil.getExpertState(user)`
|
||||||
|
- `ApiClient.js` 中已有的账号概要和资料接口
|
||||||
|
|
||||||
|
**输出:**
|
||||||
|
|
||||||
|
- 微信绑定区域只有一个明确 DOM 所有者,或使用 class 统一更新多个展示点。
|
||||||
|
- 资料编辑只走一条清晰提交路径。
|
||||||
|
|
||||||
|
- [ ] 步骤 1:搜索 `usercenter.html` 里的重复 ID。
|
||||||
|
|
||||||
|
执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
rg -n "wechat-bind-card|wechat-bind-desc|wechat-bind-button|editProfile|expertStatus|expertEnabled" html\usercenter.html
|
||||||
|
```
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 编辑前先确认重复 ID 位置。
|
||||||
|
|
||||||
|
- [ ] 步骤 2:把重复 ID 改成单一所有者或 class 更新。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 页面中不再有重复 `id`。
|
||||||
|
- 重复视觉区域使用 class。
|
||||||
|
- 微信绑定状态能同步更新所有可见区域。
|
||||||
|
|
||||||
|
- [ ] 步骤 3:验证专家申请按钮逻辑。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 已是专家:展示专家相关操作,不显示重复申请。
|
||||||
|
- 审核中:显示“申请审核中”,不允许重复提交。
|
||||||
|
- 非专家:显示“专家申请”。
|
||||||
|
- `expertStatus: 0` 这种 falsey 值不能被丢失。
|
||||||
|
|
||||||
|
- [ ] 步骤 4:验证资料编辑契约。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 昵称字段提交为 `nickName`。
|
||||||
|
- 密码只有在两次输入都存在并一致时提交。
|
||||||
|
- 头像上传结果提交为 `avatar`。
|
||||||
|
- 没有任何修改时提示“暂无修改”,不发送空请求。
|
||||||
|
|
||||||
|
- [ ] 步骤 5:静态验证。
|
||||||
|
|
||||||
|
执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git diff --check -- html\usercenter.html
|
||||||
|
```
|
||||||
|
|
||||||
|
期望:无输出。
|
||||||
|
|
||||||
|
## 任务 4:对齐登录、注册、找回密码、验证码和推广绑定
|
||||||
|
|
||||||
|
**涉及文件:**
|
||||||
|
|
||||||
|
- 修改:`utils/AuthPageUtil.js`
|
||||||
|
- 修改:`html/login.html`
|
||||||
|
- 修改:`html/reg.html`
|
||||||
|
- 修改:`html/repwd.html`
|
||||||
|
- 参考:`C:\Users\Administrator\Desktop\job\3Dyjs\utils\AuthPageUtil.js`
|
||||||
|
- 参考:`C:\Users\Administrator\Desktop\job\3Dyjs\html\login.html`
|
||||||
|
|
||||||
|
**输入:**
|
||||||
|
|
||||||
|
- `referralBindPreview`
|
||||||
|
- `referralBind`
|
||||||
|
- 登录、注册、短信验证码、滑块验证码接口
|
||||||
|
|
||||||
|
**输出:**
|
||||||
|
|
||||||
|
- 邀请码捕获、预览、缓存、登录/注册后绑定流程与参考项目一致。
|
||||||
|
|
||||||
|
- [ ] 步骤 1:对比 `AuthPageUtil.js` 和参考项目。
|
||||||
|
|
||||||
|
执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
rg -n "invite|referral|captcha|slide|viewport|resize|bind" utils\AuthPageUtil.js C:\Users\Administrator\Desktop\job\3Dyjs\utils\AuthPageUtil.js
|
||||||
|
```
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 编辑前列出缺失的邀请码和滑块移动端行为。
|
||||||
|
|
||||||
|
- [ ] 步骤 2:迁移邀请码缓存和登录/注册后绑定逻辑。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 支持参考项目里的邀请参数。
|
||||||
|
- 邀请码能在登录/注册页面跳转之间保留。
|
||||||
|
- 登录或注册成功后,在合适时机调用 `referralBind`。
|
||||||
|
|
||||||
|
- [ ] 步骤 3:迁移滑块验证码移动端定位逻辑。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 移动端验证码弹层居中。
|
||||||
|
- 多次打开不会残留旧遮罩或旧状态。
|
||||||
|
|
||||||
|
- [ ] 步骤 4:确认登录注册页面仍保持当前皮肤。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 不回退成参考项目旧样式。
|
||||||
|
- 表单字段保持当前布局,但行为与参考项目一致。
|
||||||
|
|
||||||
|
## 任务 5:对齐免费文章列表、详情、发布和收藏
|
||||||
|
|
||||||
|
**涉及文件:**
|
||||||
|
|
||||||
|
- 修改:`html/mianfeilist.html`
|
||||||
|
- 修改:`html/mianfei.html`
|
||||||
|
- 修改:`html/fabumianfeiwenzhang.html`
|
||||||
|
- 修改:`html/mianfeishoucang.html`
|
||||||
|
- 检查:`html/c.html`
|
||||||
|
- 参考:`C:\Users\Administrator\Desktop\job\3Dyjs\html\mianfeilist.html`
|
||||||
|
- 参考:`C:\Users\Administrator\Desktop\job\3Dyjs\html\mianfei.html`
|
||||||
|
|
||||||
|
**输入:**
|
||||||
|
|
||||||
|
- `ApiClient.API` 中免费文章列表、详情、收藏相关接口
|
||||||
|
|
||||||
|
**输出:**
|
||||||
|
|
||||||
|
- 免费文章列表数量、分页、详情渲染、收藏行为与参考项目等价。
|
||||||
|
|
||||||
|
- [ ] 步骤 1:对比免费文章列表接口参数。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- `pageNum`、`pageSize`、`menuId`、状态/类型筛选与参考项目一致。
|
||||||
|
- 如果页面合并导致参数适配不同,需要在文档中记录。
|
||||||
|
|
||||||
|
- [ ] 步骤 2:验证作者和标题展示。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 头像、`issuer` 昵称、标题、日期按当前需求展示。
|
||||||
|
- 标题有最大宽度和省略号。
|
||||||
|
|
||||||
|
- [ ] 步骤 3:确定收藏页主入口。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 要么 `mianfeishoucang.html` 负责所有收藏 tab,`c.html` 变成兼容入口。
|
||||||
|
- 要么两个页面职责明确,不重复。
|
||||||
|
|
||||||
|
- [ ] 步骤 4:验证富文本安全。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 文章内容渲染达到参考项目同等级别的净化或转义要求。
|
||||||
|
- 不出现未处理的危险 HTML 注入。
|
||||||
|
|
||||||
|
## 任务 6:对齐付费文章、专家详情、购买和预购
|
||||||
|
|
||||||
|
**涉及文件:**
|
||||||
|
|
||||||
|
- 修改:`html/cai.html`
|
||||||
|
- 修改:`html/caiinfo.html`
|
||||||
|
- 修改:`html/fufei.html`
|
||||||
|
- 修改:`html/usercenter.html`
|
||||||
|
- 参考:`C:\Users\Administrator\Desktop\job\3Dyjs\html\cai.html`
|
||||||
|
- 参考:`C:\Users\Administrator\Desktop\job\3Dyjs\html\caiinfo.html`
|
||||||
|
- 参考:`C:\Users\Administrator\Desktop\job\3Dyjs\html\fufei.html`
|
||||||
|
|
||||||
|
**输入:**
|
||||||
|
|
||||||
|
- 专家排行、付费文章列表/详情、购买、预购、收藏、钱包概要接口
|
||||||
|
|
||||||
|
**输出:**
|
||||||
|
|
||||||
|
- 购买、预购、已购、收藏、余额判断等行为与参考项目等价。
|
||||||
|
|
||||||
|
- [ ] 步骤 1:对比 `caiinfo.html` 状态归一化。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 已购买、可预购、已预购、文章收藏、专家收藏等状态判断与参考项目一致。
|
||||||
|
|
||||||
|
- [ ] 步骤 2:对比支付请求参数。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 价格、文章 ID、菜单 ID、专家 ID、预购字段与参考项目接口契约一致。
|
||||||
|
|
||||||
|
- [ ] 步骤 3:验证个人中心付费文章和购买记录。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 我的付费文章、已购文章、删除购买记录、空状态与参考项目等价。
|
||||||
|
|
||||||
|
## 任务 7:对齐走势图渲染规则
|
||||||
|
|
||||||
|
**涉及文件:**
|
||||||
|
|
||||||
|
- 新增或修改:`public/js/trend-pages.js`
|
||||||
|
- 修改:`html/fc3d.html`
|
||||||
|
- 修改:`html/pl3.html`
|
||||||
|
- 修改:`html/pl5.html`
|
||||||
|
- 修改:`html/kl8.html`
|
||||||
|
- 修改:`html/ssq.html`
|
||||||
|
- 修改:`html/cjdlt.html`
|
||||||
|
- 修改:`html/qlc.html`
|
||||||
|
- 修改:`html/qxc.html`
|
||||||
|
- 参考:`C:\Users\Administrator\Desktop\job\3Dyjs\public\js\trend-pages.js`
|
||||||
|
|
||||||
|
**输入:**
|
||||||
|
|
||||||
|
- `lotteryTrend`
|
||||||
|
- `latestLotteryResult`
|
||||||
|
|
||||||
|
**输出:**
|
||||||
|
|
||||||
|
- 期数选项、走势图行、颜色标记、每 10 期分隔线、统计行、横向滚动规则按彩种与参考项目一致。
|
||||||
|
|
||||||
|
- [ ] 步骤 1:对比 8 个走势图页面和参考配置。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 确认每个彩种的号码分区、蓝球规则、特别号列、统计行。
|
||||||
|
|
||||||
|
- [ ] 步骤 2:实现当前项目共享走势图渲染,或逐页完全对齐。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 期数按钮为:`10期`、`20期`、`30期`、`60期`。
|
||||||
|
- 每 10 期使用与参考项目粗细一致的分隔线。
|
||||||
|
- 红球为红色,蓝球为蓝色,特殊色只在参考项目使用时保留。
|
||||||
|
- 快乐8 不出现错误的“开奖号”表头,并正确展示 20 个号码。
|
||||||
|
|
||||||
|
- [ ] 步骤 3:静态验证 8 个页面。
|
||||||
|
|
||||||
|
执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
rg -n "10期|20期|30期|60期|trend-period-separator|lotteryTrend|latestLotteryResult" html\fc3d.html html\pl3.html html\pl5.html html\kl8.html html\ssq.html html\cjdlt.html html\qlc.html html\qxc.html
|
||||||
|
```
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 每个页面都有一致的期数选项和数据 fallback 策略。
|
||||||
|
|
||||||
|
## 任务 8:对齐钱包、充值、提现和资金明细
|
||||||
|
|
||||||
|
**涉及文件:**
|
||||||
|
|
||||||
|
- 修改:`html/chongzhi.html`
|
||||||
|
- 修改:`html/tixian.html`
|
||||||
|
- 修改:`html/tixianlist.html`
|
||||||
|
- 修改:`html/jine.html`
|
||||||
|
- 参考:`C:\Users\Administrator\Desktop\job\3Dyjs\html\tixian.html`
|
||||||
|
- 参考:`C:\Users\Administrator\Desktop\job\3Dyjs\html\tixianlist.html`
|
||||||
|
|
||||||
|
**输入:**
|
||||||
|
|
||||||
|
- 钱包概要、充值配置、充值下单、订单状态、提现账户、提现提交、资金流水接口
|
||||||
|
|
||||||
|
**输出:**
|
||||||
|
|
||||||
|
- 余额展示、二维码上传、默认账户、提现提交、记录筛选行为与参考项目等价。
|
||||||
|
|
||||||
|
- [ ] 步骤 1:对比钱包接口 key 和请求参数。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 当前页面调用的接口语义与参考项目一致。
|
||||||
|
|
||||||
|
- [ ] 步骤 2:验证充值余额和订单轮询。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 用户能看到相关余额。
|
||||||
|
- 二维码和订单状态轮询在成功或失败后正确停止。
|
||||||
|
|
||||||
|
- [ ] 步骤 3:验证提现账户管理。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 新增、编辑、删除、设默认、二维码上传、提交提现与参考项目等价。
|
||||||
|
|
||||||
|
## 任务 9:对齐推广和分销流程
|
||||||
|
|
||||||
|
**涉及文件:**
|
||||||
|
|
||||||
|
- 修改:`html/tuiguang.html`
|
||||||
|
- 修改:`html/fenxiao.html`
|
||||||
|
- 修改:`html/wodefenxiao.html`
|
||||||
|
- 修改:`utils/AuthPageUtil.js`
|
||||||
|
- 参考:`C:\Users\Administrator\Desktop\job\3Dyjs\html\fenxiao.html`
|
||||||
|
- 参考:`C:\Users\Administrator\Desktop\job\3Dyjs\html\wodefenxiao.html`
|
||||||
|
|
||||||
|
**输入:**
|
||||||
|
|
||||||
|
- 推广概要、邀请列表、返佣列表、绑定预览、绑定提交接口
|
||||||
|
|
||||||
|
**输出:**
|
||||||
|
|
||||||
|
- 推广链接、二维码、绑定关系、邀请列表、返佣列表行为与参考项目等价。
|
||||||
|
|
||||||
|
- [ ] 步骤 1:确认 `wodefenxiao.html` 的业务含义。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 如果参考项目展示返佣记录,当前项目不能静默展示邀请用户列表。
|
||||||
|
|
||||||
|
- [ ] 步骤 2:对齐接口调用和表格字段。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 用户看到的数据含义与参考项目一致。
|
||||||
|
|
||||||
|
- [ ] 步骤 3:验证推广绑定链路。
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- 匿名访问邀请链接,注册或登录后能绑定推广关系。
|
||||||
|
|
||||||
|
## 任务 10:最终跨模块回归
|
||||||
|
|
||||||
|
**涉及文件:**
|
||||||
|
|
||||||
|
- 检查所有已修改文件。
|
||||||
|
- 如决策变化,更新:`docs/reference-project-function-diff-2026-07-07.md`
|
||||||
|
|
||||||
|
**输出:**
|
||||||
|
|
||||||
|
- 当前批次的功能等价验证记录。
|
||||||
|
|
||||||
|
- [ ] 步骤 1:检查所有改动文件格式。
|
||||||
|
|
||||||
|
执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
期望:无空白错误。
|
||||||
|
|
||||||
|
- [ ] 步骤 2:搜索参考项目中当前项目不应误跳的本地路径。
|
||||||
|
|
||||||
|
执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
rg -n "zhuanjia\.html|duanzuhe\.html|shazuhe\.html|zhuanjiashoucang\.html" html utils public
|
||||||
|
```
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
- `zhuanjia.html` 不作为运行时目标出现。
|
||||||
|
- `duanzuhe.html` 和 `shazuhe.html` 要么是确认过的外链目标,要么已恢复本地等价功能。
|
||||||
|
- `zhuanjiashoucang.html` 不作为运行时目标出现,除非明确恢复。
|
||||||
|
|
||||||
|
- [ ] 步骤 3:浏览器手工回归。
|
||||||
|
|
||||||
|
期望覆盖:
|
||||||
|
|
||||||
|
- 登录、注册、找回密码。
|
||||||
|
- 首页导航和底部。
|
||||||
|
- 8 个走势图页面。
|
||||||
|
- 免费文章列表、详情、发布、收藏。
|
||||||
|
- 付费文章列表、详情、购买、预购、发布。
|
||||||
|
- 个人中心资料、签到、微信绑定、专家申请状态。
|
||||||
|
- 充值、提现、资金明细。
|
||||||
|
- 推广邀请和返佣页面。
|
||||||
|
|
||||||
|
## 执行顺序
|
||||||
|
|
||||||
|
1. 任务 1:锁定允许差异,防止缺失页面误跳。
|
||||||
|
2. 任务 2:补齐发布权限公共契约。
|
||||||
|
3. 任务 3:修正个人中心契约和重复 DOM 风险。
|
||||||
|
4. 任务 4:对齐登录、注册、验证码和推广绑定基础能力。
|
||||||
|
5. 任务 5:对齐免费文章流程。
|
||||||
|
6. 任务 6:对齐付费文章和购买流程。
|
||||||
|
7. 任务 7:对齐走势图。
|
||||||
|
8. 任务 8:对齐钱包流程。
|
||||||
|
9. 任务 9:对齐推广和分销页面。
|
||||||
|
10. 任务 10:跨模块回归。
|
||||||
|
|
||||||
|
## 自检结果
|
||||||
|
|
||||||
|
- 需求覆盖:覆盖 `docs/reference-project-function-diff-2026-07-07.md` 中记录的主要高风险差异。
|
||||||
|
- 占位检查:没有把“待定/以后实现”作为任务要求。
|
||||||
|
- 契约一致性:发布权限对象字段与参考项目 `PublishPermission.parseSummary` 保持一致。
|
||||||
|
- 范围检查:这是多批次功能等价修复,每个任务都可以独立验证,并且必须按顺序执行。
|
||||||
+13
-5
@@ -140,6 +140,7 @@
|
|||||||
<button class="gotop" id="backTop" type="button" onclick="window.scrollTo({ top: 0, behavior: 'smooth' })">↑</button>
|
<button class="gotop" id="backTop" type="button" onclick="window.scrollTo({ top: 0, behavior: 'smooth' })">↑</button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
var $;
|
||||||
var updateData = {
|
var updateData = {
|
||||||
idCardFrontId: "",
|
idCardFrontId: "",
|
||||||
idCardBackId: "",
|
idCardBackId: "",
|
||||||
@@ -148,7 +149,7 @@
|
|||||||
var expertIntroMinLength = 80;
|
var expertIntroMinLength = 80;
|
||||||
|
|
||||||
layui.use(["layer", "jquery"], function () {
|
layui.use(["layer", "jquery"], function () {
|
||||||
var $ = layui.$;
|
$ = layui.$;
|
||||||
|
|
||||||
CommonUtil.loadPageConfig({ ads: false, links: false });
|
CommonUtil.loadPageConfig({ ads: false, links: false });
|
||||||
renderLoginState();
|
renderLoginState();
|
||||||
@@ -204,13 +205,20 @@
|
|||||||
CommonUtil.getCurrentUserDetail(function (data) {
|
CommonUtil.getCurrentUserDetail(function (data) {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
$("#currentUsername").text(data.nickName || data.username || data.phoneNumber || "用户");
|
$("#currentUsername").text(data.nickName || data.username || data.phoneNumber || "用户");
|
||||||
var isExpert =
|
var expertState = CommonUtil.getExpertState
|
||||||
|
? CommonUtil.getExpertState(data)
|
||||||
|
: {
|
||||||
|
isExpert:
|
||||||
data.expert === true ||
|
data.expert === true ||
|
||||||
data.regularExpert === true ||
|
data.regularExpert === true ||
|
||||||
data.isExpert === 1 ||
|
data.isExpert === 1 ||
|
||||||
data.isExpert === "1";
|
data.isExpert === "1",
|
||||||
if (isExpert) {
|
isApplying: false,
|
||||||
$("#applyBtn").text("已申请").prop("disabled", true);
|
};
|
||||||
|
if (expertState.isExpert || expertState.isApplying) {
|
||||||
|
$("#applyBtn")
|
||||||
|
.text(expertState.isExpert ? "已是专家" : "申请审核中")
|
||||||
|
.prop("disabled", true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title id="page-title">神彩算 - 免费文章收藏</title>
|
<title id="page-title">神彩算 - 免费文章收藏</title>
|
||||||
|
<script>
|
||||||
|
window.location.replace("mianfeishoucang.html?tab=free");
|
||||||
|
</script>
|
||||||
<script src="../public/css/layui/layui.js"></script>
|
<script src="../public/css/layui/layui.js"></script>
|
||||||
<script src="../config.js"></script>
|
<script src="../config.js"></script>
|
||||||
<script src="../utils/ApiClient.js"></script>
|
<script src="../utils/ApiClient.js"></script>
|
||||||
|
|||||||
+36
-2
@@ -851,9 +851,43 @@
|
|||||||
if (!item) return false;
|
if (!item) return false;
|
||||||
const articleId = getArticleId(item);
|
const articleId = getArticleId(item);
|
||||||
if (articleId && purchasedArticleIds.has(String(articleId))) return true;
|
if (articleId && purchasedArticleIds.has(String(articleId))) return true;
|
||||||
return getArticlePurchaseKeys(item).some(function (key) {
|
const purchasedByKey = getArticlePurchaseKeys(item).some(function (key) {
|
||||||
return purchasedArticleKeys.has(key);
|
return purchasedArticleKeys.has(key);
|
||||||
});
|
});
|
||||||
|
if (purchasedByKey) return true;
|
||||||
|
|
||||||
|
return (
|
||||||
|
item.isBuy === 1 ||
|
||||||
|
item.isBuy === 2 ||
|
||||||
|
item.isBuy === true ||
|
||||||
|
item.isBuy === "1" ||
|
||||||
|
item.isBuy === "2" ||
|
||||||
|
item.buyStatus === 1 ||
|
||||||
|
item.buyStatus === "1" ||
|
||||||
|
item.purchased === true ||
|
||||||
|
item.purchased === 1 ||
|
||||||
|
item.purchased === "1" ||
|
||||||
|
item.isPurchased === true ||
|
||||||
|
item.isPurchased === 1 ||
|
||||||
|
item.isPurchased === "1" ||
|
||||||
|
item.bought === true ||
|
||||||
|
item.bought === 1 ||
|
||||||
|
item.bought === "1" ||
|
||||||
|
item.unlocked === true ||
|
||||||
|
item.unlocked === 1 ||
|
||||||
|
item.unlocked === "1" ||
|
||||||
|
item.canView === true ||
|
||||||
|
item.canView === 1 ||
|
||||||
|
item.canView === "1" ||
|
||||||
|
item.payStatus === 1 ||
|
||||||
|
item.payStatus === "1" ||
|
||||||
|
item.subscribed === true ||
|
||||||
|
item.subscribed === 1 ||
|
||||||
|
item.subscribed === "1" ||
|
||||||
|
item.subscribed === "true" ||
|
||||||
|
item.subscribeStatus === 1 ||
|
||||||
|
item.subscribeStatus === "1"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function canShowArticleRecommend(
|
function canShowArticleRecommend(
|
||||||
@@ -1836,7 +1870,7 @@
|
|||||||
const params = {
|
const params = {
|
||||||
menuId: getArticleMenuId(item),
|
menuId: getArticleMenuId(item),
|
||||||
expertId: getArticleExpertId(item),
|
expertId: getArticleExpertId(item),
|
||||||
accountType: Number(accountType) || 1,
|
accountType: Number(item.accountType || accountType) || 1,
|
||||||
issue: getArticleIssue(item),
|
issue: getArticleIssue(item),
|
||||||
count: 1,
|
count: 1,
|
||||||
walletType:
|
walletType:
|
||||||
|
|||||||
+1
-2
@@ -1,4 +1,4 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
@@ -563,7 +563,6 @@
|
|||||||
status === "success" ||
|
status === "success" ||
|
||||||
status === "paid" ||
|
status === "paid" ||
|
||||||
status === "2" ||
|
status === "2" ||
|
||||||
status === "1" ||
|
|
||||||
Number(data.status) === 2 ||
|
Number(data.status) === 2 ||
|
||||||
Number(data.payStatus) === 2
|
Number(data.payStatus) === 2
|
||||||
);
|
);
|
||||||
|
|||||||
+9
-4
@@ -66,9 +66,10 @@
|
|||||||
<div class="page-title-bar">
|
<div class="page-title-bar">
|
||||||
<div class="page-title">大乐透走势图</div>
|
<div class="page-title">大乐透走势图</div>
|
||||||
<div class="qishu">
|
<div class="qishu">
|
||||||
|
<div class="qishu-but" data-count="10">10期</div>
|
||||||
|
<div class="qishu-but" data-count="20">20期</div>
|
||||||
<div class="qishu-but active" data-count="30">30期</div>
|
<div class="qishu-but active" data-count="30">30期</div>
|
||||||
<div class="qishu-but" data-count="60">60期</div>
|
<div class="qishu-but" data-count="60">60期</div>
|
||||||
<div class="qishu-but" data-count="100">100期</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -216,9 +217,12 @@
|
|||||||
backSet[n] = true;
|
backSet[n] = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
html += "<tr>";
|
html +=
|
||||||
|
r > 0 && r % 10 === 0
|
||||||
|
? '<tr class="trend-period-separator">'
|
||||||
|
: "<tr>";
|
||||||
html += '<td class="col-issue">' + item.issue + "</td>";
|
html += '<td class="col-issue">' + item.issue + "</td>";
|
||||||
html += '<td class="col-code">' + item.openCode + "</td>";
|
html += '<td class="col-code">' + CommonUtil.renderTrendOpenCode(item.openCode, { mode: "dlt" }) + "</td>";
|
||||||
|
|
||||||
for (var i = 1; i <= FRONT_MAX; i++) {
|
for (var i = 1; i <= FRONT_MAX; i++) {
|
||||||
if (frontSet[i]) {
|
if (frontSet[i]) {
|
||||||
@@ -339,7 +343,8 @@
|
|||||||
function loadData() {
|
function loadData() {
|
||||||
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "cjdlt" }).then(function (res) {
|
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "cjdlt" }).then(function (res) {
|
||||||
if (res && res.data) {
|
if (res && res.data) {
|
||||||
allData = res.data;
|
allData = Array.isArray(res.data) ? res.data : (res.data && (res.data.records || res.data.rows || res.data.list || res.data.data)) || [];
|
||||||
|
if (!Array.isArray(allData)) allData = [];
|
||||||
renderData(currentCount);
|
renderData(currentCount);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<script src="../public/css/layui/layui.js"></script>
|
<script src="../public/css/layui/layui.js"></script>
|
||||||
<script src="../config.js"></script>
|
<script src="../config.js"></script>
|
||||||
<script src="../utils/ApiClient.js"></script>
|
<script src="../utils/ApiClient.js"></script>
|
||||||
|
<script src="../utils/PublishPermission.js"></script>
|
||||||
<script src="../utils/DateUtil.js"></script>
|
<script src="../utils/DateUtil.js"></script>
|
||||||
<script src="../utils/CommonUtil.js"></script>
|
<script src="../utils/CommonUtil.js"></script>
|
||||||
<link rel="stylesheet" href="../public/css/layui/css/layui.css" />
|
<link rel="stylesheet" href="../public/css/layui/css/layui.css" />
|
||||||
@@ -185,7 +186,6 @@
|
|||||||
canFree: false,
|
canFree: false,
|
||||||
canPaid: false,
|
canPaid: false,
|
||||||
};
|
};
|
||||||
var REQUIRED_DAILY_FREE_ARTICLES = 2;
|
|
||||||
var FREE_TITLE_MIN_LENGTH = 15;
|
var FREE_TITLE_MIN_LENGTH = 15;
|
||||||
var FREE_TITLE_MAX_LENGTH = 30;
|
var FREE_TITLE_MAX_LENGTH = 30;
|
||||||
|
|
||||||
@@ -298,146 +298,14 @@
|
|||||||
return res && (res.code === 0 || res.code === 200);
|
return res && (res.code === 0 || res.code === 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTruthy(value) {
|
|
||||||
return (
|
|
||||||
value === true ||
|
|
||||||
value === 1 ||
|
|
||||||
value === "1" ||
|
|
||||||
String(value).toLowerCase() === "true"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function padDatePart(value) {
|
|
||||||
return String(value).padStart(2, "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatLocalDate(date) {
|
|
||||||
return (
|
|
||||||
date.getFullYear() +
|
|
||||||
"-" +
|
|
||||||
padDatePart(date.getMonth() + 1) +
|
|
||||||
"-" +
|
|
||||||
padDatePart(date.getDate())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getArticleDateText(value) {
|
|
||||||
if (!value) return "";
|
|
||||||
if (typeof value === "number") {
|
|
||||||
var numericDate = new Date(value);
|
|
||||||
return isNaN(numericDate.getTime())
|
|
||||||
? ""
|
|
||||||
: formatLocalDate(numericDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
var text = String(value).trim();
|
|
||||||
var match = text.match(/^(\d{4})[-/](\d{1,2})[-/](\d{1,2})/);
|
|
||||||
if (match) {
|
|
||||||
return (
|
|
||||||
match[1] + "-" + padDatePart(match[2]) + "-" + padDatePart(match[3])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var parsedDate = new Date(text.replace(/-/g, "/"));
|
|
||||||
return isNaN(parsedDate.getTime()) ? "" : formatLocalDate(parsedDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPublishedFreeArticle(item) {
|
|
||||||
if (!item) return false;
|
|
||||||
|
|
||||||
var statusText = String(
|
|
||||||
item.statusText ||
|
|
||||||
item.auditStatusText ||
|
|
||||||
item.publishStatusText ||
|
|
||||||
item.stateText ||
|
|
||||||
"",
|
|
||||||
).toLowerCase();
|
|
||||||
if (/已发布|审核通过|通过|published|approved|pass/.test(statusText)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var status =
|
|
||||||
item.status != null
|
|
||||||
? item.status
|
|
||||||
: item.auditStatus != null
|
|
||||||
? item.auditStatus
|
|
||||||
: item.publishStatus;
|
|
||||||
return status === true || status === 1 || status === "1";
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTodayPublishedFreeCount(rows) {
|
|
||||||
var today = formatLocalDate(new Date());
|
|
||||||
return ApiClient.asArray(rows).filter(function (item) {
|
|
||||||
var articleDate = getArticleDateText(
|
|
||||||
item.createTime ||
|
|
||||||
item.create_time ||
|
|
||||||
item.publishTime ||
|
|
||||||
item.publish_time ||
|
|
||||||
item.auditTime ||
|
|
||||||
item.audit_time ||
|
|
||||||
item.updateTime ||
|
|
||||||
item.update_time ||
|
|
||||||
item.createdAt,
|
|
||||||
);
|
|
||||||
return articleDate === today && isPublishedFreeArticle(item);
|
|
||||||
}).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadTodayPublishedFreeCount() {
|
|
||||||
return ApiClient.get(ApiClient.API.mineFreeArticleList, {
|
|
||||||
pageNum: 1,
|
|
||||||
pageSize: 100,
|
|
||||||
})
|
|
||||||
.then(function (res) {
|
|
||||||
if (!ApiClient.isSuccess(res)) return 0;
|
|
||||||
var data = res.data || res;
|
|
||||||
var rows = ApiClient.asArray(
|
|
||||||
data.rows || data.records || data.list || data.data || data,
|
|
||||||
);
|
|
||||||
return getTodayPublishedFreeCount(rows);
|
|
||||||
})
|
|
||||||
.catch(function () {
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function parsePublishPermission(userData) {
|
|
||||||
var status = String(
|
|
||||||
userData.expertStatus || userData.expert_status || "",
|
|
||||||
).toLowerCase();
|
|
||||||
var isIntern = /实习|intern|trial/.test(status);
|
|
||||||
var isRegularByStatus = /正式|regular|formal/.test(status);
|
|
||||||
var hasExpertIdentity =
|
|
||||||
isTruthy(userData.is_expert) ||
|
|
||||||
isTruthy(userData.isExpert) ||
|
|
||||||
isTruthy(userData.expert) ||
|
|
||||||
isTruthy(userData.is_regular_expert) ||
|
|
||||||
isTruthy(userData.isRegularExpert) ||
|
|
||||||
isTruthy(userData.regularExpert) ||
|
|
||||||
isIntern ||
|
|
||||||
isRegularByStatus;
|
|
||||||
var isRegularExpert =
|
|
||||||
isTruthy(userData.is_regular_expert) ||
|
|
||||||
isTruthy(userData.isRegularExpert) ||
|
|
||||||
isTruthy(userData.regularExpert) ||
|
|
||||||
isRegularByStatus;
|
|
||||||
|
|
||||||
return {
|
|
||||||
hasExpertIdentity: hasExpertIdentity,
|
|
||||||
isRegularExpert: hasExpertIdentity && isRegularExpert,
|
|
||||||
canFree: hasExpertIdentity,
|
|
||||||
canPaid: false,
|
|
||||||
todayFreeArticleCount: 0,
|
|
||||||
paidReason: isRegularExpert
|
|
||||||
? "今天需先发布2篇审核通过的免费文章后,才能发布付费文章"
|
|
||||||
: "正式专家才能发布付费文章",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadPublishPermission() {
|
function loadPublishPermission() {
|
||||||
return ApiClient.get(ApiClient.API.mineSummary)
|
return ApiClient.get(ApiClient.API.mineSummary)
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
if (!ApiClient.isSuccess(res) || !res.data) {
|
if (
|
||||||
|
!ApiClient.isSuccess(res) ||
|
||||||
|
!res.data ||
|
||||||
|
!window.PublishPermission
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
hasExpertIdentity: false,
|
hasExpertIdentity: false,
|
||||||
isRegularExpert: false,
|
isRegularExpert: false,
|
||||||
@@ -448,19 +316,7 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var permission = parsePublishPermission(res.data);
|
return PublishPermission.parseSummary(res.data);
|
||||||
if (!permission.canFree || !permission.isRegularExpert) return permission;
|
|
||||||
|
|
||||||
return loadTodayPublishedFreeCount().then(function (count) {
|
|
||||||
permission.todayFreeArticleCount = count;
|
|
||||||
permission.canPaid = count >= REQUIRED_DAILY_FREE_ARTICLES;
|
|
||||||
permission.paidReason = permission.canPaid
|
|
||||||
? ""
|
|
||||||
: "今天需先发布2篇审核通过的免费文章后,才能发布付费文章(当前" +
|
|
||||||
count +
|
|
||||||
"篇)。";
|
|
||||||
return permission;
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch(function () {
|
.catch(function () {
|
||||||
return {
|
return {
|
||||||
@@ -479,7 +335,7 @@
|
|||||||
publishPermission = permission;
|
publishPermission = permission;
|
||||||
return {
|
return {
|
||||||
canPaid: permission.canPaid,
|
canPaid: permission.canPaid,
|
||||||
reason: permission.paidReason,
|
reason: permission.reason || permission.paidReason,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-4
@@ -66,9 +66,10 @@
|
|||||||
<div class="page-title-bar">
|
<div class="page-title-bar">
|
||||||
<div class="page-title">福彩3D走势图</div>
|
<div class="page-title">福彩3D走势图</div>
|
||||||
<div class="qishu">
|
<div class="qishu">
|
||||||
|
<div class="qishu-but" data-count="10">10期</div>
|
||||||
|
<div class="qishu-but" data-count="20">20期</div>
|
||||||
<div class="qishu-but active" data-count="30">30期</div>
|
<div class="qishu-but active" data-count="30">30期</div>
|
||||||
<div class="qishu-but" data-count="60">60期</div>
|
<div class="qishu-but" data-count="60">60期</div>
|
||||||
<div class="qishu-but" data-count="100">100期</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -207,9 +208,12 @@
|
|||||||
var parsed = parseCode(item.openCode);
|
var parsed = parseCode(item.openCode);
|
||||||
var type = getType(parsed.hundred, parsed.ten, parsed.unit);
|
var type = getType(parsed.hundred, parsed.ten, parsed.unit);
|
||||||
|
|
||||||
html += "<tr>";
|
html +=
|
||||||
|
r > 0 && r % 10 === 0
|
||||||
|
? '<tr class="trend-period-separator">'
|
||||||
|
: "<tr>";
|
||||||
html += '<td class="col-issue">' + item.issue + "</td>";
|
html += '<td class="col-issue">' + item.issue + "</td>";
|
||||||
html += '<td class="col-code">' + item.openCode + "</td>";
|
html += '<td class="col-code">' + CommonUtil.renderTrendOpenCode(item.openCode, { mode: "position" }) + "</td>";
|
||||||
|
|
||||||
// Type columns
|
// Type columns
|
||||||
var typeNames = ["baozi", "zu3", "zu6"];
|
var typeNames = ["baozi", "zu3", "zu6"];
|
||||||
@@ -396,7 +400,8 @@
|
|||||||
function loadData() {
|
function loadData() {
|
||||||
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "fc3d" }).then(function (res) {
|
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "fc3d" }).then(function (res) {
|
||||||
if (res && res.data) {
|
if (res && res.data) {
|
||||||
allData = res.data;
|
allData = Array.isArray(res.data) ? res.data : (res.data && (res.data.records || res.data.rows || res.data.list || res.data.data)) || [];
|
||||||
|
if (!Array.isArray(allData)) allData = [];
|
||||||
renderData(currentCount);
|
renderData(currentCount);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -196,6 +196,7 @@
|
|||||||
<script src="../public/js/lay-config.js"></script>
|
<script src="../public/js/lay-config.js"></script>
|
||||||
<script src="../config.js"></script>
|
<script src="../config.js"></script>
|
||||||
<script src="../utils/ApiClient.js"></script>
|
<script src="../utils/ApiClient.js"></script>
|
||||||
|
<script src="../utils/PublishPermission.js"></script>
|
||||||
<script src="../utils/DateUtil.js"></script>
|
<script src="../utils/DateUtil.js"></script>
|
||||||
<script src="../utils/CommonUtil.js"></script>
|
<script src="../utils/CommonUtil.js"></script>
|
||||||
<script src="../public/js/jquery.js"></script>
|
<script src="../public/js/jquery.js"></script>
|
||||||
|
|||||||
+10
-7
@@ -66,9 +66,10 @@
|
|||||||
<div class="page-title-bar">
|
<div class="page-title-bar">
|
||||||
<div class="page-title">快乐8走势图</div>
|
<div class="page-title">快乐8走势图</div>
|
||||||
<div class="qishu">
|
<div class="qishu">
|
||||||
|
<div class="qishu-but" data-count="10">10期</div>
|
||||||
|
<div class="qishu-but" data-count="20">20期</div>
|
||||||
<div class="qishu-but active" data-count="30">30期</div>
|
<div class="qishu-but active" data-count="30">30期</div>
|
||||||
<div class="qishu-but" data-count="60">60期</div>
|
<div class="qishu-but" data-count="60">60期</div>
|
||||||
<div class="qishu-but" data-count="100">100期</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -77,7 +78,6 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2" class="col-issue">期号</td>
|
<td rowspan="2" class="col-issue">期号</td>
|
||||||
<td rowspan="2" class="col-code">开奖号</td>
|
|
||||||
<td colspan="40" class="zone-g1">1-40</td>
|
<td colspan="40" class="zone-g1">1-40</td>
|
||||||
<td colspan="40" class="zone-g2">41-80</td>
|
<td colspan="40" class="zone-g2">41-80</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -198,13 +198,15 @@
|
|||||||
hitSet[n] = true;
|
hitSet[n] = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
html += "<tr>";
|
html +=
|
||||||
|
r > 0 && r % 10 === 0
|
||||||
|
? '<tr class="trend-period-separator">'
|
||||||
|
: "<tr>";
|
||||||
html += '<td class="col-issue">' + item.issue + "</td>";
|
html += '<td class="col-issue">' + item.issue + "</td>";
|
||||||
html += '<td class="col-code">' + item.openCode + "</td>";
|
|
||||||
|
|
||||||
for (var i = 1; i <= NUM_MAX; i++) {
|
for (var i = 1; i <= NUM_MAX; i++) {
|
||||||
if (hitSet[i]) {
|
if (hitSet[i]) {
|
||||||
var cls = i <= 40 ? "hit-num" : "hit-num g2";
|
var cls = "hit-num";
|
||||||
html +=
|
html +=
|
||||||
'<td><span class="' +
|
'<td><span class="' +
|
||||||
cls +
|
cls +
|
||||||
@@ -292,7 +294,7 @@
|
|||||||
html +=
|
html +=
|
||||||
'<tr class="' +
|
'<tr class="' +
|
||||||
cls +
|
cls +
|
||||||
'"><td colspan="2" class="stat-label">' +
|
'"><td colspan="1" class="stat-label">' +
|
||||||
labels[li] +
|
labels[li] +
|
||||||
"</td>";
|
"</td>";
|
||||||
for (var i = 1; i <= NUM_MAX; i++)
|
for (var i = 1; i <= NUM_MAX; i++)
|
||||||
@@ -305,7 +307,8 @@
|
|||||||
function loadData() {
|
function loadData() {
|
||||||
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "kl8" }).then(function (res) {
|
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "kl8" }).then(function (res) {
|
||||||
if (res && res.data) {
|
if (res && res.data) {
|
||||||
allData = res.data;
|
allData = Array.isArray(res.data) ? res.data : (res.data && (res.data.records || res.data.rows || res.data.list || res.data.data)) || [];
|
||||||
|
if (!Array.isArray(allData)) allData = [];
|
||||||
renderData(currentCount);
|
renderData(currentCount);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+22
-8
@@ -305,6 +305,26 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLoginRedirectUrl() {
|
||||||
|
const redirect = new URLSearchParams(window.location.search).get("redirect");
|
||||||
|
return (
|
||||||
|
redirect ||
|
||||||
|
(typeof CommonUtil !== "undefined"
|
||||||
|
? CommonUtil.siteHref("/index.html")
|
||||||
|
: "index.html")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectAfterLogin() {
|
||||||
|
window.location.href = getLoginRedirectUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindReferralThenRedirect() {
|
||||||
|
AuthPageUtil.bindPendingReferralInviteCode()
|
||||||
|
.then(redirectAfterLogin)
|
||||||
|
.catch(redirectAfterLogin);
|
||||||
|
}
|
||||||
|
|
||||||
function submitLogin() {
|
function submitLogin() {
|
||||||
const payload = Object.assign(
|
const payload = Object.assign(
|
||||||
{
|
{
|
||||||
@@ -331,14 +351,7 @@
|
|||||||
layerInstance.msg(
|
layerInstance.msg(
|
||||||
"登录成功,即将跳转到首页",
|
"登录成功,即将跳转到首页",
|
||||||
{ icon: 1, time: 1200 },
|
{ icon: 1, time: 1200 },
|
||||||
function () {
|
bindReferralThenRedirect,
|
||||||
const redirect = new URLSearchParams(window.location.search).get("redirect");
|
|
||||||
window.location.href =
|
|
||||||
redirect ||
|
|
||||||
(typeof CommonUtil !== "undefined"
|
|
||||||
? CommonUtil.siteHref("/index.html")
|
|
||||||
: "index.html");
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -363,6 +376,7 @@
|
|||||||
|
|
||||||
layui.use(["layer", "jquery"], function () {
|
layui.use(["layer", "jquery"], function () {
|
||||||
layerInstance = layui.layer;
|
layerInstance = layui.layer;
|
||||||
|
AuthPageUtil.captureReferralInviteFromUrl();
|
||||||
loadVerifyConfig().then(refreshCaptcha);
|
loadVerifyConfig().then(refreshCaptcha);
|
||||||
$("#loginForm").on("submit", function (event) {
|
$("#loginForm").on("submit", function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|||||||
+25
-5
@@ -9,6 +9,7 @@
|
|||||||
<script src="../utils/ApiClient.js"></script>
|
<script src="../utils/ApiClient.js"></script>
|
||||||
<script src="../utils/DateUtil.js"></script>
|
<script src="../utils/DateUtil.js"></script>
|
||||||
<script src="../utils/CommonUtil.js"></script>
|
<script src="../utils/CommonUtil.js"></script>
|
||||||
|
<script src="../public/js/purify.min.js"></script>
|
||||||
<link rel="stylesheet" href="../public/css/layui/css/layui.css" />
|
<link rel="stylesheet" href="../public/css/layui/css/layui.css" />
|
||||||
<link rel="stylesheet" href="../public/css/public.css" />
|
<link rel="stylesheet" href="../public/css/public.css" />
|
||||||
<link rel="stylesheet" href="../public/css/mianfei.css" />
|
<link rel="stylesheet" href="../public/css/mianfei.css" />
|
||||||
@@ -176,7 +177,11 @@
|
|||||||
const $ = layui.$;
|
const $ = layui.$;
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const pathMatch = window.location.pathname.match(/\/mianfei\/([^/]+)\.html$/);
|
const pathMatch = window.location.pathname.match(/\/mianfei\/([^/]+)\.html$/);
|
||||||
const articleId = urlParams.get("id") || (pathMatch ? decodeURIComponent(pathMatch[1]) : "");
|
const htmlPathMatch = window.location.pathname.match(/(?:^|\/)mianfei\.html\/([^/?#]+)/i);
|
||||||
|
const articleId =
|
||||||
|
urlParams.get("id") ||
|
||||||
|
(pathMatch ? decodeURIComponent(pathMatch[1]) : "") ||
|
||||||
|
(htmlPathMatch ? decodeURIComponent(htmlPathMatch[1]) : "");
|
||||||
let isLiked = false;
|
let isLiked = false;
|
||||||
let isCollected = false;
|
let isCollected = false;
|
||||||
let articleData = null;
|
let articleData = null;
|
||||||
@@ -269,7 +274,11 @@
|
|||||||
|
|
||||||
function renderArticle(data) {
|
function renderArticle(data) {
|
||||||
$("#articleTitle").text(data.title || "无标题");
|
$("#articleTitle").text(data.title || "无标题");
|
||||||
$("#articleAuthor").text(data.issuer || data.nickName || data.author || data.createUserName || "佚名");
|
$("#articleAuthor").text(
|
||||||
|
typeof CommonUtil.getFreeArticleIssuer === "function"
|
||||||
|
? CommonUtil.getFreeArticleIssuer(data)
|
||||||
|
: data.issuer || data.nickName || data.author || data.createUserName || "佚名",
|
||||||
|
);
|
||||||
$("#articleTime").text(data.updateTime || data.createTime || data.publishTime || "--");
|
$("#articleTime").text(data.updateTime || data.createTime || data.publishTime || "--");
|
||||||
$("#articleContent").html(scopeArticleContent(data.content || ""));
|
$("#articleContent").html(scopeArticleContent(data.content || ""));
|
||||||
document.title = "神彩算 - " + (data.title || "免费文章详情");
|
document.title = "神彩算 - " + (data.title || "免费文章详情");
|
||||||
@@ -289,6 +298,10 @@
|
|||||||
return '<p class="cxz-content-empty">暂无内容</p>';
|
return '<p class="cxz-content-empty">暂无内容</p>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof DOMPurify !== "undefined") {
|
||||||
|
html = DOMPurify.sanitize(html);
|
||||||
|
}
|
||||||
|
|
||||||
var scopedStyles = [];
|
var scopedStyles = [];
|
||||||
html = html.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, function (_, css) {
|
html = html.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, function (_, css) {
|
||||||
scopedStyles.push(prefixContentCss(css));
|
scopedStyles.push(prefixContentCss(css));
|
||||||
@@ -357,7 +370,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadRelatedArticles() {
|
function loadRelatedArticles() {
|
||||||
ApiClient.get(ApiClient.API.freeArticleRelated(articleId))
|
ApiClient.get(ApiClient.API.freeArticleRelated(articleId), {
|
||||||
|
limit: 12,
|
||||||
|
articleCount: 12,
|
||||||
|
})
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
if (!ApiClient.isSuccess(res)) return;
|
if (!ApiClient.isSuccess(res)) return;
|
||||||
var data = ApiClient.pickData(res, {}) || {};
|
var data = ApiClient.pickData(res, {}) || {};
|
||||||
@@ -370,14 +386,17 @@
|
|||||||
renderFreeArticleList("#freeRelatedList", rows.filter(function (item) {
|
renderFreeArticleList("#freeRelatedList", rows.filter(function (item) {
|
||||||
var id = item && (item.id || item.articleId || item.freeArticleId);
|
var id = item && (item.id || item.articleId || item.freeArticleId);
|
||||||
return id && String(id) !== String(articleId);
|
return id && String(id) !== String(articleId);
|
||||||
}).slice(0, 8));
|
}).slice(0, 12));
|
||||||
})
|
})
|
||||||
.catch(function () {});
|
.catch(function () {});
|
||||||
loadFreeArticleLatest();
|
loadFreeArticleLatest();
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadFreeArticleLatest() {
|
function loadFreeArticleLatest() {
|
||||||
ApiClient.get(ApiClient.API.freeArticleLatest, { articleCount: 8 })
|
ApiClient.get(ApiClient.API.freeArticleLatest, {
|
||||||
|
limit: 12,
|
||||||
|
articleCount: 12,
|
||||||
|
})
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
if (!ApiClient.isSuccess(res)) return;
|
if (!ApiClient.isSuccess(res)) return;
|
||||||
renderFreeArticleList("#freeLatestList", ApiClient.asArray(ApiClient.pickData(res, [])));
|
renderFreeArticleList("#freeLatestList", ApiClient.asArray(ApiClient.pickData(res, [])));
|
||||||
@@ -394,6 +413,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$.each(list, function (_, item) {
|
$.each(list, function (_, item) {
|
||||||
|
if (_ >= 12) return false;
|
||||||
var id = item.id || item.articleId || item.freeArticleId;
|
var id = item.id || item.articleId || item.freeArticleId;
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
var title = item.title || item.articleTitle || item.name || "无标题";
|
var title = item.title || item.articleTitle || item.name || "无标题";
|
||||||
|
|||||||
+76
-19
@@ -110,7 +110,7 @@
|
|||||||
<script>
|
<script>
|
||||||
const $ = layui.$;
|
const $ = layui.$;
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
let menuId = urlParams.get("id") || "";
|
let menuId = urlParams.get("id") || urlParams.get("code") || "";
|
||||||
let currentPage = 1;
|
let currentPage = 1;
|
||||||
const pageSize = 15;
|
const pageSize = 15;
|
||||||
let lotteryMenus = [];
|
let lotteryMenus = [];
|
||||||
@@ -153,16 +153,15 @@
|
|||||||
|
|
||||||
function resolveCurrentMenuId() {
|
function resolveCurrentMenuId() {
|
||||||
if (!lotteryMenus.length) return;
|
if (!lotteryMenus.length) return;
|
||||||
if (
|
const matched = menuId
|
||||||
menuId &&
|
? lotteryMenus.find(function (item) {
|
||||||
lotteryMenus.some(function (item) {
|
|
||||||
return sameLotteryKey(item, menuId);
|
return sameLotteryKey(item, menuId);
|
||||||
})
|
})
|
||||||
) {
|
: null;
|
||||||
return;
|
const current = matched || lotteryMenus[0];
|
||||||
}
|
menuId = String(
|
||||||
|
(current && (current.id || current.menuId || current.code || current.suoxie)) || "",
|
||||||
menuId = String((lotteryMenus[0] && lotteryMenus[0].id) || "");
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderLotteryTabs() {
|
function renderLotteryTabs() {
|
||||||
@@ -184,9 +183,10 @@
|
|||||||
$tabs.empty();
|
$tabs.empty();
|
||||||
$.each(lotteryMenus, function (_, item) {
|
$.each(lotteryMenus, function (_, item) {
|
||||||
const id = item.id || item.menuId || item.code || item.suoxie || "";
|
const id = item.id || item.menuId || item.code || item.suoxie || "";
|
||||||
|
const linkKey = item.code || item.suoxie || item.lotteryCode || id;
|
||||||
$tabs.append(
|
$tabs.append(
|
||||||
'<a href="mianfeilist.html?id=' +
|
'<a href="mianfeilist.html?id=' +
|
||||||
encodeURIComponent(id) +
|
encodeURIComponent(linkKey) +
|
||||||
'" class="cxz-lottery-tab" data-id="' +
|
'" class="cxz-lottery-tab" data-id="' +
|
||||||
escapeHtml(id) +
|
escapeHtml(id) +
|
||||||
'">' +
|
'">' +
|
||||||
@@ -244,6 +244,44 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function currentRequestMenuId() {
|
||||||
|
const menu = currentMenuInfo();
|
||||||
|
return String((menu && (menu.id || menu.menuId)) || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArticlePageData(res) {
|
||||||
|
const data = ApiClient.pickData(res, {}) || {};
|
||||||
|
const records = ApiClient.asArray(
|
||||||
|
data.records || data.rows || data.list || data.data || data,
|
||||||
|
);
|
||||||
|
const total =
|
||||||
|
Number(data.total) ||
|
||||||
|
Number(data.totalRow) ||
|
||||||
|
Number(data.totalCount) ||
|
||||||
|
Number(data.count) ||
|
||||||
|
Number(res && res.total) ||
|
||||||
|
records.length;
|
||||||
|
return {
|
||||||
|
records: records,
|
||||||
|
total: total,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeArticle(article, group) {
|
||||||
|
article = article || {};
|
||||||
|
group = group || {};
|
||||||
|
return Object.assign({}, article, {
|
||||||
|
id: article.id || article.articleId || article.freeArticleId || "",
|
||||||
|
title: article.title || article.articleTitle || article.name || "无标题",
|
||||||
|
dateText:
|
||||||
|
article.dateText ||
|
||||||
|
article.updateTime ||
|
||||||
|
article.createTime ||
|
||||||
|
article.publishTime ||
|
||||||
|
group.dateText,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Load articles
|
// Load articles
|
||||||
function loadArticles() {
|
function loadArticles() {
|
||||||
if (!menuId) {
|
if (!menuId) {
|
||||||
@@ -253,16 +291,26 @@
|
|||||||
renderPagination(0);
|
renderPagination(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const requestMenuId = currentRequestMenuId();
|
||||||
|
if (!requestMenuId) {
|
||||||
|
renderFallbackArticles();
|
||||||
|
return;
|
||||||
|
}
|
||||||
ApiClient.get(ApiClient.API.freeArticleList, {
|
ApiClient.get(ApiClient.API.freeArticleList, {
|
||||||
menuId: menuId,
|
menuId: requestMenuId,
|
||||||
pageNum: currentPage,
|
pageNum: currentPage,
|
||||||
pageSize: pageSize,
|
pageSize: pageSize,
|
||||||
}).then(function (res) {
|
}).then(function (res) {
|
||||||
if (ApiClient.isSuccess(res) && ApiClient.pickData(res, null)) {
|
if (ApiClient.isSuccess(res) && ApiClient.pickData(res, null)) {
|
||||||
const data = ApiClient.pickData(res, {}) || {};
|
const pageData = getArticlePageData(res);
|
||||||
const list = data.records || data.rows || data.list || ApiClient.asArray(data);
|
if (pageData.records.length || pageData.total) {
|
||||||
renderArticles(list);
|
renderArticles(pageData.records.map(function (item) {
|
||||||
renderPagination(data.totalRow || data.total || data.count || list.length || 0);
|
return normalizeArticle(item, currentFallbackGroup());
|
||||||
|
}));
|
||||||
|
renderPagination(pageData.total);
|
||||||
|
} else {
|
||||||
|
renderFallbackArticles();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
renderFallbackArticles();
|
renderFallbackArticles();
|
||||||
}
|
}
|
||||||
@@ -273,7 +321,9 @@
|
|||||||
|
|
||||||
function renderFallbackArticles() {
|
function renderFallbackArticles() {
|
||||||
const group = currentFallbackGroup();
|
const group = currentFallbackGroup();
|
||||||
const allArticles = ApiClient.asArray(group && group.articles);
|
const allArticles = ApiClient.asArray(group && group.articles).map(function (item) {
|
||||||
|
return normalizeArticle(item, group);
|
||||||
|
});
|
||||||
const startIndex = (currentPage - 1) * pageSize;
|
const startIndex = (currentPage - 1) * pageSize;
|
||||||
const records = allArticles.slice(startIndex, startIndex + pageSize);
|
const records = allArticles.slice(startIndex, startIndex + pageSize);
|
||||||
renderArticles(records);
|
renderArticles(records);
|
||||||
@@ -291,13 +341,16 @@
|
|||||||
}
|
}
|
||||||
$.each(list, function (i, item) {
|
$.each(list, function (i, item) {
|
||||||
let displayDate = "";
|
let displayDate = "";
|
||||||
if (item.createTime) displayDate = item.createTime.substring(0, 10);
|
const article = normalizeArticle(item);
|
||||||
|
const dateText = article.dateText || "";
|
||||||
|
const title = article.title || "无标题";
|
||||||
|
if (dateText) displayDate = String(dateText).substring(0, 10);
|
||||||
$container.append(
|
$container.append(
|
||||||
'<div class="cxz-article-item">' +
|
'<div class="cxz-article-item">' +
|
||||||
'<a href="' +
|
'<a href="' +
|
||||||
CommonUtil.freeArticleHref(item.id || "") +
|
CommonUtil.freeArticleHref(article.id || "") +
|
||||||
'" target="_blank">' +
|
'" target="_blank">' +
|
||||||
escapeHtml(item.title || "无标题") +
|
escapeHtml(title) +
|
||||||
"</a>" +
|
"</a>" +
|
||||||
'<span class="cxz-article-date">' +
|
'<span class="cxz-article-date">' +
|
||||||
escapeHtml(displayDate) +
|
escapeHtml(displayDate) +
|
||||||
@@ -308,6 +361,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderPagination(total) {
|
function renderPagination(total) {
|
||||||
|
$("#pagination").empty();
|
||||||
|
if (!total || total <= pageSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
layui.laypage.render({
|
layui.laypage.render({
|
||||||
elem: "pagination",
|
elem: "pagination",
|
||||||
count: total,
|
count: total,
|
||||||
|
|||||||
+9
-4
@@ -66,9 +66,10 @@
|
|||||||
<div class="page-title-bar">
|
<div class="page-title-bar">
|
||||||
<div class="page-title">排列三走势图</div>
|
<div class="page-title">排列三走势图</div>
|
||||||
<div class="qishu">
|
<div class="qishu">
|
||||||
|
<div class="qishu-but" data-count="10">10期</div>
|
||||||
|
<div class="qishu-but" data-count="20">20期</div>
|
||||||
<div class="qishu-but active" data-count="30">30期</div>
|
<div class="qishu-but active" data-count="30">30期</div>
|
||||||
<div class="qishu-but" data-count="60">60期</div>
|
<div class="qishu-but" data-count="60">60期</div>
|
||||||
<div class="qishu-but" data-count="100">100期</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -207,9 +208,12 @@
|
|||||||
var parsed = parseCode(item.openCode);
|
var parsed = parseCode(item.openCode);
|
||||||
var type = getType(parsed.hundred, parsed.ten, parsed.unit);
|
var type = getType(parsed.hundred, parsed.ten, parsed.unit);
|
||||||
|
|
||||||
html += "<tr>";
|
html +=
|
||||||
|
r > 0 && r % 10 === 0
|
||||||
|
? '<tr class="trend-period-separator">'
|
||||||
|
: "<tr>";
|
||||||
html += '<td class="col-issue">' + item.issue + "</td>";
|
html += '<td class="col-issue">' + item.issue + "</td>";
|
||||||
html += '<td class="col-code">' + item.openCode + "</td>";
|
html += '<td class="col-code">' + CommonUtil.renderTrendOpenCode(item.openCode, { mode: "position" }) + "</td>";
|
||||||
|
|
||||||
var typeNames = ["baozi", "zu3", "zu6"];
|
var typeNames = ["baozi", "zu3", "zu6"];
|
||||||
var typeLabels = ["豹子", "组三", "组六"];
|
var typeLabels = ["豹子", "组三", "组六"];
|
||||||
@@ -384,7 +388,8 @@
|
|||||||
function loadData() {
|
function loadData() {
|
||||||
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "pl3" }).then(function (res) {
|
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "pl3" }).then(function (res) {
|
||||||
if (res && res.data) {
|
if (res && res.data) {
|
||||||
allData = res.data;
|
allData = Array.isArray(res.data) ? res.data : (res.data && (res.data.records || res.data.rows || res.data.list || res.data.data)) || [];
|
||||||
|
if (!Array.isArray(allData)) allData = [];
|
||||||
renderData(currentCount);
|
renderData(currentCount);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+9
-4
@@ -66,9 +66,10 @@
|
|||||||
<div class="page-title-bar">
|
<div class="page-title-bar">
|
||||||
<div class="page-title">排列五走势图</div>
|
<div class="page-title">排列五走势图</div>
|
||||||
<div class="qishu">
|
<div class="qishu">
|
||||||
|
<div class="qishu-but" data-count="10">10期</div>
|
||||||
|
<div class="qishu-but" data-count="20">20期</div>
|
||||||
<div class="qishu-but active" data-count="30">30期</div>
|
<div class="qishu-but active" data-count="30">30期</div>
|
||||||
<div class="qishu-but" data-count="60">60期</div>
|
<div class="qishu-but" data-count="60">60期</div>
|
||||||
<div class="qishu-but" data-count="100">100期</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -204,9 +205,12 @@
|
|||||||
var item = data[r];
|
var item = data[r];
|
||||||
var nums = parseCode(item.openCode);
|
var nums = parseCode(item.openCode);
|
||||||
|
|
||||||
html += "<tr>";
|
html +=
|
||||||
|
r > 0 && r % 10 === 0
|
||||||
|
? '<tr class="trend-period-separator">'
|
||||||
|
: "<tr>";
|
||||||
html += '<td class="col-issue">' + item.issue + "</td>";
|
html += '<td class="col-issue">' + item.issue + "</td>";
|
||||||
html += '<td class="col-code">' + item.openCode + "</td>";
|
html += '<td class="col-code">' + CommonUtil.renderTrendOpenCode(item.openCode, { mode: "position" }) + "</td>";
|
||||||
|
|
||||||
for (var p = 0; p < POS_COUNT; p++) {
|
for (var p = 0; p < POS_COUNT; p++) {
|
||||||
var val = nums[p] !== undefined ? nums[p] : -1;
|
var val = nums[p] !== undefined ? nums[p] : -1;
|
||||||
@@ -313,7 +317,8 @@
|
|||||||
function loadData() {
|
function loadData() {
|
||||||
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "pl5" }).then(function (res) {
|
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "pl5" }).then(function (res) {
|
||||||
if (res && res.data) {
|
if (res && res.data) {
|
||||||
allData = res.data;
|
allData = Array.isArray(res.data) ? res.data : (res.data && (res.data.records || res.data.rows || res.data.list || res.data.data)) || [];
|
||||||
|
if (!Array.isArray(allData)) allData = [];
|
||||||
renderData(currentCount);
|
renderData(currentCount);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+20
-29
@@ -66,9 +66,10 @@
|
|||||||
<div class="page-title-bar">
|
<div class="page-title-bar">
|
||||||
<div class="page-title">七乐彩走势图</div>
|
<div class="page-title">七乐彩走势图</div>
|
||||||
<div class="qishu">
|
<div class="qishu">
|
||||||
|
<div class="qishu-but" data-count="10">10期</div>
|
||||||
|
<div class="qishu-but" data-count="20">20期</div>
|
||||||
<div class="qishu-but active" data-count="30">30期</div>
|
<div class="qishu-but active" data-count="30">30期</div>
|
||||||
<div class="qishu-but" data-count="60">60期</div>
|
<div class="qishu-but" data-count="60">60期</div>
|
||||||
<div class="qishu-but" data-count="100">100期</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -78,10 +79,9 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td rowspan="2" class="col-issue">期号</td>
|
<td rowspan="2" class="col-issue">期号</td>
|
||||||
<td rowspan="2" class="col-code">开奖号</td>
|
<td rowspan="2" class="col-code">开奖号</td>
|
||||||
<td colspan="10" class="zone-z1">一区(01-10)</td>
|
<td colspan="10" class="zone-z1">一区</td>
|
||||||
<td colspan="10" class="zone-z2">二区(11-20)</td>
|
<td colspan="10" class="zone-z2">二区</td>
|
||||||
<td colspan="10" class="zone-z3">三区(21-30)</td>
|
<td colspan="10" class="zone-z3">三区</td>
|
||||||
<td colspan="1" class="zone-sp">特别号</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr id="header-row"></tr>
|
<tr id="header-row"></tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -179,8 +179,6 @@
|
|||||||
for (var i = 1; i <= MAIN_MAX; i++) {
|
for (var i = 1; i <= MAIN_MAX; i++) {
|
||||||
html += "<td>" + (i < 10 ? "0" + i : i) + "</td>";
|
html += "<td>" + (i < 10 ? "0" + i : i) + "</td>";
|
||||||
}
|
}
|
||||||
// Special number column
|
|
||||||
html += "<td>特</td>";
|
|
||||||
$("#header-row").html(html);
|
$("#header-row").html(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,12 +211,20 @@
|
|||||||
specialSet[n] = true;
|
specialSet[n] = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
html += "<tr>";
|
html +=
|
||||||
|
r > 0 && r % 10 === 0
|
||||||
|
? '<tr class="trend-period-separator">'
|
||||||
|
: "<tr>";
|
||||||
html += '<td class="col-issue">' + item.issue + "</td>";
|
html += '<td class="col-issue">' + item.issue + "</td>";
|
||||||
html += '<td class="col-code">' + item.openCode + "</td>";
|
html += '<td class="col-code">' + CommonUtil.renderTrendOpenCode(item.openCode, { mode: "qlc" }) + "</td>";
|
||||||
|
|
||||||
for (var i = 1; i <= MAIN_MAX; i++) {
|
for (var i = 1; i <= MAIN_MAX; i++) {
|
||||||
if (mainSet[i]) {
|
if (specialSet[i]) {
|
||||||
|
html +=
|
||||||
|
'<td><span class="hit-blue">' +
|
||||||
|
(i < 10 ? "0" + i : i) +
|
||||||
|
"</span></td>";
|
||||||
|
} else if (mainSet[i]) {
|
||||||
html +=
|
html +=
|
||||||
'<td><span class="hit-main">' +
|
'<td><span class="hit-main">' +
|
||||||
(i < 10 ? "0" + i : i) +
|
(i < 10 ? "0" + i : i) +
|
||||||
@@ -230,17 +236,6 @@
|
|||||||
"</span></td>";
|
"</span></td>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Show special number
|
|
||||||
if (parsed.specials.length > 0) {
|
|
||||||
html +=
|
|
||||||
'<td><span class="hit-special">' +
|
|
||||||
(parsed.specials[0] < 10
|
|
||||||
? "0" + parsed.specials[0]
|
|
||||||
: parsed.specials[0]) +
|
|
||||||
"</span></td>";
|
|
||||||
} else {
|
|
||||||
html += "<td>-</td>";
|
|
||||||
}
|
|
||||||
html += "</tr>";
|
html += "</tr>";
|
||||||
}
|
}
|
||||||
html += buildStats(data);
|
html += buildStats(data);
|
||||||
@@ -299,11 +294,8 @@
|
|||||||
|
|
||||||
function buildStats(data) {
|
function buildStats(data) {
|
||||||
var ms = calcStats(data, MAIN_MAX, function (d) {
|
var ms = calcStats(data, MAIN_MAX, function (d) {
|
||||||
return parseCode(d.openCode).mains;
|
var parsed = parseCode(d.openCode);
|
||||||
});
|
return parsed.mains.concat(parsed.specials);
|
||||||
// Special number stats (1-30 range for special)
|
|
||||||
var ss = calcStats(data, MAIN_MAX, function (d) {
|
|
||||||
return parseCode(d.openCode).specials;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var labels = [
|
var labels = [
|
||||||
@@ -325,8 +317,6 @@
|
|||||||
"</td>";
|
"</td>";
|
||||||
for (var i = 1; i <= MAIN_MAX; i++)
|
for (var i = 1; i <= MAIN_MAX; i++)
|
||||||
stats += "<td>" + ms[keys[li]][i] + "</td>";
|
stats += "<td>" + ms[keys[li]][i] + "</td>";
|
||||||
// Special column stats
|
|
||||||
stats += "<td>-</td>";
|
|
||||||
stats += "</tr>";
|
stats += "</tr>";
|
||||||
}
|
}
|
||||||
return stats;
|
return stats;
|
||||||
@@ -335,7 +325,8 @@
|
|||||||
function loadData() {
|
function loadData() {
|
||||||
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "qlc" }).then(function (res) {
|
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "qlc" }).then(function (res) {
|
||||||
if (res && res.data) {
|
if (res && res.data) {
|
||||||
allData = res.data;
|
allData = Array.isArray(res.data) ? res.data : (res.data && (res.data.records || res.data.rows || res.data.list || res.data.data)) || [];
|
||||||
|
if (!Array.isArray(allData)) allData = [];
|
||||||
renderData(currentCount);
|
renderData(currentCount);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+22
-12
@@ -66,9 +66,10 @@
|
|||||||
<div class="page-title-bar">
|
<div class="page-title-bar">
|
||||||
<div class="page-title">七星彩走势图</div>
|
<div class="page-title">七星彩走势图</div>
|
||||||
<div class="qishu">
|
<div class="qishu">
|
||||||
|
<div class="qishu-but" data-count="10">10期</div>
|
||||||
|
<div class="qishu-but" data-count="20">20期</div>
|
||||||
<div class="qishu-but active" data-count="30">30期</div>
|
<div class="qishu-but active" data-count="30">30期</div>
|
||||||
<div class="qishu-but" data-count="60">60期</div>
|
<div class="qishu-but" data-count="60">60期</div>
|
||||||
<div class="qishu-but" data-count="100">100期</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -178,13 +179,13 @@
|
|||||||
var currentCount = 30;
|
var currentCount = 30;
|
||||||
var allData = [];
|
var allData = [];
|
||||||
var hitClasses = [
|
var hitClasses = [
|
||||||
"hit-p1",
|
"hit-red",
|
||||||
"hit-p2",
|
"hit-red",
|
||||||
"hit-p3",
|
"hit-red",
|
||||||
"hit-p4",
|
"hit-red",
|
||||||
"hit-p5",
|
"hit-red",
|
||||||
"hit-p6",
|
"hit-red",
|
||||||
"hit-p7",
|
"hit-blue",
|
||||||
];
|
];
|
||||||
var posMaxes = [9, 9, 9, 9, 9, 9, LAST_MAX]; // max value for each position
|
var posMaxes = [9, 9, 9, 9, 9, 9, LAST_MAX]; // max value for each position
|
||||||
|
|
||||||
@@ -198,7 +199,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parseCode(code) {
|
function parseCode(code) {
|
||||||
var nums = code.split(",").map(function (n) {
|
var value = String(code || "");
|
||||||
|
var parts = value.split(/[,+\s、,]+/).filter(Boolean);
|
||||||
|
if (parts.length <= 1) {
|
||||||
|
parts = value.replace(/[^\d]/g, "").split("").filter(Boolean);
|
||||||
|
}
|
||||||
|
var nums = parts.map(function (n) {
|
||||||
return parseInt(n);
|
return parseInt(n);
|
||||||
});
|
});
|
||||||
return nums;
|
return nums;
|
||||||
@@ -211,9 +217,12 @@
|
|||||||
var item = data[r];
|
var item = data[r];
|
||||||
var nums = parseCode(item.openCode);
|
var nums = parseCode(item.openCode);
|
||||||
|
|
||||||
html += "<tr>";
|
html +=
|
||||||
|
r > 0 && r % 10 === 0
|
||||||
|
? '<tr class="trend-period-separator">'
|
||||||
|
: "<tr>";
|
||||||
html += '<td class="col-issue">' + item.issue + "</td>";
|
html += '<td class="col-issue">' + item.issue + "</td>";
|
||||||
html += '<td class="col-code">' + item.openCode + "</td>";
|
html += '<td class="col-code">' + CommonUtil.renderTrendOpenCode(item.openCode, { mode: "qxc" }) + "</td>";
|
||||||
|
|
||||||
for (var p = 0; p < POS_COUNT; p++) {
|
for (var p = 0; p < POS_COUNT; p++) {
|
||||||
var max = posMaxes[p];
|
var max = posMaxes[p];
|
||||||
@@ -323,7 +332,8 @@
|
|||||||
function loadData() {
|
function loadData() {
|
||||||
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "qxc" }).then(function (res) {
|
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "qxc" }).then(function (res) {
|
||||||
if (res && res.data) {
|
if (res && res.data) {
|
||||||
allData = res.data;
|
allData = Array.isArray(res.data) ? res.data : (res.data && (res.data.records || res.data.rows || res.data.list || res.data.data)) || [];
|
||||||
|
if (!Array.isArray(allData)) allData = [];
|
||||||
renderData(currentCount);
|
renderData(currentCount);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+49
-9
@@ -653,8 +653,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getInviteCodeFromQuery() {
|
function getInviteCodeFromQuery() {
|
||||||
const params = new URLSearchParams(window.location.search);
|
return (
|
||||||
return params.get("inviteCode") || params.get("referralCode") || "";
|
AuthPageUtil.captureReferralInviteFromUrl() ||
|
||||||
|
AuthPageUtil.getPendingReferralInviteCode()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadReferralBindPreview() {
|
function loadReferralBindPreview() {
|
||||||
@@ -694,6 +696,49 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveRegisterTokenIfPresent(res) {
|
||||||
|
const data = ApiClient.pickData(res) || {};
|
||||||
|
if (!data || typeof data !== "object") return false;
|
||||||
|
|
||||||
|
const token = data.access_token || data.token || "";
|
||||||
|
if (!token) return false;
|
||||||
|
|
||||||
|
localStorage.setItem("token", token);
|
||||||
|
localStorage.setItem("userInfo", JSON.stringify(data));
|
||||||
|
sessionStorage.removeItem("authExpiredRedirecting");
|
||||||
|
sessionStorage.removeItem("authExpiredMessage");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectAfterRegister(hasToken) {
|
||||||
|
if (hasToken) {
|
||||||
|
window.location.href =
|
||||||
|
typeof CommonUtil !== "undefined"
|
||||||
|
? CommonUtil.siteHref("/index.html")
|
||||||
|
: "index.html";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = "login.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishRegisterSuccess(res) {
|
||||||
|
const hasToken = saveRegisterTokenIfPresent(res);
|
||||||
|
layerInstance.msg(
|
||||||
|
hasToken ? "注册成功,即将跳转到首页" : "注册成功,即将前往登录",
|
||||||
|
{ icon: 1, time: 1200 },
|
||||||
|
function () {
|
||||||
|
AuthPageUtil.bindPendingReferralInviteCode()
|
||||||
|
.then(function () {
|
||||||
|
redirectAfterRegister(hasToken);
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
redirectAfterRegister(hasToken);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function register() {
|
function register() {
|
||||||
$("#registerBtn").prop("disabled", true).text("注册中...");
|
$("#registerBtn").prop("disabled", true).text("注册中...");
|
||||||
ApiClient.post(ApiClient.API.authRegister, buildRegisterData(), {
|
ApiClient.post(ApiClient.API.authRegister, buildRegisterData(), {
|
||||||
@@ -701,13 +746,7 @@
|
|||||||
})
|
})
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
if (ApiClient.isSuccess(res)) {
|
if (ApiClient.isSuccess(res)) {
|
||||||
layerInstance.msg(
|
finishRegisterSuccess(res);
|
||||||
"注册成功,即将前往登录",
|
|
||||||
{ icon: 1, time: 1200 },
|
|
||||||
function () {
|
|
||||||
window.location.href = "login.html";
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
layerInstance.msg((res && res.msg) || "注册失败,请稍后重试", {
|
layerInstance.msg((res && res.msg) || "注册失败,请稍后重试", {
|
||||||
@@ -726,6 +765,7 @@
|
|||||||
|
|
||||||
layui.use(["layer", "jquery"], function () {
|
layui.use(["layer", "jquery"], function () {
|
||||||
layerInstance = layui.layer;
|
layerInstance = layui.layer;
|
||||||
|
AuthPageUtil.captureReferralInviteFromUrl();
|
||||||
loadVerifyConfig().then(refreshCaptcha);
|
loadVerifyConfig().then(refreshCaptcha);
|
||||||
loadReferralBindPreview();
|
loadReferralBindPreview();
|
||||||
$("#sendSmsBtn").on("click", sendSmsCode);
|
$("#sendSmsBtn").on("click", sendSmsCode);
|
||||||
|
|||||||
+9
-4
@@ -66,9 +66,10 @@
|
|||||||
<div class="page-title-bar">
|
<div class="page-title-bar">
|
||||||
<div class="page-title">双色球走势图</div>
|
<div class="page-title">双色球走势图</div>
|
||||||
<div class="qishu">
|
<div class="qishu">
|
||||||
|
<div class="qishu-but" data-count="10">10期</div>
|
||||||
|
<div class="qishu-but" data-count="20">20期</div>
|
||||||
<div class="qishu-but active" data-count="30">30期</div>
|
<div class="qishu-but active" data-count="30">30期</div>
|
||||||
<div class="qishu-but" data-count="60">60期</div>
|
<div class="qishu-but" data-count="60">60期</div>
|
||||||
<div class="qishu-but" data-count="100">100期</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -215,11 +216,14 @@
|
|||||||
blueSet[n] = true;
|
blueSet[n] = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
html += "<tr>";
|
html +=
|
||||||
|
r > 0 && r % 10 === 0
|
||||||
|
? '<tr class="trend-period-separator">'
|
||||||
|
: "<tr>";
|
||||||
html += '<td class="col-issue">' + item.issue + "</td>";
|
html += '<td class="col-issue">' + item.issue + "</td>";
|
||||||
html +=
|
html +=
|
||||||
'<td class="col-code">' +
|
'<td class="col-code">' +
|
||||||
item.openCode.replace(/\+/g, "+") +
|
CommonUtil.renderTrendOpenCode(item.openCode, { mode: "ssq" }) +
|
||||||
"</td>";
|
"</td>";
|
||||||
|
|
||||||
for (var i = 1; i <= RED_MAX; i++) {
|
for (var i = 1; i <= RED_MAX; i++) {
|
||||||
@@ -398,7 +402,8 @@
|
|||||||
function loadData() {
|
function loadData() {
|
||||||
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "ssq" }).then(function (res) {
|
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "ssq" }).then(function (res) {
|
||||||
if (res && res.data) {
|
if (res && res.data) {
|
||||||
allData = res.data;
|
allData = Array.isArray(res.data) ? res.data : (res.data && (res.data.records || res.data.rows || res.data.list || res.data.data)) || [];
|
||||||
|
if (!Array.isArray(allData)) allData = [];
|
||||||
renderData(currentCount);
|
renderData(currentCount);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+5
-3
@@ -1,4 +1,4 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
@@ -542,13 +542,15 @@
|
|||||||
if (!isSuccess(res)) throw new Error(res.msg || "收款码上传失败");
|
if (!isSuccess(res)) throw new Error(res.msg || "收款码上传失败");
|
||||||
const data = pickData(res) || {};
|
const data = pickData(res) || {};
|
||||||
const qrCodeFileId = fieldValue(data, ["qrCodeFileId", "qrcodeFileId", "fileId", "ossId", "id"]) || fieldValue(res, ["qrCodeFileId", "qrcodeFileId", "fileId", "ossId", "id"]);
|
const qrCodeFileId = fieldValue(data, ["qrCodeFileId", "qrcodeFileId", "fileId", "ossId", "id"]) || fieldValue(res, ["qrCodeFileId", "qrcodeFileId", "fileId", "ossId", "id"]);
|
||||||
if (!qrCodeFileId) throw new Error("收款码上传成功,但未返回二维码文件ID");
|
const qrcodeUrl = fieldValue(data, ["qrcodeUrl", "qrCodeUrl", "fileUrl", "url", "ossUrl"]) || fieldValue(res, ["qrcodeUrl", "qrCodeUrl", "fileUrl", "url", "ossUrl"]);
|
||||||
return { qrCodeFileId: qrCodeFileId };
|
if (!qrCodeFileId && !qrcodeUrl) throw new Error("收款码上传成功,但未返回二维码文件信息");
|
||||||
|
return { qrCodeFileId: qrCodeFileId, qrcodeUrl: qrcodeUrl };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendQrcodePayload(payload, uploadData) {
|
function appendQrcodePayload(payload, uploadData) {
|
||||||
if (uploadData && uploadData.qrCodeFileId) payload.qrCodeFileId = uploadData.qrCodeFileId;
|
if (uploadData && uploadData.qrCodeFileId) payload.qrCodeFileId = uploadData.qrCodeFileId;
|
||||||
|
if (uploadData && uploadData.qrcodeUrl) payload.qrcodeUrl = uploadData.qrcodeUrl;
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+67
-33
@@ -139,28 +139,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="wechatBindCard" id="wechat-bind-card">
|
|
||||||
<div class="wechatBindTop">
|
|
||||||
<div class="wechatBindIconBox">
|
|
||||||
<img class="wechatBindIcon" src="../public/img/pay-wechat.png" alt="微信" />
|
|
||||||
</div>
|
|
||||||
<div class="wechatBindInfo">
|
|
||||||
<div class="wechatBindTitle">微信账号</div>
|
|
||||||
<div class="wechatBindDesc" id="wechat-bind-desc">正在检测绑定状态...</div>
|
|
||||||
</div>
|
|
||||||
<div class="wechatBindStateDot"></div>
|
|
||||||
</div>
|
|
||||||
<div class="wechatBindLine"></div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="wechatBindButton"
|
|
||||||
id="wechat-bind-button"
|
|
||||||
onclick="openWeChatBindPopup()"
|
|
||||||
>
|
|
||||||
绑定微信
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<!-- Balance Cards -->
|
<!-- Balance Cards -->
|
||||||
<div class="uc-balance-grid">
|
<div class="uc-balance-grid">
|
||||||
<div class="uc-balance-item">
|
<div class="uc-balance-item">
|
||||||
@@ -535,9 +513,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateExpertActions(data) {
|
function updateExpertActions(data) {
|
||||||
var expert = isExpertUser(data);
|
var state = CommonUtil.getExpertState
|
||||||
$("#expertApplyBtn").toggle(!expert);
|
? CommonUtil.getExpertState(data)
|
||||||
$("#publishArticleBtn").toggle(expert);
|
: { isExpert: isExpertUser(data), isApplying: false };
|
||||||
|
var $applyBtn = $("#expertApplyBtn");
|
||||||
|
var $publishBtn = $("#publishArticleBtn");
|
||||||
|
$publishBtn.toggle(state.isExpert);
|
||||||
|
$applyBtn.toggle(!state.isExpert);
|
||||||
|
if (state.isExpert) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$applyBtn
|
||||||
|
.text(state.isApplying ? "申请审核中" : "专家申请")
|
||||||
|
.prop("disabled", !!state.isApplying);
|
||||||
|
if (state.isApplying) {
|
||||||
|
$applyBtn.removeAttr("onclick");
|
||||||
|
} else {
|
||||||
|
$applyBtn.attr("onclick", "window.location.href = 'apply.html'");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadAccountInfoForWechat() {
|
function loadAccountInfoForWechat() {
|
||||||
@@ -1379,7 +1372,7 @@
|
|||||||
? currentUserData.nickName || currentUserData.username || ""
|
? currentUserData.nickName || currentUserData.username || ""
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
uploadedAvatarUrl = "";
|
uploadedAvatarId = "";
|
||||||
userAvatarFile = null;
|
userAvatarFile = null;
|
||||||
|
|
||||||
var content =
|
var content =
|
||||||
@@ -1422,14 +1415,14 @@
|
|||||||
content: content,
|
content: content,
|
||||||
end: function () {
|
end: function () {
|
||||||
editProfileLayerIndex = null;
|
editProfileLayerIndex = null;
|
||||||
uploadedAvatarUrl = "";
|
uploadedAvatarId = "";
|
||||||
userAvatarFile = null;
|
userAvatarFile = null;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var uploadedAvatarUrl = "";
|
var uploadedAvatarId = "";
|
||||||
var editProfileLayerIndex = null;
|
var editProfileLayerIndex = null;
|
||||||
var userAvatarFile = null;
|
var userAvatarFile = null;
|
||||||
|
|
||||||
@@ -1443,14 +1436,19 @@
|
|||||||
if (!input.files || !input.files[0]) return;
|
if (!input.files || !input.files[0]) return;
|
||||||
|
|
||||||
var file = input.files[0];
|
var file = input.files[0];
|
||||||
if (!/^image\//.test(file.type || "")) {
|
if (!/^(image\/jpeg|image\/png)$/.test(file.type || "")) {
|
||||||
layui.layer.msg("请选择图片文件", { icon: 2 });
|
layui.layer.msg("只支持 JPG/PNG 格式的图片", { icon: 2 });
|
||||||
|
input.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (file.size > 2 * 1024 * 1024) {
|
||||||
|
layui.layer.msg("图片大小不能超过 2MB", { icon: 2 });
|
||||||
input.value = "";
|
input.value = "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
userAvatarFile = file;
|
userAvatarFile = file;
|
||||||
uploadedAvatarUrl = "";
|
uploadedAvatarId = "";
|
||||||
|
|
||||||
var reader = new FileReader();
|
var reader = new FileReader();
|
||||||
reader.onload = function (e) {
|
reader.onload = function (e) {
|
||||||
@@ -1504,31 +1502,67 @@
|
|||||||
if (!avatarId) {
|
if (!avatarId) {
|
||||||
throw new Error("头像上传返回缺少文件编号");
|
throw new Error("头像上传返回缺少文件编号");
|
||||||
}
|
}
|
||||||
uploadedAvatarUrl = avatarId;
|
uploadedAvatarId = avatarId;
|
||||||
return avatarId;
|
return avatarId;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateEditProfileNickname(nickName) {
|
||||||
|
var nicknameWidth = Array.from(nickName).reduce(function (total, char) {
|
||||||
|
return total + (/[\u4e00-\u9fa5\uff00-\uffef]/.test(char) ? 2 : 1);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
if (nickName.length < 2 || nicknameWidth > 12) {
|
||||||
|
layui.layer.msg("昵称长度应为2-6个汉字或2-12个英文", { icon: 2 });
|
||||||
|
$("#editNickName").focus();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function submitEditProfile() {
|
function submitEditProfile() {
|
||||||
var nickName = $("#editNickName").val().trim();
|
var nickName = $("#editNickName").val().trim();
|
||||||
var password = $("#editPassword").val();
|
var password = $("#editPassword").val();
|
||||||
var passwordConfirm = $("#editPasswordConfirm").val();
|
var passwordConfirm = $("#editPasswordConfirm").val();
|
||||||
|
var currentNickName = currentUserData
|
||||||
|
? currentUserData.nickName || currentUserData.username || ""
|
||||||
|
: "";
|
||||||
|
|
||||||
if (!nickName) {
|
if (!nickName) {
|
||||||
layui.layer.msg("昵称不能为空", { icon: 2 });
|
layui.layer.msg("昵称不能为空", { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (nickName !== currentNickName && !validateEditProfileNickname(nickName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((password || passwordConfirm) && (!password || !passwordConfirm)) {
|
||||||
|
layui.layer.msg("请输入并确认新密码", { icon: 2 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (password && password !== passwordConfirm) {
|
if (password && password !== passwordConfirm) {
|
||||||
layui.layer.msg("两次密码输入不一致", { icon: 2 });
|
layui.layer.msg("两次密码输入不一致", { icon: 2 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (password && (password.length < 6 || password.length > 20)) {
|
||||||
|
layui.layer.msg("密码长度应为6-20位", { icon: 2 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var params = { username: nickName };
|
var params = {};
|
||||||
|
if (nickName !== currentNickName) {
|
||||||
|
params.nickName = nickName;
|
||||||
|
}
|
||||||
if (password) {
|
if (password) {
|
||||||
params.password = hex_md5(password);
|
params.password = hex_md5(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!userAvatarFile && Object.keys(params).length === 0) {
|
||||||
|
layui.layer.msg("暂无修改", { icon: 0 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var $submitBtn = $("#editProfileSubmitBtn");
|
var $submitBtn = $("#editProfileSubmitBtn");
|
||||||
$submitBtn.prop("disabled", true).text("保存中...");
|
$submitBtn.prop("disabled", true).text("保存中...");
|
||||||
|
|
||||||
|
|||||||
+48
-44
@@ -1,4 +1,4 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
@@ -90,6 +90,11 @@
|
|||||||
<div class="table-section-title">
|
<div class="table-section-title">
|
||||||
<i class="layui-icon layui-icon-group"></i> 下级用户列表
|
<i class="layui-icon layui-icon-group"></i> 下级用户列表
|
||||||
</div>
|
</div>
|
||||||
|
<div class="promo-filter" id="distStatusFilter">
|
||||||
|
<button class="active" type="button" data-status="0">未转余额</button>
|
||||||
|
<button type="button" data-status="1">已转余额</button>
|
||||||
|
<button type="button" data-status="2">已提现</button>
|
||||||
|
</div>
|
||||||
<table id="distTable" lay-filter="distTable" lay-even></table>
|
<table id="distTable" lay-filter="distTable" lay-even></table>
|
||||||
<div id="distPage"></div>
|
<div id="distPage"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,6 +136,9 @@
|
|||||||
var $ = layui.$;
|
var $ = layui.$;
|
||||||
var table = layui.table;
|
var table = layui.table;
|
||||||
var laypage = layui.laypage;
|
var laypage = layui.laypage;
|
||||||
|
var currentPage = 1;
|
||||||
|
var pageSize = 20;
|
||||||
|
var currentStatus = 0;
|
||||||
|
|
||||||
// Load page config
|
// Load page config
|
||||||
CommonUtil.loadPageConfig({ ads: false, links: false });
|
CommonUtil.loadPageConfig({ ads: false, links: false });
|
||||||
@@ -206,10 +214,11 @@
|
|||||||
function loadDistList(pageNum) {
|
function loadDistList(pageNum) {
|
||||||
currentPage = pageNum;
|
currentPage = pageNum;
|
||||||
ApiClient.get(
|
ApiClient.get(
|
||||||
ApiClient.API.referralInviteList,
|
ApiClient.API.referralRebateList,
|
||||||
{
|
{
|
||||||
pageNum: pageNum,
|
pageNum: pageNum,
|
||||||
pageSize: pageSize,
|
pageSize: pageSize,
|
||||||
|
status: currentStatus,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: CommonUtil.authHeaders(),
|
headers: CommonUtil.authHeaders(),
|
||||||
@@ -217,8 +226,9 @@
|
|||||||
)
|
)
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
renderTable(res.data.records || []);
|
var records = ApiClient.asArray(res.data.records || res.data.rows || res.data.list || res.data);
|
||||||
renderPagination(res.data.total || 0);
|
renderTable(records);
|
||||||
|
renderPagination(res.data.total || res.data.totalRow || records.length || 0);
|
||||||
} else {
|
} else {
|
||||||
layer.msg(res.msg || "获取数据失败", { icon: 2 });
|
layer.msg(res.msg || "获取数据失败", { icon: 2 });
|
||||||
}
|
}
|
||||||
@@ -240,73 +250,60 @@
|
|||||||
elem: "#distTable",
|
elem: "#distTable",
|
||||||
cols: [
|
cols: [
|
||||||
[
|
[
|
||||||
|
{ field: "id", title: "记录ID", width: 140, align: "center" },
|
||||||
{
|
{
|
||||||
field: "nickName",
|
field: "nickName",
|
||||||
title: "昵称",
|
title: "订阅用户",
|
||||||
width: 160,
|
|
||||||
align: "center",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: "phoneNumber",
|
|
||||||
title: "手机号",
|
|
||||||
width: 160,
|
width: 160,
|
||||||
align: "center",
|
align: "center",
|
||||||
templet: function (d) {
|
templet: function (d) {
|
||||||
if (d.phoneNumber) {
|
return CommonUtil.escapeHtml(d.nickName || d.userName || d.inviteeName || "--");
|
||||||
return d.phoneNumber.replace(
|
|
||||||
/(\d{3})\d{4}(\d{4})/,
|
|
||||||
"$1****$2",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return "--";
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "level",
|
field: "articleTitle",
|
||||||
title: "层级",
|
title: "文章/彩种",
|
||||||
width: 100,
|
minWidth: 180,
|
||||||
align: "center",
|
align: "center",
|
||||||
templet: function (d) {
|
templet: function (d) {
|
||||||
return d.level === 1
|
return CommonUtil.escapeHtml(d.menuName || d.articleTitle || d.title || "--");
|
||||||
? '<span style="color:#102b6a;font-weight:600;">一级</span>'
|
|
||||||
: '<span style="color:#5b2c8e;font-weight:600;">二级</span>';
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "createTime",
|
field: "expertName",
|
||||||
title: "注册时间",
|
title: "专家",
|
||||||
width: 180,
|
width: 140,
|
||||||
align: "center",
|
align: "center",
|
||||||
templet: function (d) {
|
templet: function (d) {
|
||||||
return d.createTime
|
return CommonUtil.escapeHtml(d.nickNameExpert || d.expertName || "--");
|
||||||
? d.createTime.substring(0, 19).replace("T", " ")
|
|
||||||
: "--";
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "totalRecharge",
|
field: "amount",
|
||||||
title: "累计充值",
|
title: "订阅价格",
|
||||||
width: 130,
|
width: 130,
|
||||||
align: "center",
|
align: "center",
|
||||||
templet: function (d) {
|
templet: function (d) {
|
||||||
return (
|
return CommonUtil.escapeHtml(d.subscriptionPrice || d.orderAmount || d.amount || "0.00");
|
||||||
'<span style="color:#f15a22;font-weight:600;">' +
|
|
||||||
(d.totalRecharge || 0).toFixed(2) +
|
|
||||||
"</span>"
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "distributionIncome",
|
field: "rebateMoney",
|
||||||
title: "分销收益",
|
title: "分销金额",
|
||||||
width: 130,
|
width: 130,
|
||||||
align: "center",
|
align: "center",
|
||||||
templet: function (d) {
|
templet: function (d) {
|
||||||
return (
|
return '<span style="color:#f15a22;font-weight:600;">' + CommonUtil.escapeHtml(d.rebateMoney || d.rebateAmount || d.money || "0.00") + "</span>";
|
||||||
'<span style="color:#fdb933;font-weight:600;">' +
|
},
|
||||||
(d.distributionIncome || 0).toFixed(2) +
|
},
|
||||||
"</span>"
|
{
|
||||||
);
|
field: "status",
|
||||||
|
title: "状态",
|
||||||
|
width: 130,
|
||||||
|
align: "center",
|
||||||
|
templet: function (d) {
|
||||||
|
var statusMap = { 0: "未转余额", 1: "已转余额", 2: "已提现" };
|
||||||
|
return CommonUtil.escapeHtml(d.statusText || statusMap[d.status] || statusMap[d.withdrawStatus] || "--");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -333,6 +330,13 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$("#distStatusFilter").on("click", "button", function () {
|
||||||
|
$("#distStatusFilter button").removeClass("active");
|
||||||
|
$(this).addClass("active");
|
||||||
|
currentStatus = Number($(this).data("status")) || 0;
|
||||||
|
loadDistList(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -652,7 +652,7 @@ body {
|
|||||||
|
|
||||||
.draw-card__balls--compact {
|
.draw-card__balls--compact {
|
||||||
gap: 3px;
|
gap: 3px;
|
||||||
width: calc(22px * 10 + 3px * 9);
|
width: calc(25px * 10 + 3px * 9);
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -470,8 +470,8 @@ select {
|
|||||||
.ball--compact {
|
.ball--compact {
|
||||||
flex-basis: 22px;
|
flex-basis: 22px;
|
||||||
width: 22px;
|
width: 22px;
|
||||||
min-width: 22px;
|
min-width: 25px;
|
||||||
height: 22px;
|
height: 25px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+116
-3
@@ -8,7 +8,7 @@
|
|||||||
1. Breadcrumb Bar
|
1. Breadcrumb Bar
|
||||||
---------------------------------------------------------- */
|
---------------------------------------------------------- */
|
||||||
.breadcrumb-bar {
|
.breadcrumb-bar {
|
||||||
max-width: 1200px;
|
max-width: 1420px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 14px 20px 0;
|
padding: 14px 20px 0;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
2. Page Title Bar & Period Selector
|
2. Page Title Bar & Period Selector
|
||||||
---------------------------------------------------------- */
|
---------------------------------------------------------- */
|
||||||
.page-title-bar {
|
.page-title-bar {
|
||||||
max-width: 1200px;
|
max-width: 1420px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 10px 20px 14px;
|
padding: 10px 20px 14px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
3. Table Wrapper & Horizontal Scroll
|
3. Table Wrapper & Horizontal Scroll
|
||||||
---------------------------------------------------------- */
|
---------------------------------------------------------- */
|
||||||
.table-wrapper {
|
.table-wrapper {
|
||||||
max-width: 1200px;
|
max-width: 1420px;
|
||||||
margin: 12px auto 40px;
|
margin: 12px auto 40px;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
@@ -191,6 +191,10 @@
|
|||||||
background: rgba(16, 43, 106, 0.05);
|
background: rgba(16, 43, 106, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.trend-table tbody tr.trend-period-separator td {
|
||||||
|
border-top: 2px solid rgba(245, 95, 122, 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------
|
/* ----------------------------------------------------------
|
||||||
5. Issue & Code Columns (sticky left)
|
5. Issue & Code Columns (sticky left)
|
||||||
---------------------------------------------------------- */
|
---------------------------------------------------------- */
|
||||||
@@ -243,6 +247,56 @@
|
|||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.trend-open-code {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-open-number {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 10px;
|
||||||
|
height: 18px;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 18px;
|
||||||
|
background: transparent !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
text-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-open-red,
|
||||||
|
.trend-open-pos1,
|
||||||
|
.trend-open-pos2,
|
||||||
|
.trend-open-pos3,
|
||||||
|
.trend-open-pos4,
|
||||||
|
.trend-open-pos5 {
|
||||||
|
color: rgb(245, 95, 122);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-open-blue {
|
||||||
|
color: rgb(64, 137, 226);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-open-separator {
|
||||||
|
color: var(--primary);
|
||||||
|
font-weight: 800;
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-open-empty {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------
|
/* ----------------------------------------------------------
|
||||||
6. Zone Header Colors
|
6. Zone Header Colors
|
||||||
---------------------------------------------------------- */
|
---------------------------------------------------------- */
|
||||||
@@ -357,6 +411,65 @@
|
|||||||
box-shadow: 0 2px 6px rgba(241, 90, 34, 0.35);
|
box-shadow: 0 2px 6px rgba(241, 90, 34, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.NumberText {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
color: #c5c5c5;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.NumberText.redok,
|
||||||
|
.typeText.redok {
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #f15a22, #e74c3c);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
|
||||||
|
box-shadow: 0 2px 6px rgba(241, 90, 34, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.NumberText.blueok,
|
||||||
|
.typeText.blueok {
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #102b6a, #3b6fd4);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
|
||||||
|
box-shadow: 0 2px 6px rgba(16, 43, 106, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-table .hit-hundred,
|
||||||
|
.trend-table .hit-ten,
|
||||||
|
.trend-table .hit-unit,
|
||||||
|
.trend-table .hit-pos1,
|
||||||
|
.trend-table .hit-pos2,
|
||||||
|
.trend-table .hit-pos3,
|
||||||
|
.trend-table .hit-pos4,
|
||||||
|
.trend-table .hit-pos5,
|
||||||
|
.trend-table .hit-p1,
|
||||||
|
.trend-table .hit-p2,
|
||||||
|
.trend-table .hit-p3,
|
||||||
|
.trend-table .hit-p4,
|
||||||
|
.trend-table .hit-p5,
|
||||||
|
.trend-table .hit-p6,
|
||||||
|
.trend-table .hit-p7,
|
||||||
|
.trend-table .hit-num,
|
||||||
|
.trend-table .hit-num.g2,
|
||||||
|
.trend-table .hit-main {
|
||||||
|
background: linear-gradient(135deg, #f15a22, #e74c3c);
|
||||||
|
box-shadow: 0 2px 6px rgba(241, 90, 34, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-table .hit-blue,
|
||||||
|
.trend-table .hit-special {
|
||||||
|
background: linear-gradient(135deg, #102b6a, #3b6fd4);
|
||||||
|
box-shadow: 0 2px 6px rgba(16, 43, 106, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
.hit-blue {
|
.hit-blue {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -407,6 +407,13 @@
|
|||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.uc-btn:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.65;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
.uc-btn-publish {
|
.uc-btn-publish {
|
||||||
background: var(--bg-light);
|
background: var(--bg-light);
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ const $ = layui.$;
|
|||||||
let isSubmittingPayArticle = false;
|
let isSubmittingPayArticle = false;
|
||||||
let selections = [];
|
let selections = [];
|
||||||
let stepsConfig = [];
|
let stepsConfig = [];
|
||||||
const REQUIRED_DAILY_FREE_ARTICLES = 2;
|
|
||||||
// 页面加载完成后获取首页信息
|
// 页面加载完成后获取首页信息
|
||||||
layui.$(document).ready(function () {
|
layui.$(document).ready(function () {
|
||||||
CommonUtil.loadPageConfig({ ads: false, links: false });
|
CommonUtil.loadPageConfig({ ads: false, links: false });
|
||||||
@@ -33,149 +32,23 @@ const $ = layui.$;
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 判断布尔字段,兼容后端可能返回的 1/true/"true"。
|
// 付费文章发布权限以 mine/summary 返回字段为准。
|
||||||
function isTruthy(value) {
|
|
||||||
return (
|
|
||||||
value === true ||
|
|
||||||
value === 1 ||
|
|
||||||
value === "1" ||
|
|
||||||
String(value).toLowerCase() === "true"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function padDatePart(value) {
|
|
||||||
return String(value).padStart(2, "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatLocalDate(date) {
|
|
||||||
return (
|
|
||||||
date.getFullYear() +
|
|
||||||
"-" +
|
|
||||||
padDatePart(date.getMonth() + 1) +
|
|
||||||
"-" +
|
|
||||||
padDatePart(date.getDate())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getArticleDateText(value) {
|
|
||||||
if (!value) return "";
|
|
||||||
if (typeof value === "number") {
|
|
||||||
const numericDate = new Date(value);
|
|
||||||
return isNaN(numericDate.getTime())
|
|
||||||
? ""
|
|
||||||
: formatLocalDate(numericDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = String(value).trim();
|
|
||||||
const match = text.match(/^(\d{4})[-/](\d{1,2})[-/](\d{1,2})/);
|
|
||||||
if (match) {
|
|
||||||
return (
|
|
||||||
match[1] + "-" + padDatePart(match[2]) + "-" + padDatePart(match[3])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedDate = new Date(text.replace(/-/g, "/"));
|
|
||||||
return isNaN(parsedDate.getTime()) ? "" : formatLocalDate(parsedDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPublishedFreeArticle(item) {
|
|
||||||
if (!item) return false;
|
|
||||||
|
|
||||||
const statusText = String(
|
|
||||||
item.statusText ||
|
|
||||||
item.auditStatusText ||
|
|
||||||
item.publishStatusText ||
|
|
||||||
item.stateText ||
|
|
||||||
"",
|
|
||||||
).toLowerCase();
|
|
||||||
if (/已发布|审核通过|通过|published|approved|pass/.test(statusText)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const status =
|
|
||||||
item.status != null
|
|
||||||
? item.status
|
|
||||||
: item.auditStatus != null
|
|
||||||
? item.auditStatus
|
|
||||||
: item.publishStatus;
|
|
||||||
return status === true || status === 1 || status === "1";
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTodayPublishedFreeCount(rows) {
|
|
||||||
const today = formatLocalDate(new Date());
|
|
||||||
return ApiClient.asArray(rows).filter(function (item) {
|
|
||||||
const articleDate = getArticleDateText(
|
|
||||||
item.createTime ||
|
|
||||||
item.create_time ||
|
|
||||||
item.publishTime ||
|
|
||||||
item.publish_time ||
|
|
||||||
item.auditTime ||
|
|
||||||
item.audit_time ||
|
|
||||||
item.updateTime ||
|
|
||||||
item.update_time ||
|
|
||||||
item.createdAt,
|
|
||||||
);
|
|
||||||
return articleDate === today && isPublishedFreeArticle(item);
|
|
||||||
}).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadTodayPublishedFreeCount() {
|
|
||||||
return ApiClient.get(ApiClient.API.mineFreeArticleList, {
|
|
||||||
pageNum: 1,
|
|
||||||
pageSize: 100,
|
|
||||||
})
|
|
||||||
.then(function (res) {
|
|
||||||
if (!ApiClient.isSuccess(res)) return 0;
|
|
||||||
|
|
||||||
const data = res.data || res;
|
|
||||||
const rows = ApiClient.asArray(
|
|
||||||
data.rows || data.records || data.list || data.data || data,
|
|
||||||
);
|
|
||||||
return getTodayPublishedFreeCount(rows);
|
|
||||||
})
|
|
||||||
.catch(function () {
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 付费文章发布权限:专家身份 + 当天 2 篇审核通过/已发布的免费文章。
|
|
||||||
function checkPaidPublishPermission() {
|
function checkPaidPublishPermission() {
|
||||||
return ApiClient.get(ApiClient.API.mineSummary)
|
return ApiClient.get(ApiClient.API.mineSummary)
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
if (!ApiClient.isSuccess(res) || !res.data) {
|
if (
|
||||||
|
!ApiClient.isSuccess(res) ||
|
||||||
|
!res.data ||
|
||||||
|
!window.PublishPermission
|
||||||
|
) {
|
||||||
return { canPaid: false, reason: "暂时无法获取发布权限。" };
|
return { canPaid: false, reason: "暂时无法获取发布权限。" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const status = String(
|
const permission = PublishPermission.parseSummary(res.data);
|
||||||
res.data.expertStatus || res.data.expert_status || "",
|
|
||||||
).toLowerCase();
|
|
||||||
const isRegularExpert =
|
|
||||||
isTruthy(res.data.is_regular_expert) ||
|
|
||||||
isTruthy(res.data.isRegularExpert) ||
|
|
||||||
isTruthy(res.data.regularExpert) ||
|
|
||||||
/正式|regular|formal/.test(status);
|
|
||||||
const hasExpertIdentity =
|
|
||||||
isTruthy(res.data.is_expert) ||
|
|
||||||
isTruthy(res.data.isExpert) ||
|
|
||||||
isTruthy(res.data.expert) ||
|
|
||||||
isRegularExpert ||
|
|
||||||
/实习|intern|trial|正式|regular|formal/.test(status);
|
|
||||||
|
|
||||||
if (!hasExpertIdentity || !isRegularExpert) {
|
|
||||||
return { canPaid: false, reason: "正式专家才能发布付费文章。" };
|
|
||||||
}
|
|
||||||
|
|
||||||
return loadTodayPublishedFreeCount().then(function (count) {
|
|
||||||
return {
|
return {
|
||||||
canPaid: count >= REQUIRED_DAILY_FREE_ARTICLES,
|
...permission,
|
||||||
reason:
|
reason: permission.reason || permission.paidReason,
|
||||||
count >= REQUIRED_DAILY_FREE_ARTICLES
|
|
||||||
? ""
|
|
||||||
: "今天需先发布2篇审核通过的免费文章后,才能发布付费文章(当前" +
|
|
||||||
count +
|
|
||||||
"篇)。",
|
|
||||||
};
|
};
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch(function () {
|
.catch(function () {
|
||||||
return { canPaid: false, reason: "暂时无法获取发布权限。" };
|
return { canPaid: false, reason: "暂时无法获取发布权限。" };
|
||||||
|
|||||||
+16
-2
@@ -768,6 +768,19 @@ const ApiClient = {
|
|||||||
const params = this.normalizePageParams(data || {});
|
const params = this.normalizePageParams(data || {});
|
||||||
const code = params.code || params.suoxie || params.lotteryCode || '';
|
const code = params.code || params.suoxie || params.lotteryCode || '';
|
||||||
const pageSize = params.pageSize || params.limit || params.count || 100;
|
const pageSize = params.pageSize || params.limit || params.count || 100;
|
||||||
|
const normalizeResponse = (res) => {
|
||||||
|
if (!this.isSuccess(res)) return res;
|
||||||
|
const payload = this.pickData(res, {});
|
||||||
|
const list = this.asArray(payload);
|
||||||
|
return Object.assign({}, res, {
|
||||||
|
code: 0,
|
||||||
|
success: true,
|
||||||
|
data: list,
|
||||||
|
rawData: payload,
|
||||||
|
rows: list,
|
||||||
|
records: list,
|
||||||
|
});
|
||||||
|
};
|
||||||
if (code && this.API.lotteryTrend) {
|
if (code && this.API.lotteryTrend) {
|
||||||
try {
|
try {
|
||||||
const trendRes = await this.get(this.API.lotteryTrend, {
|
const trendRes = await this.get(this.API.lotteryTrend, {
|
||||||
@@ -775,13 +788,14 @@ const ApiClient = {
|
|||||||
pageSize,
|
pageSize,
|
||||||
});
|
});
|
||||||
if (this.isSuccess(trendRes) && this.asArray(this.pickData(trendRes, [])).length) {
|
if (this.isSuccess(trendRes) && this.asArray(this.pickData(trendRes, [])).length) {
|
||||||
return trendRes;
|
return normalizeResponse(trendRes);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Fall back to the older result-list endpoint below.
|
// Fall back to the older result-list endpoint below.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.requestWithMenu('/api/web/lottery/result/list', data);
|
const listRes = await this.requestWithMenu('/api/web/lottery/result/list', data);
|
||||||
|
return normalizeResponse(listRes);
|
||||||
},
|
},
|
||||||
|
|
||||||
async loadLegacyExpertDetailCompat(data = {}) {
|
async loadLegacyExpertDetailCompat(data = {}) {
|
||||||
|
|||||||
+333
-2
@@ -3,6 +3,109 @@ const AuthPageUtil = {
|
|||||||
slideUuid: '',
|
slideUuid: '',
|
||||||
slideX: 0,
|
slideX: 0,
|
||||||
slideToken: '',
|
slideToken: '',
|
||||||
|
slideCaptchaViewportCleanup: null,
|
||||||
|
referralInviteSessionKey: 'pendingReferralInviteCode',
|
||||||
|
|
||||||
|
getSessionStorage() {
|
||||||
|
if (typeof sessionStorage !== 'undefined') return sessionStorage;
|
||||||
|
if (typeof window !== 'undefined' && window.sessionStorage) return window.sessionStorage;
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
normalizeReferralInviteCode(inviteCode) {
|
||||||
|
return String(inviteCode || '').trim();
|
||||||
|
},
|
||||||
|
|
||||||
|
getReferralInviteCodeFromUrl(search) {
|
||||||
|
const query =
|
||||||
|
search != null
|
||||||
|
? search
|
||||||
|
: (typeof window !== 'undefined' && window.location && window.location.search) || '';
|
||||||
|
const Params =
|
||||||
|
typeof URLSearchParams !== 'undefined'
|
||||||
|
? URLSearchParams
|
||||||
|
: typeof window !== 'undefined' && window.URLSearchParams;
|
||||||
|
|
||||||
|
if (!Params) return '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = new Params(query || '');
|
||||||
|
return this.normalizeReferralInviteCode(
|
||||||
|
params.get('inviteCode') || params.get('referralCode') || '',
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setPendingReferralInviteCode(inviteCode) {
|
||||||
|
const code = this.normalizeReferralInviteCode(inviteCode);
|
||||||
|
const storage = this.getSessionStorage();
|
||||||
|
|
||||||
|
if (code && storage) {
|
||||||
|
storage.setItem(this.referralInviteSessionKey, code);
|
||||||
|
}
|
||||||
|
|
||||||
|
return code;
|
||||||
|
},
|
||||||
|
|
||||||
|
getPendingReferralInviteCode() {
|
||||||
|
const storage = this.getSessionStorage();
|
||||||
|
if (!storage) return '';
|
||||||
|
return this.normalizeReferralInviteCode(storage.getItem(this.referralInviteSessionKey));
|
||||||
|
},
|
||||||
|
|
||||||
|
clearPendingReferralInviteCode() {
|
||||||
|
const storage = this.getSessionStorage();
|
||||||
|
if (storage) storage.removeItem(this.referralInviteSessionKey);
|
||||||
|
},
|
||||||
|
|
||||||
|
captureReferralInviteFromUrl(search) {
|
||||||
|
const code = this.getReferralInviteCodeFromUrl(search);
|
||||||
|
|
||||||
|
if (code) {
|
||||||
|
this.setPendingReferralInviteCode(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
return code;
|
||||||
|
},
|
||||||
|
|
||||||
|
bindPendingReferralInviteCode() {
|
||||||
|
const inviteCode = this.getPendingReferralInviteCode();
|
||||||
|
|
||||||
|
if (!inviteCode) {
|
||||||
|
return Promise.resolve({ skipped: true, reason: 'empty' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof ApiClient === 'undefined' ||
|
||||||
|
!ApiClient.API ||
|
||||||
|
!ApiClient.API.referralBind ||
|
||||||
|
typeof ApiClient.post !== 'function'
|
||||||
|
) {
|
||||||
|
return Promise.resolve({ skipped: true, reason: 'missing-api' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof ApiClient.token === 'function' && !ApiClient.token()) {
|
||||||
|
return Promise.resolve({ skipped: true, reason: 'unauthenticated' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiClient.post(ApiClient.API.referralBind, { inviteCode })
|
||||||
|
.then((res) => {
|
||||||
|
const isSuccess =
|
||||||
|
typeof ApiClient.isSuccess === 'function'
|
||||||
|
? ApiClient.isSuccess(res)
|
||||||
|
: !!(res && (res.success === true || Number(res.code) === 0 || Number(res.code) === 200));
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
|
this.clearPendingReferralInviteCode();
|
||||||
|
return { skipped: false, success: true, res };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { skipped: false, success: false, res };
|
||||||
|
})
|
||||||
|
.catch((error) => ({ skipped: false, success: false, error }));
|
||||||
|
},
|
||||||
|
|
||||||
isPhone(phone) {
|
isPhone(phone) {
|
||||||
return /^1[3-9]\d{9}$/.test(String(phone || '').trim());
|
return /^1[3-9]\d{9}$/.test(String(phone || '').trim());
|
||||||
@@ -122,6 +225,234 @@ const AuthPageUtil = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
blurActiveElement() {
|
||||||
|
if (typeof document === 'undefined') return;
|
||||||
|
|
||||||
|
const activeElement = document.activeElement;
|
||||||
|
if (activeElement && activeElement !== document.body && typeof activeElement.blur === 'function') {
|
||||||
|
activeElement.blur();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
ensureSlideCaptchaMobileCenterStyle() {
|
||||||
|
if (typeof document === 'undefined' || document.getElementById('auth-slide-captcha-mobile-center-style')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = 'auth-slide-captcha-mobile-center-style';
|
||||||
|
style.textContent = `
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
#rvplus-slide-captcha-root.rvplus-slide-mask {
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
width: 100vw;
|
||||||
|
max-width: 100vw;
|
||||||
|
min-height: 100vh;
|
||||||
|
min-height: 100dvh;
|
||||||
|
padding: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#rvplus-slide-captcha-root .rvplus-slide-modal {
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-width: calc(100vw - 24px);
|
||||||
|
max-height: calc(100vh - 24px);
|
||||||
|
max-height: calc(100dvh - 24px);
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
},
|
||||||
|
|
||||||
|
getSlideCaptchaViewport() {
|
||||||
|
const fallbackWidth =
|
||||||
|
(typeof window !== 'undefined' && window.innerWidth) ||
|
||||||
|
(typeof document !== 'undefined' && document.documentElement && document.documentElement.clientWidth) ||
|
||||||
|
0;
|
||||||
|
const fallbackHeight =
|
||||||
|
(typeof window !== 'undefined' && window.innerHeight) ||
|
||||||
|
(typeof document !== 'undefined' && document.documentElement && document.documentElement.clientHeight) ||
|
||||||
|
0;
|
||||||
|
const viewport = typeof window !== 'undefined' ? window.visualViewport : null;
|
||||||
|
const screenWidth = (typeof window !== 'undefined' && window.screen && window.screen.width) || 0;
|
||||||
|
const screenHeight = (typeof window !== 'undefined' && window.screen && window.screen.height) || 0;
|
||||||
|
const width = Math.round((viewport && viewport.width) || fallbackWidth);
|
||||||
|
const height = Math.round((viewport && viewport.height) || fallbackHeight);
|
||||||
|
let scale = Number(viewport && viewport.scale) || 1;
|
||||||
|
|
||||||
|
if (screenWidth && screenWidth <= 640 && width > screenWidth && scale === 1) {
|
||||||
|
scale = screenWidth / width;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
offsetLeft: Math.round((viewport && viewport.offsetLeft) || 0),
|
||||||
|
offsetTop: Math.round((viewport && viewport.offsetTop) || 0),
|
||||||
|
scale,
|
||||||
|
displayWidth: Math.round(width * scale),
|
||||||
|
displayHeight: Math.round(height * scale),
|
||||||
|
screenWidth,
|
||||||
|
screenHeight,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
isCompactSlideCaptchaViewport(viewport) {
|
||||||
|
return viewport.displayWidth <= 640 || (viewport.screenWidth > 0 && viewport.screenWidth <= 640);
|
||||||
|
},
|
||||||
|
|
||||||
|
centerSlideCaptchaDialog() {
|
||||||
|
if (typeof document === 'undefined') return false;
|
||||||
|
|
||||||
|
const root = document.getElementById('rvplus-slide-captcha-root');
|
||||||
|
if (!root) return false;
|
||||||
|
|
||||||
|
const viewport = this.getSlideCaptchaViewport();
|
||||||
|
if (!viewport.width || !viewport.height) return true;
|
||||||
|
|
||||||
|
const compact = this.isCompactSlideCaptchaViewport(viewport);
|
||||||
|
const visualPadding = compact ? 12 : 20;
|
||||||
|
const padding = visualPadding / (viewport.scale || 1);
|
||||||
|
const modalWidth = Math.max(0, Math.round(viewport.width - padding * 2));
|
||||||
|
const modalHeight = Math.max(0, Math.round(viewport.height - padding * 2));
|
||||||
|
|
||||||
|
root.style.position = 'fixed';
|
||||||
|
root.style.inset = 'auto';
|
||||||
|
root.style.left = viewport.offsetLeft + 'px';
|
||||||
|
root.style.top = viewport.offsetTop + 'px';
|
||||||
|
root.style.width = viewport.width + 'px';
|
||||||
|
root.style.height = viewport.height + 'px';
|
||||||
|
root.style.display = 'flex';
|
||||||
|
root.style.alignItems = 'center';
|
||||||
|
root.style.justifyContent = 'center';
|
||||||
|
root.style.boxSizing = 'border-box';
|
||||||
|
root.style.padding = padding + 'px';
|
||||||
|
|
||||||
|
const modal = root.querySelector && root.querySelector('.rvplus-slide-modal');
|
||||||
|
if (modal) {
|
||||||
|
modal.style.boxSizing = 'border-box';
|
||||||
|
modal.style.maxWidth = modalWidth + 'px';
|
||||||
|
modal.style.maxHeight = modalHeight + 'px';
|
||||||
|
modal.style.overflowY = 'auto';
|
||||||
|
modal.style.webkitOverflowScrolling = 'touch';
|
||||||
|
modal.style.width = compact ? modalWidth + 'px' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
clearSlideCaptchaViewportCentering() {
|
||||||
|
if (typeof this.slideCaptchaViewportCleanup !== 'function') return;
|
||||||
|
|
||||||
|
const cleanup = this.slideCaptchaViewportCleanup;
|
||||||
|
this.slideCaptchaViewportCleanup = null;
|
||||||
|
cleanup();
|
||||||
|
},
|
||||||
|
|
||||||
|
scheduleSlideCaptchaCentering(callback) {
|
||||||
|
if (typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function') {
|
||||||
|
window.requestAnimationFrame(callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
|
||||||
|
bindSlideCaptchaViewportCentering() {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
|
||||||
|
this.clearSlideCaptchaViewportCentering();
|
||||||
|
|
||||||
|
const cleanups = [];
|
||||||
|
const recenter = () => {
|
||||||
|
if (!this.centerSlideCaptchaDialog()) {
|
||||||
|
this.clearSlideCaptchaViewportCentering();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const schedule = () => this.scheduleSlideCaptchaCentering(recenter);
|
||||||
|
const addListener = (target, type) => {
|
||||||
|
if (target && typeof target.addEventListener === 'function') {
|
||||||
|
target.addEventListener(type, schedule);
|
||||||
|
cleanups.push(() => target.removeEventListener(type, schedule));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
addListener(window.visualViewport, 'resize');
|
||||||
|
addListener(window.visualViewport, 'scroll');
|
||||||
|
addListener(window, 'resize');
|
||||||
|
addListener(window, 'orientationchange');
|
||||||
|
|
||||||
|
this.slideCaptchaViewportCleanup = () => {
|
||||||
|
cleanups.forEach((cleanup) => cleanup());
|
||||||
|
};
|
||||||
|
|
||||||
|
schedule();
|
||||||
|
|
||||||
|
if (typeof window.setTimeout === 'function') {
|
||||||
|
window.setTimeout(schedule, 80);
|
||||||
|
window.setTimeout(schedule, 260);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
deferClearSlideCaptchaViewportCentering() {
|
||||||
|
const clear = () => this.clearSlideCaptchaViewportCentering();
|
||||||
|
if (typeof window !== 'undefined' && typeof window.setTimeout === 'function') {
|
||||||
|
window.setTimeout(clear, 320);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear();
|
||||||
|
},
|
||||||
|
|
||||||
|
wrapSlideCaptchaCallbacks(options = {}) {
|
||||||
|
const wrapped = { ...options };
|
||||||
|
const wrap = (callback) => (...args) => {
|
||||||
|
try {
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
return callback(...args);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.deferClearSlideCaptchaViewportCentering();
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
wrapped.onSuccess = wrap(options.onSuccess);
|
||||||
|
wrapped.onCancel = wrap(options.onCancel);
|
||||||
|
return wrapped;
|
||||||
|
},
|
||||||
|
|
||||||
|
patchSlideCaptchaMobileCentering(SlideCaptcha) {
|
||||||
|
if (!SlideCaptcha || SlideCaptcha.__authPageMobileCentered || typeof SlideCaptcha.open !== 'function') {
|
||||||
|
return SlideCaptcha;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nativeOpen = SlideCaptcha.open.bind(SlideCaptcha);
|
||||||
|
const nativeClose = typeof SlideCaptcha.close === 'function' ? SlideCaptcha.close.bind(SlideCaptcha) : null;
|
||||||
|
|
||||||
|
SlideCaptcha.open = (options = {}) => {
|
||||||
|
this.blurActiveElement();
|
||||||
|
this.ensureSlideCaptchaMobileCenterStyle();
|
||||||
|
const result = nativeOpen(this.wrapSlideCaptchaCallbacks(options));
|
||||||
|
this.bindSlideCaptchaViewportCentering();
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (nativeClose) {
|
||||||
|
SlideCaptcha.close = (...args) => {
|
||||||
|
this.clearSlideCaptchaViewportCentering();
|
||||||
|
return nativeClose(...args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
SlideCaptcha.__authPageMobileCentered = true;
|
||||||
|
return SlideCaptcha;
|
||||||
|
},
|
||||||
|
|
||||||
startSmsCountdown(buttonSelector, seconds = 60) {
|
startSmsCountdown(buttonSelector, seconds = 60) {
|
||||||
const $button = layui.$(buttonSelector);
|
const $button = layui.$(buttonSelector);
|
||||||
let left = seconds;
|
let left = seconds;
|
||||||
@@ -140,7 +471,7 @@ const AuthPageUtil = {
|
|||||||
|
|
||||||
loadSlideCaptchaScript() {
|
loadSlideCaptchaScript() {
|
||||||
this.installSlideCaptchaTenantHeader();
|
this.installSlideCaptchaTenantHeader();
|
||||||
if (window.SlideCaptcha) return Promise.resolve(window.SlideCaptcha);
|
if (window.SlideCaptcha) return Promise.resolve(this.patchSlideCaptchaMobileCentering(window.SlideCaptcha));
|
||||||
if (window.__slideCaptchaLoading) return window.__slideCaptchaLoading;
|
if (window.__slideCaptchaLoading) return window.__slideCaptchaLoading;
|
||||||
window.__slideCaptchaLoading = new Promise((resolve, reject) => {
|
window.__slideCaptchaLoading = new Promise((resolve, reject) => {
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
@@ -148,7 +479,7 @@ const AuthPageUtil = {
|
|||||||
script.dataset.apiBase = ApiClient.baseUrl || '';
|
script.dataset.apiBase = ApiClient.baseUrl || '';
|
||||||
script.dataset.tenantId = ApiClient.tenantId || '936208';
|
script.dataset.tenantId = ApiClient.tenantId || '936208';
|
||||||
script.onload = () => {
|
script.onload = () => {
|
||||||
if (window.SlideCaptcha) resolve(window.SlideCaptcha);
|
if (window.SlideCaptcha) resolve(this.patchSlideCaptchaMobileCentering(window.SlideCaptcha));
|
||||||
else reject(new Error('滑动验证组件加载失败'));
|
else reject(new Error('滑动验证组件加载失败'));
|
||||||
};
|
};
|
||||||
script.onerror = () => reject(new Error('滑动验证组件加载失败'));
|
script.onerror = () => reject(new Error('滑动验证组件加载失败'));
|
||||||
|
|||||||
@@ -148,6 +148,70 @@ const CommonUtil = {
|
|||||||
return '会员';
|
return '会员';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getExpertState(user = {}) {
|
||||||
|
const truthy = (value) =>
|
||||||
|
value === true ||
|
||||||
|
value === 1 ||
|
||||||
|
value === '1' ||
|
||||||
|
String(value).toLowerCase() === 'true';
|
||||||
|
const firstPresent = (...values) => {
|
||||||
|
for (const value of values) {
|
||||||
|
if (value !== undefined && value !== null && value !== '') return value;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
const statusText = String(
|
||||||
|
firstPresent(
|
||||||
|
user.expertStatus,
|
||||||
|
user.expert_status,
|
||||||
|
user.expertApplyStatus,
|
||||||
|
user.expert_apply_status,
|
||||||
|
user.applyStatus,
|
||||||
|
user.apply_status,
|
||||||
|
user.auditStatus,
|
||||||
|
user.audit_status,
|
||||||
|
user.expertAuditStatus,
|
||||||
|
user.expert_audit_status
|
||||||
|
)
|
||||||
|
).toLowerCase();
|
||||||
|
const isExpert =
|
||||||
|
truthy(user.regularExpert) ||
|
||||||
|
truthy(user.isRegularExpert) ||
|
||||||
|
truthy(user.is_regular_expert) ||
|
||||||
|
truthy(user.formalExpert) ||
|
||||||
|
truthy(user.isFormalExpert) ||
|
||||||
|
truthy(user.internExpert) ||
|
||||||
|
truthy(user.isInternExpert) ||
|
||||||
|
truthy(user.is_intern_expert) ||
|
||||||
|
truthy(user.trialExpert) ||
|
||||||
|
truthy(user.isTrialExpert) ||
|
||||||
|
truthy(user.is_expert) ||
|
||||||
|
truthy(user.isExpert) ||
|
||||||
|
truthy(user.expert) ||
|
||||||
|
/实习|正式|intern|trial|regular|formal|expert/.test(statusText);
|
||||||
|
const isApplying =
|
||||||
|
!isExpert &&
|
||||||
|
([
|
||||||
|
'0',
|
||||||
|
'pending',
|
||||||
|
'reviewing',
|
||||||
|
'review',
|
||||||
|
'audit',
|
||||||
|
'auditing',
|
||||||
|
'apply',
|
||||||
|
'applying',
|
||||||
|
'submitted',
|
||||||
|
'wait',
|
||||||
|
'waiting',
|
||||||
|
].includes(statusText) ||
|
||||||
|
/审核|待审|申请中|pending|review|audit|applying|waiting/.test(statusText));
|
||||||
|
return {
|
||||||
|
isExpert,
|
||||||
|
isApplying,
|
||||||
|
status: statusText,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
getUserLevelTextLegacy(user = {}) {
|
getUserLevelTextLegacy(user = {}) {
|
||||||
if (user.regularExpert || user.isExpert === '1' || user.expertStatus === 'regular') {
|
if (user.regularExpert || user.isExpert === '1' || user.expertStatus === 'regular') {
|
||||||
return '正式专家';
|
return '正式专家';
|
||||||
@@ -1752,6 +1816,55 @@ const CommonUtil = {
|
|||||||
$tempContainer.remove();
|
$tempContainer.remove();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renderTrendOpenCode(openCode, options = {}) {
|
||||||
|
const value = String(openCode === undefined || openCode === null ? '' : openCode).trim();
|
||||||
|
if (!value) return '<span class="trend-open-empty">--</span>';
|
||||||
|
|
||||||
|
const mode = options.mode || 'red';
|
||||||
|
const splitTokens = (text) =>
|
||||||
|
String(text || '')
|
||||||
|
.split(/[,\s、,]+/)
|
||||||
|
.map((item) => item.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const splitDigits = (text) => {
|
||||||
|
const tokens = splitTokens(text);
|
||||||
|
if (tokens.length > 1) return tokens;
|
||||||
|
return String(text || '')
|
||||||
|
.replace(/[^\d]/g, '')
|
||||||
|
.split('')
|
||||||
|
.filter(Boolean);
|
||||||
|
};
|
||||||
|
const pad = (num, shouldPad = true) => (shouldPad && /^\d$/.test(num) ? `0${num}` : num);
|
||||||
|
const renderNum = (num, className, shouldPad = true) =>
|
||||||
|
`<span class="trend-open-number ${className}">${this.escapeHtml(pad(String(num), shouldPad))}</span>`;
|
||||||
|
const renderGroup = (list, className, shouldPad = true) =>
|
||||||
|
list.map((num) => renderNum(num, className, shouldPad)).join('');
|
||||||
|
const renderPlus = () => '<span class="trend-open-separator">+</span>';
|
||||||
|
const parts = value.split('+');
|
||||||
|
|
||||||
|
let body = '';
|
||||||
|
if (mode === 'ssq' || mode === 'dlt' || mode === 'qlc') {
|
||||||
|
const front = splitTokens(parts[0]);
|
||||||
|
let back = parts.slice(1).flatMap(splitTokens);
|
||||||
|
const frontLimit = mode === 'ssq' ? 6 : mode === 'dlt' ? 5 : 7;
|
||||||
|
if (!back.length && front.length > frontLimit) back = front.splice(frontLimit);
|
||||||
|
body = renderGroup(front, 'trend-open-red');
|
||||||
|
if (back.length) body += renderPlus() + renderGroup(back, 'trend-open-blue');
|
||||||
|
} else if (mode === 'qxc') {
|
||||||
|
const nums = splitDigits(value);
|
||||||
|
const front = nums.slice(0, 6);
|
||||||
|
const back = nums.slice(6);
|
||||||
|
body = renderGroup(front, 'trend-open-red', false);
|
||||||
|
if (back.length) body += renderPlus() + renderGroup(back, 'trend-open-blue', false);
|
||||||
|
} else if (mode === 'position') {
|
||||||
|
body = renderGroup(splitDigits(value), 'trend-open-red', false);
|
||||||
|
} else {
|
||||||
|
body = renderGroup(splitTokens(value), 'trend-open-red');
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<span class="trend-open-code">${body || this.escapeHtml(value)}</span>`;
|
||||||
|
},
|
||||||
|
|
||||||
sanitizeHTML(html) {
|
sanitizeHTML(html) {
|
||||||
if (!html) return '';
|
if (!html) return '';
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
(function (global) {
|
||||||
|
var DEFAULT_REQUIRED_DAILY_FREE_ARTICLES = 2;
|
||||||
|
|
||||||
|
function isTruthy(value) {
|
||||||
|
return (
|
||||||
|
value === true ||
|
||||||
|
value === 1 ||
|
||||||
|
value === '1' ||
|
||||||
|
String(value).toLowerCase() === 'true'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function firstDefined(source, keys) {
|
||||||
|
if (!source || typeof source !== 'object') return undefined;
|
||||||
|
|
||||||
|
for (var i = 0; i < keys.length; i += 1) {
|
||||||
|
var key = keys[i];
|
||||||
|
if (
|
||||||
|
Object.prototype.hasOwnProperty.call(source, key) &&
|
||||||
|
source[key] !== undefined &&
|
||||||
|
source[key] !== null
|
||||||
|
) {
|
||||||
|
return source[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toNumber(value, fallback) {
|
||||||
|
var numberValue = Number(value);
|
||||||
|
return Number.isFinite(numberValue) ? numberValue : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildQuotaReason(todayCount, requiredCount) {
|
||||||
|
return (
|
||||||
|
'今天需先发布' +
|
||||||
|
requiredCount +
|
||||||
|
'篇审核通过的免费文章后,才能发布付费文章(当前' +
|
||||||
|
todayCount +
|
||||||
|
'篇)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSummary(userData, options) {
|
||||||
|
var data = userData && typeof userData === 'object' ? userData : {};
|
||||||
|
var config = options || {};
|
||||||
|
var status = String(
|
||||||
|
firstDefined(data, ['expertStatus', 'expert_status']) || ''
|
||||||
|
).toLowerCase();
|
||||||
|
var isIntern = /实习|intern|trial/.test(status);
|
||||||
|
var isRegularByStatus = /正式|regular|formal/.test(status);
|
||||||
|
var isRegularExpert =
|
||||||
|
isTruthy(
|
||||||
|
firstDefined(data, [
|
||||||
|
'is_regular_expert',
|
||||||
|
'isRegularExpert',
|
||||||
|
'regularExpert',
|
||||||
|
])
|
||||||
|
) || isRegularByStatus;
|
||||||
|
var hasExpertIdentity =
|
||||||
|
isTruthy(firstDefined(data, ['is_expert', 'isExpert', 'expert'])) ||
|
||||||
|
isTruthy(
|
||||||
|
firstDefined(data, [
|
||||||
|
'is_regular_expert',
|
||||||
|
'isRegularExpert',
|
||||||
|
'regularExpert',
|
||||||
|
])
|
||||||
|
) ||
|
||||||
|
isIntern ||
|
||||||
|
isRegularByStatus;
|
||||||
|
var requiredCount = toNumber(
|
||||||
|
firstDefined(data, [
|
||||||
|
'requiredDailyApprovedFreeArticleCount',
|
||||||
|
'required_daily_approved_free_article_count',
|
||||||
|
]),
|
||||||
|
config.requiredDailyFreeArticleCount || DEFAULT_REQUIRED_DAILY_FREE_ARTICLES
|
||||||
|
);
|
||||||
|
var todayCount = toNumber(
|
||||||
|
firstDefined(data, [
|
||||||
|
'todayApprovedFreeArticleCount',
|
||||||
|
'today_approved_free_article_count',
|
||||||
|
]),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
var quotaExemptRaw = firstDefined(data, [
|
||||||
|
'payArticleFreeQuotaExempt',
|
||||||
|
'pay_article_free_quota_exempt',
|
||||||
|
]);
|
||||||
|
var canPublishRaw = firstDefined(data, [
|
||||||
|
'canPublishPayArticle',
|
||||||
|
'can_publish_pay_article',
|
||||||
|
]);
|
||||||
|
var backendReason = String(
|
||||||
|
firstDefined(data, [
|
||||||
|
'payArticlePublishReason',
|
||||||
|
'pay_article_publish_reason',
|
||||||
|
]) || ''
|
||||||
|
);
|
||||||
|
var quotaExempt = isTruthy(quotaExemptRaw);
|
||||||
|
var hasBackendPaidDecision = canPublishRaw !== undefined;
|
||||||
|
var canPaid = false;
|
||||||
|
var paidReason = '';
|
||||||
|
|
||||||
|
if (quotaExempt) {
|
||||||
|
canPaid = true;
|
||||||
|
} else if (hasBackendPaidDecision) {
|
||||||
|
canPaid = isTruthy(canPublishRaw);
|
||||||
|
paidReason = canPaid
|
||||||
|
? ''
|
||||||
|
: backendReason ||
|
||||||
|
(!hasExpertIdentity || !isRegularExpert
|
||||||
|
? '正式专家才能发布付费文章'
|
||||||
|
: buildQuotaReason(todayCount, requiredCount));
|
||||||
|
} else if (!hasExpertIdentity || !isRegularExpert) {
|
||||||
|
paidReason = '正式专家才能发布付费文章';
|
||||||
|
} else {
|
||||||
|
canPaid = todayCount >= requiredCount;
|
||||||
|
paidReason = canPaid ? '' : buildQuotaReason(todayCount, requiredCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasExpertIdentity: hasExpertIdentity,
|
||||||
|
isRegularExpert: hasExpertIdentity && isRegularExpert,
|
||||||
|
canFree: hasExpertIdentity,
|
||||||
|
canPaid: canPaid,
|
||||||
|
quotaExempt: quotaExempt,
|
||||||
|
todayFreeArticleCount: todayCount,
|
||||||
|
requiredDailyFreeArticleCount: requiredCount,
|
||||||
|
paidReason: paidReason,
|
||||||
|
reason: paidReason,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
global.PublishPermission = {
|
||||||
|
isTruthy: isTruthy,
|
||||||
|
parseSummary: parseSummary,
|
||||||
|
};
|
||||||
|
})(typeof window !== 'undefined' ? window : globalThis);
|
||||||
Reference in New Issue
Block a user