刘帅
2026-09-11 6de24ed9addc1e730d1029079263b40401568d2d
feat&bug:app下载页管理页面调整
5个文件已修改
49 ■■■■■ 已修改文件
cloud-server-management/src/main/java/com/dsh/course/entity/AppGuideConfig.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/course/entity/QrLink.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/AppGuideManagementController.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/WEB-INF/view/system/appGuide/appGuide.html 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/static/modular/system/appGuide/appGuide.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/course/entity/AppGuideConfig.java
@@ -28,6 +28,8 @@
    /** Android 应用包名。 */
    @TableField("androidPackage")
    private String androidPackage;
    @TableField("iosPackage")
    private String iosPackage;
    /** iOS App Store 地址。 */
    @TableField("iosStoreUrl")
    private String iosStoreUrl;
@@ -40,6 +42,10 @@
    /** APK 下载地址。 */
    @TableField("apkUrl")
    private String apkUrl;
    @TableField("androidDeepLink")
    private String androidDeepLink;
    @TableField("iosDeepLink")
    private String iosDeepLink;
    /** 二维码引导域名。 */
    @TableField("guideDomain")
    private String guideDomain;
cloud-server-management/src/main/java/com/dsh/course/entity/QrLink.java
@@ -39,6 +39,7 @@
    private String name;
    /** 投放渠道。 */
    private String channel;
    @TableField("pageName") private String pageName;
    /** 失效时间。 */
    @TableField("expiresAt")
    private Date expiresAt;
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/AppGuideManagementController.java
@@ -20,6 +20,7 @@
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;
@@ -55,7 +56,16 @@
        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;
    }
@@ -88,7 +98,7 @@
    @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("该场地已有启用二维码,请先停用或使用现有二维码");
@@ -97,6 +107,7 @@
        link.setSiteId(siteId);
        link.setName(name);
        link.setChannel(channel);
        link.setPageName(pageName);
        link.setType("SITE_BOOKING");
        link.setEnabled(1);
        link.setShortCode(nextCode());
@@ -117,6 +128,14 @@
        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);
@@ -130,10 +149,7 @@
    }
    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() {
cloud-server-management/src/main/webapp/WEB-INF/view/system/appGuide/appGuide.html
@@ -2,13 +2,12 @@
<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>
@}
cloud-server-management/src/main/webapp/static/modular/system/appGuide/appGuide.js
@@ -1,10 +1,11 @@
(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)};
})();