Compare commits

...

20 Commits

Author SHA1 Message Date
guke ae170fb315 修复比价记录页"上报更低价"按钮位置 2026-07-02 17:13:24 +08:00
guke f8f05e8812 Merge branch 'main' of https://gitea.shaguabijia.com/WonderableAI/shaguabijia-app-server into feat/h5-offline-pool 2026-07-02 11:07:36 +08:00
guke 39d6323b42 删除预加载 2026-07-02 11:02:16 +08:00
陈世睿 667cbdf8ad Merge remote-tracking branch 'origin/feat/h5-sgbridge' into feat/h5-sgbridge 2026-06-30 20:05:47 +08:00
陈世睿 7ac3ca8fd6 新增设置页与相关协议 H5(头像底部 sheet+裁剪、昵称/退出/注销弹窗、页面不滚动、手势条避让)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 20:02:49 +08:00
陈世睿 b81ba70235 新增意见反馈、反馈历史 H5 页(去框 + 接 /api/v1/feedback,真机对齐)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 20:02:49 +08:00
陈世睿 85523a99d2 全 H5 页中文字体改用阿里普惠体真字模,移除 text-shadow 伪粗体兜底
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 20:02:48 +08:00
陈世睿 a076bc9a2c Merge branch 'main' of https://gitea.shaguabijia.com/WonderableAI/shaguabijia-app-server into feat/h5-sgbridge 2026-06-30 15:07:46 +08:00
guke 8e3b282c79 feat(h5): 首页 H5 壳从定稿原型抽取(#home 满屏,静态 mock,零桥) (#96)
首页webview改造合并

---------

Co-authored-by: guke <guke@autohome.com.cn>
Reviewed-on: #96
2026-06-30 11:57:47 +08:00
陈世睿 4d730169bf 真机验收修复:我的页去框沉浸式 + 三页字体加粗 + 卡片细节
我的页(h5/mine)整页去框满屏 + 顶部状态栏沉浸式(云朵铺到状态栏下/隐藏假状态栏/内容 safe-area/头像对齐原型);三页统一注入 faux-bold(安卓 WebView 中文回退字体不合成粗体兜底);上报最低价选记录态列表间距去框对齐(top 198→154);上报记录卡平台改主名+主logo(美团/京东/淘宝)+ 菜品名 2 行截断

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:52:07 +08:00
陈世睿 cdfe66dcee 接入上报记录列表 H5(第三个 session 的孤儿页,补提交整合)
h5/reports/index.html(审核中/已通过/未通过三态+计数,真接 /api/v1/report/records)+ logos;android REPORT_RECORDS 路由稍后接 H5

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 17:13:21 +08:00
陈世睿 8d08986c00 合并:上报最低价 H5 2026-06-29 17:12:53 +08:00
陈世睿 be77ffa721 我的页迁远程 H5
h5/mine/index.html改用SGBridge.getAuthState/navigate(同源相对API);拷入页面静态资源h5/mine/assets/

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 17:12:08 +08:00
陈世睿 0bed761c00 上报最低价流程对齐原型/原生 + 接桥
toast改感谢您的反馈/D校验文案/提交失败解析后端detail/模拟键盘补小数点后2位上限/取消改closePage真返回/上报记录入口接SGBridge.navigate(reportRecords)/头部未读红点默认隐藏

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 17:12:00 +08:00
guke 109aebaa16 chore(scripts): records 页比价记录 mock 数据脚本(真机验收用,幂等)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 14:33:35 +08:00
guke 84ae950ea7 feat(h5): records 页迁入 server 改用 SGBridge(同源相对 API,保留 multipart 上传)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 13:14:18 +08:00
guke 122766c911 feat(h5): SGBridge 新增 closePage(返回上一页)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 13:05:33 +08:00
guke cee4f3e0a7 fix(h5): no-cache 按响应 content-type 判定,覆盖目录式 URL(/h5/x/)
html=True 时 /h5/x/ 经 Starlette 回落 index.html,但 path 仍为 x/ 不带 .html,旧判定漏设 no-cache。改用响应 content-type=text/html 判定,并加目录式 URL 回归测试。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 13:03:36 +08:00
guke 4fb8f6447c feat(h5): FastAPI StaticFiles 同源托管 /h5(.html no-cache)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 12:51:42 +08:00
guke 46c650ecb8 docs(h5): 新增 H5/WebView 改造方案(方案1 SGBridge,服务端托管)
由 app-server 同源托管业务 H5(StaticFiles 挂 /h5);records/reports 从安卓迁入改用 SGBridge/SGApi;bridge.js 补 closePage;后端业务接口零改动;含远程开关、缓存热更、M1-M4 里程碑。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 12:14:15 +08:00
138 changed files with 15028 additions and 81 deletions
+19
View File
@@ -167,3 +167,22 @@ app.mount(
StaticFiles(directory=str(_media_root)),
name="media",
)
# 业务 H5 同源托管(/h5)。与 /api/v1 同 host → H5 用相对路径调后端,免 CORS。
# .html 加 no-cache:后端改完用户重开页即拉新(远程热更核心);js/图片走默认缓存。
class _NoCacheHTMLStaticFiles(StaticFiles):
async def get_response(self, path: str, scope):
resp = await super().get_response(path, scope)
# 按响应 content-type 判定,而非请求 path:html=True 时目录式 URL(/h5/x/)
# 经 Starlette 内部回落到 index.html,此刻 path 仍是 "x/"(不带 .html)。
if resp.headers.get("content-type", "").startswith("text/html"):
resp.headers["Cache-Control"] = "no-cache"
return resp
_h5_root = Path(__file__).resolve().parent.parent / "h5"
app.mount(
"/h5",
_NoCacheHTMLStaticFiles(directory=str(_h5_root), html=True),
name="h5",
)
+154
View File
@@ -0,0 +1,154 @@
# H5 / WebView 改造方案(app-server 端)
> 仓库:`shaguabijia-app-server` 分支:`feat/h5-sgbridge` 日期:2026-06-29
> 配套文档:`shaguabijia-app-android` 仓库 `docs/H5-WebView改造方案.md`(原生壳 + SGBridge.kt)
> 本方案 = **方案1:以本仓已有 `SGBridge` 为统一基准**,安卓退役其 `NativeBridge`。
---
## 1. 背景与现状
项目里同时存在**两套互不兼容**的 H5 方案,尚未收敛:
| | 本仓那套(已在 `main`,PR #89) | 安卓那套(`feat/records-h5`,未进 main) |
|---|---|---|
| 桥 | `SGBridge`(JS)↔ `SGBridgeNative`(原生),协议丰富 + 事件总线 | `NativeBridge`,仅 4 法 |
| 托管 | **设计为**后端同源托管(相对 `/api/v1`、免 CORS) | 本地 assets `file://`(跨域 + 放行 CORS) |
| 页面 | `h5/mine/index.html`(7771)+ `h5/shared/bridge.js`+`api.js` | `records`(1778)、`reports`(603) |
| 落地 | **半成品**:安卓从无 `SGBridge.kt`;且 `h5/` 实际没挂(StaticFiles 只挂了 `data/media` 头像上传) | 已接进安卓导航但只在分支上 |
**结论(已对齐):** 目标是「远程后端托管 + 四 tab 全 H5」,该方向天然契合本仓 SGBridge(同源相对路径、`mine` 已成型、`shared/` 已就绪)。故统一到 SGBridge,把安卓的 records/reports 页**迁入本仓** `h5/`
---
## 2. 目标与范围
**目标:** 由 app-server 同源托管全部业务 H5,配齐 `shared/` 桥与接口封装,支持安卓「原生壳 + 远程 H5」一套桥贯通,可不发版热更。
**本仓(app-server)范围:**
- H5 托管:新增 **FastAPI StaticFiles 挂载 `/h5`** → 指向仓库 `h5/` 目录(同源、免 CORS)
- 迁入 records / reports 两页(从安卓 `feat/records-h5` 移植),改用 `SGBridge` / `SGApi`
- `shared/bridge.js`**`closePage`(返回)**;`shared/api.js` 已同源就绪(必要时补 multipart 说明)
- 远程开关配置接口(返回 per-page `native`/`h5` 标志)
- 缓存 / 热更策略(cache-control + 版本)
**不在本仓(见 android 文档):** `SGBridge.kt`、WebView 宿主、原生/H5 切换接线。
**后端业务接口:零改动** —— H5 复用现有 `/api/v1/...`(`/compare/records``/report``/wallet/account``/coupon/stats` 等),只是请求方从 Kotlin 改成网页 fetch。
---
## 3. 关键决策(已锁定)
| # | 决策 | 选定 | 理由 |
|---|---|---|---|
| D1 | 桥基准 | **SGBridge**(安卓退役 NativeBridge) | 协议齐、mine 成型、三端对齐 |
| D2 | 托管 | **FastAPI StaticFiles 挂 `/h5`** | 同源免 CORS、复用现有 `/media` 方式、nginx 已反代 `/`→uvicorn,dev+prod 都能跑 |
| D3 | 离线兜底 | 纯远程 + 安卓侧重试页 | YAGNI;后端不需为离线做特殊处理 |
| D4 | 地址 | 安卓用 `BackendUrl.current` 推导 H5 地址(同源) | H5 与 `/api/v1` 同 host,自动跟随环境 |
| D5 | 迁移顺序 | records → reports → 首页/福利;mine 最后校准 | 用小页验证桥,风险分散 |
---
## 4. H5 托管(D2)
`app/main.py` 现有挂载(仅头像):
```python
app.mount(settings.MEDIA_URL_PREFIX, StaticFiles(directory=str(_media_root)), name="media")
```
**新增** H5 静态挂载(指向仓库根 `h5/`,`html=True` 让目录路径回落到 `index.html`):
```python
_h5_root = Path(__file__).resolve().parent.parent / "h5" # 仓库根 h5/
app.mount("/h5", StaticFiles(directory=str(_h5_root), html=True), name="h5")
```
- 访问:`https://app-api.shaguabijia.com/h5/records/index.html`(prod)/ `http://<dev-host>:8770/h5/...`(debug)。
-`/api/v1` **同 host → 同源**:H5 内 `SGApi` 用相对 `/api/v1`,免 CORS、免 `getApiBase`
- prod 可选改由 nginx 直发 `h5/`(绕过 uvicorn)以提性能——本期先用 StaticFiles,nginx 优化留后。
---
## 5. H5 工程结构
```
h5/
├── shared/
│ ├── bridge.js # SGBridge:H5↔原生桥(本期补 closePage)
│ └── api.js # SGApi:同源相对 /api/v1 封装(已就绪)
├── mine/index.html # 我的页(已成型,M4 按真实 SGBridge.kt 校准)
├── records/index.html # ← 从安卓迁入(M1)
└── reports/index.html # ← 从安卓迁入(M2)
```
各页头部引入:`<script src="../shared/bridge.js"></script>` + `<script src="../shared/api.js"></script>`
---
## 6. records / reports 页迁入(从安卓移植)
**搬运 + 改桥(迁移、非重写,UI/CSS/逻辑占绝大部分原样保留):**
| 安卓原写法(NativeBridge) | 迁入后(SGBridge / SGApi) |
|---|---|
| `NativeBridge.getApiBase()` 拼绝对 URL(8 处) | **删除**,改相对 `/api/v1/...`(同源) |
| `NativeBridge.getToken()`(7 处) | `SGBridge.getToken()`(同名) |
| `NativeBridge.onUnauthorized()`(6 处) | 走 `SGApi` 内置 401 → `SGBridge.requestLogin()` |
| `NativeBridge.closePage()`(5 处) | `SGBridge.closePage()`(**bridge.js 本期新增**) |
| 普通 GET/POST | 改用 `SGApi.get/post('/...')`(自动带 token、处理 401) |
**上报传图(records)特别说明:**`<input type="file">` + 客户端压缩 + **`FormData` multipart** POST `/api/v1/report``SGApi` 只发 JSON,**不覆盖 multipart**,故这段**保留自定义 `fetch`**:URL 改相对 `/api/v1/report``Authorization` 头取 `SGBridge.getToken()`。文件选择依赖**安卓宿主 `onShowFileChooser`**(见 android 文档),后端无需改动。
---
## 7. `shared/bridge.js` 改动
- **新增 `closePage()`**:`SGBridge.closePage()` → 调 `SGBridgeNative.closePage()`(无桥时 mock `console.log`)。records/reports 顶栏返回箭头、`?from=profile` 返回都用它。
- 其余方法(`getToken`/`requestLogin`/`navigate`/`getAuthState`/事件总线…)已就绪,保持签名,供 mine 及后续 tab 使用。
- 浏览器无 `SGBridgeNative` 时走 MOCK,便于本地 `python -m http.server` 或 StaticFiles 直接调样式。
---
## 8. SGBridge 接口契约(三端共享)
> 与 android 文档同表;`shared/bridge.js` 为权威。新增方法三端同步。
**查询类(同步返回 String):** `getAuthState` / `getToken` ✓ / `getDeviceId` / `getAppVersion` / `getInstalledApps` / `getCouponClaimedToday`
**动作类(无返回):** `navigate` / `requestLogin` ✓ / `toast` / `openMeituan` / `startCompare` / `startCouponClaim` / `launchApp` / **`closePage`【新增】✓**
**事件类(原生→H5,`_emit`):** `onAuthChange` / `onBalanceChange` / `onSigninChange` / `onResume`
(✓ = records 阶段 M1 最小子集)
---
## 9. 远程开关配置接口
- 提供一个轻量配置:返回各页渲染方式 `{ records: "h5"|"native", reports: ..., home: ..., welfare: ..., mine: ... }`
- 安卓 `AppNavHost` 据此决定加载 H5 还是原生页,**不发版即可灰度 / 一键回退**。
- 复用现有远程配置/开关基建(参考安卓 `ad-config-remote-delivery``comparing-ad-remote-flag` 的下发方式),无则新增一个 `GET /api/v1/app/ui-flags` 之类端点。
---
## 10. 缓存与热更
- H5 是后端托管 → **改完即生效**(用户重开页即拉新),这是远程方案的核心收益。
- `index.html``Cache-Control: no-cache`(或短 TTL)保证及时性;`shared/*.js`、图片等带版本/指纹便于长缓存。StaticFiles 可配响应头或交 nginx 处理。
---
## 11. 迁移顺序与风险
| 里程碑 | server 侧动作 |
|---|---|
| **M1 records** | 挂 `/h5`;`bridge.js``closePage`;records 迁入 + 改桥;配 `index.html` no-cache |
| **M2 reports** | reports 迁入 + 改桥 |
| **M3 首页/福利** | 首页/福利页 H5 化(需 SGBridge P2+ 协议,配合安卓) |
| **M4 mine + 收尾** | mine 按真实 SGBridge.kt 校准;远程开关接口上线 |
| 风险 | 缓解 |
|---|---|
| SGBridge 原生侧未验证 | M1 用 records 小页验证(详见 android 文档) |
| multipart 上传被误套进 SGApi | 文档明确:上报保留自定义 multipart fetch,不走 SGApi |
| 同源被破坏(H5 与 API 不同 host) | 坚持 StaticFiles 同源托管;若改独立前端域名,需回评 SGApi 相对路径策略 |
| 缓存导致改了不生效 | `index.html` no-cache + 资源指纹 |
+223
View File
@@ -0,0 +1,223 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="../../shared/fonts.css">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'PuHuiTi',-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#F5F5F5;color:#1A1A1A;padding:4px 14px 40px;-webkit-text-size-adjust:100%}
.ag-title{font-size:19px;font-weight:700;line-height:1.4;color:#1A1A1A;text-align:center;margin:10px 0 6px}
.ag-meta{font-size:12px;font-weight:400;line-height:1.7;color:#999;text-align:center;margin-bottom:14px}
.ag-card{background:#fff;border-radius:14px;box-shadow:0 1px 2px rgba(0,0,0,.03);padding:18px 16px 20px}
.ag-lead{font-size:13px;font-weight:400;line-height:1.85;color:#666;margin-bottom:10px}
.ag-h{font-size:16px;font-weight:600;line-height:1.5;color:#1A1A1A;margin:22px 0 8px}
.ag-h3{font-size:14px;font-weight:600;line-height:1.5;color:#1A1A1A;margin:14px 0 6px}
.ag-p{font-size:13px;font-weight:400;line-height:1.85;color:#666;margin-bottom:8px}
.ag-toc{list-style:none;padding-left:0}
strong{font-weight:600;color:#1A1A1A}
ul,ol{margin:4px 0 8px;padding-left:20px}
li{font-size:13px;font-weight:400;line-height:1.8;color:#666;margin-bottom:6px}
.ag-hr{border:none;border-top:0.5px solid #E5E5E5;margin:16px 0}
table{width:100%;border-collapse:collapse;margin:10px 0;font-size:11px;line-height:1.55}
th,td{border:0.5px solid #E0E0E0;padding:5px 6px;text-align:left;vertical-align:top;color:#666;word-break:break-word}
th{background:#F7F7F7;color:#1A1A1A;font-weight:600}
a{color:#1565C0;text-decoration:none;word-break:break-all}
.ag-note{font-size:12px;color:#999;line-height:1.7;margin-top:8px}
.ag-foot{text-align:center;font-size:12px;font-weight:400;color:#CCC;line-height:1.7;margin:24px 0 8px}
</style>
</head>
<body>
<div class="ag-meta">生效日期:【2026 年 06 月 25 日】</div>
<div class="ag-card">
<div class="ag-lead">万德一博(北京)科技有限公司(以下简称“我们”)深知个人信息对您的重要性,并会尽全力保护您的个人信息安全可靠。我们致力于维持您对我们的信任,恪守适用法律和我们对您的承诺,遵循正当、合法、必要、诚信、最小必要的原则处理您的个人信息。</div>
<div class="ag-p">请您在使用“傻瓜比价”App(以下简称“本 App”或“本产品”)前,仔细阅读并充分理解本《傻瓜比价隐私政策》(以下简称“本政策”,<strong>我们已将重点内容以加粗形式提示,请您特别关注</strong>),并在确认充分理解并同意后再开始使用。如您对本政策有任何疑问,可通过本政策第八条“如何联系我们”提供的方式与我们联系。</div>
<div class="ag-p"><strong>【特别提示】</strong>本 App 是一款“比价工具”。我们本身不销售任何商品、不提供外卖/团购等交易服务,也不是您与电商/外卖/团购平台之间交易的相对方。您看到的商品、价格、优惠均来自淘宝、京东、拼多多、抖音、美团等第三方平台,最终下单、支付、履约均在相应第三方平台内完成,受该第三方平台自己的协议与隐私政策约束。</div>
<div class="ag-p">本政策将帮助您了解以下内容:</div>
<ul class="ag-toc">
<li>一、我们如何收集和使用您的个人信息</li>
<li>二、我们如何使用 Cookie 和同类技术</li>
<li>三、我们如何共享、转让、公开披露您的个人信息</li>
<li>四、我们如何保存和保护您的个人信息</li>
<li>五、您如何管理您的个人信息</li>
<li>六、未成年人的个人信息保护</li>
<li>七、通知和修订</li>
<li>八、如何联系我们</li>
<li>九、附录(第三方 SDK 清单、敏感个人信息清单)</li>
</ul>
<div class="ag-hr"></div>
<div class="ag-h">一、我们如何收集和使用您的个人信息</div>
<div class="ag-p">本 App 的基本功能为“跨平台商品比价”。您同意本政策表示您已了解本 App 提供的基本功能,以及基本功能运行所必需的个人信息,并给予相应的收集使用授权。但这并不代表您同意我们收集、处理非必要个人信息;扩展功能所需的个人信息、以及涉及敏感个人信息、第三方共享、调用系统权限等情形,我们将在您实际使用具体功能时单独征求您的同意。<strong>如您拒绝开启扩展功能或提供非必要个人信息,不会影响您使用基本功能。</strong></div>
<div class="ag-p">我们会遵循正当、合法、必要的原则,出于以下目的收集和使用您的个人信息:</div>
<div class="ag-h3">(一)帮助您注册和登录账号</div>
<div class="ag-p">当您注册、登录本 App 时,您需要向我们提供手机号码,我们通过发送短信验证码验证您的身份。您的手机号码用于创建账号、登录、账号安全保护以及向您发送服务通知。</div>
<div class="ag-p">为方便您快捷登录,您可以选择“一键登录”功能,我们会使用基础电信运营商提供的快捷登录能力,经您授权后获取您当前设备的本机手机号为您完成注册/登录。如您不希望使用,可通过短信验证码方式登录。</div>
<div class="ag-p">在您完成首次比价体验前,<strong>您无需登录即可使用核心比价功能</strong>(详见下文第(三)条);只有在您需要领取金币奖励、提现、查看比价记录等需要账号的功能时,才需要登录。</div>
<div class="ag-h3">(二)权限与设备信息的获取</div>
<div class="ag-p">为实现比价功能、保障账号与运行安全,我们会在您授权后获取以下信息。<strong>所有系统权限我们均不会默认开启,会在您使用到对应功能时向您申请,您可随时在系统设置中关闭:</strong></div>
<ol>
<li><strong>悬浮窗权限(在其他应用上层显示):</strong>用于在您浏览购物平台时显示“比价”悬浮按钮、比价进度与比价结果。这是本产品“无需打开 App 即可比价”体验的必要权限。</li>
<li><strong>无障碍服务权限(Accessibility Service):</strong>用于实现核心比价功能,详见第(三)条专门说明。</li>
<li><strong>设备与网络信息:</strong>为保障账号安全、识别风险、适配界面并排查故障,我们会收集您的设备型号、操作系统及版本、应用版本、屏幕分辨率、语言设置、设备标识符(如 OAID、Android ID,安卓系统不再提供 IMEI 时不强制收集)、网络状态、IP 地址、电信运营商等软硬件特征信息。</li>
<li><strong>自启动、忽略电池优化等系统设置:</strong>为保证比价与悬浮窗服务在后台稳定运行、避免被系统中断,我们会引导您开启应用自启动、将本 App 加入电池优化白名单(忽略电池优化)。这些设置仅用于维持服务可用性,不收集您的个人信息。您可随时在系统设置中关闭,关闭后可能影响比价服务的稳定性。</li>
</ol>
<div class="ag-h3">(三)核心比价功能所需的系统能力</div>
<div class="ag-p">为实现“在购物页面一键比价”以及“省钱记录”,本 App 需要您开启系统的无障碍服务与悬浮窗权限。开启后,<strong>仅在您使用比价相关功能时</strong>,本 App 会:①当您点击“比价”时,识别您当前查看的商品信息(如名称、价格、规格),并代您在其他购物平台搜索同款、读取价格,生成比价结果;②当您通过比价结果跳转并在第三方平台完成购买后,识别下单/支付成功结果,用于为您生成省钱记录。我们就此说明:</div>
<ul>
<li>商品识别尽量在您的设备本地完成,仅将商品文字关键信息(如名称/关键词、品牌、规格、价格)上传至服务器用于同款匹配,<strong>不会上传商品页面截图或任何屏幕图像</strong></li>
<li>下单记录:当您完成购买后,本 App 会识别下单/支付成功页面的结果信息(如下单平台、商品、金额、下单时间),用于在您的“比价记录/省钱统计”中展示。<strong>我们仅记录上述用于省钱统计所必需的信息,不会记录您的收货地址、收件人、银行卡号、支付密码、短信验证码或聊天等内容</strong></li>
<li>本 App 不会代您下单或支付,也不会进行与上述比价、省钱记录无关的操作;</li>
<li>比价结果仅供参考,各平台价格与优惠实时变动,最终价格、优惠及交易以第三方平台实际展示和结算为准;</li>
<li>您可随时在系统设置中关闭相关权限,关闭后将无法使用自动比价与省钱记录功能,但不影响 App 内其他功能。</li>
</ul>
<div class="ag-h3">(四)为您提供比价记录与“省钱统计”</div>
<div class="ag-p">为向您展示历史比价记录与累计为您节省的金额,我们会记录:</div>
<ul>
<li>比价记录:您在本 App 内发起的比价(如比价时间、目标商品、各平台价格对比结果);</li>
<li>下单记录:通过无障碍服务识别您在第三方平台的下单/支付成功结果(下单平台、商品、金额、下单时间),用于统计为您节省的金额并在记录页展示。<strong>我们仅记录用于上述统计所必需的信息,不记录您的收货地址、收件人、银行卡号、支付密码等信息。</strong>识别方式详见第(三)项关于无障碍服务的说明。</li>
</ul>
<div class="ag-p">此外,返佣/返现结算:我们通过所对接的 CPS 联盟接口(京东联盟、淘宝闪购联盟、美团联盟等)获取相关订单的结算状态,用于发放返现/奖励。</div>
<div class="ag-h3">(五)金币、奖励与提现功能(含敏感个人信息)</div>
<div class="ag-p">本 App 提供金币体系:您可通过签到、记录比价战绩、观看激励视频、邀请好友等任务获取金币,金币可按 App 内公示规则提现(具体兑换比例、提现门槛以 App 内最新公示为准)。为此我们会处理:</div>
<ul>
<li>任务与金币记录:您的签到、任务完成、金币收支、邀请关系等记录;</li>
<li>提现相关信息:当您申请提现时,我们通过微信支付向您的微信账户发放提现金额。<strong>实名核验与收款均由微信支付侧完成,我们不收集、不存储您的身份证件号码、银行卡号等信息</strong>;我们仅处理完成微信提现所必需的信息(如您的微信授权标识/OpenID、提现金额与提现记录)。如您不使用提现功能,则无需提供上述信息。</li>
</ul>
<div class="ag-h3">(六)为您展示内容与改进服务</div>
<div class="ag-p">为向您展示首页低价商品、“低价盲盒”、福利任务等内容,并改进我们的产品与服务,我们会收集您的浏览、点击、搜索、比价、收藏等行为信息进行统计分析。<strong>我们不基于上述信息对您进行个性化推荐。</strong></div>
<div class="ag-h3">(七)广告功能</div>
<div class="ag-p">在比价等待期的悬浮窗中、以及福利 Tab“看视频赚金币”任务中,我们会向您展示广告(含激励视频广告)。为实现广告展示与反作弊,接入的广告 SDK 可能收集设备信息等(详见第九条 SDK 清单)。</div>
<ul>
<li><strong>首次比价不展示任何广告;广告从第二次比价开始展示。</strong></li>
<li>您可通过【我的 → 设置 → 关于傻瓜比价 → 个性化广告】管理个性化广告;关闭后您看到的广告相关性会降低,但广告不会消失。</li>
</ul>
<div class="ag-h3">(八)跳转第三方平台与私域社群</div>
<ul>
<li>跳转购买:您点击比价结果卡片或低价商品时,我们会通过 CPS 联盟推广链接将您跳转至对应第三方平台的商品页面,您在该平台内完成下单与支付。<strong>我们不会获取您在第三方平台输入的账户密码、支付密码。</strong></li>
<li>私域社群:如您主动选择加入我们的福利社群,可能跳转至企业微信等第三方工具,相关信息处理适用该第三方的规则。</li>
</ul>
<div class="ag-h3">(九)为您提供客服与安全保障</div>
<ul>
<li>客服:当您联系客服时,我们会记录沟通记录、并核验您的账号信息,以处理您的问题。</li>
<li>安全保障:为保障账号、交易与系统运行安全,预防欺诈、薅羊毛、刷量等行为,我们会收集设备信息、日志信息、IP 地址、应用运行与崩溃信息等用于风险识别与防控。</li>
</ul>
<div class="ag-h3">(十)征得授权同意的例外</div>
<div class="ag-p">根据法律法规,在下列情形下我们处理您的个人信息无需征得您的授权同意:与履行法定义务相关、与国家安全/公共安全/公共卫生/重大公共利益直接相关、为订立或履行您作为一方当事人的合同所必需、在合理范围内处理您自行公开或已合法公开的信息、应急情况下为保护自然人生命健康和财产安全所必需,以及法律法规规定的其他情形。</div>
<div class="ag-hr"></div>
<div class="ag-h">二、我们如何使用 Cookie 和同类技术</div>
<div class="ag-p">为确保本产品正常运转、为您提供更便捷的体验,我们会在您的设备上存储名为 Cookie、设备标识或同类技术的小型数据文件,用于:记住您的身份与偏好设置、保障登录与数据安全、分析产品使用情况以优化体验。您可在系统/浏览器中清除或管理上述数据,但清除后可能影响您使用部分功能。</div>
<div class="ag-hr"></div>
<div class="ag-h">三、我们如何共享、转让、公开披露您的个人信息</div>
<div class="ag-h3">(一)共享</div>
<div class="ag-p">我们不会与任何公司、组织和个人共享您的个人信息,但以下情形除外:</div>
<ol>
<li>事先取得您的明确同意或授权;涉及敏感个人信息或向第三方提供个人信息时,我们将取得您的单独同意;</li>
<li>根据法律法规、行政或司法机关的强制性要求;</li>
<li>在符合法律法规的前提下,为实现本政策所述目的,与下列合作伙伴在必要范围内共享:
<ul>
<li>第三方 SDK 服务商(如广告、统计、推送、一键登录、崩溃监控等,详见第九条清单);</li>
<li>CPS/CPA 联盟平台:为完成返佣结算、订单追踪、拉新结算,我们会与所对接的电商/外卖联盟在必要范围内交互订单/设备标识等信息;</li>
<li>云服务、短信、实名核验、收款/支付通道服务商:为实现注册登录、提现到账、实名核验等。</li>
</ul>
</li>
</ol>
<div class="ag-p">对受托处理个人信息的合作方,我们会与其签订严格的数据保护协议,要求其按照本政策及法律法规处理您的个人信息。</div>
<div class="ag-h3">(二)转让</div>
<div class="ag-p">我们不会向任何公司、组织和个人转让您的个人信息,但以下情形除外:取得您的明确同意;在涉及合并、分立、收购、资产转让或类似交易时,我们会要求新的持有方继续受本政策约束,否则将要求其重新征得您的授权同意。</div>
<div class="ag-h3">(三)公开披露</div>
<div class="ag-p">我们仅在取得您单独同意、或基于法律法规、强制性行政/司法要求的情形下公开披露您的个人信息,并采取符合业界标准的安全防护措施。</div>
<div class="ag-hr"></div>
<div class="ag-h">四、我们如何保存和保护您的个人信息</div>
<div class="ag-h3">(一)保存地点</div>
<div class="ag-p">您的个人信息存储于中华人民共和国境内(我们使用阿里云作为云服务商,数据存储于其境内数据中心)。<strong>我们不会向境外传输您的个人信息。</strong></div>
<div class="ag-h3">(二)保存期限</div>
<div class="ag-p">我们仅在实现本政策所述目的所必需的最短期限内保存您的个人信息,并结合法律的强制留存要求确定保存期限(例如《电子商务法》要求商品和服务信息、交易信息自交易完成之日起保存不少于三年)。超出保存期限后,我们将对您的个人信息进行删除或匿名化处理。</div>
<div class="ag-h3">(三)保护措施</div>
<div class="ag-p">我们采用符合业界标准的安全防护措施保护您的个人信息,包括传输与存储加密(如 HTTPS)、访问权限控制、数据脱敏、内部安全管理制度与员工保密义务等,防止信息遭到未经授权的访问、使用、修改或泄露、损毁、丢失。</div>
<div class="ag-h3">(四)安全事件处置</div>
<div class="ag-p">若不幸发生个人信息安全事件,我们将按法律法规要求及时向您告知:事件基本情况与可能影响、我们已采取或将采取的处置措施、您可自主防范和降低风险的建议、对您的补救措施等,并以推送、短信、邮件、公告等方式告知您,同时按规定向监管部门上报。</div>
<div class="ag-h3">(五)产品或服务停止运营时的处置</div>
<div class="ag-p">如本 App 停止运营,我们将:①以推送、公告等显著方式通知您;②停止继续收集您的个人信息;③对我们持有的您的个人信息进行删除或匿名化处理,法律法规另有强制留存要求的除外。</div>
<div class="ag-hr"></div>
<div class="ag-h">五、您如何管理您的个人信息</div>
<div class="ag-p">您对您的个人信息享有以下权利,可通过【我的 → 设置 → 隐私管理】或本政策第八条的联系方式行使:</div>
<ol>
<li>访问、复制、更正、补充您的个人信息;</li>
<li>删除您的个人信息(在我们违法处理、未经同意处理、或您注销账号等情形下,您可请求删除);</li>
<li>改变或撤回授权同意:您可关闭相应系统权限或在设置中撤回授权。请您理解,撤回同意不影响撤回前基于您同意已进行的处理;撤回后我们将无法继续提供对应功能;</li>
<li>注销账号:您可通过【我的 → 设置 → 账户与安全 → 注销账号】注销。注销后我们将停止为您提供产品/服务,并依法删除或匿名化您的个人信息;</li>
<li>获取个人信息副本;</li>
<li>响应您的请求:我们将在收到并核验您身份后的 15 天内予以答复。对合理请求原则上不收取费用;对重复、超出合理限度的请求,我们可能酌情收取成本费用或予以拒绝。</li>
</ol>
<div class="ag-p">如您对我们的答复不满意,特别是认为我们的处理行为损害了您的合法权益,您可向网信、电信、公安、市场监管等监管部门投诉、举报。</div>
<div class="ag-hr"></div>
<div class="ag-h">六、未成年人的个人信息保护</div>
<div class="ag-p">本 App 主要面向成年人。若您是 18 周岁以下的未成年人,请在监护人陪同下阅读本政策,并在监护人同意后再使用本产品及提供个人信息。对于不满 14 周岁儿童的个人信息,我们将依据《儿童个人信息网络保护规定》等要求,在取得其监护人单独同意后方可处理,并采取严格保护措施。如您是未成年人的监护人,对未成年人个人信息有任何疑问,请通过第八条方式联系我们。</div>
<div class="ag-hr"></div>
<div class="ag-h">七、通知和修订</div>
<div class="ag-p">为提供更好的服务,本政策可能适时更新。<strong>未经您明确同意,我们不会削减您依据本政策应享有的权利。</strong>政策更新后,我们会通过 App 内公告、弹窗、推送或其他适当方式提示您。对于重大变更(如处理目的、处理信息类型、共享对象、您行使权利的方式发生重大变化,或所有权结构发生重大变化等),我们会提供更显著的通知并在生效前再次征得您的同意(如适用)。</div>
<div class="ag-hr"></div>
<div class="ag-h">八、如何联系我们</div>
<div class="ag-p">如您对本政策、您的个人信息处理、或需要投诉、举报、咨询,请通过以下方式与我们联系:</div>
<ul>
<li>公司全称:万德一博(北京)科技有限公司</li>
<li>注册地址:北京市丰台区丰管路甲1号北楼三层302-765室</li>
<li>客服 / 个人信息保护事务邮箱:support@wonderable.ai</li>
</ul>
<div class="ag-p">一般情况下,我们将在 15 天内回复您的请求。</div>
<div class="ag-hr"></div>
<div class="ag-h">九、附录</div>
<div class="ag-h3">附录一:第三方 SDK 与合作方清单</div>
<div class="ag-p">本清单是应用商店审核与监管检查的重点项。穿山甲为聚合广告平台(GroMore),其下游可能调用快手、腾讯优量汇等第三方广告网络,这些下游网络也会收集设备标识等信息,已在表中一并披露。</div>
<table>
<thead>
<tr><th>SDK / 合作方名称</th><th>提供方公司全称</th><th>使用目的</th><th>收集的个人信息</th><th>隐私政策链接</th></tr>
</thead>
<tbody>
<tr><td>穿山甲(GroMore 聚合广告平台,含激励视频广告)</td><td>北京巨量引擎网络技术有限公司</td><td>展示激励视频广告、广告归因与反作弊</td><td>设备标识符(OAID 等)、设备信息、网络信息、广告交互数据</td><td><a href="https://www.csjplatform.com/privacy">https://www.csjplatform.com/privacy</a></td></tr>
<tr><td>└ 经穿山甲聚合调用的下游广告网络(快手广告)</td><td>北京快手科技有限公司</td><td>通过聚合平台填充与展示广告</td><td>设备标识符、设备信息、网络信息</td><td><a href="https://www.kuaishou.com/about/policy">https://www.kuaishou.com/about/policy</a></td></tr>
<tr><td>└ 经穿山甲聚合调用的下游广告网络(腾讯优量汇)</td><td>深圳市腾讯计算机系统有限公司</td><td>通过聚合平台填充与展示广告</td><td>设备标识符、设备信息、网络信息</td><td><a href="https://e.qq.com/optout.html">https://e.qq.com/optout.html</a></td></tr>
<tr><td>极光(JIGUANG,一键登录 / 号码认证 / 短信验证)</td><td>深圳市和讯华谷信息技术有限公司</td><td>一键登录取号、短信验证码下发与号码认证</td><td>本机号码(运营商掩码取号)、设备信息、网络信息</td><td><a href="https://www.jiguang.cn/license/privacy">https://www.jiguang.cn/license/privacy</a></td></tr>
<tr><td>美团联盟(API,无 SDK</td><td>北京三快科技有限公司</td><td>外卖/团购比价的返佣订单追踪与结算</td><td>订单信息、推广标识</td><td>以美团联盟官方规则为准</td></tr>
<tr><td>京东联盟(API/链接,无 SDK</td><td>北京京东叁佰陆拾度电子商务有限公司及其关联公司</td><td>电商比价的返佣订单追踪与结算</td><td>订单信息、推广标识</td><td>以京东联盟官方规则为准</td></tr>
<tr><td>淘宝闪购联盟(API/链接,无 SDK)</td><td>阿里巴巴(中国)软件有限公司 / 阿里妈妈</td><td>电商/闪购比价的返佣订单追踪与结算</td><td>订单信息、推广标识</td><td>以对应联盟官方规则为准</td></tr>
<tr><td>阿里云</td><td>阿里云计算有限公司</td><td>云服务器、数据存储等基础设施(受托处理)</td><td>受托存储上述各项个人信息</td><td><a href="https://www.aliyun.com/">https://www.aliyun.com/</a></td></tr>
<tr><td>微信支付(提现)</td><td>财付通支付科技有限公司</td><td>向用户发放微信提现、实名核验与收款(由微信侧完成)</td><td>微信授权标识/OpenID、提现金额与记录</td><td>以微信支付官方规则为准</td></tr>
</tbody>
</table>
<div class="ag-note">说明:本 App 未接入独立的统计分析、消息推送 SDK,也不会将屏幕图像上传至云端 AI 大模型。</div>
<div class="ag-h3">附录二:本政策涉及的敏感个人信息清单</div>
<div class="ag-p">为便于您识别,下列为本 App 在特定功能下处理的、属于敏感或受单独同意约束的信息:</div>
<ul>
<li>下单记录:通过无障碍服务识别您的下单/支付成功结果(下单平台、商品、金额、下单时间)用于省钱统计,详见正文第一条第(三)项;</li>
<li>提现:实名核验与银行卡/身份证件号码由微信支付侧收集与处理,本 App 不收集、不存储。</li>
<li>本 App 不处理人脸、指纹等生物识别信息,不收集身份证件号码、银行卡号,不收集行踪轨迹,不读取通讯录、短信、相册(除您主动上传的反馈截图外)。</li>
</ul>
</div>
<div class="ag-foot">傻瓜比价 · 隐私政策<br>万德一博(北京)科技有限公司</div>
</body>
</html>
+133
View File
@@ -0,0 +1,133 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="../../shared/fonts.css">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'PuHuiTi',-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:#F5F5F5;color:#1A1A1A;padding:4px 14px 40px;-webkit-text-size-adjust:100%}
.ag-title{font-size:19px;font-weight:700;line-height:1.4;color:#1A1A1A;text-align:center;margin:10px 0 6px}
.ag-meta{font-size:12px;font-weight:400;line-height:1.7;color:#999;text-align:center;margin-bottom:14px}
.ag-card{background:#fff;border-radius:14px;box-shadow:0 1px 2px rgba(0,0,0,.03);padding:18px 16px 20px}
.ag-lead{font-size:13px;font-weight:400;line-height:1.85;color:#666;margin-bottom:10px}
.ag-h{font-size:16px;font-weight:600;line-height:1.5;color:#1A1A1A;margin:22px 0 8px}
.ag-p{font-size:13px;font-weight:400;line-height:1.85;color:#666;margin-bottom:8px}
strong{font-weight:600;color:#1A1A1A}
ol{margin:4px 0 8px;padding-left:20px}
li{font-size:13px;font-weight:400;line-height:1.8;color:#666;margin-bottom:6px}
.ag-hr{border:none;border-top:0.5px solid #E5E5E5;margin:16px 0}
a{color:#1565C0;text-decoration:none;word-break:break-all}
.ag-foot{text-align:center;font-size:12px;font-weight:400;color:#CCC;line-height:1.7;margin:24px 0 8px}
</style>
</head>
<body>
<div class="ag-meta">生效日期:【2026 年 06 月 25 日】</div>
<div class="ag-card">
<div class="ag-lead">欢迎您使用“傻瓜比价”App!本《傻瓜比价用户服务协议》(以下简称“本协议”)是您与万德一博(北京)科技有限公司(注册地址:北京市丰台区丰管路甲1号北楼三层302-765室,以下简称“我们”或“傻瓜比价”)之间就您注册、使用“傻瓜比价”App(以下简称“本 App”或“本服务”)所订立的协议。</div>
<div class="ag-p"><strong>请您务必审慎阅读、充分理解本协议各条款,特别是以加粗形式提示您的免除或限制我们责任、需您自行承担风险或责任的条款。</strong>如您未满 18 周岁,请在监护人陪同下阅读并在其同意后使用本服务。</div>
<div class="ag-p"><strong>当您勾选同意、或以注册、登录、使用本服务等任何方式表示接受本协议时,即视为您已阅读并同意本协议的全部内容,自愿接受其约束。</strong>如您不同意本协议任何内容,请勿注册或使用本服务。</div>
<div class="ag-p">本协议内容同时包括《傻瓜比价隐私政策》及我们在 App 内公示的与本服务相关的各项规则(如金币规则、活动规则等),上述内容为本协议不可分割的组成部分,与本协议正文具有同等效力。</div>
<div class="ag-hr"></div>
<div class="ag-h">一、服务说明(请重点阅读本 App 的性质)</div>
<ol>
<li><strong>本 App 是一款“比价工具”/信息服务,不是商品或服务的销售方、交易方。</strong>本 App 帮助您在淘宝、京东、拼多多、抖音、美团等第三方平台浏览商品时,自动跨平台搜索同款商品并展示价格对比结果。</li>
<li><strong>我们不销售任何商品、不提供外卖/团购/物流等交易及履约服务。</strong>您看到的商品、价格、优惠、店铺均来自第三方平台;您的下单、支付、发货、售后、退换货等全部在相应第三方平台内完成,并受该第三方平台自身的用户协议、规则与隐私政策约束。因商品质量、发货、售后、虚假宣传等产生的争议,应由您与该第三方平台或商家解决,我们不承担销售者/服务提供者责任,但会在合理范围内为您提供必要协助。</li>
<li><strong>比价结果仅供参考。</strong>各平台价格、库存、优惠券、满减、返现等实时变动,且受地域、账号、活动、时间等多种因素影响。我们力求比价结果准确及时,但<strong>不对比价结果(包括但不限于“全网最低价”的表述、同款匹配的准确性、价格的实时性)作任何明示或默示的保证;最终价格、优惠及交易以第三方平台实际展示和结算为准。</strong>请您在下单前自行核对。</li>
<li><strong>本服务的核心功能依赖系统无障碍服务与悬浮窗权限。</strong>您在使用前需要在系统设置中授予相应权限。关于该等权限读取与处理信息的范围、本地处理原则及您的关闭方式,详见《傻瓜比价隐私政策》第一条第(三)项。</li>
</ol>
<div class="ag-h">二、比价功能的开启与使用</div>
<ol>
<li>本服务的比价功能需要您开启系统的无障碍服务与悬浮窗权限。开启后,仅在您使用比价相关功能时,本 App 会识别您当前查看的商品并代您在其他购物平台搜索同款、比对价格,并在您完成购买后识别下单结果用于为您生成省钱记录;不会代您下单、支付或修改账户信息,也不会进行与比价、省钱记录无关的操作。关于权限读取与处理信息的范围、本地处理原则及关闭方式,详见《傻瓜比价隐私政策》第一条第(三)项。</li>
<li><strong>您理解并同意,是否使用比价功能由您自主选择。</strong>您可随时在系统设置中关闭相关权限,关闭后比价功能将不可用,但不影响您使用 App 内其他功能。</li>
</ol>
<div class="ag-h">三、账号的注册与使用</div>
<ol>
<li><strong>【注册资料】</strong>您应以诚实信用原则提供真实、准确、合法的注册信息(如手机号码),并在信息变动时及时更新。因您提供的信息不真实、不准确或未及时更新导致的后果,由您自行承担。</li>
<li><strong>【实名与提现】</strong>因法律法规、反洗钱及收款渠道要求,您在使用提现等功能时可能需要完成实名核验并提供收款账号信息,详见《傻瓜比价隐私政策》。未完成必要核验的,您可能无法使用相应功能。</li>
<li><strong>【账号安全】</strong>您应妥善保管账号及密码,对账号项下的全部行为负责。如发现账号被盗用或存在安全问题,应立即通知我们。因您自身保管不善导致的账号丢失、泄露及由此产生的损失,我们不承担责任(我们存在过错的除外)。</li>
<li><strong>【账号管理】</strong>原则上一名用户对应一个账号。如您存在不当注册或不当使用多个账号、利用账号从事违规行为的情形,我们有权对相关账号采取限制、冻结、合并、注销等措施。</li>
</ol>
<div class="ag-h">四、金币、奖励与提现规则</div>
<ol>
<li><strong>【金币性质】</strong>金币是本 App 内用于激励用户的虚拟权益,不是法定货币、不可在用户间转让、不可用于本 App 约定用途之外的交易。金币的获取、消耗、兑换、提现规则以 App 内公示的最新规则为准。</li>
<li><strong>【提现】</strong>金币可按 App 内公示的兑换比例与提现门槛提现(具体数值以 App 内最新公示页面为准)。当前提现通过微信支付发放,实名核验与收款由微信支付侧完成。</li>
<li><strong>【反作弊】(请重点阅读)您不得通过作弊、外挂、机器批量操作、虚假注册、刷量、薅羊毛、利用系统漏洞等任何不正当方式获取金币或奖励。一经发现,我们有权扣除相应金币/奖励、驳回提现、冻结或注销账号,并保留追究法律责任的权利。</strong>金币规则及反作弊规则如需调整,我们将提前以合理方式公示;如您对反作弊判定有异议,可通过第五条约定的申诉渠道提出申诉。</li>
<li><strong>【活动规则】</strong>各项福利任务、裂变、邀请等活动的具体规则以活动页面公示为准;如活动规则与本协议不一致,就该活动事项以活动规则为准。</li>
</ol>
<div class="ag-h">五、用户行为规范</div>
<div class="ag-p">您承诺在使用本服务过程中遵守法律法规,不从事下列行为,否则我们有权视情形采取警告、限制功能、冻结或注销账号等措施,由此产生的责任由您承担:</div>
<ol>
<li>发布、传播违反宪法确定的基本原则,危害国家安全、社会稳定、违背公序良俗,或法律法规禁止的其他内容;</li>
<li>侵犯他人知识产权、隐私权、名誉权等合法权益;</li>
<li>实施网络攻击、传播病毒/木马,破解、干扰、破坏本服务或其相关系统;</li>
<li>未经我们书面许可,自行或协助第三方对本服务进行非法抓取、反向工程、反编译,或使用外挂、插件、机器人等干扰本服务正常运行;</li>
<li>利用本服务从事诈骗、洗钱、刷单炒信、薅羊毛等违法或不正当行为;</li>
<li>其他违反法律法规或损害我们、其他用户、第三方合法权益的行为。</li>
</ol>
<div class="ag-p"><strong>【处罚与申诉】</strong>我们对账号采取限制、冻结、注销等处罚措施时,将根据违规行为的性质与严重程度合理处理,并以适当方式告知您处罚的事由。如您认为处罚有误,可通过 support@wonderable.ai 或 App 内客服渠道提出申诉,我们将在收到申诉后 15 个工作日内核实并答复。我们不会在不提供任何申诉渠道的情况下对账号作出无理由的永久封禁。</div>
<div class="ag-h">六、知识产权</div>
<ol>
<li>除另有声明外,本服务所包含的内容(包括但不限于软件、技术、程序、界面、文字、图片、图标、音视频、版面设计、商标、标识等)的知识产权归我们或相关权利人所有,受法律保护。</li>
<li>未经我们事先书面许可,您不得以任何形式使用、复制、修改、发布、出售、出租、反向工程、反编译上述内容,或制作衍生作品。</li>
<li>您在本服务中上传/发布的内容(如“反馈更低价”提交的链接、截图、文字等),应为您原创或已获合法授权,不侵犯他人合法权益;您授权我们在为提供和改进本服务所必需的范围内对该等内容进行使用。</li>
<li><strong>【侵权投诉处理】</strong>如您认为本服务中的内容侵犯了您的知识产权或其他合法权益,可通过 support@wonderable.ai 向我们发起投诉并提供权属证明及相关材料。我们核实后将依法采取删除、屏蔽等必要措施;被投诉方可提交不侵权声明进行申诉。</li>
</ol>
<div class="ag-h">七、第三方平台与第三方服务</div>
<ol>
<li>本服务包含跳转至第三方平台(电商/外卖/团购平台、CPS 联盟链接、私域社群、广告等)的功能。该等第三方平台与服务由第三方独立提供并独立承担责任,受其自身协议与规则约束。我们对第三方平台的内容、商品、服务、价格及其行为不作保证,亦不承担责任。</li>
<li>您通过本服务跳转第三方平台进行的交易,是您与该第三方平台/商家之间的交易,相关权利义务由您与该第三方解决。</li>
</ol>
<div class="ag-h">八、广告</div>
<ol>
<li>您理解并同意,为维持本服务的免费运营,我们或我们授权的第三方可能在本服务中(如比价等待期悬浮窗、福利任务)向您展示广告(含激励视频广告)。</li>
<li><strong>您应对广告内容自行审慎判断,除法律法规明确规定外,您应对依广告信息进行的交易自行负责。</strong>您可按《傻瓜比价隐私政策》的说明管理个性化广告。</li>
</ol>
<div class="ag-h">九、免责声明与责任限制(请重点阅读)</div>
<ol>
<li><strong>【服务“现状”提供】</strong>在法律允许的范围内,本服务按“现状”和“可得”基础提供。我们不保证本服务不会中断,也不保证其及时性、安全性、准确性、无错误。</li>
<li><strong>【比价准确性免责】</strong>如前所述,我们不对比价结果的准确性、完整性、实时性及“最低价”表述作保证,您应在第三方平台下单前自行核对价格与优惠。</li>
<li><strong>【第三方原因免责】</strong>对因第三方平台规则变化、接口变动、风控封禁、第三方 SDK/服务故障等非我们直接原因导致的服务异常或损失,在法律允许范围内我们不承担责任。</li>
<li><strong>【第三方平台规则风险】</strong>您理解并同意,是否使用本服务由您自主选择。各第三方平台对其平台的访问与使用有其自身规则,若您与第三方平台之间因平台规则产生账号限制等后果,在法律允许范围内由您与该第三方平台依其规则处理,我们不对该等第三方平台的处理结果承担责任;我们会在合理范围内持续优化服务以降低此类情况发生。</li>
<li><strong>【不可抗力】</strong>因不可抗力(如自然灾害、网络故障、电力中断、政府行为、法律法规变动等)导致服务中断或无法提供的,在法律允许范围内我们免责,但会尽力减少对您的影响。</li>
<li><strong>【责任限额】</strong>在法律允许的最大范围内,对于因使用或无法使用本服务造成的间接、偶然、惩罚性损失(如利润损失、数据损失),我们不承担责任;我们对您承担的累计赔偿责任总额,以法律强制规定为限【如需设置上限,由法务确定具体表述】。本条不排除或限制依法不可排除或限制的责任(如因我们故意或重大过失、或侵害您人身权益依法应承担的责任)。</li>
</ol>
<div class="ag-h">十、协议的变更、中止与终止</div>
<ol>
<li><strong>【协议变更】</strong>我们可根据法律法规变化或运营需要修订本协议,并通过 App 内公告、弹窗、推送等合理方式提前通知。变更内容将在公示的生效日期起施行(通常不短于公示后 7-8 个自然日)。如您不同意变更内容,应停止使用相关服务;变更生效后您继续使用的,视为同意变更后的协议。</li>
<li><strong>【服务中止/终止】</strong>在您违反本协议、法律法规要求、或我们基于合理商业安排经合理通知终止服务等情形下,我们有权中止或终止向您提供部分或全部服务。</li>
<li><strong>【账号注销】</strong>您可按 App 内流程注销账号;注销后我们将停止为您提供服务,并依法处理您的个人信息。</li>
<li>协议终止不影响在此之前已产生的权利义务,以及知识产权、责任限制等依其性质应继续有效的条款。</li>
</ol>
<div class="ag-h">十一、通知与送达</div>
<div class="ag-p">我们可通过以下任一方式向您送达通知:App 内公告/弹窗、站内信、推送、向您注册的手机号发送短信、向您提供的邮箱发送邮件等。上述通知于发送成功或刊登完成时视为送达。请您保证联系方式真实有效并及时更新,因联系方式不准确或未及时更新导致通知无法送达的后果由您承担。</div>
<div class="ag-h">十二、法律适用与争议解决</div>
<ol>
<li>本协议的订立、生效、履行、解释及争议解决,适用中华人民共和国法律(不含冲突法及港澳台地区法律)。</li>
<li>因本协议或本服务产生的争议,您与我们应友好协商解决;协商不成的,任何一方均可向本协议签订地有管辖权的人民法院提起诉讼。本协议签订地为北京市丰台区。(注:约定公司所在地法院管辖属合理约定;不得约定与双方无实际联系的偏远地区法院,否则该管辖约定可能被认定无效。)</li>
</ol>
<div class="ag-h">十三、其他</div>
<ol>
<li>本协议各条款标题仅为方便阅读,不影响条款含义的解释。</li>
<li>本协议任一条款被认定为无效或不可执行的,不影响其余条款的效力。</li>
<li>如您对本协议有任何问题或建议,可通过《傻瓜比价隐私政策》“如何联系我们”中的方式与我们联系。</li>
</ol>
</div>
<div class="ag-foot">傻瓜比价 · 用户服务协议<br>万德一博(北京)科技有限公司</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

+335
View File
@@ -0,0 +1,335 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>

<meta charset="UTF-8">
<link rel="stylesheet" href="../shared/fonts.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>意见反馈 · 傻瓜比价</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; overflow: hidden; }
/* 【去框】原型 body 黑底 + flex 居中(浏览器预览用)。WebView 里直接铺页面色、不居中。 */
body {
font-family: 'PuHuiTi', -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
background: #F5F5F5;
color: #1A1A1A;
}
/* 【去框】原型 .device 是 375x812 预览框(圆角+阴影+固定宽高)→ 全屏自适应容器。 */
.device { position: relative; width: 100%; height: 100%; background: #F5F5F5; overflow: hidden; }
/* 意见反馈屏:灰底白卡(原型 #feedback background #F5F5F5 */
#feedback { position: absolute; inset: 0; display: flex; flex-direction: column; background: #F5F5F5; }
/* header —— 返回 | 标题 | 反馈历史(原型 .header space-between;去框后顶部内距由 40 改 10,状态栏交给原生) */
.header { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px 12px; background: #F5F5F5; flex-shrink: 0; position: relative; }
.btn-back { background: none; border: none; cursor: pointer; padding: 0; display: flex; align-items: center; }
.btn-back svg { width: 24px; height: 24px; display: block; }
.btn-back:active { opacity: .5; }
.header-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; line-height: 1.4; color: #1A1A1A; }
.fb-history-entry { background: none; border: none; cursor: pointer; font-family: inherit; font-size: 14px; font-weight: 400; color: #666666; padding: 6px 0; white-space: nowrap; transition: opacity .15s; }
.fb-history-entry:active { opacity: .6; }
/* body —— 滚动区,确定按钮 margin-top:auto 贴底 */
.fb-body { flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 8px 16px 0; display: flex; flex-direction: column; gap: 16px; }
.fb-body::-webkit-scrollbar { display: none; }
.fb-card { background: #FFFFFF; border-radius: 14px; box-shadow: 0 1px 2px rgba(0,0,0,.03); }
/* 顶部引导文案 + 奖励 chip */
.fb-intro { margin: -4px 2px 0; }
.fb-intro-text { font-size: 13px; font-weight: 400; line-height: 1.6; color: #666666; margin: 0; }
.fb-intro-reward { display: inline-flex; align-items: center; gap: 5px; margin-top: 10px; padding: 5px 10px; background: #FFF8E1; border-radius: 8px; font-size: 12px; font-weight: 600; color: #7A4F00; }
.fb-intro-reward .coin { width: 14px; height: 14px; display: block; flex-shrink: 0; }
.fb-section-label { font-size: 14px; font-weight: 400; line-height: 1.5; color: #1A1A1A; margin: 4px 0 8px; }
.fb-section-label .req { color: #E02E24; margin-left: 2px; }
.fb-section-label .count { color: #999999; font-weight: 400; font-size: 13px; margin-left: 4px; }
.fb-textarea { width: 100%; min-height: 140px; background: #FFFFFF; border: none; border-radius: 14px; padding: 14px 16px; font-size: 14px; font-weight: 400; color: #1A1A1A; line-height: 1.5; resize: none; outline: none; font-family: inherit; box-shadow: 0 1px 2px rgba(0,0,0,.03); }
.fb-textarea::placeholder { color: #CCCCCC; font-weight: 400; }
.fb-upload-row { display: flex; flex-wrap: wrap; gap: 10px; }
.fb-upload-tile { width: 72px; height: 72px; background: #FFFFFF; border-radius: 14px; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 1px 2px rgba(0,0,0,.03); }
.fb-thumb { position: relative; width: 72px; height: 72px; border-radius: 14px; overflow: hidden; flex-shrink: 0; box-shadow: 0 1px 2px rgba(0,0,0,.03); }
.fb-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.fb-thumb-del { position: absolute; top: 4px; right: 4px; width: 18px; height: 18px; border-radius: 50%; background: rgba(0,0,0,.55); color: #FFFFFF; border: none; font-size: 13px; line-height: 1; padding: 0; cursor: pointer; display: flex; align-items: center; justify-content: center; }
/* 加群二维码卡 */
.fb-qr-card { display: flex; gap: 14px; align-items: center; padding: 14px; margin-top: 8px; }
.fb-qr { width: 88px; height: 88px; background: #FFFFFF; border-radius: 8px; flex-shrink: 0; display: block; border: 1px solid #F0F0F0; overflow: hidden; }
.fb-qr svg, .fb-qr img { width: 100%; height: 100%; display: block; }
.fb-qr img { object-fit: cover; }
.fb-qr-text { font-size: 13px; font-weight: 400; line-height: 1.6; color: #1A1A1A; }
.fb-qr-text .quote { font-weight: 600; }
/* 确定按钮(DESIGN cta-primary17/700 + radius 24 + 黄渐变 + 高光 + 暖阴影) */
.fb-submit { width: 100%; border: none; padding: 14px 28px; border-radius: 24px; font-size: 17px; font-weight: 700; line-height: 1; font-family: inherit; background: linear-gradient(180deg, #FFE066 0%, #FFC400 100%); color: #1A1A1A; box-shadow: inset 0 1px 0 rgba(255,255,255,.7), 0 2px 6px rgba(255,179,0,.35); cursor: not-allowed; opacity: .45; transition: opacity .15s; }
.fb-submit.active { opacity: 1; cursor: pointer; }
.fb-submit:disabled { cursor: not-allowed; }
.fb-submit-bar { flex-shrink: 0; padding: 8px 16px 16px; background: #F5F5F5; }
/* 居中 toast */
.toast { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,.82); color: #fff; font-size: 14px; padding: 10px 18px; border-radius: 10px; z-index: 9999; opacity: 0; pointer-events: none; transition: opacity .2s; max-width: 70%; text-align: center; line-height: 1.5; }
.toast.show { opacity: 1; }
</style>
</head>
<body>
<div class="device" id="app">
<div class="screen active" id="feedback">
<div class="header">
<button class="btn-back" onclick="goBack()" aria-label="返回">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><path d="M15 6L9 12L15 18" stroke="#1A1A1A" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<span class="header-title">意见反馈</span>
<button class="fb-history-entry" onclick="goHistory()" aria-label="反馈历史">反馈历史</button>
</div>
<div class="fb-body">
<div class="fb-intro">
<p class="fb-intro-text">您的建议和问题我们都会认真查看,评价越真实详细,金币奖励越高,感谢您帮我们做得更好~</p>
<div class="fb-intro-reward">
<img class="coin" src="assets/welfare/index/checkin-coin-single.png" alt="" aria-hidden="true">
反馈采纳后,即得金币奖励
</div>
</div>
<div>
<div class="fb-section-label">问题和意见<span class="req">*</span></div>
<textarea class="fb-textarea" id="fbContent" inputmode="text" maxlength="200" placeholder="说说您的建议或问题,以便我们提供更好的服务(15个字以上)"></textarea>
</div>
<div>
<div class="fb-section-label">上传截图<span class="count" id="fbUpCount">(0/6)</span></div>
<div class="fb-upload-row" id="fbUpRow">
<div class="fb-upload-tile" id="fbAddTile" role="button" aria-label="上传截图" onclick="document.getElementById('fbFileInput').click()">
<svg width="26" height="26" viewBox="0 0 24 24" fill="none"><path d="M12 5V19M5 12H19" stroke="#CCCCCC" stroke-width="2" stroke-linecap="round"/></svg>
</div>
</div>
<input type="file" id="fbFileInput" accept="image/*" multiple style="display: none;">
</div>
<div class="fb-card fb-qr-card" id="fbQrCard" style="display: none;">
<div class="fb-qr" id="fbQrSlot">
<svg viewBox="0 0 88 88" xmlns="http://www.w3.org/2000/svg" aria-label="二维码">
<rect width="88" height="88" fill="#FFFFFF"/>
<g fill="#1A1A1A">
<rect x="6" y="6" width="20" height="20"/><rect x="9" y="9" width="14" height="14" fill="#FFFFFF"/><rect x="12" y="12" width="8" height="8"/>
<rect x="62" y="6" width="20" height="20"/><rect x="65" y="9" width="14" height="14" fill="#FFFFFF"/><rect x="68" y="12" width="8" height="8"/>
<rect x="6" y="62" width="20" height="20"/><rect x="9" y="65" width="14" height="14" fill="#FFFFFF"/><rect x="12" y="68" width="8" height="8"/>
<rect x="30" y="6" width="3" height="3"/><rect x="36" y="6" width="3" height="3"/><rect x="42" y="6" width="3" height="3"/><rect x="51" y="6" width="3" height="3"/><rect x="57" y="6" width="3" height="3"/>
<rect x="30" y="12" width="3" height="3"/><rect x="39" y="12" width="3" height="3"/><rect x="48" y="12" width="3" height="3"/><rect x="54" y="12" width="3" height="3"/>
<rect x="33" y="18" width="3" height="3"/><rect x="42" y="18" width="3" height="3"/><rect x="51" y="18" width="3" height="3"/><rect x="57" y="18" width="3" height="3"/>
<rect x="30" y="24" width="3" height="3"/><rect x="36" y="24" width="3" height="3"/><rect x="45" y="24" width="3" height="3"/><rect x="54" y="24" width="3" height="3"/>
<rect x="6" y="30" width="3" height="3"/><rect x="15" y="30" width="3" height="3"/><rect x="24" y="30" width="3" height="3"/><rect x="33" y="30" width="3" height="3"/><rect x="42" y="30" width="3" height="3"/><rect x="48" y="30" width="3" height="3"/><rect x="60" y="30" width="3" height="3"/><rect x="69" y="30" width="3" height="3"/><rect x="78" y="30" width="3" height="3"/>
<rect x="9" y="36" width="3" height="3"/><rect x="18" y="36" width="3" height="3"/><rect x="27" y="36" width="3" height="3"/><rect x="39" y="36" width="3" height="3"/><rect x="51" y="36" width="3" height="3"/><rect x="63" y="36" width="3" height="3"/><rect x="72" y="36" width="3" height="3"/>
<rect x="6" y="42" width="3" height="3"/><rect x="12" y="42" width="3" height="3"/><rect x="21" y="42" width="3" height="3"/><rect x="30" y="42" width="3" height="3"/><rect x="36" y="42" width="3" height="3"/><rect x="45" y="42" width="3" height="3"/><rect x="54" y="42" width="3" height="3"/><rect x="66" y="42" width="3" height="3"/><rect x="75" y="42" width="3" height="3"/>
<rect x="15" y="48" width="3" height="3"/><rect x="24" y="48" width="3" height="3"/><rect x="33" y="48" width="3" height="3"/><rect x="42" y="48" width="3" height="3"/><rect x="51" y="48" width="3" height="3"/><rect x="60" y="48" width="3" height="3"/><rect x="69" y="48" width="3" height="3"/><rect x="78" y="48" width="3" height="3"/>
<rect x="9" y="54" width="3" height="3"/><rect x="18" y="54" width="3" height="3"/><rect x="30" y="54" width="3" height="3"/><rect x="39" y="54" width="3" height="3"/><rect x="48" y="54" width="3" height="3"/><rect x="57" y="54" width="3" height="3"/><rect x="66" y="54" width="3" height="3"/><rect x="75" y="54" width="3" height="3"/>
<rect x="33" y="60" width="3" height="3"/><rect x="42" y="60" width="3" height="3"/><rect x="51" y="60" width="3" height="3"/><rect x="60" y="60" width="3" height="3"/><rect x="72" y="60" width="3" height="3"/>
<rect x="30" y="66" width="3" height="3"/><rect x="39" y="66" width="3" height="3"/><rect x="48" y="66" width="3" height="3"/><rect x="57" y="66" width="3" height="3"/><rect x="69" y="66" width="3" height="3"/><rect x="78" y="66" width="3" height="3"/>
<rect x="33" y="72" width="3" height="3"/><rect x="45" y="72" width="3" height="3"/><rect x="54" y="72" width="3" height="3"/><rect x="63" y="72" width="3" height="3"/><rect x="75" y="72" width="3" height="3"/>
<rect x="36" y="78" width="3" height="3"/><rect x="42" y="78" width="3" height="3"/><rect x="48" y="78" width="3" height="3"/><rect x="57" y="78" width="3" height="3"/><rect x="66" y="78" width="3" height="3"/><rect x="72" y="78" width="3" height="3"/>
</g>
</svg>
</div>
<div class="fb-qr-text">
<div id="fbQrTitle">长按图片保存二维码</div>
<div>直通<span class="quote" id="fbQrGroup">「傻瓜比价官方群」</span></div>
<div id="fbQrSub">一起唠嗑共创、解锁新玩法</div>
</div>
</div>
</div>
<div class="fb-submit-bar">
<button class="fb-submit" id="fbSubmit" disabled>确定</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script src="../shared/bridge.js"></script>
<script src="../shared/api.js"></script>
<script>
(function () {
var content = document.getElementById('fbContent');
var submit = document.getElementById('fbSubmit');
var fileInput = document.getElementById('fbFileInput');
var row = document.getElementById('fbUpRow');
var addTile = document.getElementById('fbAddTile');
var countEl = document.getElementById('fbUpCount');
var toastEl = document.getElementById('toast');
if (!content || !submit) return;
var MAX_IMG = 6;
var files = []; // 已选 File 对象(提交时压缩后 multipart 上传)
var urls = []; // 与 files 对应的 objectURL(缩略图预览,删除时回收)
var submitting = false;
var toastTimer = null;
function showToast(msg) {
toastEl.textContent = msg;
toastEl.classList.add('show');
if (toastTimer) clearTimeout(toastTimer);
toastTimer = setTimeout(function () { toastEl.classList.remove('show'); }, 1600);
}
// 确定按钮:正文非空即可点(文案虽提示「15个字以上」但不强制下限,对齐原型 sync)
function sync() {
var ok = content.value.trim().length >= 1 && !submitting;
submit.classList.toggle('active', ok);
submit.disabled = !ok;
}
content.addEventListener('input', sync);
// 渲染已选截图缩略图(末尾保留「+」继续上传 tile)
function renderShots() {
row.querySelectorAll('.fb-thumb').forEach(function (t) { t.remove(); });
files.forEach(function (f, i) {
var thumb = document.createElement('div');
thumb.className = 'fb-thumb';
var img = document.createElement('img');
img.src = urls[i]; img.alt = '截图' + (i + 1);
var del = document.createElement('button');
del.className = 'fb-thumb-del'; del.type = 'button';
del.setAttribute('aria-label', '删除'); del.textContent = '×';
del.onclick = function () {
try { URL.revokeObjectURL(urls[i]); } catch (e) {}
files.splice(i, 1); urls.splice(i, 1); renderShots();
};
thumb.appendChild(img); thumb.appendChild(del);
row.insertBefore(thumb, addTile);
});
if (countEl) countEl.textContent = '(' + files.length + '/' + MAX_IMG + ')';
addTile.style.display = files.length >= MAX_IMG ? 'none' : 'flex';
}
// 点「+」→ 系统相册多选 → 回填缩略图,最多 6 张
if (fileInput) {
fileInput.addEventListener('change', function () {
Array.prototype.slice.call(fileInput.files || []).forEach(function (f) {
if (files.length >= MAX_IMG) return;
if (!/^image\//.test(f.type)) return;
files.push(f);
urls.push(URL.createObjectURL(f));
});
renderShots();
fileInput.value = ''; // 允许重复选择同一张
});
}
// 提交前逐张压缩(长边 ≤1600,jpeg .85);解码失败兜底原 file
function compressImage(file) {
return new Promise(function (resolve) {
try {
var url = URL.createObjectURL(file);
var img = new Image();
img.onload = function () {
try {
var max = 1600, w = img.width, h = img.height;
if (w > max || h > max) { var s = Math.min(max / w, max / h); w = Math.round(w * s); h = Math.round(h * s); }
var canvas = document.createElement('canvas');
canvas.width = w; canvas.height = h;
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
URL.revokeObjectURL(url);
canvas.toBlob(function (blob) { resolve(blob || file); }, 'image/jpeg', 0.85);
} catch (e) { URL.revokeObjectURL(url); resolve(file); }
};
img.onerror = function () { URL.revokeObjectURL(url); resolve(file); };
img.src = url;
} catch (e) { resolve(file); }
});
}
// 加群二维码卡:接 /feedback/config(运营可配开关+图+三行文案)
function renderQr(cfg) {
var card = document.getElementById('fbQrCard');
// 明确关闭才隐藏;未拉到(null)按默认渲染,避免空白闪烁(对齐原生 QrCard)
if (cfg && cfg.enabled === false) { card.style.display = 'none'; return; }
card.style.display = 'flex';
var title = (cfg && cfg.title) || '长按图片保存二维码';
var group = (cfg && cfg.group_name) || '傻瓜比价官方群';
var sub = (cfg && cfg.subtitle) || '一起唠嗑共创、解锁新玩法';
document.getElementById('fbQrTitle').textContent = title;
document.getElementById('fbQrGroup').textContent = '「' + group + '」';
document.getElementById('fbQrSub').textContent = sub;
var imgUrl = cfg && cfg.image_url;
if (imgUrl) {
var slot = document.getElementById('fbQrSlot');
if (slot) {
var el = document.createElement('div');
el.className = 'fb-qr'; el.id = 'fbQrSlot';
var im = document.createElement('img');
im.src = imgUrl; im.alt = '官方群二维码';
el.appendChild(im);
slot.parentNode.replaceChild(el, slot);
}
}
}
renderQr(null); // 先按默认渲染
if (window.SGApi) { SGApi.get('/feedback/config').then(renderQr).catch(function () {}); }
// 提交反馈:真接 POST /api/v1/feedbackmultipartcontent + 逐张 images
function submitFeedback() {
if (submit.disabled || submitting) return;
var text = content.value.trim();
if (!text) { showToast('请填写问题和意见'); return; }
var hasBridge = window.SGBridge && SGBridge.hasNative && typeof SGBridge.getToken === 'function';
if (!hasBridge) { // 浏览器预览(无原生桥):不真发
console.log('[stub] submitFeedback(浏览器预览,无 Bridge,不真发)', { content: text, images: files.length });
showToast('感谢您的反馈');
return;
}
submitting = true; sync();
var oldText = submit.textContent; submit.textContent = '提交中…';
(async function () {
try {
var token = SGBridge.getToken();
var blobs = [];
for (var i = 0; i < files.length; i++) { blobs.push(await compressImage(files[i])); }
var fd = new FormData();
fd.append('content', text);
blobs.forEach(function (b, i) { fd.append('images', b, 'fb_' + (i + 1) + '.jpg'); });
// 只放 Authorization,不手动设 Content-Type(让浏览器自动带 multipart boundary
var res = await fetch('/api/v1/feedback', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token }, body: fd });
if (res.ok) {
// 成功:页内居中 toast 显示后再 closePage 回我的(native 未实现 SGBridge.toast,故不走原生 toast
showToast('感谢您的反馈');
setTimeout(function () { if (window.SGBridge && SGBridge.closePage) SGBridge.closePage(); }, 1200);
return; // 保持按钮禁用,等页面关闭,避免重复提交
} else if (res.status === 401) {
if (SGBridge.requestLogin) SGBridge.requestLogin();
showToast('请先登录');
} else {
var detail = '';
try { var j = await res.json(); detail = (j && j.detail) ? j.detail : ''; } catch (e) {}
showToast(detail || ('提交失败(' + res.status + '),请稍后再试'));
}
} catch (e) {
showToast('网络异常,提交失败');
}
// 失败兜底:恢复按钮,允许重试(成功分支已 return,不会走到这)
submitting = false; submit.textContent = oldText; sync();
})();
}
submit.addEventListener('click', submitFeedback);
sync();
// 顶栏返回 / 反馈历史入口
window.goBack = function () {
if (window.SGBridge && SGBridge.closePage) { SGBridge.closePage(); }
else if (history.length > 1) { history.back(); }
};
window.goHistory = function () {
if (window.SGBridge && SGBridge.navigate) { SGBridge.navigate('feedbackHistory'); }
else { console.log('[nav] feedbackHistory'); }
};
})();
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

+266
View File
@@ -0,0 +1,266 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>

<meta charset="UTF-8">
<link rel="stylesheet" href="../shared/fonts.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>反馈历史 · 傻瓜比价</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
:root {
--color-primary: #FFD600;
--color-on-primary: #1A1A1A;
--color-surface-page: #F5F5F5;
--color-surface-card: #FFFFFF;
--color-surface-muted: #FAFAFA;
--color-ink: #1A1A1A;
--color-ink-muted: #666666;
--color-ink-subtle: #999999;
--color-ink-disabled: #CCCCCC;
--color-divider: #F0F0F0;
--color-divider-strong: #E5E5E5;
--color-state-success-dark: #2E7D32;
--color-state-success-bg: #E8F5E9;
--color-state-warning-bg: #FFF3E0;
--color-state-warning-ink: #B57400;
--color-state-danger: #E53935;
--color-state-danger-bg: #FFEBEE;
--color-coin-deep: #5A3A00;
--radius: 14px;
--radius-sm: 10px;
--radius-pill: 999px;
--shadow-card: 0 1px 2px rgba(0,0,0,.03);
--font-num: 'DIN Alternate', 'DIN Pro', 'SF Pro Display', system-ui;
}
html, body { height: 100%; }
/* 【去框】原型 body 黑底 + flex 居中(浏览器预览用)→ 铺页面色、不居中。 */
body {
font-family: 'PuHuiTi', -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
background: var(--color-surface-page);
color: var(--color-ink);
}
/* 【去框】原型 .device 375x812 预览框 → 全屏自适应容器。 */
.device { position: relative; width: 100%; height: 100%; min-height: 100vh; background: var(--color-surface-page); overflow: hidden; }
.screen { position: absolute; inset: 0; display: flex; flex-direction: column; overflow: hidden; }
/* 隐藏原型假状态栏(顶部状态栏交给原生) */
.statusbar { display: none; }
.topnav { height: 44px; display: flex; align-items: center; padding: 0 12px; background: var(--color-surface-page); flex-shrink: 0; position: relative; }
.topnav-back { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; cursor: pointer; background: none; border: none; padding: 0; }
.topnav-back:active { opacity: .5; }
.topnav-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: var(--color-ink); }
.scroll { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 8px 14px calc(24px + env(safe-area-inset-bottom, 0px)); }
.scroll::-webkit-scrollbar { display: none; }
/* === 顶部汇总:反馈采纳累计奖励 === */
.fb-summary { display: flex; align-items: center; justify-content: space-between; background: linear-gradient(135deg, #FFF3CC 0%, #FFE9A8 100%); border-radius: var(--radius); padding: 14px 16px; margin: 4px 0 14px; box-shadow: inset 0 1px 0 rgba(255,255,255,.6), 0 2px 8px rgba(255,179,0,.18); }
.fb-summary-label { font-size: 12px; font-weight: 400; color: var(--color-coin-deep); opacity: .8; }
.fb-summary-num { font-family: var(--font-num); font-size: 28px; font-weight: 700; color: var(--color-coin-deep); line-height: 1.2; margin-top: 2px; }
.fb-summary-num .unit { font-family: 'PuHuiTi', -apple-system, 'PingFang SC', sans-serif; font-size: 13px; font-weight: 600; margin-left: 4px; }
.fb-summary-sub { font-size: 11px; font-weight: 400; color: var(--color-coin-deep); opacity: .7; margin-top: 4px; }
.fb-summary-coin { width: 52px; height: 52px; flex-shrink: 0; }
.filter-row { display: flex; gap: 8px; padding: 0 0 12px; overflow-x: auto; }
.filter-row::-webkit-scrollbar { display: none; }
.filter-chip { flex-shrink: 0; padding: 6px 14px; border-radius: var(--radius-pill); font-size: 13px; font-weight: 400; color: var(--color-ink-muted); background: var(--color-surface-card); border: .5px solid var(--color-divider-strong); cursor: pointer; }
.filter-chip.active { background: var(--color-ink); color: #fff; font-weight: 600; border-color: var(--color-ink); }
.filter-chip .count { margin-left: 4px; opacity: .7; font-weight: 400; }
.rec-card { background: var(--color-surface-card); border-radius: var(--radius); padding: 14px; margin-bottom: 12px; box-shadow: var(--shadow-card); overflow: hidden; }
.rec-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.fb-headtext { flex: 1; min-width: 0; }
.fb-type-row { display: flex; align-items: center; gap: 8px; }
.fb-date { font-size: 12px; font-weight: 400; color: var(--color-ink-subtle); font-family: var(--font-num); }
.fb-content { font-size: 14px; font-weight: 400; color: var(--color-ink); line-height: 1.6; margin-top: 8px; word-break: break-word; }
/* 截图缩略 */
.fb-shots { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.fb-shot { width: 48px; height: 48px; border-radius: 8px; background: var(--color-divider); border: .5px solid var(--color-divider-strong); overflow: hidden; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.fb-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.badge { flex-shrink: 0; display: inline-flex; align-items: center; gap: 3px; padding: 3px 8px; border-radius: var(--radius-pill); font-size: 11px; font-weight: 700; line-height: 1.2; white-space: nowrap; }
.badge svg { width: 12px; height: 12px; }
.badge-pending { background: var(--color-state-warning-bg); color: var(--color-state-warning-ink); }
.badge-success { background: var(--color-state-success-bg); color: var(--color-state-success-dark); }
.badge-danger { background: var(--color-state-danger-bg); color: var(--color-state-danger); }
/* 已采纳:金币奖励条 */
.rec-reward { margin-top: 12px; background: linear-gradient(90deg, #FFF8E1 0%, #FFFDE7 100%); border-radius: var(--radius-sm); padding: 8px 12px; display: flex; align-items: center; gap: 4px; font-size: 13px; color: var(--color-coin-deep); font-weight: 400; }
.rec-reward img { width: 16px; height: 16px; margin-right: 2px; }
.rec-reward-num { font-family: var(--font-num); font-size: 15px; font-weight: 700; }
/* 审核中:进度提示 */
.fb-pending-note { margin-top: 12px; background: var(--color-surface-muted); border-radius: var(--radius-sm); padding: 8px 12px; font-size: 12px; color: var(--color-ink-subtle); line-height: 1.5; }
/* 未采纳:原因 */
.rec-reject { margin-top: 12px; background: var(--color-state-danger-bg); border-radius: var(--radius-sm); padding: 10px 12px; font-size: 13px; color: var(--color-state-danger); line-height: 1.5; }
.rec-reject b { font-weight: 600; }
.list-end { text-align: center; padding: 24px 0 8px; font-size: 12px; color: var(--color-ink-disabled); font-weight: 400; }
.list-empty { text-align: center; padding: 56px 0; font-size: 13px; color: var(--color-ink-subtle); }
/* === 空态:无任何反馈记录 === */
.empty-wrap { flex: 1; display: none; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 0 40px 80px; }
.empty-icon { width: 96px; height: 96px; border-radius: 50%; background: var(--color-divider); display: flex; align-items: center; justify-content: center; margin-bottom: 20px; }
.empty-icon svg { width: 44px; height: 44px; }
.empty-title { font-size: 17px; font-weight: 600; color: var(--color-ink); margin-bottom: 8px; }
.empty-sub { font-size: 13px; font-weight: 400; color: var(--color-ink-subtle); line-height: 1.6; margin-bottom: 28px; }
.empty-cta { border: none; cursor: pointer; font-family: inherit; font-size: 15px; font-weight: 700; color: var(--color-on-primary); padding: 0 32px; height: 44px; border-radius: 24px; background: linear-gradient(180deg, #FFE066 0%, #FFD600 100%); box-shadow: inset 0 1px 0 rgba(255,255,255,.7), 0 6px 18px rgba(255,179,0,.35); transition: transform .12s ease, opacity .15s ease; }
.empty-cta:active { transform: translateY(1px); opacity: .9; }
</style>
</head>
<body>
<div class="device" id="app">
<div class="screen active" id="feedbackHistory">
<div class="topnav">
<button class="topnav-back" onclick="goBack()" aria-label="返回">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
</button>
<div class="topnav-title">反馈历史</div>
</div>
<div class="scroll" id="fbhScroll">
<div class="fb-summary" id="fbhSummary" style="display: none;">
<div>
<div class="fb-summary-label">反馈采纳累计奖励</div>
<div class="fb-summary-num"><span id="fbhCoinSum">0</span><span class="unit">金币</span></div>
<div class="fb-summary-sub">已采纳 <span id="fbhAdoptCount">0</span> 条 · 反馈越真实详细,奖励越高</div>
</div>
<img class="fb-summary-coin" src="assets/welfare/index/checkin-coin-single.png" alt="">
</div>
<div class="filter-row">
<div class="filter-chip active" data-st="all">全部<span class="count" id="fbhCntAll">0</span></div>
<div class="filter-chip" data-st="pending">审核中<span class="count" id="fbhCntPending">0</span></div>
<div class="filter-chip" data-st="adopted">已采纳<span class="count" id="fbhCntAdopted">0</span></div>
<div class="filter-chip" data-st="rejected">未采纳<span class="count" id="fbhCntRejected">0</span></div>
</div>
<div id="fbhList"></div>
<div class="list-end" id="fbhListEnd" style="display: none;"></div>
</div>
<div class="empty-wrap" id="fbhEmpty">
<div class="empty-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="#B3B3B3" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/><path d="M8.5 11h.01M12 11h.01M15.5 11h.01"/></svg>
</div>
<div class="empty-title">还没有反馈记录</div>
<div class="empty-sub">你的每条建议我们都会认真查看<br>被采纳还能领金币奖励</div>
<button class="empty-cta" onclick="goBack()">去反馈</button>
</div>
</div>
</div>
<script src="../shared/bridge.js"></script>
<script src="../shared/api.js"></script>
<script>
(function () {
var scroll = document.getElementById('fbhScroll');
var listEl = document.getElementById('fbhList');
var emptyEl = document.getElementById('fbhEmpty');
var summaryEl = document.getElementById('fbhSummary');
var listEnd = document.getElementById('fbhListEnd');
var RECORDS = [], COUNTS = { all: 0, pending: 0, adopted: 0, rejected: 0 };
var filter = 'all';
function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]; }); }
// created_at 取前 10 位(YYYY-MM-DD),避免时区换算偏移
function fmtDate(iso) { var s = String(iso || ''); return s.length >= 10 ? s.slice(0, 10) : s; }
var COIN = 'assets/welfare/index/checkin-coin-single.png';
var BADGE = {
pending: { cls: 'badge-pending', text: '审核中', icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>' },
adopted: { cls: 'badge-success', text: '已采纳', icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7"/></svg>' },
rejected: { cls: 'badge-danger', text: '未采纳', icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M9 9l6 6M15 9l-6 6"/></svg>' }
};
function cardHtml(rec) {
var st = BADGE[rec.status] || BADGE.pending;
var shots = '';
if (rec.images && rec.images.length) {
shots = '<div class="fb-shots">' + rec.images.map(function (u) { return '<div class="fb-shot"><img src="' + esc(u) + '" alt=""></div>'; }).join('') + '</div>';
}
var extra = '';
if (rec.status === 'adopted') {
extra = '<div class="rec-reward"><img src="' + COIN + '" alt=""><span>+</span><span class="rec-reward-num">' + (rec.reward_coins || 0) + '</span><span>金币已到账</span></div>';
} else if (rec.status === 'rejected') {
extra = '<div class="rec-reject"><b>未采纳原因:</b>' + esc(rec.reject_reason || '经评估,本次反馈暂未采纳。') + '</div>';
} else {
extra = '<div class="fb-pending-note">我们正在认真查看,预计 5 个工作日内完成审核,采纳后金币自动到账。</div>';
}
return '<div class="rec-card"><div class="rec-head"><div class="fb-headtext"><div class="fb-type-row"><span class="fb-date">' + esc(fmtDate(rec.created_at)) + '</span></div><div class="fb-content">' + esc(rec.content) + '</div></div><span class="badge ' + st.cls + '">' + st.icon + st.text + '</span></div>' + shots + extra + '</div>';
}
function currentList() { return filter === 'all' ? RECORDS : RECORDS.filter(function (r) { return r.status === filter; }); }
// 汇总卡:当前筛选视图里有「已采纳」≥1 才显示(对齐原型 refreshSummary
function refreshSummary(view) {
var adopted = view.filter(function (r) { return r.status === 'adopted'; });
if (!adopted.length) { summaryEl.style.display = 'none'; return; }
summaryEl.style.display = 'flex';
var sum = adopted.reduce(function (s, r) { return s + (r.reward_coins || 0); }, 0);
document.getElementById('fbhCoinSum').textContent = sum.toLocaleString('en-US');
document.getElementById('fbhAdoptCount').textContent = adopted.length;
}
// chip 计数:用后端全量 counts(固定,不随筛选变)
function renderCounts() {
document.getElementById('fbhCntAll').textContent = COUNTS.all || 0;
document.getElementById('fbhCntPending').textContent = COUNTS.pending || 0;
document.getElementById('fbhCntAdopted').textContent = COUNTS.adopted || 0;
document.getElementById('fbhCntRejected').textContent = COUNTS.rejected || 0;
}
function render() {
if (!RECORDS.length) { scroll.style.display = 'none'; emptyEl.style.display = 'flex'; return; }
emptyEl.style.display = 'none'; scroll.style.display = '';
var view = currentList();
if (!view.length) {
listEl.innerHTML = '<div class="list-empty">暂无该状态的反馈</div>';
listEnd.style.display = 'none';
} else {
listEl.innerHTML = view.map(cardHtml).join('');
listEnd.style.display = '';
listEnd.textContent = '— 已显示全部 ' + view.length + ' 条 —';
}
refreshSummary(view);
}
Array.prototype.slice.call(document.querySelectorAll('.filter-chip')).forEach(function (chip) {
chip.addEventListener('click', function () {
document.querySelectorAll('.filter-chip').forEach(function (c) { c.classList.remove('active'); });
chip.classList.add('active');
filter = chip.getAttribute('data-st') || 'all';
render();
});
});
async function load() {
var data = null;
try { if (window.SGApi) data = await SGApi.get('/feedback/records'); } catch (e) {}
if (data) { RECORDS = data.records || []; COUNTS = data.counts || COUNTS; }
else { RECORDS = []; }
renderCounts();
render();
}
load();
// 返回 / 「去反馈」:closePage 回意见反馈页(对齐原生 onGoFeedback = popBackStack
window.goBack = function () {
if (window.SGBridge && SGBridge.closePage) { SGBridge.closePage(); }
else if (history.length > 1) { history.back(); }
};
// 回前台刷新
if (window.SGBridge && SGBridge.on) { SGBridge.on('onResume', load); }
})();
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 960 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 975 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

@@ -0,0 +1,34 @@
<svg width="172" height="148" viewBox="0 0 172 148" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="buttonShadow" x="0" y="0" width="172" height="148" color-interpolation-filters="sRGB">
<feDropShadow dx="0" dy="10" stdDeviation="7" flood-color="#D79A1E" flood-opacity=".22"/>
<feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="#FFFFFF" flood-opacity=".88"/>
</filter>
<linearGradient id="buttonFill" x1="43" y1="16" x2="126" y2="135" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#FFFCE3"/>
<stop offset=".38" stop-color="#FFF2A8"/>
<stop offset=".72" stop-color="#FFE27A"/>
<stop offset="1" stop-color="#FFD15A"/>
</linearGradient>
<linearGradient id="buttonStroke" x1="35" y1="11" x2="136" y2="139" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#FFF7C2"/>
<stop offset=".48" stop-color="#FFE484"/>
<stop offset="1" stop-color="#EAB237"/>
</linearGradient>
<radialGradient id="buttonGlow" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(70 44) rotate(62) scale(86 66)">
<stop stop-color="#FFFFFF" stop-opacity=".86"/>
<stop offset=".58" stop-color="#FFFFFF" stop-opacity=".24"/>
<stop offset="1" stop-color="#FFFFFF" stop-opacity="0"/>
</radialGradient>
<linearGradient id="arrowStroke" x1="50" y1="49" x2="125" y2="98" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#FFAE16"/>
<stop offset="1" stop-color="#F08300"/>
</linearGradient>
</defs>
<g filter="url(#buttonShadow)">
<path d="M45 13H127C151.8 13 166 35.8 166 62V86C166 112.2 151.8 135 127 135H45C20.2 135 6 112.2 6 86V62C6 35.8 20.2 13 45 13Z" fill="url(#buttonFill)" stroke="url(#buttonStroke)" stroke-width="3"/>
<path d="M48 19H124C146.1 19 159 38.7 159 63V84C159 108.3 146.1 128 124 128H48C25.9 128 13 108.3 13 84V63C13 38.7 25.9 19 48 19Z" fill="url(#buttonGlow)"/>
<path d="M58 51L81 74L58 97" stroke="url(#arrowStroke)" stroke-width="15" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M93 51L116 74L93 97" stroke="url(#arrowStroke)" stroke-width="15" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 899 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 899 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

+43
View File
@@ -0,0 +1,43 @@
# Shared Icons
通用图标的单一事实源(canonical source)。每个图标都是独立 SVG 文件,但**实际页面里必须内联 SVG markup**(不用 `<img src>``<svg src>` 引用),符合 prototypes 仓库"每个 HTML 自包含"原则。
## 工作流
1. 改图标 → 改这里的 SVG 文件
2. 在所有用到的 HTML 里同步替换内联 SVG markup
3. DESIGN.md 引用本目录路径作为基准
## 现有图标
| 文件 | 用途 | 推荐内联尺寸 |
|---|---|---|
| `back-chevron.svg` | 顶部导航返回按钮 | 24×24 |
| `tab-home-outline.svg` | 底 tab "首页" 未选中态 | 24×24 |
| `tab-home-filled.svg` | 底 tab "首页" 选中态(黄填充)| 24×24 |
| `tab-welfare-outline.svg` | 底 tab "福利" 未选中态 | 24×24 |
| `tab-welfare-filled.svg` | 底 tab "福利" 选中态(黄填充)| 24×24 |
| `tab-profile-outline.svg` | 底 tab "我的" 未选中态 | 24×24 |
| `tab-profile-filled.svg` | 底 tab "我的" 选中态(黄填充)| 24×24 |
## 标准 button 包装
```html
<button class="btn-back" onclick="goBack()" aria-label="返回">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 6L9 12L15 18" stroke="#1A1A1A" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
```
```css
.btn-back {
width: 44px; height: 44px;
display: flex; align-items: center; justify-content: center;
background: transparent; border: none; padding: 0;
cursor: pointer; -webkit-tap-highlight-color: transparent;
flex-shrink: 0;
}
.btn-back:active { opacity: 0.5; }
.btn-back svg { width: 24px; height: 24px; display: block; }
```
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 6L9 12L15 18" stroke="#1A1A1A" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 217 B

@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 12l9-8 9 8" stroke="#1A1A1A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 10v9a1 1 0 001 1h3v-5h6v5h3a1 1 0 001-1v-9" fill="#FFD600" stroke="#1A1A1A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 368 B

@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
<path d="M3 12l9-8 9 8"/>
<path d="M5 10v9a1 1 0 001 1h3v-5h6v5h3a1 1 0 001-1v-9"/>
</svg>

After

Width:  |  Height:  |  Size: 272 B

@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="8" r="4" fill="#FFD600" stroke="#1A1A1A" stroke-width="2"/>
<path d="M4 20c0-4 4-6 8-6s8 2 8 6" fill="#FFD600" stroke="#1A1A1A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 321 B

@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="8" r="4"/>
<path d="M4 20c0-4 4-6 8-6s8 2 8 6"/>
</svg>

After

Width:  |  Height:  |  Size: 257 B

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="8" width="18" height="4" rx="1" fill="#FFD600" stroke="#1A1A1A" stroke-width="2"/>
<path d="M5 12v7a1 1 0 001 1h12a1 1 0 001-1v-7" fill="#FFD600" stroke="#1A1A1A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 8v12" stroke="#1A1A1A" stroke-width="2"/>
<path d="M7.5 8C6 8 5 6.5 6 5.5S9 4 9.5 5.5c.4 1.2 2.5 2.5 2.5 2.5s2.1-1.3 2.5-2.5C15 4 17 4.5 18 5.5s-1 2.5-2.5 2.5" stroke="#1A1A1A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 611 B

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#1A1A1A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="8" width="18" height="4" rx="1"/>
<path d="M12 8v12"/>
<path d="M5 12v7a1 1 0 001 1h12a1 1 0 001-1v-7"/>
<path d="M7.5 8C6 8 5 6.5 6 5.5S9 4 9.5 5.5c.4 1.2 2.5 2.5 2.5 2.5s2.1-1.3 2.5-2.5C15 4 17 4.5 18 5.5s-1 2.5-2.5 2.5"/>
</svg>

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
<defs>
<clipPath id="appIconClip">
<rect x="0" y="0" width="512" height="512" rx="105" ry="105"/>
</clipPath>
</defs>
<rect x="0" y="0" width="512" height="512" rx="105" ry="105" fill="rgb(255, 217, 61)"/>
<image href="sb-brand.png" x="0" y="0" width="512" height="512" preserveAspectRatio="none" clip-path="url(#appIconClip)"/>
</svg>

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

+10397
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1009 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1001 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

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