feat: h5页面

This commit is contained in:
2026-07-27 14:06:19 +08:00
parent a1047368fd
commit f054226683
17 changed files with 852 additions and 0 deletions
@@ -0,0 +1,31 @@
package com.budwk.app.zhgh.pointsmall.h5.index;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.pointsmall.supplier.service.PointsMallSupplierService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
@IocBean
@Ok("json:full")
@At("/platform/h5/points-mall")
public class PointsMallH5IndexController {
@Inject
private PointsMallSupplierService pointsMallSupplierService;
@At("")
@Ok("beetl:/platform/zhghh5/points-mall/index.html")
@SaCheckPermission("h5.points.mall")
public void index() {
}
@At("/supplierList")
@SaCheckPermission("h5.points.mall")
public Result supplierList(@Param("mallType") Integer mallType) {
return Result.success(pointsMallSupplierService.listByMallType(mallType));
}
}
@@ -0,0 +1,18 @@
package com.budwk.app.zhgh.pointsmall.h5.loveaid;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
@IocBean
@Ok("json:full")
@At("/platform/h5/points-mall/loveAid")
public class PointsMallH5LoveAidController {
@At("")
@Ok("beetl:/platform/zhghh5/points-mall/loveAid/index.html")
@SaCheckPermission("h5.points.mall")
public void index() {
}
}
@@ -0,0 +1,50 @@
package com.budwk.app.zhgh.pointsmall.h5.me;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.pointsmall.points.service.PointsMallUserPointsService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
@IocBean
@Ok("json:full")
@At("/platform/h5/points-mall/me")
public class PointsMallH5MeController {
@Inject
private PointsMallUserPointsService pointsMallUserPointsService;
@At("")
@Ok("beetl:/platform/zhghh5/points-mall/me/index.html")
@SaCheckPermission("h5.points.mall")
public void index() {
}
@At("/points")
@SaCheckPermission("h5.points.mall")
public Result points(@Param("userId") String userId) {
return Result.success(pointsMallUserPointsService.availablePoints(currentUserId(userId)));
}
@At("/userInfo")
@SaCheckPermission("h5.points.mall")
public Result userInfo(@Param("userId") String userId) {
return Result.success(pointsMallUserPointsService.userInfo(currentUserId(userId)));
}
private String currentUserId(String userId) {
if (StrUtil.isNotBlank(userId)) {
return userId;
}
try {
return StpUtil.getLoginIdAsString();
} catch (Exception e) {
return "";
}
}
}
@@ -0,0 +1,18 @@
package com.budwk.app.zhgh.pointsmall.h5.movies;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
@IocBean
@Ok("json:full")
@At("/platform/h5/points-mall/movies")
public class PointsMallH5MoviesController {
@At("")
@Ok("beetl:/platform/zhghh5/points-mall/movies/index.html")
@SaCheckPermission("h5.points.mall")
public void index() {
}
}
@@ -0,0 +1,53 @@
package com.budwk.app.zhgh.pointsmall.h5.order;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.stp.StpUtil;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.pointsmall.order.param.PointsMallMobileOrderPageParam;
import com.budwk.app.zhgh.pointsmall.order.service.PointsMallOrderService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
@IocBean
@Ok("json:full")
@At("/platform/h5/points-mall/order")
public class PointsMallH5OrderController {
@Inject
private PointsMallOrderService pointsMallOrderService;
@At("")
@Ok("beetl:/platform/zhghh5/points-mall/order/index.html")
@SaCheckPermission("h5.points.mall")
public void index() {
}
@At("/stateList")
@SaCheckPermission("h5.points.mall")
public Result stateList() {
return Result.success(pointsMallOrderService.mobileOrderStates());
}
@At("/pageData")
@SaCheckPermission("h5.points.mall")
public Result pageData(PointsMallMobileOrderPageParam param) {
return Result.success(pointsMallOrderService.mobileOrderPage(param, currentUserId()));
}
@At("/supplierOrderUrl")
@SaCheckPermission("h5.points.mall")
public Result supplierOrderUrl(@Param("supplierId") String supplierId, @Param("orderId") String orderId, @Param("orderType") Integer orderType) {
return Result.success(pointsMallOrderService.supplierOrderUrl(supplierId, orderId, orderType));
}
private String currentUserId() {
try {
return StpUtil.getLoginIdAsString();
} catch (Exception e) {
return "";
}
}
}
@@ -0,0 +1,15 @@
package com.budwk.app.zhgh.pointsmall.order.param;
import com.budwk.app.base.param.PageForm;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class PointsMallMobileOrderPageParam extends PageForm {
private String supplierId;
private String orderId;
private String userId;
private Integer orderState;
}
@@ -3,6 +3,7 @@ package com.budwk.app.zhgh.pointsmall.order.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderMain;
import com.budwk.app.zhgh.pointsmall.order.param.PointsMallMobileOrderPageParam;
import com.budwk.app.zhgh.pointsmall.supplier.models.PointsMallSupplier;
import com.budwk.app.zhgh.pointsmall.order.param.PointsMallOrderPageParam;
import org.nutz.dao.entity.Record;
@@ -15,6 +16,12 @@ public interface PointsMallOrderService extends BaseService<PointsMallOrderMain>
Pagination<Record> pageSub(PointsMallOrderPageParam param);
Pagination<Record> mobileOrderPage(PointsMallMobileOrderPageParam param, String defaultUserId);
List<Record> mobileOrderStates();
Record supplierOrderUrl(String supplierId, String orderId, Integer orderType);
List<Record> listExchange(String mainOrderId);
List<Record> listSubProducts(String orderId);
@@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.pointsmall.order.models.PointsMallOrderMain;
import com.budwk.app.zhgh.pointsmall.order.param.PointsMallMobileOrderPageParam;
import com.budwk.app.zhgh.pointsmall.supplier.models.PointsMallSupplier;
import com.budwk.app.zhgh.pointsmall.order.param.PointsMallOrderPageParam;
import com.budwk.app.zhgh.pointsmall.order.service.PointsMallOrderService;
@@ -14,6 +15,7 @@ import org.nutz.dao.sql.Sql;
import org.nutz.dao.Sqls;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.Arrays;
import java.util.List;
@IocBean(args = {"refer:dao"})
@@ -62,6 +64,58 @@ public class PointsMallOrderServiceImpl extends BaseServiceImpl<PointsMallOrderM
return listPageMap(param.getPageNumber(), param.getPageSize(), sql);
}
@Override
public Pagination<Record> mobileOrderPage(PointsMallMobileOrderPageParam param, String defaultUserId) {
Cnd cnd = Cnd.where("o.delFlag", "=", false);
String userId = StrUtil.blankToDefault(param.getUserId(), defaultUserId);
if (StrUtil.isNotBlank(userId)) {
cnd.and("o.userId", "=", userId);
}
if (param.getOrderState() != null && param.getOrderState() >= 0) {
cnd.and("o.orderState", "=", param.getOrderState());
}
if (StrUtil.isNotBlank(param.getSupplierId())) {
cnd.and("o.supplierId", "=", param.getSupplierId());
}
if (StrUtil.isNotBlank(param.getOrderId())) {
cnd.and("o.orderId", "like", "%" + param.getOrderId() + "%");
}
cnd.desc("o.orderCreateTime");
Sql sql = Sqls.create("select o.*, s.supplierName, s.supplierOrderUrl, s.supplierReturnOrderUrl from points_mall_order_sub o left join points_mall_supplier s on o.supplierId = s.supplierId and s.delFlag = 0 $condition");
sql.setCondition(cnd);
Pagination<Record> page = listPageMap(param.getPageNumber(), param.getPageSize(), sql);
for (Record item : page.getList()) {
item.put("productImgList", productImages(item.getString("orderId")));
}
return page;
}
@Override
public List<Record> mobileOrderStates() {
return Arrays.asList(
state(-1, "全部"),
state(0, "待付款"),
state(1, "已支付"),
state(2, "进行中"),
state(4, "已签收"),
state(5, "已完成"),
state(6, "换货"),
state(7, "退货")
);
}
@Override
public Record supplierOrderUrl(String supplierId, String orderId, Integer orderType) {
PointsMallSupplier supplier = dao().fetch(PointsMallSupplier.class, Cnd.where("supplierId", "=", supplierId).and("delFlag", "=", false));
Record record = new Record();
record.put("supplierOrderUrl", "");
if (supplier != null) {
String url = orderType != null && orderType == 2 ? supplier.getSupplierReturnOrderUrl() : supplier.getSupplierOrderUrl();
record.put("supplierOrderUrl", appendOrderId(url, orderId));
}
return record;
}
@Override
public List<Record> listExchange(String mainOrderId) {
Cnd cnd = Cnd.where("e.delFlag", "=", false);
@@ -95,4 +149,26 @@ public class PointsMallOrderServiceImpl extends BaseServiceImpl<PointsMallOrderM
public List<PointsMallSupplier> listSuppliers() {
return dao().query(PointsMallSupplier.class, Cnd.where("delFlag", "=", false).asc("sortCode"));
}
private List<String> productImages(String orderId) {
Sql sql = Sqls.create("select picInfo from points_mall_order_sub_product where orderSubId=@orderId and delFlag=0 and picInfo is not null and picInfo<>'' order by createdAt asc");
sql.params().set("orderId", orderId);
sql.setCallback(Sqls.callback.strList());
dao().execute(sql);
return sql.getList(String.class);
}
private String appendOrderId(String url, String orderId) {
if (StrUtil.isBlank(url) || StrUtil.isBlank(orderId)) {
return url;
}
return url + (url.contains("?") ? "&" : "?") + "orderId=" + orderId;
}
private Record state(Integer code, String name) {
Record record = new Record();
record.put("code", code);
record.put("name", name);
return record;
}
}
@@ -9,6 +9,7 @@ import com.budwk.app.zhgh.pointsmall.points.param.PointsMallUserPointsLogPagePar
import com.budwk.app.zhgh.pointsmall.points.param.PointsMallUserPointsPageParam;
import org.nutz.dao.entity.Record;
import java.math.BigDecimal;
import java.util.List;
public interface PointsMallUserPointsService extends BaseService<PointsMallUserPoints> {
@@ -19,6 +20,10 @@ public interface PointsMallUserPointsService extends BaseService<PointsMallUserP
List<String> userIds(PointsMallUserPointsPageParam param);
BigDecimal availablePoints(String userId);
Record userInfo(String userId);
void grant(PointsMallUserPointsChangeParam param);
void deduction(PointsMallUserPointsChangeParam param);
@@ -88,6 +88,28 @@ public class PointsMallUserPointsServiceImpl extends BaseServiceImpl<PointsMallU
return sql.getList(String.class);
}
@Override
public BigDecimal availablePoints(String userId) {
if (StrUtil.isBlank(userId)) {
return BigDecimal.ZERO;
}
PointsMallUserPoints points = fetch(Cnd.where("userId", "=", userId).and("delFlag", "=", false));
return points == null || points.getAvailablePoints() == null ? BigDecimal.ZERO : points.getAvailablePoints();
}
@Override
public Record userInfo(String userId) {
Record record = new Record();
record.put("userId", userId);
if (StrUtil.isBlank(userId)) {
return record;
}
Sql sql = Sqls.create("select id, loginname, username, mobile from sys_user where id=@id or loginname=@id or username=@id limit 1");
sql.params().set("id", userId);
Record user = (Record) dao().execute(sql.setCallback(Sqls.callback.record())).getResult();
return user == null ? record : user;
}
@Override
public void grant(PointsMallUserPointsChangeParam param) {
BigDecimal points = checkedPoints(param.getPoints());
@@ -13,5 +13,7 @@ public interface PointsMallSupplierService extends BaseService<PointsMallSupplie
boolean existsSupplierId(String supplierId, String excludeId);
List<PointsMallSupplier> listByMallType(Integer mallType);
void deleteByIds(List<String> ids);
}
@@ -43,6 +43,16 @@ public class PointsMallSupplierServiceImpl extends BaseServiceImpl<PointsMallSup
return dao().count(PointsMallSupplier.class, cnd) > 0;
}
@Override
public List<PointsMallSupplier> listByMallType(Integer mallType) {
Cnd cnd = Cnd.where("delFlag", "=", false);
if (mallType != null) {
cnd.and("mallType", "=", mallType);
}
cnd.asc("sortCode");
return dao().query(PointsMallSupplier.class, cnd);
}
@Override
public void deleteByIds(List<String> ids) {
if (CollUtil.isEmpty(ids)) {
@@ -0,0 +1,160 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar title="积分商城" left-arrow left-text="返回" @click-left="back" fixed placeholder></van-nav-bar>
<div class="mall-page">
<section class="hero">
<div>
<div class="hero-title">积分商城</div>
<div class="hero-subtitle">好物兑换,积分随心用</div>
</div>
<div class="hero-points">
<span>可用积分</span>
<strong>{{ userPoints }}</strong>
</div>
</section>
<section class="quick-grid">
<div class="quick-card" @click="go('/platform/h5/points-mall/order')">
<van-icon name="orders-o"></van-icon>
<div>
<strong>我的订单</strong>
<span>查看兑换记录</span>
</div>
</div>
<div class="quick-card" @click="go('/platform/h5/points-mall/me')">
<van-icon name="manager-o"></van-icon>
<div>
<strong>我的积分</strong>
<span>账户积分概览</span>
</div>
</div>
</section>
<van-swipe class="banner" :autoplay="3500" indicator-color="#fff">
<van-swipe-item>
<div class="banner-inner red">
<strong>品牌专区</strong>
<span>精选供应商服务入口</span>
</div>
</van-swipe-item>
<van-swipe-item>
<div class="banner-inner blue">
<strong>积分抵扣</strong>
<span>1 积分可抵扣 1 元</span>
</div>
</van-swipe-item>
</van-swipe>
<section class="supplier-list">
<div class="section-title">供应商专区</div>
<van-skeleton title :row="6" :loading="loading" animated>
<van-empty v-if="!supplierList.length" description="暂无供应商"></van-empty>
<div class="supplier-card" v-for="item in supplierList" :key="item.id">
<div class="supplier-head">
<img :src="item.supplierPic || defaultLogo" class="supplier-logo" @error="onImgError">
<div class="supplier-meta">
<strong>{{ item.supplierName }}</strong>
<span>品牌专区</span>
</div>
<van-tag plain type="danger">积分兑换</van-tag>
</div>
<div class="supplier-products">
<div class="product-placeholder" v-for="n in 3" :key="n">
<van-icon name="gift-o"></van-icon>
<span>精选好物</span>
</div>
</div>
<van-button block color="#d64536" round size="small" @click="openSupplier(item)">查看更多</van-button>
</div>
</van-skeleton>
</section>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
loading: false,
supplierList: [],
userPoints: 0,
defaultLogo: "/assets/platform/images/logo.png"
}
},
methods: {
back() {
this.$pjaxReplace("/platform/h5/home")
},
go(url) {
this.$pjaxReplace(url)
},
async loadSuppliers() {
this.loading = true
const resp = await this.$axios.post("/platform/h5/points-mall/supplierList", { mallType: 1 })
this.loading = false
if (resp.code === 0) {
this.supplierList = resp.data || []
} else {
this.$toast(resp.msg || "供应商加载失败")
}
},
async loadPoints() {
const resp = await this.$axios.post("/platform/h5/points-mall/me/points")
if (resp.code === 0) {
this.userPoints = resp.data || 0
}
},
openSupplier(item) {
if (!item.supplierUrl) {
this.$toast("暂无跳转地址")
return
}
window.location.href = item.supplierUrl
},
onImgError(e) {
e.target.src = this.defaultLogo
}
},
created() {
this.loadSuppliers()
this.loadPoints()
}
})
</script>
<style>
body { background: #f6f7fb; }
.mall-page { min-height: 100vh; padding: 12px 12px 24px; box-sizing: border-box; }
.hero { min-height: 126px; border-radius: 14px; padding: 18px; color: #fff; background: linear-gradient(135deg, #c62828 0%, #f2a13a 100%); display: flex; justify-content: space-between; align-items: flex-end; box-shadow: 0 8px 20px rgba(198, 40, 40, .18); }
.hero-title { font-size: 24px; font-weight: 700; }
.hero-subtitle { margin-top: 8px; font-size: 14px; opacity: .9; }
.hero-points { text-align: right; }
.hero-points span { display: block; font-size: 12px; opacity: .85; }
.hero-points strong { display: block; margin-top: 4px; font-size: 26px; }
.quick-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 12px; }
.quick-card { background: #fff; border-radius: 10px; padding: 14px 12px; display: flex; align-items: center; gap: 10px; box-shadow: 0 4px 14px rgba(20, 24, 35, .05); }
.quick-card .van-icon { font-size: 28px; color: #d64536; }
.quick-card strong { display: block; color: #222; font-size: 15px; }
.quick-card span { display: block; margin-top: 4px; color: #8a8f99; font-size: 12px; }
.banner { height: 86px; margin-top: 12px; border-radius: 12px; overflow: hidden; }
.banner-inner { height: 100%; color: #fff; padding: 18px; box-sizing: border-box; }
.banner-inner strong { display: block; font-size: 20px; }
.banner-inner span { display: block; margin-top: 8px; font-size: 13px; opacity: .9; }
.banner-inner.red { background: linear-gradient(135deg, #8f2d2d, #e85c4a); }
.banner-inner.blue { background: linear-gradient(135deg, #2262a6, #42a5f5); }
.section-title { margin: 18px 2px 10px; font-size: 17px; font-weight: 700; color: #222; }
.supplier-card { background: #fff; border-radius: 12px; padding: 12px; margin-bottom: 12px; box-shadow: 0 4px 14px rgba(20, 24, 35, .05); }
.supplier-head { display: flex; align-items: center; gap: 10px; }
.supplier-logo { width: 42px; height: 42px; border-radius: 10px; object-fit: cover; background: #f3f4f6; }
.supplier-meta { flex: 1; min-width: 0; }
.supplier-meta strong { display: block; font-size: 16px; color: #222; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.supplier-meta span { display: block; margin-top: 4px; font-size: 12px; color: #999; }
.supplier-products { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: 12px 0; }
.product-placeholder { height: 74px; background: #fff7ed; border: 1px solid #ffe1bd; border-radius: 8px; color: #c76b2b; display: flex; flex-direction: column; justify-content: center; align-items: center; font-size: 12px; }
.product-placeholder .van-icon { font-size: 24px; margin-bottom: 6px; }
</style>
<!--#
}
#-->
@@ -0,0 +1,101 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar title="爱心帮扶" left-arrow left-text="返回" @click-left="back" fixed placeholder></van-nav-bar>
<div class="aid-page">
<section class="aid-hero">
<div class="aid-title">互助关爱基金</div>
<div class="aid-subtitle">自愿参与,互助共济,专款专用</div>
</section>
<section class="aid-card">
<div class="action-row">
<van-button round block color="#2f6fbd" @click="introVisible = true">查看基金介绍</van-button>
<van-button round block color="#d64536" @click="recordVisible = true">我的爱心记录</van-button>
</div>
<div class="identity-tabs">
<button :class="{active: anonymousType === 1}" @click="anonymousType = 1">实名</button>
<button :class="{active: anonymousType === 2}" @click="anonymousType = 2">匿名</button>
</div>
<div class="donate-box">
<div class="donate-title">捐款渠道</div>
<p v-if="anonymousType === 1">
当前为实名捐款。请通过支付宝或微信扫码捐款,捐款人信息将以当前登录用户记录。
</p>
<p v-else>
当前为匿名捐款。系统不会记录个人捐款身份,可从支付渠道记录中查询。
</p>
<div class="qr-placeholder">
<van-icon name="qr"></van-icon>
<span>二维码接口接入后展示</span>
</div>
</div>
</section>
</div>
<van-popup v-model="introVisible" round position="center" :style="{ width: '86%' }">
<div class="popup-content">
<h3>互助关爱基金</h3>
<p>
为完善员工福利与帮扶体系,互助关爱基金用于在员工家庭遭遇突发困难时提供及时、有力的支持。
</p>
<p>
基金遵循自愿参与、互助共济、公平公正、专款专用原则。
</p>
<van-button block round color="#d64536" @click="introVisible = false">我知道了</van-button>
</div>
</van-popup>
<van-popup v-model="recordVisible" round position="bottom" :style="{ height: '70%' }">
<div class="record-sheet">
<div class="sheet-title">我的爱心记录</div>
<van-empty description="记录接口接入后展示"></van-empty>
<van-button block round color="#d64536" @click="recordVisible = false">关闭</van-button>
</div>
</van-popup>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
anonymousType: 1,
introVisible: false,
recordVisible: false
}
},
methods: {
back() {
this.$pjaxReplace("/platform/h5/points-mall")
}
}
})
</script>
<style>
body { background: #f6f7fb; }
.aid-page { min-height: 100vh; padding-bottom: 24px; background: #f6f7fb; }
.aid-hero { min-height: 180px; padding: 28px 18px; color: #fff; box-sizing: border-box; background: linear-gradient(135deg, #1f5f8b, #d64536); display: flex; flex-direction: column; justify-content: flex-end; }
.aid-title { font-size: 27px; font-weight: 700; }
.aid-subtitle { margin-top: 8px; font-size: 14px; opacity: .9; }
.aid-card { margin: -24px 12px 0; border-radius: 14px; background: #fff; padding: 14px; box-shadow: 0 8px 22px rgba(20, 24, 35, .08); position: relative; }
.action-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.identity-tabs { margin: 16px 0; padding: 4px; border-radius: 10px; background: #f1f3f6; display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
.identity-tabs button { height: 38px; border: 0; border-radius: 8px; background: transparent; color: #666; font-size: 15px; }
.identity-tabs button.active { background: #2f6fbd; color: #fff; font-weight: 700; }
.donate-box { border: 1px solid #e8edf5; border-radius: 12px; padding: 14px; }
.donate-title { font-size: 17px; font-weight: 700; color: #222; }
.donate-box p { color: #555; font-size: 14px; line-height: 1.7; }
.qr-placeholder { height: 190px; border-radius: 12px; background: #f7f8fb; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #999; }
.qr-placeholder .van-icon { font-size: 58px; color: #c8ccd6; margin-bottom: 8px; }
.popup-content { padding: 22px; }
.popup-content h3 { text-align: center; margin: 0 0 14px; color: #1f5f8b; }
.popup-content p { font-size: 14px; line-height: 1.8; color: #444; }
.record-sheet { padding: 18px; }
.sheet-title { text-align: center; font-size: 18px; font-weight: 700; margin-bottom: 12px; }
</style>
<!--#
}
#-->
@@ -0,0 +1,74 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar title="我的" left-arrow left-text="返回" @click-left="back" fixed placeholder></van-nav-bar>
<div class="me-page">
<section class="profile">
<div class="avatar">{{ avatarText }}</div>
<div class="profile-text">
<strong>{{ displayName }}</strong>
<span>工号:{{ userInfo.loginname || userInfo.username || userInfo.userId || "-" }}</span>
</div>
</section>
<section class="points-card">
<div>
<span>我的积分</span>
<strong>{{ userPoints }}</strong>
</div>
<van-icon name="gold-coin-o"></van-icon>
</section>
<van-cell-group inset>
<van-cell title="我的订单" icon="orders-o" is-link @click="go('/platform/h5/points-mall/order')"></van-cell>
<van-cell title="积分商城" icon="shop-o" is-link @click="go('/platform/h5/points-mall')"></van-cell>
<van-cell title="影票专区" icon="video-o" is-link @click="go('/platform/h5/points-mall/movies')"></van-cell>
</van-cell-group>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return { userInfo: {}, userPoints: 0 }
},
computed: {
displayName() {
return this.userInfo.username || this.userInfo.loginname || "用户"
},
avatarText() {
return this.displayName ? this.displayName.slice(0, 1) : "我"
}
},
methods: {
back() { this.$pjaxReplace("/platform/h5/points-mall") },
go(url) { this.$pjaxReplace(url) },
async loadUser() {
const resp = await this.$axios.post("/platform/h5/points-mall/me/userInfo")
if (resp.code === 0) this.userInfo = resp.data || {}
},
async loadPoints() {
const resp = await this.$axios.post("/platform/h5/points-mall/me/points")
if (resp.code === 0) this.userPoints = resp.data || 0
}
},
created() {
this.loadUser()
this.loadPoints()
}
})
</script>
<style>
body { background: #f6f7fb; }
.me-page { min-height: 100vh; padding: 12px; box-sizing: border-box; }
.profile { min-height: 128px; border-radius: 14px; padding: 18px; display: flex; align-items: center; gap: 14px; background: linear-gradient(135deg, #c62828, #28456f); color: #fff; box-shadow: 0 8px 20px rgba(20, 24, 35, .12); }
.avatar { width: 58px; height: 58px; border-radius: 50%; background: rgba(255,255,255,.2); border: 2px solid rgba(255,255,255,.75); display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: 700; }
.profile-text strong { display: block; font-size: 19px; }
.profile-text span { display: block; margin-top: 8px; font-size: 13px; opacity: .9; }
.points-card { margin: 12px 0; border-radius: 12px; padding: 18px; background: #fff; display: flex; justify-content: space-between; align-items: center; box-shadow: 0 4px 14px rgba(20, 24, 35, .05); }
.points-card span { display: block; color: #888; font-size: 13px; }
.points-card strong { display: block; margin-top: 6px; color: #d64536; font-size: 28px; }
.points-card .van-icon { color: #f2a13a; font-size: 44px; }
</style>
<!--#
}
#-->
@@ -0,0 +1,73 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar title="影票专区" left-arrow left-text="返回" @click-left="back" fixed placeholder></van-nav-bar>
<div class="mall-page">
<section class="hero movie">
<div>
<div class="hero-title">影票专区</div>
<div class="hero-subtitle">精选观影权益,积分兑换更轻松</div>
</div>
<van-icon name="video-o"></van-icon>
</section>
<section class="supplier-list">
<div class="section-title">影票供应商</div>
<van-empty v-if="!supplierList.length && !loading" description="暂无影票供应商"></van-empty>
<van-skeleton title :row="5" :loading="loading" animated>
<div class="supplier-card" v-for="item in supplierList" :key="item.id" @click="openSupplier(item)">
<div class="supplier-head">
<img :src="item.supplierPic || defaultLogo" class="supplier-logo" @error="onImgError">
<div class="supplier-meta">
<strong>{{ item.supplierName }}</strong>
<span>点击进入兑换</span>
</div>
<van-icon name="arrow"></van-icon>
</div>
</div>
</van-skeleton>
</section>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return { loading: false, supplierList: [], defaultLogo: "/assets/platform/images/logo.png" }
},
methods: {
back() { this.$pjaxReplace("/platform/h5/points-mall") },
async loadSuppliers() {
this.loading = true
const resp = await this.$axios.post("/platform/h5/points-mall/supplierList", { mallType: 2 })
this.loading = false
if (resp.code === 0) this.supplierList = resp.data || []
},
openSupplier(item) {
if (item.supplierUrl) window.location.href = item.supplierUrl
else this.$toast("暂无跳转地址")
},
onImgError(e) { e.target.src = this.defaultLogo }
},
created() { this.loadSuppliers() }
})
</script>
<style>
body { background: #f6f7fb; }
.mall-page { min-height: 100vh; padding: 12px; box-sizing: border-box; }
.hero { min-height: 126px; border-radius: 14px; padding: 18px; color: #fff; display: flex; justify-content: space-between; align-items: flex-end; box-shadow: 0 8px 20px rgba(20, 24, 35, .12); }
.hero.movie { background: linear-gradient(135deg, #28456f, #d64536); }
.hero .van-icon { font-size: 46px; opacity: .88; }
.hero-title { font-size: 24px; font-weight: 700; }
.hero-subtitle { margin-top: 8px; font-size: 14px; opacity: .9; }
.section-title { margin: 18px 2px 10px; font-size: 17px; font-weight: 700; color: #222; }
.supplier-card { background: #fff; border-radius: 12px; padding: 14px; margin-bottom: 12px; box-shadow: 0 4px 14px rgba(20, 24, 35, .05); }
.supplier-head { display: flex; align-items: center; gap: 10px; }
.supplier-logo { width: 44px; height: 44px; border-radius: 10px; object-fit: cover; background: #f3f4f6; }
.supplier-meta { flex: 1; min-width: 0; }
.supplier-meta strong { display: block; font-size: 16px; color: #222; }
.supplier-meta span { display: block; margin-top: 4px; font-size: 12px; color: #999; }
</style>
<!--#
}
#-->
@@ -0,0 +1,137 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar title="我的订单" left-arrow left-text="返回" @click-left="back" fixed placeholder></van-nav-bar>
<section class="order-summary">
<span>剩余积分</span>
<strong>{{ userPoints }}</strong>
</section>
<van-sticky offset-top="46px">
<van-tabs v-model="pageForm.orderState" color="#d64536" title-active-color="#d64536" @change="doSearch">
<van-tab v-for="tab in tabList" :key="tab.code" :name="tab.code" :title="tab.name"></van-tab>
</van-tabs>
</van-sticky>
<van-list v-model="tableLoading" :finished="tableFinished" finished-text="没有更多了" @load="pageData">
<div class="order-card" v-for="item in tableData" :key="item.id || item.orderId">
<div class="card-head">
<strong>{{ item.supplierName || "积分商城" }}</strong>
<span :class="'state state-' + item.orderState">{{ orderStateName(item.orderState) }}</span>
</div>
<div class="product-row" v-if="item.productImgList && item.productImgList.length">
<van-image v-for="(img, index) in item.productImgList.slice(0, 3)" :key="index" :src="img" fit="cover" radius="6"></van-image>
</div>
<div class="order-info">
<div><span>订单号</span><em>{{ item.orderId }}</em></div>
<div><span>消费积分</span><em class="points">{{ item.pointsPrice || 0 }}</em></div>
<div><span>运费</span><em>{{ item.freightCost || 0 }}</em></div>
<div><span>创建时间</span><em>{{ item.orderCreateTime || "-" }}</em></div>
</div>
<div class="card-actions">
<van-button v-if="Number(item.orderState) === 4" round size="small" color="#4caf50" @click="openSupplierOrder(item)">确认收货</van-button>
<van-button round size="small" color="#409eff" @click="openSupplierOrder(item)">查看订单</van-button>
</div>
</div>
<van-empty v-if="!tableLoading && !tableData.length" description="暂无订单"></van-empty>
</van-list>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
userPoints: 0,
tableLoading: false,
tableFinished: false,
tableData: [],
tabList: [],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
orderState: -1
}
}
},
methods: {
back() { this.$pjaxReplace("/platform/h5/points-mall") },
async loadTabs() {
const resp = await this.$axios.post("/platform/h5/points-mall/order/stateList")
if (resp.code === 0) this.tabList = resp.data || []
},
async loadPoints() {
const resp = await this.$axios.post("/platform/h5/points-mall/me/points")
if (resp.code === 0) this.userPoints = resp.data || 0
},
doSearch() {
this.pageForm.pageNumber = 1
this.tableData = []
this.tableFinished = false
this.pageData()
},
async pageData() {
if (this.tableFinished) return
this.tableLoading = true
const resp = await this.$axios.post("/platform/h5/points-mall/order/pageData", this.pageForm)
this.tableLoading = false
if (resp.code !== 0) {
this.$toast(resp.msg || "订单加载失败")
this.tableFinished = true
return
}
const data = resp.data || {}
const rows = data.list || []
this.tableData = this.tableData.concat(rows)
this.pageForm.totalCount = data.totalCount || 0
if (!rows.length || this.tableData.length >= this.pageForm.totalCount) {
this.tableFinished = true
} else {
this.pageForm.pageNumber++
}
},
async openSupplierOrder(item) {
const resp = await this.$axios.post("/platform/h5/points-mall/order/supplierOrderUrl", {
supplierId: item.supplierId,
orderId: item.orderId,
orderType: item.orderType
})
const url = resp.code === 0 && resp.data ? resp.data.supplierOrderUrl : ""
if (url) window.location.href = url
else this.$toast("暂无订单跳转地址")
},
orderStateName(value) {
const item = this.tabList.find((tab) => String(tab.code) === String(value))
if (item) return item.name
const map = { 0: "待付款", 1: "已支付", 2: "进行中", 4: "已签收", 5: "已完成", 6: "换货", 7: "退货" }
return map[value] || "未知"
}
},
created() {
this.loadTabs()
this.loadPoints()
}
})
</script>
<style>
body { background: #f6f7fb; }
.order-summary { margin: 12px; min-height: 92px; border-radius: 14px; padding: 18px; color: #fff; background: linear-gradient(135deg, #c62828, #f2a13a); box-sizing: border-box; }
.order-summary span { display: block; font-size: 13px; opacity: .9; }
.order-summary strong { display: block; margin-top: 8px; font-size: 28px; }
.order-card { margin: 12px; padding: 14px; border-radius: 12px; background: #fff; box-shadow: 0 4px 14px rgba(20, 24, 35, .05); }
.card-head { display: flex; justify-content: space-between; align-items: center; padding-bottom: 10px; border-bottom: 1px solid #f0f1f5; }
.card-head strong { font-size: 15px; color: #222; }
.state { font-size: 13px; color: #999; }
.state-4, .state-5 { color: #43a047; }
.state-6, .state-7 { color: #d64536; }
.product-row { display: flex; gap: 8px; padding: 12px 0; }
.product-row .van-image { width: 70px; height: 70px; background: #f3f4f6; }
.order-info { padding: 10px; border-radius: 8px; background: #fafafa; }
.order-info div { display: flex; justify-content: space-between; font-size: 13px; line-height: 28px; }
.order-info span { color: #888; }
.order-info em { color: #333; font-style: normal; max-width: 65%; text-align: right; word-break: break-all; }
.order-info .points { color: #d64536; font-weight: 700; }
.card-actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: 12px; }
</style>
<!--#
}
#-->