Files
shaguabijia-app-server/h5/feedback/index.html
T

336 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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; height: 48px; padding: 0 16px; 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>