7ac3ca8fd6
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
542 lines
36 KiB
HTML
542 lines
36 KiB
HTML
<!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; }
|
||
/* 【去框 + 不滚动】页面本身不滚动(固定屏),内部 set-scroll 才滚;不用 min-height:100vh(避免 vh 与 WebView 视口差导致整页能下滑一点) */
|
||
html, body { height: 100%; overflow: hidden; }
|
||
body { font-family: 'PuHuiTi', -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; background: #F5F5F5; color: #1A1A1A; }
|
||
.device { position: relative; width: 100%; height: 100%; background: #F5F5F5; overflow: hidden; }
|
||
.set-screen { position: absolute; inset: 0; display: flex; flex-direction: column; background: #F5F5F5; }
|
||
|
||
/* header */
|
||
.set-header { height: 48px; flex-shrink: 0; display: flex; align-items: center; padding: 0 8px; position: relative; background: #F5F5F5; }
|
||
.set-back { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; background: none; border: none; cursor: pointer; padding: 0; }
|
||
.set-back:active { opacity: .5; }
|
||
.set-back svg { width: 24px; height: 24px; display: block; }
|
||
.set-title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; color: #1A1A1A; }
|
||
|
||
/* panels */
|
||
.set-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }
|
||
.set-body[hidden] { display: none; }
|
||
.set-scroll { flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 12px 14px; display: flex; flex-direction: column; gap: 12px; }
|
||
.set-scroll::-webkit-scrollbar { display: none; }
|
||
.set-bottom-group { flex-shrink: 0; padding: 8px 14px 20px; display: flex; flex-direction: column; gap: 6px; }
|
||
|
||
/* card + row */
|
||
.set-card { background: #FFFFFF; border-radius: 14px; padding: 0 16px; box-shadow: 0 1px 2px rgba(0,0,0,.03); }
|
||
.set-row { display: flex; align-items: center; min-height: 56px; padding: 12px 0; cursor: pointer; }
|
||
.set-row:active { background: rgba(0,0,0,.02); }
|
||
.set-row.set-static { cursor: default; }
|
||
.set-row.set-static:active { background: transparent; }
|
||
.set-row-label { flex: 1; font-size: 15px; font-weight: 400; color: #1A1A1A; line-height: 1.5; }
|
||
.set-row-right { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
||
.set-value { font-size: 15px; color: #999999; max-width: 52vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.set-chevron { display: flex; align-items: center; flex-shrink: 0; }
|
||
.set-chevron svg { width: 8px; height: 14px; display: block; }
|
||
.set-divider { height: .5px; background: #E5E5E5; }
|
||
|
||
/* 头像缩略(对齐原型 .set-avatar-thumb:渐变圆 + 卡通头像 SVG) */
|
||
.set-avatar-thumb { width: 40px; height: 40px; border-radius: 50%; overflow: hidden; background: radial-gradient(circle at 42% 32%, #FFE0B2 0%, #FFE066 43%, #FFEB3B 100%); border: 2px solid #fff; display: flex; align-items: center; justify-content: center; flex-shrink: 0; box-shadow: 0 6px 16px rgba(255,137,32,.20); }
|
||
.set-avatar-thumb svg { width: 38px; height: 38px; display: block; }
|
||
.set-avatar-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||
|
||
/* 退出 / 注销 */
|
||
.set-logout-row { justify-content: center; }
|
||
.set-logout-row .set-row-label { flex: 0 0 auto; font-size: 15px; color: #666666; text-align: center; white-space: nowrap; }
|
||
.set-delete { font-size: 12px; color: #999999; padding: 6px 12px; border-radius: 6px; cursor: pointer; align-self: center; }
|
||
.set-delete:active { background: rgba(0,0,0,.04); }
|
||
|
||
/* 关于 hero */
|
||
.set-about-hero { padding: 28px 0 34px; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; }
|
||
.set-about-brand { display: inline-flex; align-items: center; justify-content: center; gap: 12px; }
|
||
.set-about-logo { width: 48px; height: 48px; border-radius: 12px; display: block; box-shadow: 0 5px 14px rgba(0,0,0,.08); }
|
||
.set-about-name { font-size: 30px; font-weight: 900; line-height: 1.05; color: #000000; white-space: nowrap; }
|
||
.set-about-version { margin-top: 10px; font-size: 13px; font-weight: 400; line-height: 1.35; color: #999999; }
|
||
|
||
/* 个性化广告 假开关 */
|
||
.set-switch-row { display: flex; align-items: flex-start; gap: 12px; padding: 14px 0; }
|
||
.set-switch-text { flex: 1; min-width: 0; }
|
||
.set-switch-label { font-size: 15px; color: #1A1A1A; line-height: 22px; }
|
||
.set-switch-desc { font-size: 12px; color: #999999; line-height: 18px; margin-top: 4px; }
|
||
.set-switch { width: 50px; height: 30px; border-radius: 15px; background: #E5E5E5; position: relative; flex-shrink: 0; cursor: pointer; transition: background .2s; margin-top: 2px; }
|
||
.set-switch.on { background: #34C759; }
|
||
.set-switch-knob { position: absolute; top: 2px; left: 2px; width: 26px; height: 26px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.2); transition: transform .2s; }
|
||
.set-switch.on .set-switch-knob { transform: translateX(20px); }
|
||
|
||
/* ===== 昵称编辑弹窗(对齐原型 .nick-modal:按钮 radius 10 四角圆弧,输入 #FAFAFA,含字数) ===== */
|
||
.nick-modal { display: none; position: fixed; inset: 0; z-index: 285; background: rgba(0,0,0,.45); align-items: center; justify-content: center; padding: 0 32px; }
|
||
.nick-modal.open { display: flex; }
|
||
.nick-modal-card { width: 100%; background: #FFFFFF; border-radius: 14px; padding: 20px 18px 16px; }
|
||
.nick-modal-title { font-size: 16px; font-weight: 600; line-height: 1.5; color: #1A1A1A; text-align: center; margin-bottom: 14px; }
|
||
.nick-modal-input { width: 100%; height: 40px; padding: 0 12px; border: none; background: #FAFAFA; border-radius: 10px; font-size: 15px; font-weight: 400; color: #1A1A1A; font-family: inherit; outline: none; margin-bottom: 6px; }
|
||
.nick-modal-count { text-align: right; font-size: 12px; font-weight: 400; line-height: 1.5; color: #999999; margin: 0 2px 14px; }
|
||
.nick-modal-actions { display: flex; gap: 12px; }
|
||
.nick-modal-btn { flex: 1; height: 40px; border-radius: 10px; border: none; font-size: 15px; font-weight: 700; line-height: 1; cursor: pointer; font-family: inherit; }
|
||
.nick-modal-btn.cancel { background: #FAFAFA; color: #666666; }
|
||
.nick-modal-btn.confirm { background: #FFD600; color: #1A1A1A; }
|
||
|
||
/* ===== 退出 / 注销确认弹窗(对齐原型:radius 18 卡 + 按钮 radius 12 四角圆弧) ===== */
|
||
.cfm-mask { display: none; position: fixed; inset: 0; z-index: 260; background: rgba(0,0,0,.45); align-items: center; justify-content: center; padding: 40px 32px; }
|
||
.cfm-mask.open { display: flex; }
|
||
.cfm-card { background: #fff; border-radius: 18px; max-width: 295px; width: 100%; padding: 24px 24px 16px; text-align: center; }
|
||
.cfm-warn { font-size: 48px; line-height: 1; margin-bottom: 12px; }
|
||
.cfm-title { font-size: 18px; font-weight: 700; margin-bottom: 8px; color: #1A1A1A; }
|
||
.cfm-title.danger { color: #E53935; }
|
||
.cfm-msg { font-size: 14px; color: #999999; line-height: 22px; margin-bottom: 24px; }
|
||
.cfm-msg.tight { font-size: 13px; margin-bottom: 8px; white-space: nowrap; }
|
||
.cfm-msg .danger { color: #E53935; font-weight: 600; }
|
||
.cfm-databox { text-align: left; background: #FAFAFA; border-radius: 8px; padding: 12px 16px; margin-bottom: 24px; font-size: 13px; color: #666666; line-height: 22px; }
|
||
.cfm-actions { display: flex; gap: 12px; }
|
||
.cfm-btn { flex: 1; padding: 12px; border-radius: 12px; font-size: 15px; font-weight: 600; cursor: pointer; font-family: inherit; }
|
||
.cfm-btn.outline { border: 1px solid #CCCCCC; background: #fff; color: #4D4D4D; }
|
||
.cfm-btn.cta { border: none; background: linear-gradient(180deg, #FFE066 0%, #FFD600 100%); color: #1A1A1A; box-shadow: inset 0 1px 0 rgba(255,255,255,.7), 0 6px 18px rgba(255,179,0,.35); }
|
||
|
||
/* ===== 头像底部 sheet(对齐原型 .avatar-sheet:设置页下方弹出) ===== */
|
||
.avatar-sheet-mask { display: none; position: fixed; inset: 0; z-index: 290; background: rgba(0,0,0,.45); }
|
||
.avatar-sheet-mask.open { display: block; }
|
||
.avatar-sheet { position: fixed; left: 0; right: 0; bottom: 0; z-index: 291; transform: translateY(100%); transition: transform .26s cubic-bezier(.32,.72,.27,1); padding: 0 8px 8px; pointer-events: none; }
|
||
.avatar-sheet.open { transform: translateY(0); pointer-events: auto; }
|
||
.avatar-sheet-group { background: rgba(255,255,255,.98); border-radius: 14px; overflow: hidden; }
|
||
.avatar-sheet-grip { width: 36px; height: 4px; background: rgba(0,0,0,.18); border-radius: 2px; margin: 8px auto 4px; }
|
||
.avatar-sheet-btn { width: 100%; height: 56px; border: none; background: transparent; font-size: 17px; font-weight: 400; line-height: 1.5; color: #1A1A1A; cursor: pointer; font-family: inherit; }
|
||
.avatar-sheet-btn:active { background: rgba(0,0,0,.05); }
|
||
.avatar-sheet-btn + .avatar-sheet-btn { border-top: .5px solid #E5E5E5; }
|
||
.avatar-sheet-cancel { margin-top: 8px; background: rgba(255,255,255,.98); border-radius: 14px; font-weight: 700; }
|
||
|
||
/* ===== 头像裁剪(移动和缩放) ===== */
|
||
.crop-screen { display: none; position: fixed; inset: 0; z-index: 300; background: #000; flex-direction: column; }
|
||
.crop-screen.open { display: flex; }
|
||
.crop-header { height: 48px; flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; padding: 0 16px; }
|
||
.crop-header-btn { background: none; border: none; color: #fff; font-size: 16px; font-weight: 400; cursor: pointer; font-family: inherit; padding: 8px 0; }
|
||
.crop-header-btn.confirm { color: #FFD600; font-weight: 700; }
|
||
.crop-header-title { color: #fff; font-size: 16px; font-weight: 500; }
|
||
.crop-stage { flex: 1; position: relative; overflow: hidden; touch-action: none; }
|
||
.crop-stage img { position: absolute; left: 0; top: 0; transform-origin: 0 0; will-change: transform; -webkit-user-drag: none; user-select: none; pointer-events: none; }
|
||
.crop-mask { position: absolute; inset: 0; pointer-events: none; }
|
||
.crop-tips { flex-shrink: 0; text-align: center; color: rgba(255,255,255,.7); font-size: 13px; padding: 14px 0 18px; }
|
||
|
||
/* toast */
|
||
.set-toast { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,.78); color: #FFFFFF; padding: 12px 20px; border-radius: 10px; font-size: 14px; line-height: 1.5; max-width: 70%; text-align: center; z-index: 320; opacity: 0; pointer-events: none; transition: opacity .2s; }
|
||
.set-toast.show { opacity: 1; }
|
||
|
||
/* 忙碌遮罩(上传中) */
|
||
.busy-mask { display: none; position: fixed; inset: 0; z-index: 310; background: rgba(0,0,0,.2); align-items: center; justify-content: center; }
|
||
.busy-mask.open { display: flex; }
|
||
.busy-spin { width: 38px; height: 38px; border: 3px solid rgba(255,255,255,.5); border-top-color: #FFD600; border-radius: 50%; animation: busySpin .8s linear infinite; }
|
||
@keyframes busySpin { to { transform: rotate(360deg); } }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="device">
|
||
<div class="set-screen">
|
||
<div class="set-header">
|
||
<button class="set-back" onclick="onBack()" aria-label="返回">
|
||
<svg 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>
|
||
<div class="set-title" id="setTitle">设置</div>
|
||
</div>
|
||
|
||
<!-- ===== 主面板 ===== -->
|
||
<div class="set-body" id="panel-main">
|
||
<div class="set-scroll">
|
||
<div class="set-card">
|
||
<div class="set-row" onclick="openAvatarSheet()">
|
||
<span class="set-row-label">头像</span>
|
||
<div class="set-row-right">
|
||
<span class="set-avatar-thumb" id="avatarThumb"></span>
|
||
<span class="set-chevron"><svg viewBox="0 0 8 14" fill="none"><path d="M1 1L7 7L1 13" stroke="#C7C7CC" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
|
||
</div>
|
||
</div>
|
||
<div class="set-divider"></div>
|
||
<div class="set-row" onclick="openNickname()">
|
||
<span class="set-row-label">昵称</span>
|
||
<div class="set-row-right"><span class="set-value" id="nicknameVal"></span><span class="set-chevron"><svg viewBox="0 0 8 14" fill="none"><path d="M1 1L7 7L1 13" stroke="#C7C7CC" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span></div>
|
||
</div>
|
||
<div class="set-divider"></div>
|
||
<div class="set-row" onclick="showPanel('phone')">
|
||
<span class="set-row-label">手机号</span>
|
||
<div class="set-row-right"><span class="set-value" id="phoneVal"></span><span class="set-chevron"><svg viewBox="0 0 8 14" fill="none"><path d="M1 1L7 7L1 13" stroke="#C7C7CC" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="set-card">
|
||
<div class="set-row" onclick="nav('notificationSettings')">
|
||
<span class="set-row-label">通知设置</span>
|
||
<div class="set-row-right"><span class="set-chevron"><svg viewBox="0 0 8 14" fill="none"><path d="M1 1L7 7L1 13" stroke="#C7C7CC" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="set-card">
|
||
<div class="set-row" onclick="showPanel('about')">
|
||
<span class="set-row-label">关于傻瓜比价</span>
|
||
<div class="set-row-right"><span class="set-chevron"><svg viewBox="0 0 8 14" fill="none"><path d="M1 1L7 7L1 13" stroke="#C7C7CC" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="set-bottom-group">
|
||
<div class="set-card">
|
||
<div class="set-row set-logout-row" onclick="openLogout()"><span class="set-row-label">退出登录</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== 手机号 ===== -->
|
||
<div class="set-body" id="panel-phone" hidden>
|
||
<div class="set-scroll">
|
||
<div class="set-card">
|
||
<div class="set-row set-static"><span class="set-row-label">手机号</span><div class="set-row-right"><span class="set-value" id="phoneVal2"></span></div></div>
|
||
</div>
|
||
</div>
|
||
<div class="set-bottom-group"><span class="set-delete" onclick="openDelete()">注销账号</span></div>
|
||
</div>
|
||
|
||
<!-- ===== 关于傻瓜比价 ===== -->
|
||
<div class="set-body" id="panel-about" hidden>
|
||
<div class="set-scroll">
|
||
<div class="set-about-hero">
|
||
<div class="set-about-brand">
|
||
<img class="set-about-logo" src="assets/shared/logos/sb-brand.png" alt="傻瓜比价">
|
||
<span class="set-about-name">傻瓜比价</span>
|
||
</div>
|
||
<div class="set-about-version" id="aboutVersion">version</div>
|
||
</div>
|
||
<div class="set-card">
|
||
<div class="set-row" onclick="nav('checkUpdate')"><span class="set-row-label">检查更新</span><div class="set-row-right"><span class="set-value" id="updVersion"></span></div></div>
|
||
</div>
|
||
<div class="set-card">
|
||
<div class="set-row" onclick="showPanel('agreements')"><span class="set-row-label">相关协议</span><div class="set-row-right"><span class="set-chevron"><svg viewBox="0 0 8 14" fill="none"><path d="M1 1L7 7L1 13" stroke="#C7C7CC" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span></div></div>
|
||
<div class="set-divider"></div>
|
||
<div class="set-row" onclick="nav('permissions')"><span class="set-row-label">权限管理</span><div class="set-row-right"><span class="set-chevron"><svg viewBox="0 0 8 14" fill="none"><path d="M1 1L7 7L1 13" stroke="#C7C7CC" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span></div></div>
|
||
<div class="set-divider"></div>
|
||
<div class="set-row" onclick="showPanel('ads')"><span class="set-row-label">个性化广告</span><div class="set-row-right"><span class="set-chevron"><svg viewBox="0 0 8 14" fill="none"><path d="M1 1L7 7L1 13" stroke="#C7C7CC" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span></div></div>
|
||
<div class="set-divider"></div>
|
||
<div class="set-row" onclick="nav('devSettings')"><span class="set-row-label">开发设置</span><div class="set-row-right"><span class="set-chevron"><svg viewBox="0 0 8 14" fill="none"><path d="M1 1L7 7L1 13" stroke="#C7C7CC" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span></div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== 相关协议 ===== -->
|
||
<div class="set-body" id="panel-agreements" hidden>
|
||
<div class="set-scroll">
|
||
<div class="set-card">
|
||
<div class="set-row" onclick="nav('agreement:user')"><span class="set-row-label">用户服务协议</span><div class="set-row-right"><span class="set-chevron"><svg viewBox="0 0 8 14" fill="none"><path d="M1 1L7 7L1 13" stroke="#C7C7CC" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span></div></div>
|
||
<div class="set-divider"></div>
|
||
<div class="set-row" onclick="nav('agreement:privacy')"><span class="set-row-label">隐私政策</span><div class="set-row-right"><span class="set-chevron"><svg viewBox="0 0 8 14" fill="none"><path d="M1 1L7 7L1 13" stroke="#C7C7CC" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span></div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== 个性化广告(假开关,纯 H5) ===== -->
|
||
<div class="set-body" id="panel-ads" hidden>
|
||
<div class="set-scroll">
|
||
<div class="set-card">
|
||
<div class="set-switch-row">
|
||
<div class="set-switch-text">
|
||
<div class="set-switch-label">个性化广告推荐</div>
|
||
<div class="set-switch-desc">关闭个性化推荐不会减少广告数量,但广告与你的相关性可能降低。</div>
|
||
</div>
|
||
<div class="set-switch on" id="adsSwitch" onclick="toggleAds()"><span class="set-switch-knob"></span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 头像底部 sheet -->
|
||
<div class="avatar-sheet-mask" id="avatarSheetMask" onclick="closeAvatarSheet()"></div>
|
||
<div class="avatar-sheet" id="avatarSheet">
|
||
<div class="avatar-sheet-group">
|
||
<div class="avatar-sheet-grip"></div>
|
||
<button class="avatar-sheet-btn" onclick="pickAvatar(true)">拍照</button>
|
||
<button class="avatar-sheet-btn" onclick="pickAvatar(false)">从相册中选择</button>
|
||
</div>
|
||
<button class="avatar-sheet-btn avatar-sheet-cancel" onclick="closeAvatarSheet()">取消</button>
|
||
</div>
|
||
<input type="file" id="avCam" accept="image/*" capture="environment" style="display:none">
|
||
<input type="file" id="avAlbum" accept="image/*" style="display:none">
|
||
|
||
<!-- 头像裁剪(移动和缩放) -->
|
||
<div class="crop-screen" id="cropScreen">
|
||
<div class="crop-header">
|
||
<button class="crop-header-btn" onclick="closeCrop()">取消</button>
|
||
<span class="crop-header-title">移动和缩放</span>
|
||
<button class="crop-header-btn confirm" onclick="confirmCrop()">选取</button>
|
||
</div>
|
||
<div class="crop-stage" id="cropStage">
|
||
<img id="cropImg" alt="">
|
||
<canvas class="crop-mask" id="cropMask"></canvas>
|
||
</div>
|
||
<div class="crop-tips">捏合手势可缩放,拖动可调整位置</div>
|
||
</div>
|
||
|
||
<!-- 昵称编辑弹窗 -->
|
||
<div class="nick-modal" id="nickModal" onclick="closeNickname(event)">
|
||
<div class="nick-modal-card" onclick="event.stopPropagation()">
|
||
<div class="nick-modal-title">修改昵称</div>
|
||
<input type="text" id="nickInput" class="nick-modal-input" maxlength="20" placeholder="请输入昵称" oninput="updateNickCount()">
|
||
<div id="nickCount" class="nick-modal-count">0/20</div>
|
||
<div class="nick-modal-actions">
|
||
<button class="nick-modal-btn cancel" onclick="closeNickname()">取消</button>
|
||
<button class="nick-modal-btn confirm" onclick="saveNickname()">保存</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 退出确认 -->
|
||
<div class="cfm-mask" id="logoutModal">
|
||
<div class="cfm-card">
|
||
<div class="cfm-title">退出登录</div>
|
||
<div class="cfm-msg">退出后将无法继续累积金币收益</div>
|
||
<div class="cfm-actions">
|
||
<button class="cfm-btn outline" onclick="doLogout()">退出</button>
|
||
<button class="cfm-btn outline" onclick="closeLogout()">取消</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 注销确认 -->
|
||
<div class="cfm-mask" id="deleteModal">
|
||
<div class="cfm-card">
|
||
<div class="cfm-warn">⚠️</div>
|
||
<div class="cfm-title danger">确认注销账号?</div>
|
||
<div class="cfm-msg tight">注销后以下数据将<span class="danger">永久删除且无法恢复</span>:</div>
|
||
<div class="cfm-databox">· 全部金币余额和现金收益<br>· 比价记录和购买战绩<br>· 邀请好友关系和返佣收益</div>
|
||
<div class="cfm-actions">
|
||
<button class="cfm-btn outline" onclick="doDelete()">确认注销</button>
|
||
<button class="cfm-btn cta" onclick="closeDelete()">取消</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="busy-mask" id="busyMask"><div class="busy-spin"></div></div>
|
||
<div class="set-toast" id="toast"></div>
|
||
|
||
<script src="../shared/bridge.js"></script>
|
||
<script src="../shared/api.js"></script>
|
||
<script>
|
||
(function () {
|
||
var DEFAULT_AVATAR = '<svg viewBox="0 0 80 80" fill="none"><defs><radialGradient id="sgAvBg" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(31 24) rotate(58) scale(52)"><stop offset="0" stop-color="#FFF5B5"/><stop offset=".58" stop-color="#FFD45F"/><stop offset="1" stop-color="#FFB43A"/></radialGradient><linearGradient id="sgAvFace" x1="39" y1="25" x2="39" y2="55" gradientUnits="userSpaceOnUse"><stop stop-color="#FFE0B0"/><stop offset="1" stop-color="#FFC783"/></linearGradient><linearGradient id="sgAvHood" x1="40" y1="45" x2="40" y2="78" gradientUnits="userSpaceOnUse"><stop stop-color="#FFD742"/><stop offset="1" stop-color="#FFA923"/></linearGradient></defs><circle cx="40" cy="40" r="40" fill="url(#sgAvBg)"/><path d="M15 72c2.5-13 12.2-21 25-21s22.5 8 25 21H15z" fill="url(#sgAvHood)"/><path d="M26 62c2.8 4.8 7.4 7.4 14 7.4 6.5 0 11.2-2.6 14-7.4-2.6-7.2-7.2-10.8-14-10.8S28.6 54.8 26 62z" fill="#FFE789" opacity=".8"/><path d="M20.5 37.5c0-13.2 8.3-22.2 20.3-22.2 11.7 0 19.7 8.8 19.7 22.1 0 2.4-.3 4.6-.8 6.8H21.3c-.5-2.2-.8-4.5-.8-6.7z" fill="#3B2416"/><path d="M23.5 36.3c.8-11.4 7.7-18 17.1-18 9.8 0 16.4 6.9 16.8 18.3-3.3-5.2-8.1-7.7-14.4-7.5-3.1.1-5.2 1.6-8 1.6-3.8 0-7.7 1.5-11.5 5.6z" fill="#5B3520"/><path d="M22.8 39.6c-2.9.1-5.2 2.5-5.1 5.7.1 3.1 2.4 5.3 5.5 5.4l.5-11.1h-.9z" fill="#FFC783"/><path d="M57.2 39.6c2.9.1 5.2 2.5 5.1 5.7-.1 3.1-2.4 5.3-5.5 5.4l-.5-11.1h.9z" fill="#FFC783"/><path d="M22.5 38.4c0-10.5 7.2-17.7 17.5-17.7s17.5 7.2 17.5 17.7v5.8c0 10.2-7.2 17.2-17.5 17.2s-17.5-7-17.5-17.2v-5.8z" fill="url(#sgAvFace)"/><path d="M22.7 35.8c2.5-8.7 9.7-13.2 18-13.2 7.7 0 13.7 4.4 16.1 11.9-4.8-.3-8.5-2.5-11-6.5-3.2 4.7-10 7.8-23.1 7.8z" fill="#3B2416"/><circle cx="31.3" cy="42.5" r="2.1" fill="#3A2113"/><circle cx="48.7" cy="42.5" r="2.1" fill="#3A2113"/><circle cx="28.2" cy="48.4" r="3.2" fill="#FF916C" opacity=".54"/><circle cx="51.8" cy="48.4" r="3.2" fill="#FF916C" opacity=".54"/><path d="M35.8 50.8c2.7 2.1 5.7 2.1 8.4 0" stroke="#6B2C19" stroke-width="1.8" stroke-linecap="round"/><path d="M27 34.2c1.8-3.2 3.9-5.7 6.3-7.3-1.7 3.3-2.5 6.6-2.4 9.8-1.2-.8-2.5-1.6-3.9-2.5zM39.5 22c-4.1 1.7-7.2 4.1-9.4 7.3 5.8-.1 10.5-1.6 14-4.5L39.5 22z" fill="#2A170E"/><path d="M33 63.7c3.7 1.8 10.3 1.8 14 0" stroke="#FFF2B3" stroke-width="2.2" stroke-linecap="round" opacity=".75"/></svg>';
|
||
var toastEl = document.getElementById('toast'), toastTimer = null;
|
||
function showToast(m) { toastEl.textContent = m; toastEl.classList.add('show'); if (toastTimer) clearTimeout(toastTimer); toastTimer = setTimeout(function () { toastEl.classList.remove('show'); }, 1600); }
|
||
function escAttr(s) { return String(s == null ? '' : s).replace(/"/g, '"').replace(/</g, '<'); }
|
||
function bridgeReady() { return window.SGBridge && SGBridge.hasNative && typeof SGBridge.getToken === 'function'; }
|
||
function busy(on) { document.getElementById('busyMask').classList.toggle('open', !!on); }
|
||
|
||
// ===== 面板切换 =====
|
||
var PARENT = { main: null, phone: 'main', about: 'main', agreements: 'about', ads: 'about' };
|
||
var TITLE = { main: '设置', phone: '手机号', about: '', agreements: '相关协议', ads: '个性化广告' };
|
||
var curPanel = 'main';
|
||
function showPanel(name) {
|
||
['main', 'phone', 'about', 'agreements', 'ads'].forEach(function (p) { document.getElementById('panel-' + p).hidden = (p !== name); });
|
||
document.getElementById('setTitle').textContent = TITLE[name] || '';
|
||
curPanel = name;
|
||
}
|
||
window.showPanel = showPanel;
|
||
// 返回:裁剪>弹窗>面板>退出,逐级
|
||
window.onBack = function () {
|
||
if (document.getElementById('cropScreen').classList.contains('open')) { closeCrop(); return; }
|
||
if (document.getElementById('avatarSheet').classList.contains('open')) { closeAvatarSheet(); return; }
|
||
var modals = ['nickModal', 'logoutModal', 'deleteModal'];
|
||
for (var i = 0; i < modals.length; i++) { var m = document.getElementById(modals[i]); if (m && m.classList.contains('open')) { m.classList.remove('open'); return; } }
|
||
var p = PARENT[curPanel];
|
||
if (p) { showPanel(p); return; }
|
||
if (window.SGBridge && SGBridge.closePage) { SGBridge.closePage(); } else if (history.length > 1) { history.back(); }
|
||
};
|
||
|
||
// ===== 跳原生 =====
|
||
window.nav = function (route) {
|
||
if (route === 'permissions' || route === 'devSettings' || route.indexOf('agreement:') === 0) {
|
||
try { localStorage.setItem('sg_set_panel', curPanel); } catch (e) {}
|
||
}
|
||
if (window.SGBridge && SGBridge.navigate) { SGBridge.navigate(route); } else { console.log('[nav]', route); }
|
||
};
|
||
|
||
// ===== 用户资料 =====
|
||
function loadProfile() {
|
||
var a = (window.SGBridge && SGBridge.getAuthState) ? (SGBridge.getAuthState() || {}) : {};
|
||
document.getElementById('nicknameVal').textContent = a.nickname || '傻瓜用户';
|
||
var phone = a.phone || '未绑定';
|
||
document.getElementById('phoneVal').textContent = phone;
|
||
document.getElementById('phoneVal2').textContent = phone;
|
||
setAvatar(a.avatarUrl);
|
||
var ver = (window.SGBridge && SGBridge.getAppVersion) ? (SGBridge.getAppVersion() || '') : '';
|
||
ver = ver || '1.0.0';
|
||
document.getElementById('aboutVersion').textContent = 'version v' + ver;
|
||
document.getElementById('updVersion').textContent = 'v' + ver;
|
||
}
|
||
function setAvatar(url) {
|
||
var el = document.getElementById('avatarThumb');
|
||
// 任意非空 url 都用 <img>:http(s) 绝对、/media 同源相对、blob: 本地预览均可
|
||
if (url) { el.innerHTML = '<img src="' + escAttr(url) + '" alt="头像">'; }
|
||
else { el.innerHTML = DEFAULT_AVATAR; }
|
||
}
|
||
|
||
// ===== 头像 sheet =====
|
||
window.openAvatarSheet = function () { document.getElementById('avatarSheetMask').classList.add('open'); document.getElementById('avatarSheet').classList.add('open'); };
|
||
window.closeAvatarSheet = function () { document.getElementById('avatarSheetMask').classList.remove('open'); document.getElementById('avatarSheet').classList.remove('open'); };
|
||
window.pickAvatar = function (useCamera) { closeAvatarSheet(); document.getElementById(useCamera ? 'avCam' : 'avAlbum').click(); };
|
||
['avCam', 'avAlbum'].forEach(function (id) {
|
||
document.getElementById(id).addEventListener('change', function () {
|
||
var f = this.files && this.files[0]; this.value = '';
|
||
if (f && /^image\//.test(f.type)) openCrop(f);
|
||
});
|
||
});
|
||
|
||
// ===== 裁剪(移动和缩放)=====
|
||
var crop = { img: null, natW: 0, natH: 0, base: 1, scale: 1, x: 0, y: 0, D: 0, cx: 0, cy: 0, pts: {}, startDist: 0, startScale: 1, midX: 0, midY: 0, file: null };
|
||
var cropImgEl = document.getElementById('cropImg'), cropStage = document.getElementById('cropStage'), cropMask = document.getElementById('cropMask');
|
||
function openCrop(file) {
|
||
crop.file = file;
|
||
var url = URL.createObjectURL(file);
|
||
var im = new Image();
|
||
im.onload = function () {
|
||
crop.img = im; crop.natW = im.naturalWidth; crop.natH = im.naturalHeight;
|
||
document.getElementById('cropScreen').classList.add('open');
|
||
// 等布局生效后按 stage 实际尺寸初始化
|
||
setTimeout(function () { initCrop(); }, 30);
|
||
};
|
||
im.onerror = function () { URL.revokeObjectURL(url); showToast('图片读取失败'); };
|
||
cropImgEl.src = url; im.src = url;
|
||
}
|
||
function initCrop() {
|
||
var sw = cropStage.clientWidth, sh = cropStage.clientHeight;
|
||
crop.D = Math.min(sw, sh) * 0.86;
|
||
crop.cx = (sw - crop.D) / 2; crop.cy = (sh - crop.D) / 2;
|
||
crop.base = Math.max(crop.D / crop.natW, crop.D / crop.natH); // cover 裁剪框
|
||
crop.scale = crop.base;
|
||
crop.x = (sw - crop.natW * crop.scale) / 2;
|
||
crop.y = (sh - crop.natH * crop.scale) / 2;
|
||
drawMaskRing(sw, sh);
|
||
applyCrop();
|
||
}
|
||
function drawMaskRing(sw, sh) {
|
||
var dpr = window.devicePixelRatio || 1;
|
||
cropMask.width = sw * dpr; cropMask.height = sh * dpr; cropMask.style.width = sw + 'px'; cropMask.style.height = sh + 'px';
|
||
var ctx = cropMask.getContext('2d'); ctx.setTransform(dpr, 0, 0, dpr, 0, 0); ctx.clearRect(0, 0, sw, sh);
|
||
ctx.fillStyle = 'rgba(0,0,0,.55)'; ctx.fillRect(0, 0, sw, sh);
|
||
ctx.globalCompositeOperation = 'destination-out';
|
||
ctx.beginPath(); ctx.arc(crop.cx + crop.D / 2, crop.cy + crop.D / 2, crop.D / 2, 0, Math.PI * 2); ctx.fill();
|
||
ctx.globalCompositeOperation = 'source-over';
|
||
ctx.strokeStyle = 'rgba(255,255,255,.9)'; ctx.lineWidth = 1.5;
|
||
ctx.beginPath(); ctx.arc(crop.cx + crop.D / 2, crop.cy + crop.D / 2, crop.D / 2, 0, Math.PI * 2); ctx.stroke();
|
||
}
|
||
function clampCrop() {
|
||
var w = crop.natW * crop.scale, h = crop.natH * crop.scale;
|
||
if (crop.x > crop.cx) crop.x = crop.cx;
|
||
if (crop.x + w < crop.cx + crop.D) crop.x = crop.cx + crop.D - w;
|
||
if (crop.y > crop.cy) crop.y = crop.cy;
|
||
if (crop.y + h < crop.cy + crop.D) crop.y = crop.cy + crop.D - h;
|
||
}
|
||
function applyCrop() { clampCrop(); cropImgEl.style.transform = 'translate(' + crop.x + 'px,' + crop.y + 'px) scale(' + crop.scale + ')'; }
|
||
cropStage.addEventListener('pointerdown', function (e) { cropStage.setPointerCapture(e.pointerId); crop.pts[e.pointerId] = { x: e.clientX, y: e.clientY }; var ids = Object.keys(crop.pts); if (ids.length === 2) { var a = crop.pts[ids[0]], b = crop.pts[ids[1]]; crop.startDist = Math.hypot(a.x - b.x, a.y - b.y); crop.startScale = crop.scale; var r = cropStage.getBoundingClientRect(); crop.midX = (a.x + b.x) / 2 - r.left; crop.midY = (a.y + b.y) / 2 - r.top; } });
|
||
cropStage.addEventListener('pointermove', function (e) {
|
||
if (!crop.pts[e.pointerId]) return;
|
||
var prev = crop.pts[e.pointerId]; crop.pts[e.pointerId] = { x: e.clientX, y: e.clientY };
|
||
var ids = Object.keys(crop.pts);
|
||
if (ids.length === 1) { crop.x += e.clientX - prev.x; crop.y += e.clientY - prev.y; applyCrop(); }
|
||
else if (ids.length === 2) {
|
||
var a = crop.pts[ids[0]], b = crop.pts[ids[1]]; var dist = Math.hypot(a.x - b.x, a.y - b.y);
|
||
if (crop.startDist > 0) {
|
||
var ns = Math.max(crop.base, Math.min(crop.base * 5, crop.startScale * dist / crop.startDist));
|
||
// 以双指中点为锚缩放
|
||
var k = ns / crop.scale;
|
||
crop.x = crop.midX - (crop.midX - crop.x) * k;
|
||
crop.y = crop.midY - (crop.midY - crop.y) * k;
|
||
crop.scale = ns; applyCrop();
|
||
}
|
||
}
|
||
});
|
||
function endPt(e) { delete crop.pts[e.pointerId]; var ids = Object.keys(crop.pts); if (ids.length === 1) { } else if (ids.length === 0) { crop.startDist = 0; } }
|
||
cropStage.addEventListener('pointerup', endPt);
|
||
cropStage.addEventListener('pointercancel', endPt);
|
||
window.closeCrop = function () { document.getElementById('cropScreen').classList.remove('open'); if (crop.img) { try { URL.revokeObjectURL(crop.img.src); } catch (e) {} } crop.img = null; crop.pts = {}; };
|
||
window.confirmCrop = function () {
|
||
if (!crop.img) return;
|
||
var out = 512, cv = document.createElement('canvas'); cv.width = out; cv.height = out;
|
||
var sx = (crop.cx - crop.x) / crop.scale, sy = (crop.cy - crop.y) / crop.scale, sd = crop.D / crop.scale;
|
||
try { cv.getContext('2d').drawImage(crop.img, sx, sy, sd, sd, 0, 0, out, out); } catch (e) { showToast('裁剪失败'); return; }
|
||
cv.toBlob(function (blob) { document.getElementById('cropScreen').classList.remove('open'); uploadAvatar(blob || crop.file); }, 'image/jpeg', 0.9);
|
||
};
|
||
|
||
// ===== 上传头像 =====
|
||
function uploadAvatar(blob) {
|
||
var thumb = document.getElementById('avatarThumb');
|
||
var prevHtml = thumb.innerHTML;
|
||
setAvatar(URL.createObjectURL(blob)); // 乐观:裁剪完立即显示本地图,不等服务端(修「重进才变」)
|
||
if (!bridgeReady()) { showToast('(预览)头像已更新'); return; }
|
||
busy(true);
|
||
(async function () {
|
||
try {
|
||
var fd = new FormData(); fd.append('file', blob, 'avatar.jpg');
|
||
var res = await fetch('/api/v1/user/avatar', { method: 'POST', headers: { 'Authorization': 'Bearer ' + SGBridge.getToken() }, body: fd });
|
||
if (res.ok) {
|
||
showToast('头像已更新'); // 本地图已显示,保持不动(避免再拉服务端图闪一下)
|
||
if (SGBridge.navigate) SGBridge.navigate('profileChanged'); // 同步原生缓存(我的页一致)
|
||
} else if (res.status === 401) { thumb.innerHTML = prevHtml; if (SGBridge.requestLogin) SGBridge.requestLogin(); }
|
||
else { thumb.innerHTML = prevHtml; var d = ''; try { var e = await res.json(); d = (e && e.detail) ? e.detail : ''; } catch (x) {} showToast(d || '头像上传失败,请重试'); }
|
||
} catch (e) { thumb.innerHTML = prevHtml; showToast('网络异常,请重试'); } finally { busy(false); if (crop.img) { try { URL.revokeObjectURL(crop.img.src); } catch (e) {} crop.img = null; } }
|
||
})();
|
||
}
|
||
|
||
// ===== 昵称 =====
|
||
window.openNickname = function () { var v = document.getElementById('nicknameVal').textContent || ''; document.getElementById('nickInput').value = v; updateNickCount(); document.getElementById('nickModal').classList.add('open'); setTimeout(function () { document.getElementById('nickInput').focus(); }, 60); };
|
||
window.closeNickname = function (ev) { if (ev && ev.target && ev.target.id !== 'nickModal') return; document.getElementById('nickModal').classList.remove('open'); };
|
||
window.updateNickCount = function () { var v = document.getElementById('nickInput').value || ''; document.getElementById('nickCount').textContent = v.length + '/20'; };
|
||
window.saveNickname = function () {
|
||
var name = (document.getElementById('nickInput').value || '').trim();
|
||
if (!name) { showToast('昵称不能为空'); return; }
|
||
if (!bridgeReady()) { document.getElementById('nicknameVal').textContent = name; document.getElementById('nickModal').classList.remove('open'); showToast('昵称已更新'); return; }
|
||
(async function () {
|
||
try {
|
||
var res = await fetch('/api/v1/user/profile', { method: 'PATCH', headers: { 'Authorization': 'Bearer ' + SGBridge.getToken(), 'Content-Type': 'application/json' }, body: JSON.stringify({ nickname: name }) });
|
||
if (res.ok) {
|
||
var j = null; try { j = await res.json(); } catch (e) {}
|
||
document.getElementById('nicknameVal').textContent = (j && j.nickname) || name;
|
||
document.getElementById('nickModal').classList.remove('open');
|
||
showToast('昵称已更新');
|
||
if (SGBridge.navigate) SGBridge.navigate('profileChanged');
|
||
} else if (res.status === 401) { if (SGBridge.requestLogin) SGBridge.requestLogin(); }
|
||
else { var d = ''; try { var e = await res.json(); d = (e && e.detail) ? e.detail : ''; } catch (x) {} showToast(d || '更新失败,请重试'); }
|
||
} catch (e) { showToast('网络异常,请重试'); }
|
||
})();
|
||
};
|
||
|
||
// ===== 退出 / 注销 =====
|
||
window.openLogout = function () { document.getElementById('logoutModal').classList.add('open'); };
|
||
window.closeLogout = function () { document.getElementById('logoutModal').classList.remove('open'); };
|
||
window.doLogout = function () { closeLogout(); if (window.SGBridge && SGBridge.navigate) { SGBridge.navigate('logout'); } else { showToast('(预览)已退出'); } };
|
||
window.openDelete = function () { document.getElementById('deleteModal').classList.add('open'); };
|
||
window.closeDelete = function () { document.getElementById('deleteModal').classList.remove('open'); };
|
||
window.doDelete = function () {
|
||
if (!bridgeReady()) { closeDelete(); showToast('(预览)已注销'); return; }
|
||
closeDelete();
|
||
(async function () {
|
||
try {
|
||
var res = await fetch('/api/v1/user', { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + SGBridge.getToken() } });
|
||
if (res.ok) { if (SGBridge.navigate) SGBridge.navigate('logout'); }
|
||
else if (res.status === 401) { if (SGBridge.requestLogin) SGBridge.requestLogin(); }
|
||
else { var d = ''; try { var e = await res.json(); d = (e && e.detail) ? e.detail : ''; } catch (x) {} showToast(d || '注销失败,请重试'); }
|
||
} catch (e) { showToast('网络异常,请重试'); }
|
||
})();
|
||
};
|
||
|
||
window.toggleAds = function () { document.getElementById('adsSwitch').classList.toggle('on'); };
|
||
|
||
// ===== init =====
|
||
(function () { var p = 'main'; try { var sp = localStorage.getItem('sg_set_panel'); if (sp) { p = sp; localStorage.removeItem('sg_set_panel'); } } catch (e) {} showPanel(p); })();
|
||
loadProfile();
|
||
if (window.SGBridge && SGBridge.on) { SGBridge.on('onResume', loadProfile); SGBridge.on('onAuthChange', loadProfile); }
|
||
})();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|