r101/app.js · 约 19639 字符
1/* ===== 同城跑腿 H5 · 应用逻辑 ===== */
2(function ($) {
3 'use strict';
4
5 var ROUTES = ['home', 'send', 'buy', 'orders', 'detail', 'runner', 'me', 'wallet', 'message', 'address'];
6 var CUR = 'home';
7 var ORD_TAB = '进行中';
8
9 // ---- 数据 ----
10 var ORDERS = {
11 '进行中': [
12 ['📦', '帮我送 · 文件', '取件:科技路 18 号 → 高新万达', '10:24', '配送中', '¥8.0', 'o1'],
13 ['🛵', '万能帮忙 · 排队', '医院挂号排队 3 号窗口', '10:02', '取件中', '¥15.0', 'o6']
14 ],
15 '待支付': [
16 ['🛒', '帮我买 · 咖啡', '星巴克(赛格店) → 家中', '09:50', '待支付', '¥36.0', 'o2']
17 ],
18 '已完成': [
19 ['📮', '代取快递 · 顺丰', '菜鸟驿站 → 公司前台', '昨天', '已完成', '¥5.0', 'o3'],
20 ['🔧', '万能帮忙 · 搬东西', '师大路 → 6 号楼', '07-25', '已完成', '¥30.0', 'o4']
21 ],
22 '已取消': [
23 ['📄', '文件速递', '已取消', '07-20', '已取消', '¥0.0', 'o5']
24 ]
25 };
26
27 var MSG = {
28 '系统通知': [
29 ['📢', '系统升级维护通知', '为提升体验,7月30日 02:00-04:00 系统维护', '07-28'],
30 ['🎉', '新人券包已到账', '您有 3 张优惠券等待使用', '07-27']
31 ],
32 '订单消息': [
33 ['📦', '订单已接单', '王师傅已接单,正在赶往取件点', '10:25'],
34 ['🛵', '订单配送中', '您的文件正在配送中,预计 12 分钟送达', '10:31']
35 ],
36 '活动推送': [
37 ['🎁', '邀请有奖', '邀请好友得 ¥5 现金红包', '07-26'],
38 ['🔥', '深夜急送 8 折', '22:00-06:00 全场 8 折', '07-25']
39 ]
40 };
41
42 // ---- 小工具 ----
43 function go(h) { location.hash = h; }
44 window.go = go;
45 function goAttr(h) { return 'onclick="go(\'' + h + '\')"'; }
46 function alertAttr(t) { return 'onclick="alert(\'' + t + '\')"'; }
47 window.chipTap = function (el) { $(el).siblings().removeClass('on'); $(el).addClass('on'); };
48
49 // ---- 顶部栏 ----
50 function homeTop() {
51 return '<div class="topbar home">'
52 + '<div class="loc" ' + goAttr('me') + '>西安<span class="arr">▾</span></div>'
53 + '<div class="search" ' + goAttr('send') + '>🔍 搜索:文件、美食、代购、取件…</div>'
54 + '<div class="bell" ' + goAttr('message') + '>🔔</div>'
55 + '</div>';
56 }
57 function innTop(title, back) {
58 back = back || 'home';
59 return '<div class="topbar">'
60 + '<div class="back" ' + goAttr(back) + '>‹</div>'
61 + '<div class="ttl">' + title + '</div>'
62 + '<div class="right"></div>'
63 + '</div>';
64 }
65 function runnerTop() {
66 return '<div class="topbar home"><div class="loc">跑腿员中心</div>'
67 + '<div class="online" style="margin-left:auto">在线<div class="toggle on" id="ron" onclick="$(\'#ron\').toggleClass(\'on\')"></div></div></div>';
68 }
69 function tabbar(active) {
70 var items = [
71 ['home', '🏠', '首页'],
72 ['orders', '📋', '订单'],
73 ['message', '🔔', '消息'],
74 ['me', '👤', '我的']
75 ];
76 var h = '<div class="tabbar">';
77 items.forEach(function (it) {
78 var on = (it[0] === active) ? ' on' : '';
79 h += '<div class="tb' + on + '" ' + goAttr(it[0]) + '><div class="ic">' + it[1] + '</div><div class="lb">' + it[2] + '</div></div>';
80 });
81 h += '</div>';
82 return h;
83 }
84
85 // ---- 首页 ----
86 function banner() {
87 return '<div class="banner">'
88 + '<h3>新用户首单立减 ¥10</h3>'
89 + '<p>同城跑腿 · 30 分钟极速送达</p>'
90 + '<span class="tag">点击领取新人券包</span>'
91 + '</div>'
92 + '<div class="dots"><i class="on"></i><i></i><i></i></div>';
93 }
94 function grid() {
95 var items = [
96 ['📦', '帮我送', 'send'],
97 ['🛒', '帮我买', 'buy'],
98 ['📮', '代取快递', 'send'],
99 ['⏳', '排队代办', 'send'],
100 ['🔧', '万能帮忙', 'send'],
101 ['🍔', '美食代取', 'buy'],
102 ['📄', '文件速递', 'send'],
103 ['➕', '更多服务', 'send']
104 ];
105 var h = '<div class="grid">';
106 items.forEach(function (it) {
107 h += '<div class="it" ' + goAttr(it[2]) + '><div class="ic">' + it[0] + '</div><div class="lb">' + it[1] + '</div></div>';
108 });
109 h += '</div>';
110 return h;
111 }
112 function runners() {
113 var rs = [
114 ['🧑🦱', '王师傅', '4.9分 · 距您 0.8km · 今日已接 32 单'],
115 ['👨🦰', '李跑跑', '4.8分 · 距您 1.2km · 今日已接 21 单'],
116 ['🧔', '赵哥', '5.0分 · 距您 0.5km · 今日已接 40 单']
117 ];
118 var h = '<div class="sec"><div class="sec-h"><div class="t">附近跑腿员</div><div class="more">查看全部 ›</div></div>';
119 rs.forEach(function (r) {
120 h += '<div class="card runner"><div class="av">' + r[0] + '</div>'
121 + '<div><div class="nm">' + r[1] + '</div><div class="meta">' + r[2] + '</div></div>'
122 + '<div class="go" ' + goAttr('runner') + '>接单中</div></div>';
123 });
124 h += '</div>';
125 return h;
126 }
127 function promos() {
128 return '<div class="sec"><div class="sec-h"><div class="t">限时特惠</div><div class="more">更多 ›</div></div>'
129 + '<div class="promo">'
130 + '<div class="p a"><h4>会员免配送费</h4><span>开通即享全城免运</span></div>'
131 + '<div class="p b"><h4>深夜急送 8 折</h4><span>22:00-06:00 专属</span></div>'
132 + '</div></div>';
133 }
134 function homeBody() { return banner() + grid() + runners() + promos(); }
135
136 // ---- 通用表单 ----
137 function fld(label, type, ph) {
138 return '<div class="fld"><label>' + label + '</label><input type="' + type + '" placeholder="' + ph + '"></div>';
139 }
140 function chip(t) { return '<div class="chip" onclick="chipTap(this)">' + t + '</div>'; }
141
142 function sendBody() {
143 return '<div class="frm">'
144 + fld('取件地址', 'text', '请输入取件位置')
145 + fld('收件地址', 'text', '请输入收件位置')
146 + '<div class="fld"><label>物品类型</label><div class="chips" id="itype">'
147 + chip('文件') + chip('美食') + chip('数码') + chip('服饰') + chip('其他')
148 + '</div></div>'
149 + '<div class="fld"><label>期望时间</label><select><option>尽快送达(约 30 分钟)</option><option>1 小时内</option><option>指定时间</option></select></div>'
150 + '<div class="fld"><label>加急配送</label><div class="toggle" data-k="urgent"></div></div>'
151 + '<div class="fld"><label>物品保价</label><div class="toggle" data-k="insure"></div></div>'
152 + '<div class="fld"><label>备注</label><input placeholder="如:放门口、轻拿轻放"></div>'
153 + '</div>'
154 + '<div class="fee"><span>预估费用</span><b>¥8.0</b></div>'
155 + '<div class="btn" ' + goAttr('detail') + '>立即下单</div>';
156 }
157 function buyBody() {
158 return '<div class="frm">'
159 + fld('商品/店铺', 'text', '如:星巴克(赛格店) 拿铁')
160 + fld('购买地点', 'text', '请输入商家位置')
161 + fld('数量规格', 'text', '如:中杯 2 杯')
162 + fld('预算上限', 'text', '如:¥50')
163 + fld('送达地址', 'text', '请输入送达位置')
164 + fld('备注', 'text', '如:少糖、去冰')
165 + '</div>'
166 + '<div class="card" style="margin-top:12px;font-size:13px;color:#8a8f9c;line-height:1.7">💡 <b style="color:#FF6A00">实报实销</b>:跑腿员按实际小票金额垫付,您确认收货后按小票 + 服务费结算,全程透明可追溯。</div>'
167 + '<div class="fee"><span>预估服务费</span><b>¥6.0</b></div>'
168 + '<div class="btn" ' + goAttr('detail') + '>立即下单</div>';
169 }
170 function bindForm() {
171 $('.toggle').off('click').on('click', function () { $(this).toggleClass('on'); });
172 $('#itype .chip').off('click').on('click', function () { $('#itype .chip').removeClass('on'); $(this).addClass('on'); });
173 }
174
175 // ---- 订单列表 ----
176 function ordList(tab) {
177 var arr = ORDERS[tab] || [];
178 if (!arr.length) return '<div class="empty">暂无' + tab + '订单</div>';
179 var h = '';
180 arr.forEach(function (o) {
181 h += '<div class="ord" ' + goAttr('detail?id=' + o[6]) + '>'
182 + '<div class="oic">' + o[0] + '</div>'
183 + '<div class="mid"><div class="ti">' + o[1] + '</div><div class="ln">' + o[2] + '</div><div class="tm">' + o[3] + '</div></div>'
184 + '<div class="rt"><div class="st">' + o[4] + '</div><div class="amt">' + o[5] + '</div></div>'
185 + '</div>';
186 });
187 return h;
188 }
189 function ordersBody() {
190 var keys = ['进行中', '待支付', '已完成', '已取消'];
191 var t = '<div class="tabs" id="otabs">';
192 keys.forEach(function (k) { t += '<div class="tb' + (k === ORD_TAB ? ' on' : '') + '" data-k="' + k + '">' + k + '</div>'; });
193 t += '</div><div id="olist">' + ordList(ORD_TAB) + '</div>';
194 return t;
195 }
196 function bindOrders() {
197 $('#otabs .tb').off('click').on('click', function () {
198 ORD_TAB = $(this).data('k');
199 $('#otabs .tb').removeClass('on'); $(this).addClass('on');
200 $('#olist').html(ordList(ORD_TAB));
201 });
202 }
203
204 // ---- 订单详情 ----
205 function detailBody(q) {
206 var id = (q && q.id) || 'o1';
207 var all = [];
208 $.each(ORDERS, function (k, a) { all = all.concat(a); });
209 var o = all.filter(function (x) { return x[6] === id; })[0] || all[0];
210 var steps = ['已接单', '取件中', '配送中', '已完成'];
211 var h = '<div class="card"><div style="font-size:13px;color:#9aa0ac">订单号 ' + id.toUpperCase() + '</div>'
212 + '<div style="font-size:18px;font-weight:800;margin:6px 0">' + o[1] + '</div>'
213 + '<div class="steps">';
214 steps.forEach(function (s, idx) {
215 h += '<div class="s' + (idx < 3 ? ' on' : '') + '"><div class="dot">' + (idx < 3 ? '✓' : (idx + 1)) + '</div><div class="lb">' + s + '</div></div>';
216 });
217 h += '</div></div>';
218 h += '<div class="card runner"><div class="av">🧑🦱</div><div><div class="nm">王师傅 · 4.9分</div><div class="meta">陕A·8888 · 已为您服务 12 次</div></div><div class="go" ' + alertAttr('已联系跑腿员') + '>联系</div></div>';
219 h += '<div class="map"><span class="pin a">📍</span><span class="pin b">🏁</span><span class="route">实时轨迹 · 预计 12 分钟送达</span></div>';
220 h += '<div class="card"><div class="fees">'
221 + '<div class="fr"><span>基础配送费</span><span>¥6.0</span></div>'
222 + '<div class="fr"><span>加急费</span><span>¥2.0</span></div>'
223 + '<div class="fr"><span>夜间服务费</span><span>¥0.0</span></div>'
224 + '<div class="fr tot"><span>合计</span><span>' + o[5] + '</span></div>'
225 + '</div></div>';
226 h += '<div class="btn" ' + alertAttr('已发起取消申请') + '>取消订单</div>';
227 return h;
228 }
229
230 // ---- 跑腿员中心 ----
231 function runnerBody() {
232 var b = '<div class="stats">'
233 + '<div class="c"><b>¥128</b><span>今日收入</span></div>'
234 + '<div class="c"><b>12</b><span>接单量</span></div>'
235 + '<div class="c"><b>4.9</b><span>综合评分</span></div></div>';
236 var grabs = [
237 ['📦', '帮我送 · 文件', '距您 0.6km · 预计 18 分钟', '¥8.0'],
238 ['📮', '代取快递 · 京东', '距您 1.1km · 预计 10 分钟', '¥5.0'],
239 ['🍔', '美食代取 · 奶茶', '距您 0.9km · 预计 15 分钟', '¥7.0']
240 ];
241 b += '<div class="sec"><div class="sec-h"><div class="t">附近可抢订单</div><div class="more">刷新 ›</div></div>';
242 grabs.forEach(function (g) {
243 b += '<div class="card runner"><div class="av">' + g[0] + '</div><div><div class="nm">' + g[1] + '</div><div class="meta">' + g[2] + '</div></div><div class="go" ' + alertAttr('抢单成功') + '>抢单</div></div>';
244 });
245 b += '</div>';
246 b += '<div class="card" ' + goAttr('wallet') + ' style="display:flex;align-items:center;justify-content:space-between;margin-top:12px"><div>💰 我的钱包</div><div style="color:#FF6A00;font-weight:700">查看 ›</div></div>';
247 return b;
248 }
249
250 // ---- 我的 ----
251 function mg(ic, lb, to) { return '<div class="it" ' + goAttr(to) + '><div class="ic">' + ic + '</div><div class="lb">' + lb + '</div></div>'; }
252 function meBody() {
253 var h = '<div class="me-h"><div class="av">🛵</div><div><div class="nm">跑腿小哥</div><div class="vip">👑 VIP 会员</div></div></div>';
254 h += '<div class="me-grid">'
255 + mg('💰', '我的钱包', 'wallet') + mg('🎟️', '优惠券', 'wallet') + mg('📍', '我的地址', 'address') + mg('👥', '常用收件人', 'address')
256 + mg('📋', '我的订单', 'orders') + mg('🏃', '跑腿接单', 'runner') + mg('💬', '在线客服', 'message') + mg('⚙️', '设置', 'me')
257 + '</div>';
258 h += '<div class="invite"><div class="em">🎁</div><div class="tx"><b>邀请好友得现金</b><span>每邀请 1 人得 ¥5,上不封顶</span></div><div class="btn2" ' + alertAttr('已复制邀请链接') + '>立即邀请</div></div>';
259 return h;
260 }
261
262 // ---- 钱包 / 优惠券 ----
263 function coupons() {
264 var cs = [
265 ['减5', '满20', '全场跑腿通用', '2026.08.31 到期', '#FF7A00'],
266 ['免', '配送费券', '下单立免基础运费', '2026.08.15 到期', '#6A5CFF'],
267 ['减10', '新人', '首单专享', '2026.09.01 到期', '#FF5252']
268 ];
269 var h = '';
270 cs.forEach(function (c) {
271 h += '<div class="card" style="display:flex;align-items:center;gap:14px">'
272 + '<div style="width:64px;height:64px;border-radius:12px;background:linear-gradient(135deg,' + c[4] + ',#ff9a6b);color:#fff;display:flex;flex-direction:column;align-items:center;justify-content:center;flex:0 0 auto"><b style="font-size:18px">' + c[0] + '</b><span style="font-size:10px">' + c[1] + '</span></div>'
273 + '<div style="flex:1"><div style="font-weight:700">' + c[2] + '</div><div style="font-size:12px;color:#9aa0ac;margin-top:3px">' + c[3] + '</div></div>'
274 + '<div style="background:#FFF1E8;color:#FF6A00;font-size:12px;font-weight:700;padding:6px 12px;border-radius:16px">去使用</div></div>';
275 });
276 return h;
277 }
278 function bills() {
279 var bs = [['微信支付充值', '+¥50.00', '07-28'], ['帮送订单支付', '-¥8.00', '07-27'], ['新人立减', '-¥10.00', '07-26']];
280 var h = '';
281 bs.forEach(function (b) {
282 h += '<div class="card" style="display:flex;justify-content:space-between;align-items:center"><div><div style="font-weight:600">' + b[0] + '</div><div style="font-size:11px;color:#bcc0ca">' + b[2] + '</div></div><div style="font-weight:700;color:' + (b[1][0] === '+' ? '#22c55e' : '#1f2330') + '">' + b[1] + '</div></div>';
283 });
284 return h;
285 }
286 function walletBody() {
287 var b = '<div class="bal"><div class="l">账户余额(元)</div><div class="v">¥0.00</div><div class="rech" ' + alertAttr('前往充值') + '>充值</div></div>';
288 b += '<div class="tabs" id="wtabs"><div class="tb on" data-k="优惠券">优惠券</div><div class="tb" data-k="账单">账单</div></div>';
289 b += '<div id="wbody">' + coupons() + '</div>';
290 return b;
291 }
292 function bindWallet() {
293 $('#wtabs .tb').off('click').on('click', function () {
294 $('#wtabs .tb').removeClass('on'); $(this).addClass('on');
295 var k = $(this).data('k');
296 $('#wbody').html(k === '优惠券' ? coupons() : bills());
297 });
298 }
299
300 // ---- 消息中心 ----
301 function msgList(arr) {
302 var h = '';
303 arr.forEach(function (m) {
304 var cls = m[0] === '📢' ? 'a' : (m[0] === '📦' ? 'b' : 'c');
305 h += '<div class="msg"><div class="mic ' + cls + '">' + m[0] + '</div><div class="mid"><div class="ti">' + m[1] + '<span class="tm">' + m[3] + '</span></div><div class="sn">' + m[2] + '</div></div></div>';
306 });
307 return h;
308 }
309 function messageBody() {
310 var keys = ['系统通知', '订单消息', '活动推送'];
311 var t = '<div class="tabs" id="mtabs">';
312 keys.forEach(function (k, i) { t += '<div class="tb' + (i === 0 ? ' on' : '') + '" data-k="' + k + '">' + k + '</div>'; });
313 t += '</div><div id="mlist">' + msgList(MSG[keys[0]]) + '</div>';
314 return t;
315 }
316 function bindMessage() {
317 $('#mtabs .tb').off('click').on('click', function () {
318 $('#mtabs .tb').removeClass('on'); $(this).addClass('on');
319 var k = $(this).data('k');
320 $('#mlist').html(msgList(MSG[k]));
321 });
322 }
323
324 // ---- 地址管理 ----
325 function addressBody() {
326 var addrs = [
327 ['默认', '🏠', '张三', '189****8888', '西安市雁塔区科技路18号 高新万达2号楼1503', true],
328 ['公司', '🏢', '张三', '189****8888', '西安市高新区唐延路25号 软件园F座6层', false],
329 ['学校', '🏫', '张三', '189****8888', '西安市碑林区师大路1号 6号楼302', false]
330 ];
331 var h = '';
332 addrs.forEach(function (a) {
333 h += '<div class="addr">'
334 + '<div class="' + (a[5] ? 'def' : '') + '">' + (a[5] ? '默认' : a[0]) + '</div>'
335 + '<div class="info"><div class="nm">' + a[1] + ' ' + a[2] + ' ' + a[3] + '</div><div class="ln">' + a[4] + '</div></div>'
336 + '<div class="op" ' + alertAttr('编辑地址') + '>编辑</div>'
337 + '<div class="op" ' + alertAttr('删除地址') + '>删除</div>'
338 + '</div>';
339 });
340 h += '<div class="btn" ' + alertAttr('新增地址') + '>+ 新增地址</div>';
341 return h;
342 }
343
344 // ---- 页面表 ----
345 var PAGES = {
346 home: function () { return { top: homeTop(), body: homeBody(), tab: true }; },
347 send: function () { return { top: innTop('帮我送'), body: sendBody(), bind: bindForm }; },
348 buy: function () { return { top: innTop('帮我买'), body: buyBody(), bind: bindForm }; },
349 orders: function () { return { top: innTop('我的订单'), body: ordersBody(), bind: bindOrders, tab: true }; },
350 detail: function (q) { return { top: innTop('订单详情'), body: detailBody(q) }; },
351 runner: function () { return { top: runnerTop(), body: runnerBody() }; },
352 me: function () { return { top: innTop('我的'), body: meBody(), tab: true }; },
353 wallet: function () { return { top: innTop('钱包'), body: walletBody(), bind: bindWallet }; },
354 message:function () { return { top: innTop('消息中心'), body: messageBody(), bind: bindMessage, tab: true }; },
355 address:function () { return { top: innTop('地址管理'), body: addressBody() }; }
356 };
357
358 // ---- 路由 ----
359 function parseHash() {
360 var h = location.hash.replace(/^#/, '');
361 if (!h) h = 'home';
362 var q = {};
363 var qi = h.indexOf('?');
364 if (qi >= 0) {
365 var qs = h.substr(qi + 1); h = h.substr(0, qi);
366 qs.split('&').forEach(function (p) {
367 var kv = p.split('=');
368 q[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1] || '');
369 });
370 }
371 return { name: h, q: q };
372 }
373 function render() {
374 var p = parseHash();
375 var name = ROUTES.indexOf(p.name) >= 0 ? p.name : 'home';
376 CUR = name;
377 var v = PAGES[name](p.q);
378 var html = (v.top || '') + '<div class="page">' + (v.body || '') + '</div>' + (v.tab ? tabbar(name) : '');
379 $('#app').html(html);
380 if (v.bind) v.bind();
381 $('.page').scrollTop(0);
382 }
383
384 $(window).on('hashchange', render);
385 $(function () { render(); });
386})(jQuery);
387