| | |
| | | /** Android 应用包名。 */ |
| | | @TableField("androidPackage") |
| | | private String androidPackage; |
| | | @TableField("iosPackage") |
| | | private String iosPackage; |
| | | /** iOS App Store 地址。 */ |
| | | @TableField("iosStoreUrl") |
| | | private String iosStoreUrl; |
| | |
| | | /** APK 下载地址。 */ |
| | | @TableField("apkUrl") |
| | | private String apkUrl; |
| | | @TableField("androidDeepLink") |
| | | private String androidDeepLink; |
| | | @TableField("iosDeepLink") |
| | | private String iosDeepLink; |
| | | /** 二维码引导域名。 */ |
| | | @TableField("guideDomain") |
| | | private String guideDomain; |
| | |
| | | private String name; |
| | | /** 投放渠道。 */ |
| | | private String channel; |
| | | @TableField("pageName") private String pageName; |
| | | /** 失效时间。 */ |
| | | @TableField("expiresAt") |
| | | private Date expiresAt; |
| | |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.IOException; |
| | | import java.security.SecureRandom; |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | |
| | | Map<String, Object> r = new HashMap<>(); |
| | | r.put("config", appGuideConfigMapper.selectOne(new QueryWrapper<AppGuideConfig>().orderByDesc("id").last("limit 1"))); |
| | | r.put("banners", appGuideBannerMapper.selectList(new QueryWrapper<AppGuideBanner>().orderByAsc("sort").orderByDesc("id"))); |
| | | r.put("links", qrLinkMapper.selectList(new QueryWrapper<QrLink>().eq("type", "SITE_BOOKING").orderByDesc("id"))); |
| | | List<QrLink> links = qrLinkMapper.selectList(new QueryWrapper<QrLink>().eq("type", "SITE_BOOKING").orderByDesc("id")); |
| | | List<Map<String, Object>> linkData = new ArrayList<>(); |
| | | for (QrLink link : links) { |
| | | Map<String, Object> item = new HashMap<>(); |
| | | // Map 不会触发实体的 @JsonSerialize;雪花 ID 必须转字符串,避免前端 Number 精度丢失。 |
| | | item.put("id", String.valueOf(link.getId())); item.put("name", link.getName()); item.put("pageName", link.getPageName()); |
| | | item.put("storeId", link.getStoreId()); item.put("siteId", link.getSiteId()); item.put("shortCode", link.getShortCode()); |
| | | item.put("enabled", link.getEnabled()); item.put("guideUrl", guideUrl(link)); linkData.add(item); |
| | | } |
| | | r.put("links", linkData); |
| | | return r; |
| | | } |
| | | |
| | |
| | | |
| | | @ResponseBody |
| | | @PostMapping("/siteQr") |
| | | public Map<String, Object> createSiteQr(Integer storeId, Integer siteId, String name, String channel) { |
| | | public Map<String, Object> createSiteQr(Integer storeId, Integer siteId, String name, String channel, String pageName) { |
| | | if (storeId == null || siteId == null) return error("门店 ID 和场地 ID 必填"); |
| | | QrLink old = qrLinkMapper.selectOne(new QueryWrapper<QrLink>().eq("type", "SITE_BOOKING").eq("siteId", siteId).eq("enabled", 1).last("limit 1")); |
| | | if (old != null) return error("该场地已有启用二维码,请先停用或使用现有二维码"); |
| | |
| | | link.setSiteId(siteId); |
| | | link.setName(name); |
| | | link.setChannel(channel); |
| | | link.setPageName(pageName); |
| | | link.setType("SITE_BOOKING"); |
| | | link.setEnabled(1); |
| | | link.setShortCode(nextCode()); |
| | |
| | | return ok(); |
| | | } |
| | | |
| | | /** 逻辑删除场地订场二维码。 */ |
| | | @ResponseBody |
| | | @PostMapping("/siteQr/delete") |
| | | public Map<String, Object> deleteSiteQr(Long id) { |
| | | qrLinkMapper.deleteById(id); |
| | | return ok(); |
| | | } |
| | | |
| | | @GetMapping("/siteQr/{id}.png") |
| | | public void qrImage(@PathVariable Long id, HttpServletResponse response) throws IOException { |
| | | QrLink link = qrLinkMapper.selectById(id); |
| | |
| | | } |
| | | |
| | | private String guideUrl(QrLink link) { |
| | | AppGuideConfig c = appGuideConfigMapper.selectOne(new QueryWrapper<AppGuideConfig>().orderByDesc("id").last("limit 1")); |
| | | String domain = c == null ? null : c.getGuideDomain(); |
| | | if (domain == null || domain.trim().isEmpty()) domain = "https://online.daowepark.com/other"; |
| | | return domain.replaceAll("/$", "") + "/q/" + link.getShortCode(); |
| | | return "https://www.weparklife.com/appdown?code=" + link.getShortCode(); |
| | | } |
| | | |
| | | private String nextCode() { |
| | |
| | | <style>.app-guide-section{margin-bottom:25px}.qr{width:90px;height:90px}.table td{vertical-align:middle}</style> |
| | | <div class="row"><div class="col-sm-12"><div class="ibox float-e-margins"><div class="ibox-title"><h5>APP 下载引导与场地订场二维码</h5></div><div class="ibox-content"> |
| | | <div class="app-guide-section"><h4>下载配置</h4><form id="configForm" class="form-inline"> |
| | | <input type="hidden" name="id" id="configId"><p><input class="form-control" name="appName" placeholder="APP 名称"> <input class="form-control" name="appScheme" placeholder="Scheme,如 playpai"> <input class="form-control" name="androidPackage" placeholder="Android 包名"></p> |
| | | <p><input class="form-control" name="guideDomain" style="width:350px" placeholder="引导域名,如 https://h5.playpai.com"> <input class="form-control" name="iosStoreUrl" style="width:350px" placeholder="iOS App Store 地址"></p> |
| | | <p><input class="form-control" name="androidDefaultUrl" style="width:350px" placeholder="安卓默认应用市场地址"> <input class="form-control" name="apkUrl" style="width:350px" placeholder="APK 下载地址"></p> |
| | | <p><textarea class="form-control" name="androidMarketUrls" style="width:710px" placeholder='安卓品牌市场地址 JSON,例如 {"huawei":"https://..."}'></textarea></p> |
| | | <input type="hidden" name="id" id="configId"><p><input class="form-control" name="appName" placeholder="APP 名称"> <input class="form-control" name="androidPackage" placeholder="Android 包名,例如 com.sinata.wepark"> <input class="form-control" name="iosPackage" placeholder="iOS 包名,例如 com.sinata.wepark"></p> |
| | | <p><input class="form-control" name="iosStoreUrl" style="width:350px" placeholder="iOS App Store 地址"> <input class="form-control" name="apkUrl" style="width:350px" placeholder="APK 下载地址"></p> |
| | | <p><input class="form-control" name="androidDeepLink" style="width:350px" placeholder="Android Deep Link"> <input class="form-control" name="iosDeepLink" style="width:350px" placeholder="iOS Deep Link"></p> |
| | | <button type="button" class="btn btn-primary" onclick="saveConfig()">保存下载配置</button></form></div> |
| | | <hr><div class="app-guide-section"><h4>宣传图</h4><form class="form-inline"><input id="bannerFile" type="file" accept="image/*" class="form-control"><input id="bannerUrl" type="hidden"><button type="button" class="btn btn-default" onclick="uploadBanner()">上传图片</button><input id="bannerTitle" class="form-control" placeholder="标题(可选)"><input id="bannerSort" class="form-control" style="width:100px" placeholder="排序"><button type="button" class="btn btn-primary" onclick="addBanner()">添加</button></form><p id="uploadTip" class="text-muted"></p><table class="table table-bordered"><thead><tr><th>预览</th><th>标题</th><th>排序</th><th>状态</th><th>操作</th></tr></thead><tbody id="bannerRows"></tbody></table></div> |
| | | <hr><div class="app-guide-section"><h4>场地订场二维码</h4><p class="text-muted">从现有场地管理数据中选择,每个场地仅允许一个启用二维码。</p><form class="form-inline"><select id="siteSelect" class="form-control" style="min-width:360px"><option value="">请选择场地</option></select><input id="channel" class="form-control" placeholder="渠道(可选)"><button type="button" class="btn btn-primary" onclick="createQr()">生成二维码</button></form><table class="table table-bordered"><thead><tr><th>名称</th><th>门店/场地</th><th>二维码</th><th>引导链接</th><th>状态</th><th>操作</th></tr></thead><tbody id="linkRows"></tbody></table></div> |
| | | <hr><div class="app-guide-section"><h4>场地订场二维码</h4><p class="text-muted">从现有场地管理数据中选择,每个场地仅允许一个启用二维码。</p><form class="form-inline"><select id="siteSelect" class="form-control" style="min-width:360px"><option value="">请选择场地</option></select><input id="pageName" class="form-control" style="width:360px" placeholder="App页面,例如 com.sinata.wepark.ui.home.PlaceDetailActivity"><input id="channel" class="form-control" placeholder="渠道(可选)"><button type="button" class="btn btn-primary" onclick="createQr()">生成二维码</button></form><table class="table table-bordered"><thead><tr><th>名称</th><th>App页面</th><th>门店/场地</th><th>二维码</th><th>引导链接</th><th>状态</th><th>操作</th></tr></thead><tbody id="linkRows"></tbody></table></div> |
| | | </div></div></div></div> |
| | | <script src="${ctxPath}/modular/system/appGuide/appGuide.js"></script> |
| | | @} |
| | |
| | | (function(){ |
| | | function esc(v){return $('<div>').text(v==null?'':v).html()} function val(n,v){$('#configForm [name='+n+']').val(v==null?'':v)} |
| | | window.reloadAppGuide=function(){ $.get(Feng.ctxPath+'/appGuide/data',function(d){var c=d.config||{};['id','appName','appScheme','androidPackage','guideDomain','iosStoreUrl','androidDefaultUrl','apkUrl','androidMarketUrls'].forEach(function(k){val(k,c[k])});$('#bannerRows').html((d.banners||[]).map(function(x){return '<tr><td><img class="qr" src="'+esc(x.imageUrl)+'"></td><td>'+esc(x.title)+'</td><td>'+esc(x.sort)+'</td><td>'+((x.enabled===1)?'启用':'停用')+'</td><td><button class="btn btn-danger btn-xs" onclick="deleteBanner(\''+x.id+'\')">删除</button></td></tr>'}).join(''));$('#linkRows').html((d.links||[]).map(function(x){var id=String(x.id),q=Feng.ctxPath+'/appGuide/siteQr/'+encodeURIComponent(id)+'.png';return '<tr><td>'+esc(x.name)+'</td><td>'+x.storeId+' / '+x.siteId+'</td><td><a href="'+q+'" target="_blank"><img class="qr" src="'+q+'"></a></td><td><code>'+esc((c.guideDomain||'https://online.daowepark.com/other').replace(/\/$/,'')+'/q/'+x.shortCode)+'</code></td><td>'+((x.enabled===1)?'启用':'停用')+'</td><td><button class="btn btn-warning btn-xs" onclick="changeQr(\''+id+'\','+(x.enabled===1?0:1)+')">'+(x.enabled===1?'停用':'启用')+'</button></td></tr>'}).join(''))})}; |
| | | window.reloadAppGuide=function(){ $.get(Feng.ctxPath+'/appGuide/data',function(d){var c=d.config||{};['id','appName','androidPackage','iosPackage','iosStoreUrl','apkUrl','androidDeepLink','iosDeepLink'].forEach(function(k){val(k,c[k])});$('#bannerRows').html((d.banners||[]).map(function(x){return '<tr><td><img class="qr" src="'+esc(x.imageUrl)+'"></td><td>'+esc(x.title)+'</td><td>'+esc(x.sort)+'</td><td>'+((x.enabled===1)?'启用':'停用')+'</td><td><button class="btn btn-danger btn-xs" onclick="deleteBanner(\''+x.id+'\')">删除</button></td></tr>'}).join(''));$('#linkRows').html((d.links||[]).map(function(x){var id=String(x.id),q=Feng.ctxPath+'/appGuide/siteQr/'+encodeURIComponent(id)+'.png';return '<tr><td>'+esc(x.name)+'</td><td>'+esc(x.pageName)+'</td><td>'+x.storeId+' / '+x.siteId+'</td><td><a href="'+q+'" target="_blank"><img class="qr" src="'+q+'"></a></td><td><code>'+esc(x.guideUrl)+'</code></td><td>'+((x.enabled===1)?'启用':'停用')+'</td><td><button class="btn btn-warning btn-xs" onclick="changeQr(\''+id+'\','+(x.enabled===1?0:1)+')">'+(x.enabled===1?'停用':'启用')+'</button> <button class="btn btn-danger btn-xs" onclick="deleteQr(\''+id+'\')">删除</button></td></tr>'}).join(''))})}; |
| | | window.saveConfig=function(){$.post(Feng.ctxPath+'/appGuide/config',$('#configForm').serialize(),function(){Feng.success('保存成功');reloadAppGuide()})}; |
| | | window.uploadBanner=function(){var f=$('#bannerFile')[0].files[0];if(!f){Feng.info('请选择图片');return}var d=new FormData();d.append('file',f);$.ajax({url:Feng.ctxPath+'/mgr/uploadFile',data:d,type:'POST',contentType:false,processData:false,success:function(url){$('#bannerUrl').val(url);$('#uploadTip').text('上传成功')}})}; |
| | | window.addBanner=function(){if(!$('#bannerUrl').val()){Feng.info('请先上传图片');return}$.post(Feng.ctxPath+'/appGuide/banner',{imageUrl:$('#bannerUrl').val(),title:$('#bannerTitle').val(),sort:$('#bannerSort').val()||0,enabled:1},function(){reloadAppGuide();$('#bannerUrl,#bannerTitle,#bannerSort,#bannerFile').val('')})}; |
| | | window.deleteBanner=function(id){$.post(Feng.ctxPath+'/appGuide/banner/delete',{id:id},reloadAppGuide)}; |
| | | window.createQr=function(){var o=$('#siteSelect option:selected');if(!o.val()){Feng.info('请选择场地');return}$.post(Feng.ctxPath+'/appGuide/siteQr',{storeId:o.data('store'),siteId:o.val(),name:o.text(),channel:$('#channel').val()},function(r){if(!r.success){Feng.error(r.message);return}Feng.success('已生成');reloadAppGuide()})}; |
| | | window.createQr=function(){var o=$('#siteSelect option:selected'),pageName=$('#pageName').val();if(!o.val()){Feng.info('请选择场地');return}if(!pageName){Feng.info('请输入 App 页面');return}$.post(Feng.ctxPath+'/appGuide/siteQr',{storeId:o.data('store'),siteId:o.val(),name:o.text(),channel:$('#channel').val(),pageName:pageName},function(r){if(!r.success){Feng.error(r.message);return}Feng.success('已生成');reloadAppGuide()})}; |
| | | window.changeQr=function(id,enabled){$.post(Feng.ctxPath+'/appGuide/siteQr/status',{id:id,enabled:enabled},reloadAppGuide)};$.get(Feng.ctxPath+'/appGuide/sites',function(s){$('#siteSelect').append((s||[]).map(function(x){return '<option value="'+x.id+'" data-store="'+x.storeId+'">['+x.storeId+'] '+esc(x.name)+'</option>'}).join(''))});$(reloadAppGuide); |
| | | window.deleteQr=function(id){if(!confirm('确认删除该场地订场二维码?'))return;$.post(Feng.ctxPath+'/appGuide/siteQr/delete',{id:id},reloadAppGuide)}; |
| | | })(); |