样式修改完成,待测试
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` 保持一致。
|
||||
- 范围检查:这是多批次功能等价修复,每个任务都可以独立验证,并且必须按顺序执行。
|
||||
+16
-8
@@ -140,6 +140,7 @@
|
||||
<button class="gotop" id="backTop" type="button" onclick="window.scrollTo({ top: 0, behavior: 'smooth' })">↑</button>
|
||||
|
||||
<script>
|
||||
var $;
|
||||
var updateData = {
|
||||
idCardFrontId: "",
|
||||
idCardBackId: "",
|
||||
@@ -148,7 +149,7 @@
|
||||
var expertIntroMinLength = 80;
|
||||
|
||||
layui.use(["layer", "jquery"], function () {
|
||||
var $ = layui.$;
|
||||
$ = layui.$;
|
||||
|
||||
CommonUtil.loadPageConfig({ ads: false, links: false });
|
||||
renderLoginState();
|
||||
@@ -204,13 +205,20 @@
|
||||
CommonUtil.getCurrentUserDetail(function (data) {
|
||||
if (!data) return;
|
||||
$("#currentUsername").text(data.nickName || data.username || data.phoneNumber || "用户");
|
||||
var isExpert =
|
||||
data.expert === true ||
|
||||
data.regularExpert === true ||
|
||||
data.isExpert === 1 ||
|
||||
data.isExpert === "1";
|
||||
if (isExpert) {
|
||||
$("#applyBtn").text("已申请").prop("disabled", true);
|
||||
var expertState = CommonUtil.getExpertState
|
||||
? CommonUtil.getExpertState(data)
|
||||
: {
|
||||
isExpert:
|
||||
data.expert === true ||
|
||||
data.regularExpert === true ||
|
||||
data.isExpert === 1 ||
|
||||
data.isExpert === "1",
|
||||
isApplying: false,
|
||||
};
|
||||
if (expertState.isExpert || expertState.isApplying) {
|
||||
$("#applyBtn")
|
||||
.text(expertState.isExpert ? "已是专家" : "申请审核中")
|
||||
.prop("disabled", true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title id="page-title">神彩算 - 免费文章收藏</title>
|
||||
<script>
|
||||
window.location.replace("mianfeishoucang.html?tab=free");
|
||||
</script>
|
||||
<script src="../public/css/layui/layui.js"></script>
|
||||
<script src="../config.js"></script>
|
||||
<script src="../utils/ApiClient.js"></script>
|
||||
|
||||
+36
-2
@@ -851,9 +851,43 @@
|
||||
if (!item) return false;
|
||||
const articleId = getArticleId(item);
|
||||
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);
|
||||
});
|
||||
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(
|
||||
@@ -1836,7 +1870,7 @@
|
||||
const params = {
|
||||
menuId: getArticleMenuId(item),
|
||||
expertId: getArticleExpertId(item),
|
||||
accountType: Number(accountType) || 1,
|
||||
accountType: Number(item.accountType || accountType) || 1,
|
||||
issue: getArticleIssue(item),
|
||||
count: 1,
|
||||
walletType:
|
||||
|
||||
+1
-2
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -563,7 +563,6 @@
|
||||
status === "success" ||
|
||||
status === "paid" ||
|
||||
status === "2" ||
|
||||
status === "1" ||
|
||||
Number(data.status) === 2 ||
|
||||
Number(data.payStatus) === 2
|
||||
);
|
||||
|
||||
+9
-4
@@ -66,9 +66,10 @@
|
||||
<div class="page-title-bar">
|
||||
<div class="page-title">大乐透走势图</div>
|
||||
<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" data-count="60">60期</div>
|
||||
<div class="qishu-but" data-count="100">100期</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -216,9 +217,12 @@
|
||||
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-code">' + item.openCode + "</td>";
|
||||
html += '<td class="col-code">' + CommonUtil.renderTrendOpenCode(item.openCode, { mode: "dlt" }) + "</td>";
|
||||
|
||||
for (var i = 1; i <= FRONT_MAX; i++) {
|
||||
if (frontSet[i]) {
|
||||
@@ -339,7 +343,8 @@
|
||||
function loadData() {
|
||||
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "cjdlt" }).then(function (res) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<script src="../public/css/layui/layui.js"></script>
|
||||
<script src="../config.js"></script>
|
||||
<script src="../utils/ApiClient.js"></script>
|
||||
<script src="../utils/PublishPermission.js"></script>
|
||||
<script src="../utils/DateUtil.js"></script>
|
||||
<script src="../utils/CommonUtil.js"></script>
|
||||
<link rel="stylesheet" href="../public/css/layui/css/layui.css" />
|
||||
@@ -185,7 +186,6 @@
|
||||
canFree: false,
|
||||
canPaid: false,
|
||||
};
|
||||
var REQUIRED_DAILY_FREE_ARTICLES = 2;
|
||||
var FREE_TITLE_MIN_LENGTH = 15;
|
||||
var FREE_TITLE_MAX_LENGTH = 30;
|
||||
|
||||
@@ -298,146 +298,14 @@
|
||||
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() {
|
||||
return ApiClient.get(ApiClient.API.mineSummary)
|
||||
.then(function (res) {
|
||||
if (!ApiClient.isSuccess(res) || !res.data) {
|
||||
if (
|
||||
!ApiClient.isSuccess(res) ||
|
||||
!res.data ||
|
||||
!window.PublishPermission
|
||||
) {
|
||||
return {
|
||||
hasExpertIdentity: false,
|
||||
isRegularExpert: false,
|
||||
@@ -448,19 +316,7 @@
|
||||
};
|
||||
}
|
||||
|
||||
var permission = parsePublishPermission(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;
|
||||
});
|
||||
return PublishPermission.parseSummary(res.data);
|
||||
})
|
||||
.catch(function () {
|
||||
return {
|
||||
@@ -479,7 +335,7 @@
|
||||
publishPermission = permission;
|
||||
return {
|
||||
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">福彩3D走势图</div>
|
||||
<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" data-count="60">60期</div>
|
||||
<div class="qishu-but" data-count="100">100期</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -207,9 +208,12 @@
|
||||
var parsed = parseCode(item.openCode);
|
||||
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-code">' + item.openCode + "</td>";
|
||||
html += '<td class="col-code">' + CommonUtil.renderTrendOpenCode(item.openCode, { mode: "position" }) + "</td>";
|
||||
|
||||
// Type columns
|
||||
var typeNames = ["baozi", "zu3", "zu6"];
|
||||
@@ -396,7 +400,8 @@
|
||||
function loadData() {
|
||||
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "fc3d" }).then(function (res) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -196,6 +196,7 @@
|
||||
<script src="../public/js/lay-config.js"></script>
|
||||
<script src="../config.js"></script>
|
||||
<script src="../utils/ApiClient.js"></script>
|
||||
<script src="../utils/PublishPermission.js"></script>
|
||||
<script src="../utils/DateUtil.js"></script>
|
||||
<script src="../utils/CommonUtil.js"></script>
|
||||
<script src="../public/js/jquery.js"></script>
|
||||
|
||||
+10
-7
@@ -66,9 +66,10 @@
|
||||
<div class="page-title-bar">
|
||||
<div class="page-title">快乐8走势图</div>
|
||||
<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" data-count="60">60期</div>
|
||||
<div class="qishu-but" data-count="100">100期</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -77,7 +78,6 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<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-g2">41-80</td>
|
||||
</tr>
|
||||
@@ -198,13 +198,15 @@
|
||||
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-code">' + item.openCode + "</td>";
|
||||
|
||||
for (var i = 1; i <= NUM_MAX; i++) {
|
||||
if (hitSet[i]) {
|
||||
var cls = i <= 40 ? "hit-num" : "hit-num g2";
|
||||
var cls = "hit-num";
|
||||
html +=
|
||||
'<td><span class="' +
|
||||
cls +
|
||||
@@ -292,7 +294,7 @@
|
||||
html +=
|
||||
'<tr class="' +
|
||||
cls +
|
||||
'"><td colspan="2" class="stat-label">' +
|
||||
'"><td colspan="1" class="stat-label">' +
|
||||
labels[li] +
|
||||
"</td>";
|
||||
for (var i = 1; i <= NUM_MAX; i++)
|
||||
@@ -305,7 +307,8 @@
|
||||
function loadData() {
|
||||
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "kl8" }).then(function (res) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
+22
-8
@@ -305,6 +305,26 @@
|
||||
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() {
|
||||
const payload = Object.assign(
|
||||
{
|
||||
@@ -331,14 +351,7 @@
|
||||
layerInstance.msg(
|
||||
"登录成功,即将跳转到首页",
|
||||
{ icon: 1, time: 1200 },
|
||||
function () {
|
||||
const redirect = new URLSearchParams(window.location.search).get("redirect");
|
||||
window.location.href =
|
||||
redirect ||
|
||||
(typeof CommonUtil !== "undefined"
|
||||
? CommonUtil.siteHref("/index.html")
|
||||
: "index.html");
|
||||
},
|
||||
bindReferralThenRedirect,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -363,6 +376,7 @@
|
||||
|
||||
layui.use(["layer", "jquery"], function () {
|
||||
layerInstance = layui.layer;
|
||||
AuthPageUtil.captureReferralInviteFromUrl();
|
||||
loadVerifyConfig().then(refreshCaptcha);
|
||||
$("#loginForm").on("submit", function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
+25
-5
@@ -9,6 +9,7 @@
|
||||
<script src="../utils/ApiClient.js"></script>
|
||||
<script src="../utils/DateUtil.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/public.css" />
|
||||
<link rel="stylesheet" href="../public/css/mianfei.css" />
|
||||
@@ -176,7 +177,11 @@
|
||||
const $ = layui.$;
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
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 isCollected = false;
|
||||
let articleData = null;
|
||||
@@ -269,7 +274,11 @@
|
||||
|
||||
function renderArticle(data) {
|
||||
$("#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 || "--");
|
||||
$("#articleContent").html(scopeArticleContent(data.content || ""));
|
||||
document.title = "神彩算 - " + (data.title || "免费文章详情");
|
||||
@@ -289,6 +298,10 @@
|
||||
return '<p class="cxz-content-empty">暂无内容</p>';
|
||||
}
|
||||
|
||||
if (typeof DOMPurify !== "undefined") {
|
||||
html = DOMPurify.sanitize(html);
|
||||
}
|
||||
|
||||
var scopedStyles = [];
|
||||
html = html.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, function (_, css) {
|
||||
scopedStyles.push(prefixContentCss(css));
|
||||
@@ -357,7 +370,10 @@
|
||||
}
|
||||
|
||||
function loadRelatedArticles() {
|
||||
ApiClient.get(ApiClient.API.freeArticleRelated(articleId))
|
||||
ApiClient.get(ApiClient.API.freeArticleRelated(articleId), {
|
||||
limit: 12,
|
||||
articleCount: 12,
|
||||
})
|
||||
.then(function (res) {
|
||||
if (!ApiClient.isSuccess(res)) return;
|
||||
var data = ApiClient.pickData(res, {}) || {};
|
||||
@@ -370,14 +386,17 @@
|
||||
renderFreeArticleList("#freeRelatedList", rows.filter(function (item) {
|
||||
var id = item && (item.id || item.articleId || item.freeArticleId);
|
||||
return id && String(id) !== String(articleId);
|
||||
}).slice(0, 8));
|
||||
}).slice(0, 12));
|
||||
})
|
||||
.catch(function () {});
|
||||
loadFreeArticleLatest();
|
||||
}
|
||||
|
||||
function loadFreeArticleLatest() {
|
||||
ApiClient.get(ApiClient.API.freeArticleLatest, { articleCount: 8 })
|
||||
ApiClient.get(ApiClient.API.freeArticleLatest, {
|
||||
limit: 12,
|
||||
articleCount: 12,
|
||||
})
|
||||
.then(function (res) {
|
||||
if (!ApiClient.isSuccess(res)) return;
|
||||
renderFreeArticleList("#freeLatestList", ApiClient.asArray(ApiClient.pickData(res, [])));
|
||||
@@ -394,6 +413,7 @@
|
||||
return;
|
||||
}
|
||||
$.each(list, function (_, item) {
|
||||
if (_ >= 12) return false;
|
||||
var id = item.id || item.articleId || item.freeArticleId;
|
||||
if (!id) return;
|
||||
var title = item.title || item.articleTitle || item.name || "无标题";
|
||||
|
||||
+78
-21
@@ -110,7 +110,7 @@
|
||||
<script>
|
||||
const $ = layui.$;
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
let menuId = urlParams.get("id") || "";
|
||||
let menuId = urlParams.get("id") || urlParams.get("code") || "";
|
||||
let currentPage = 1;
|
||||
const pageSize = 15;
|
||||
let lotteryMenus = [];
|
||||
@@ -153,16 +153,15 @@
|
||||
|
||||
function resolveCurrentMenuId() {
|
||||
if (!lotteryMenus.length) return;
|
||||
if (
|
||||
menuId &&
|
||||
lotteryMenus.some(function (item) {
|
||||
return sameLotteryKey(item, menuId);
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
menuId = String((lotteryMenus[0] && lotteryMenus[0].id) || "");
|
||||
const matched = menuId
|
||||
? lotteryMenus.find(function (item) {
|
||||
return sameLotteryKey(item, menuId);
|
||||
})
|
||||
: null;
|
||||
const current = matched || lotteryMenus[0];
|
||||
menuId = String(
|
||||
(current && (current.id || current.menuId || current.code || current.suoxie)) || "",
|
||||
);
|
||||
}
|
||||
|
||||
function renderLotteryTabs() {
|
||||
@@ -184,9 +183,10 @@
|
||||
$tabs.empty();
|
||||
$.each(lotteryMenus, function (_, item) {
|
||||
const id = item.id || item.menuId || item.code || item.suoxie || "";
|
||||
const linkKey = item.code || item.suoxie || item.lotteryCode || id;
|
||||
$tabs.append(
|
||||
'<a href="mianfeilist.html?id=' +
|
||||
encodeURIComponent(id) +
|
||||
encodeURIComponent(linkKey) +
|
||||
'" class="cxz-lottery-tab" data-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
|
||||
function loadArticles() {
|
||||
if (!menuId) {
|
||||
@@ -253,16 +291,26 @@
|
||||
renderPagination(0);
|
||||
return;
|
||||
}
|
||||
const requestMenuId = currentRequestMenuId();
|
||||
if (!requestMenuId) {
|
||||
renderFallbackArticles();
|
||||
return;
|
||||
}
|
||||
ApiClient.get(ApiClient.API.freeArticleList, {
|
||||
menuId: menuId,
|
||||
menuId: requestMenuId,
|
||||
pageNum: currentPage,
|
||||
pageSize: pageSize,
|
||||
}).then(function (res) {
|
||||
if (ApiClient.isSuccess(res) && ApiClient.pickData(res, null)) {
|
||||
const data = ApiClient.pickData(res, {}) || {};
|
||||
const list = data.records || data.rows || data.list || ApiClient.asArray(data);
|
||||
renderArticles(list);
|
||||
renderPagination(data.totalRow || data.total || data.count || list.length || 0);
|
||||
const pageData = getArticlePageData(res);
|
||||
if (pageData.records.length || pageData.total) {
|
||||
renderArticles(pageData.records.map(function (item) {
|
||||
return normalizeArticle(item, currentFallbackGroup());
|
||||
}));
|
||||
renderPagination(pageData.total);
|
||||
} else {
|
||||
renderFallbackArticles();
|
||||
}
|
||||
} else {
|
||||
renderFallbackArticles();
|
||||
}
|
||||
@@ -273,7 +321,9 @@
|
||||
|
||||
function renderFallbackArticles() {
|
||||
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 records = allArticles.slice(startIndex, startIndex + pageSize);
|
||||
renderArticles(records);
|
||||
@@ -291,13 +341,16 @@
|
||||
}
|
||||
$.each(list, function (i, item) {
|
||||
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(
|
||||
'<div class="cxz-article-item">' +
|
||||
'<a href="' +
|
||||
CommonUtil.freeArticleHref(item.id || "") +
|
||||
CommonUtil.freeArticleHref(article.id || "") +
|
||||
'" target="_blank">' +
|
||||
escapeHtml(item.title || "无标题") +
|
||||
escapeHtml(title) +
|
||||
"</a>" +
|
||||
'<span class="cxz-article-date">' +
|
||||
escapeHtml(displayDate) +
|
||||
@@ -308,6 +361,10 @@
|
||||
}
|
||||
|
||||
function renderPagination(total) {
|
||||
$("#pagination").empty();
|
||||
if (!total || total <= pageSize) {
|
||||
return;
|
||||
}
|
||||
layui.laypage.render({
|
||||
elem: "pagination",
|
||||
count: total,
|
||||
|
||||
+9
-4
@@ -66,9 +66,10 @@
|
||||
<div class="page-title-bar">
|
||||
<div class="page-title">排列三走势图</div>
|
||||
<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" data-count="60">60期</div>
|
||||
<div class="qishu-but" data-count="100">100期</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -207,9 +208,12 @@
|
||||
var parsed = parseCode(item.openCode);
|
||||
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-code">' + item.openCode + "</td>";
|
||||
html += '<td class="col-code">' + CommonUtil.renderTrendOpenCode(item.openCode, { mode: "position" }) + "</td>";
|
||||
|
||||
var typeNames = ["baozi", "zu3", "zu6"];
|
||||
var typeLabels = ["豹子", "组三", "组六"];
|
||||
@@ -384,7 +388,8 @@
|
||||
function loadData() {
|
||||
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "pl3" }).then(function (res) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
+9
-4
@@ -66,9 +66,10 @@
|
||||
<div class="page-title-bar">
|
||||
<div class="page-title">排列五走势图</div>
|
||||
<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" data-count="60">60期</div>
|
||||
<div class="qishu-but" data-count="100">100期</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -204,9 +205,12 @@
|
||||
var item = data[r];
|
||||
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-code">' + item.openCode + "</td>";
|
||||
html += '<td class="col-code">' + CommonUtil.renderTrendOpenCode(item.openCode, { mode: "position" }) + "</td>";
|
||||
|
||||
for (var p = 0; p < POS_COUNT; p++) {
|
||||
var val = nums[p] !== undefined ? nums[p] : -1;
|
||||
@@ -313,7 +317,8 @@
|
||||
function loadData() {
|
||||
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "pl5" }).then(function (res) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
+21
-30
@@ -66,9 +66,10 @@
|
||||
<div class="page-title-bar">
|
||||
<div class="page-title">七乐彩走势图</div>
|
||||
<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" data-count="60">60期</div>
|
||||
<div class="qishu-but" data-count="100">100期</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -78,10 +79,9 @@
|
||||
<tr>
|
||||
<td rowspan="2" class="col-issue">期号</td>
|
||||
<td rowspan="2" class="col-code">开奖号</td>
|
||||
<td colspan="10" class="zone-z1">一区(01-10)</td>
|
||||
<td colspan="10" class="zone-z2">二区(11-20)</td>
|
||||
<td colspan="10" class="zone-z3">三区(21-30)</td>
|
||||
<td colspan="1" class="zone-sp">特别号</td>
|
||||
<td colspan="10" class="zone-z1">一区</td>
|
||||
<td colspan="10" class="zone-z2">二区</td>
|
||||
<td colspan="10" class="zone-z3">三区</td>
|
||||
</tr>
|
||||
<tr id="header-row"></tr>
|
||||
</thead>
|
||||
@@ -179,8 +179,6 @@
|
||||
for (var i = 1; i <= MAIN_MAX; i++) {
|
||||
html += "<td>" + (i < 10 ? "0" + i : i) + "</td>";
|
||||
}
|
||||
// Special number column
|
||||
html += "<td>特</td>";
|
||||
$("#header-row").html(html);
|
||||
}
|
||||
|
||||
@@ -213,12 +211,20 @@
|
||||
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-code">' + item.openCode + "</td>";
|
||||
html += '<td class="col-code">' + CommonUtil.renderTrendOpenCode(item.openCode, { mode: "qlc" }) + "</td>";
|
||||
|
||||
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 +=
|
||||
'<td><span class="hit-main">' +
|
||||
(i < 10 ? "0" + i : i) +
|
||||
@@ -227,19 +233,8 @@
|
||||
html +=
|
||||
'<td><span class="miss-num">' +
|
||||
(i < 10 ? "0" + i : i) +
|
||||
"</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>";
|
||||
}
|
||||
@@ -299,11 +294,8 @@
|
||||
|
||||
function buildStats(data) {
|
||||
var ms = calcStats(data, MAIN_MAX, function (d) {
|
||||
return parseCode(d.openCode).mains;
|
||||
});
|
||||
// Special number stats (1-30 range for special)
|
||||
var ss = calcStats(data, MAIN_MAX, function (d) {
|
||||
return parseCode(d.openCode).specials;
|
||||
var parsed = parseCode(d.openCode);
|
||||
return parsed.mains.concat(parsed.specials);
|
||||
});
|
||||
|
||||
var labels = [
|
||||
@@ -325,8 +317,6 @@
|
||||
"</td>";
|
||||
for (var i = 1; i <= MAIN_MAX; i++)
|
||||
stats += "<td>" + ms[keys[li]][i] + "</td>";
|
||||
// Special column stats
|
||||
stats += "<td>-</td>";
|
||||
stats += "</tr>";
|
||||
}
|
||||
return stats;
|
||||
@@ -335,7 +325,8 @@
|
||||
function loadData() {
|
||||
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "qlc" }).then(function (res) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
+22
-12
@@ -66,9 +66,10 @@
|
||||
<div class="page-title-bar">
|
||||
<div class="page-title">七星彩走势图</div>
|
||||
<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" data-count="60">60期</div>
|
||||
<div class="qishu-but" data-count="100">100期</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -178,13 +179,13 @@
|
||||
var currentCount = 30;
|
||||
var allData = [];
|
||||
var hitClasses = [
|
||||
"hit-p1",
|
||||
"hit-p2",
|
||||
"hit-p3",
|
||||
"hit-p4",
|
||||
"hit-p5",
|
||||
"hit-p6",
|
||||
"hit-p7",
|
||||
"hit-red",
|
||||
"hit-red",
|
||||
"hit-red",
|
||||
"hit-red",
|
||||
"hit-red",
|
||||
"hit-red",
|
||||
"hit-blue",
|
||||
];
|
||||
var posMaxes = [9, 9, 9, 9, 9, 9, LAST_MAX]; // max value for each position
|
||||
|
||||
@@ -198,7 +199,12 @@
|
||||
}
|
||||
|
||||
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 nums;
|
||||
@@ -211,9 +217,12 @@
|
||||
var item = data[r];
|
||||
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-code">' + item.openCode + "</td>";
|
||||
html += '<td class="col-code">' + CommonUtil.renderTrendOpenCode(item.openCode, { mode: "qxc" }) + "</td>";
|
||||
|
||||
for (var p = 0; p < POS_COUNT; p++) {
|
||||
var max = posMaxes[p];
|
||||
@@ -323,7 +332,8 @@
|
||||
function loadData() {
|
||||
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "qxc" }).then(function (res) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
+49
-9
@@ -653,8 +653,10 @@
|
||||
}
|
||||
|
||||
function getInviteCodeFromQuery() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
return params.get("inviteCode") || params.get("referralCode") || "";
|
||||
return (
|
||||
AuthPageUtil.captureReferralInviteFromUrl() ||
|
||||
AuthPageUtil.getPendingReferralInviteCode()
|
||||
);
|
||||
}
|
||||
|
||||
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() {
|
||||
$("#registerBtn").prop("disabled", true).text("注册中...");
|
||||
ApiClient.post(ApiClient.API.authRegister, buildRegisterData(), {
|
||||
@@ -701,13 +746,7 @@
|
||||
})
|
||||
.then(function (res) {
|
||||
if (ApiClient.isSuccess(res)) {
|
||||
layerInstance.msg(
|
||||
"注册成功,即将前往登录",
|
||||
{ icon: 1, time: 1200 },
|
||||
function () {
|
||||
window.location.href = "login.html";
|
||||
},
|
||||
);
|
||||
finishRegisterSuccess(res);
|
||||
return;
|
||||
}
|
||||
layerInstance.msg((res && res.msg) || "注册失败,请稍后重试", {
|
||||
@@ -726,6 +765,7 @@
|
||||
|
||||
layui.use(["layer", "jquery"], function () {
|
||||
layerInstance = layui.layer;
|
||||
AuthPageUtil.captureReferralInviteFromUrl();
|
||||
loadVerifyConfig().then(refreshCaptcha);
|
||||
loadReferralBindPreview();
|
||||
$("#sendSmsBtn").on("click", sendSmsCode);
|
||||
|
||||
+9
-4
@@ -66,9 +66,10 @@
|
||||
<div class="page-title-bar">
|
||||
<div class="page-title">双色球走势图</div>
|
||||
<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" data-count="60">60期</div>
|
||||
<div class="qishu-but" data-count="100">100期</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -215,11 +216,14 @@
|
||||
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-code">' +
|
||||
item.openCode.replace(/\+/g, "+") +
|
||||
CommonUtil.renderTrendOpenCode(item.openCode, { mode: "ssq" }) +
|
||||
"</td>";
|
||||
|
||||
for (var i = 1; i <= RED_MAX; i++) {
|
||||
@@ -398,7 +402,8 @@
|
||||
function loadData() {
|
||||
ApiClient.requestLegacyLotteryResultCompat({ suoxie: "ssq" }).then(function (res) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
+5
-3
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -542,13 +542,15 @@
|
||||
if (!isSuccess(res)) throw new Error(res.msg || "收款码上传失败");
|
||||
const data = pickData(res) || {};
|
||||
const qrCodeFileId = fieldValue(data, ["qrCodeFileId", "qrcodeFileId", "fileId", "ossId", "id"]) || fieldValue(res, ["qrCodeFileId", "qrcodeFileId", "fileId", "ossId", "id"]);
|
||||
if (!qrCodeFileId) throw new Error("收款码上传成功,但未返回二维码文件ID");
|
||||
return { qrCodeFileId: qrCodeFileId };
|
||||
const qrcodeUrl = fieldValue(data, ["qrcodeUrl", "qrCodeUrl", "fileUrl", "url", "ossUrl"]) || fieldValue(res, ["qrcodeUrl", "qrCodeUrl", "fileUrl", "url", "ossUrl"]);
|
||||
if (!qrCodeFileId && !qrcodeUrl) throw new Error("收款码上传成功,但未返回二维码文件信息");
|
||||
return { qrCodeFileId: qrCodeFileId, qrcodeUrl: qrcodeUrl };
|
||||
});
|
||||
}
|
||||
|
||||
function appendQrcodePayload(payload, uploadData) {
|
||||
if (uploadData && uploadData.qrCodeFileId) payload.qrCodeFileId = uploadData.qrCodeFileId;
|
||||
if (uploadData && uploadData.qrcodeUrl) payload.qrcodeUrl = uploadData.qrcodeUrl;
|
||||
return payload;
|
||||
}
|
||||
|
||||
|
||||
+67
-33
@@ -139,28 +139,6 @@
|
||||
</button>
|
||||
</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 -->
|
||||
<div class="uc-balance-grid">
|
||||
<div class="uc-balance-item">
|
||||
@@ -535,9 +513,24 @@
|
||||
}
|
||||
|
||||
function updateExpertActions(data) {
|
||||
var expert = isExpertUser(data);
|
||||
$("#expertApplyBtn").toggle(!expert);
|
||||
$("#publishArticleBtn").toggle(expert);
|
||||
var state = CommonUtil.getExpertState
|
||||
? CommonUtil.getExpertState(data)
|
||||
: { 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() {
|
||||
@@ -1379,7 +1372,7 @@
|
||||
? currentUserData.nickName || currentUserData.username || ""
|
||||
: "";
|
||||
|
||||
uploadedAvatarUrl = "";
|
||||
uploadedAvatarId = "";
|
||||
userAvatarFile = null;
|
||||
|
||||
var content =
|
||||
@@ -1422,14 +1415,14 @@
|
||||
content: content,
|
||||
end: function () {
|
||||
editProfileLayerIndex = null;
|
||||
uploadedAvatarUrl = "";
|
||||
uploadedAvatarId = "";
|
||||
userAvatarFile = null;
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var uploadedAvatarUrl = "";
|
||||
var uploadedAvatarId = "";
|
||||
var editProfileLayerIndex = null;
|
||||
var userAvatarFile = null;
|
||||
|
||||
@@ -1443,14 +1436,19 @@
|
||||
if (!input.files || !input.files[0]) return;
|
||||
|
||||
var file = input.files[0];
|
||||
if (!/^image\//.test(file.type || "")) {
|
||||
layui.layer.msg("请选择图片文件", { icon: 2 });
|
||||
if (!/^(image\/jpeg|image\/png)$/.test(file.type || "")) {
|
||||
layui.layer.msg("只支持 JPG/PNG 格式的图片", { icon: 2 });
|
||||
input.value = "";
|
||||
return;
|
||||
}
|
||||
if (file.size > 2 * 1024 * 1024) {
|
||||
layui.layer.msg("图片大小不能超过 2MB", { icon: 2 });
|
||||
input.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
userAvatarFile = file;
|
||||
uploadedAvatarUrl = "";
|
||||
uploadedAvatarId = "";
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
@@ -1504,31 +1502,67 @@
|
||||
if (!avatarId) {
|
||||
throw new Error("头像上传返回缺少文件编号");
|
||||
}
|
||||
uploadedAvatarUrl = avatarId;
|
||||
uploadedAvatarId = 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() {
|
||||
var nickName = $("#editNickName").val().trim();
|
||||
var password = $("#editPassword").val();
|
||||
var passwordConfirm = $("#editPasswordConfirm").val();
|
||||
var currentNickName = currentUserData
|
||||
? currentUserData.nickName || currentUserData.username || ""
|
||||
: "";
|
||||
|
||||
if (!nickName) {
|
||||
layui.layer.msg("昵称不能为空", { icon: 2 });
|
||||
return;
|
||||
}
|
||||
if (nickName !== currentNickName && !validateEditProfileNickname(nickName)) {
|
||||
return;
|
||||
}
|
||||
if ((password || passwordConfirm) && (!password || !passwordConfirm)) {
|
||||
layui.layer.msg("请输入并确认新密码", { icon: 2 });
|
||||
return;
|
||||
}
|
||||
if (password && password !== passwordConfirm) {
|
||||
layui.layer.msg("两次密码输入不一致", { icon: 2 });
|
||||
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) {
|
||||
params.password = hex_md5(password);
|
||||
}
|
||||
|
||||
if (!userAvatarFile && Object.keys(params).length === 0) {
|
||||
layui.layer.msg("暂无修改", { icon: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
var $submitBtn = $("#editProfileSubmitBtn");
|
||||
$submitBtn.prop("disabled", true).text("保存中...");
|
||||
|
||||
|
||||
+48
-44
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -90,6 +90,11 @@
|
||||
<div class="table-section-title">
|
||||
<i class="layui-icon layui-icon-group"></i> 下级用户列表
|
||||
</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>
|
||||
<div id="distPage"></div>
|
||||
</div>
|
||||
@@ -131,6 +136,9 @@
|
||||
var $ = layui.$;
|
||||
var table = layui.table;
|
||||
var laypage = layui.laypage;
|
||||
var currentPage = 1;
|
||||
var pageSize = 20;
|
||||
var currentStatus = 0;
|
||||
|
||||
// Load page config
|
||||
CommonUtil.loadPageConfig({ ads: false, links: false });
|
||||
@@ -206,10 +214,11 @@
|
||||
function loadDistList(pageNum) {
|
||||
currentPage = pageNum;
|
||||
ApiClient.get(
|
||||
ApiClient.API.referralInviteList,
|
||||
ApiClient.API.referralRebateList,
|
||||
{
|
||||
pageNum: pageNum,
|
||||
pageSize: pageSize,
|
||||
status: currentStatus,
|
||||
},
|
||||
{
|
||||
headers: CommonUtil.authHeaders(),
|
||||
@@ -217,8 +226,9 @@
|
||||
)
|
||||
.then(function (res) {
|
||||
if (res.code === 0 && res.data) {
|
||||
renderTable(res.data.records || []);
|
||||
renderPagination(res.data.total || 0);
|
||||
var records = ApiClient.asArray(res.data.records || res.data.rows || res.data.list || res.data);
|
||||
renderTable(records);
|
||||
renderPagination(res.data.total || res.data.totalRow || records.length || 0);
|
||||
} else {
|
||||
layer.msg(res.msg || "获取数据失败", { icon: 2 });
|
||||
}
|
||||
@@ -240,73 +250,60 @@
|
||||
elem: "#distTable",
|
||||
cols: [
|
||||
[
|
||||
{ field: "id", title: "记录ID", width: 140, align: "center" },
|
||||
{
|
||||
field: "nickName",
|
||||
title: "昵称",
|
||||
width: 160,
|
||||
align: "center",
|
||||
},
|
||||
{
|
||||
field: "phoneNumber",
|
||||
title: "手机号",
|
||||
title: "订阅用户",
|
||||
width: 160,
|
||||
align: "center",
|
||||
templet: function (d) {
|
||||
if (d.phoneNumber) {
|
||||
return d.phoneNumber.replace(
|
||||
/(\d{3})\d{4}(\d{4})/,
|
||||
"$1****$2",
|
||||
);
|
||||
}
|
||||
return "--";
|
||||
return CommonUtil.escapeHtml(d.nickName || d.userName || d.inviteeName || "--");
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "level",
|
||||
title: "层级",
|
||||
width: 100,
|
||||
field: "articleTitle",
|
||||
title: "文章/彩种",
|
||||
minWidth: 180,
|
||||
align: "center",
|
||||
templet: function (d) {
|
||||
return d.level === 1
|
||||
? '<span style="color:#102b6a;font-weight:600;">一级</span>'
|
||||
: '<span style="color:#5b2c8e;font-weight:600;">二级</span>';
|
||||
return CommonUtil.escapeHtml(d.menuName || d.articleTitle || d.title || "--");
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "createTime",
|
||||
title: "注册时间",
|
||||
width: 180,
|
||||
field: "expertName",
|
||||
title: "专家",
|
||||
width: 140,
|
||||
align: "center",
|
||||
templet: function (d) {
|
||||
return d.createTime
|
||||
? d.createTime.substring(0, 19).replace("T", " ")
|
||||
: "--";
|
||||
return CommonUtil.escapeHtml(d.nickNameExpert || d.expertName || "--");
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "totalRecharge",
|
||||
title: "累计充值",
|
||||
field: "amount",
|
||||
title: "订阅价格",
|
||||
width: 130,
|
||||
align: "center",
|
||||
templet: function (d) {
|
||||
return (
|
||||
'<span style="color:#f15a22;font-weight:600;">' +
|
||||
(d.totalRecharge || 0).toFixed(2) +
|
||||
"</span>"
|
||||
);
|
||||
return CommonUtil.escapeHtml(d.subscriptionPrice || d.orderAmount || d.amount || "0.00");
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "distributionIncome",
|
||||
title: "分销收益",
|
||||
field: "rebateMoney",
|
||||
title: "分销金额",
|
||||
width: 130,
|
||||
align: "center",
|
||||
templet: function (d) {
|
||||
return (
|
||||
'<span style="color:#fdb933;font-weight:600;">' +
|
||||
(d.distributionIncome || 0).toFixed(2) +
|
||||
"</span>"
|
||||
);
|
||||
return '<span style="color:#f15a22;font-weight:600;">' + CommonUtil.escapeHtml(d.rebateMoney || d.rebateAmount || d.money || "0.00") + "</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>
|
||||
</body>
|
||||
|
||||
@@ -652,7 +652,7 @@ body {
|
||||
|
||||
.draw-card__balls--compact {
|
||||
gap: 3px;
|
||||
width: calc(22px * 10 + 3px * 9);
|
||||
width: calc(25px * 10 + 3px * 9);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -470,8 +470,8 @@ select {
|
||||
.ball--compact {
|
||||
flex-basis: 22px;
|
||||
width: 22px;
|
||||
min-width: 22px;
|
||||
height: 22px;
|
||||
min-width: 25px;
|
||||
height: 25px;
|
||||
font-size: 11px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
+117
-4
@@ -8,7 +8,7 @@
|
||||
1. Breadcrumb Bar
|
||||
---------------------------------------------------------- */
|
||||
.breadcrumb-bar {
|
||||
max-width: 1200px;
|
||||
max-width: 1420px;
|
||||
margin: 0 auto;
|
||||
padding: 14px 20px 0;
|
||||
font-size: 13px;
|
||||
@@ -40,7 +40,7 @@
|
||||
2. Page Title Bar & Period Selector
|
||||
---------------------------------------------------------- */
|
||||
.page-title-bar {
|
||||
max-width: 1200px;
|
||||
max-width: 1420px;
|
||||
margin: 0 auto;
|
||||
padding: 10px 20px 14px;
|
||||
display: flex;
|
||||
@@ -97,7 +97,7 @@
|
||||
3. Table Wrapper & Horizontal Scroll
|
||||
---------------------------------------------------------- */
|
||||
.table-wrapper {
|
||||
max-width: 1200px;
|
||||
max-width: 1420px;
|
||||
margin: 12px auto 40px;
|
||||
padding: 0 20px;
|
||||
overflow-x: auto;
|
||||
@@ -191,6 +191,10 @@
|
||||
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)
|
||||
---------------------------------------------------------- */
|
||||
@@ -243,6 +247,56 @@
|
||||
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
|
||||
---------------------------------------------------------- */
|
||||
@@ -357,6 +411,65 @@
|
||||
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 {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -1010,4 +1123,4 @@
|
||||
font-size: 9px !important;
|
||||
padding-left: 4px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,6 +407,13 @@
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.uc-btn:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.65;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.uc-btn-publish {
|
||||
background: var(--bg-light);
|
||||
color: var(--primary);
|
||||
|
||||
@@ -11,7 +11,6 @@ const $ = layui.$;
|
||||
let isSubmittingPayArticle = false;
|
||||
let selections = [];
|
||||
let stepsConfig = [];
|
||||
const REQUIRED_DAILY_FREE_ARTICLES = 2;
|
||||
// 页面加载完成后获取首页信息
|
||||
layui.$(document).ready(function () {
|
||||
CommonUtil.loadPageConfig({ ads: false, links: false });
|
||||
@@ -33,149 +32,23 @@ const $ = layui.$;
|
||||
});
|
||||
});
|
||||
|
||||
// 判断布尔字段,兼容后端可能返回的 1/true/"true"。
|
||||
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 篇审核通过/已发布的免费文章。
|
||||
// 付费文章发布权限以 mine/summary 返回字段为准。
|
||||
function checkPaidPublishPermission() {
|
||||
return ApiClient.get(ApiClient.API.mineSummary)
|
||||
.then(function (res) {
|
||||
if (!ApiClient.isSuccess(res) || !res.data) {
|
||||
if (
|
||||
!ApiClient.isSuccess(res) ||
|
||||
!res.data ||
|
||||
!window.PublishPermission
|
||||
) {
|
||||
return { canPaid: false, reason: "暂时无法获取发布权限。" };
|
||||
}
|
||||
|
||||
const status = String(
|
||||
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 {
|
||||
canPaid: count >= REQUIRED_DAILY_FREE_ARTICLES,
|
||||
reason:
|
||||
count >= REQUIRED_DAILY_FREE_ARTICLES
|
||||
? ""
|
||||
: "今天需先发布2篇审核通过的免费文章后,才能发布付费文章(当前" +
|
||||
count +
|
||||
"篇)。",
|
||||
};
|
||||
});
|
||||
const permission = PublishPermission.parseSummary(res.data);
|
||||
return {
|
||||
...permission,
|
||||
reason: permission.reason || permission.paidReason,
|
||||
};
|
||||
})
|
||||
.catch(function () {
|
||||
return { canPaid: false, reason: "暂时无法获取发布权限。" };
|
||||
|
||||
+16
-2
@@ -768,6 +768,19 @@ const ApiClient = {
|
||||
const params = this.normalizePageParams(data || {});
|
||||
const code = params.code || params.suoxie || params.lotteryCode || '';
|
||||
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) {
|
||||
try {
|
||||
const trendRes = await this.get(this.API.lotteryTrend, {
|
||||
@@ -775,13 +788,14 @@ const ApiClient = {
|
||||
pageSize,
|
||||
});
|
||||
if (this.isSuccess(trendRes) && this.asArray(this.pickData(trendRes, [])).length) {
|
||||
return trendRes;
|
||||
return normalizeResponse(trendRes);
|
||||
}
|
||||
} catch (e) {
|
||||
// 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 = {}) {
|
||||
|
||||
+333
-2
@@ -3,6 +3,109 @@ const AuthPageUtil = {
|
||||
slideUuid: '',
|
||||
slideX: 0,
|
||||
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) {
|
||||
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) {
|
||||
const $button = layui.$(buttonSelector);
|
||||
let left = seconds;
|
||||
@@ -140,7 +471,7 @@ const AuthPageUtil = {
|
||||
|
||||
loadSlideCaptchaScript() {
|
||||
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;
|
||||
window.__slideCaptchaLoading = new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
@@ -148,7 +479,7 @@ const AuthPageUtil = {
|
||||
script.dataset.apiBase = ApiClient.baseUrl || '';
|
||||
script.dataset.tenantId = ApiClient.tenantId || '936208';
|
||||
script.onload = () => {
|
||||
if (window.SlideCaptcha) resolve(window.SlideCaptcha);
|
||||
if (window.SlideCaptcha) resolve(this.patchSlideCaptchaMobileCentering(window.SlideCaptcha));
|
||||
else reject(new Error('滑动验证组件加载失败'));
|
||||
};
|
||||
script.onerror = () => reject(new Error('滑动验证组件加载失败'));
|
||||
|
||||
@@ -148,6 +148,70 @@ const CommonUtil = {
|
||||
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 = {}) {
|
||||
if (user.regularExpert || user.isExpert === '1' || user.expertStatus === 'regular') {
|
||||
return '正式专家';
|
||||
@@ -1752,6 +1816,55 @@ const CommonUtil = {
|
||||
$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) {
|
||||
if (!html) return '';
|
||||
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