Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e7c8f52f6 | ||
|
|
b4710b6397 | ||
|
|
daff741c65 | ||
|
|
2df598c0d8 | ||
|
|
174e33053e | ||
|
|
29b73c1f67 |
@@ -128,11 +128,7 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
|
||||
</dependency>
|
||||
<!-- Redis响应式支持(会员卡模块需要) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
-3
@@ -12,9 +12,6 @@ public class SearchMemberDto {
|
||||
// 搜索字段 - 包括 会员号、昵称、手机号
|
||||
private String searchValue;
|
||||
|
||||
// 排序
|
||||
private String filter;
|
||||
|
||||
// 页码
|
||||
private Integer pageNum = 1;
|
||||
|
||||
|
||||
+4
-2
@@ -1,7 +1,9 @@
|
||||
package cn.novalon.gym.manage.member.dto;
|
||||
|
||||
import cn.novalon.gym.manage.member.enums.GenderEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@@ -17,10 +19,10 @@ public class UpdateMemberInfoDto {
|
||||
private String nickname;
|
||||
|
||||
// 性别
|
||||
private Integer gender;
|
||||
private GenderEnum gender;
|
||||
|
||||
// 生日
|
||||
private Date birthday;
|
||||
private LocalDate birthday;
|
||||
|
||||
// 头像
|
||||
private String avatar;
|
||||
|
||||
+3
@@ -33,6 +33,9 @@ public abstract class BaseEntity implements Persistable<Long> {
|
||||
@Column("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@Column("deleted_at")
|
||||
private LocalDateTime deletedAt;
|
||||
|
||||
// 判断当前实体是否是新建的
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
|
||||
+3
-6
@@ -1,13 +1,10 @@
|
||||
package cn.novalon.gym.manage.member.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.*;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
@@ -44,7 +41,7 @@ public class Member extends BaseEntity {
|
||||
|
||||
//生日
|
||||
@Column("birthday")
|
||||
private Date birthday;
|
||||
private LocalDate birthday;
|
||||
|
||||
//地址
|
||||
@Column("address")
|
||||
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
package cn.novalon.gym.manage.member.entity;
|
||||
|
||||
import lombok.*;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table("sign_in_record")
|
||||
public class SignInRecord {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
// 会员ID
|
||||
@Column("member_id")
|
||||
private Long memberId;
|
||||
|
||||
// 签到日期
|
||||
@Column("sign_in_date")
|
||||
private LocalDate signInDate;
|
||||
|
||||
// 签到时间
|
||||
@Column("sign_in_time")
|
||||
private LocalDateTime signInTime;
|
||||
|
||||
// 创建时间
|
||||
@CreatedDate
|
||||
@Column("created_at")
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package cn.novalon.gym.manage.member.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 性别枚举
|
||||
*
|
||||
* @author 付嘉
|
||||
* @date 2026-05-29
|
||||
*/
|
||||
@Getter
|
||||
public enum GenderEnum {
|
||||
|
||||
UNKNOWN(0, "未知"),
|
||||
MALE(1, "男"),
|
||||
FEMALE(2, "女");
|
||||
|
||||
private final Integer code;
|
||||
private final String desc;
|
||||
|
||||
GenderEnum(Integer code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public static GenderEnum fromCode(Integer code) {
|
||||
if (code == null) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
for (GenderEnum gender : values()) {
|
||||
if (gender.code.equals(code)) {
|
||||
return gender;
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
+6
@@ -1,12 +1,18 @@
|
||||
package cn.novalon.gym.manage.member.es.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Document(indexName = "gym_members")
|
||||
public class MemberES {
|
||||
|
||||
|
||||
+3
-2
@@ -2,6 +2,7 @@ package cn.novalon.gym.manage.member.es.repository;
|
||||
|
||||
import cn.novalon.gym.manage.member.es.entity.MemberES;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.elasticsearch.annotations.Query;
|
||||
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -15,6 +16,6 @@ public interface MemberESRepository extends ReactiveElasticsearchRepository<Memb
|
||||
/**
|
||||
* 前台通用搜索:会员号(精确匹配) 或 昵称(模糊匹配) 或 手机号(精确匹配)并且 性别筛选(精确匹配)
|
||||
*/
|
||||
Flux<MemberES> findByMemberNoOrPhoneOrNicknameContainingAndGender(
|
||||
String memberNo, String phone, String nickname,String gender, Pageable pageable);
|
||||
Flux<MemberES> findByMemberNoOrPhoneOrNicknameContaining(
|
||||
String memberNo, String phone, String nickname, Pageable pageable);
|
||||
}
|
||||
+41
-87
@@ -8,8 +8,11 @@ import cn.novalon.gym.manage.member.service.MemberService;
|
||||
import cn.novalon.gym.manage.member.service.WechatAuthService;
|
||||
import cn.novalon.gym.manage.member.service.WechatOfficialService;
|
||||
import cn.novalon.gym.manage.member.util.AesUtil;
|
||||
import cn.novalon.gym.manage.member.util.WechatPhoneUtil;
|
||||
import cn.novalon.gym.manage.sys.util.AuthUtil;
|
||||
import cn.novalon.gym.manage.sys.security.JwtTokenProvider;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
@@ -29,21 +32,15 @@ import reactor.core.publisher.Mono;
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "会员管理", description = "会员信息管理、微信绑定、服务号关注等")
|
||||
public class MemberHandler {
|
||||
|
||||
private final MemberService memberService;
|
||||
private final WechatAuthService wechatAuthService;
|
||||
private final WechatOfficialService wechatOfficialService;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final WechatProperties wechatProperties;
|
||||
private final AuthUtil authUtil;
|
||||
|
||||
/**
|
||||
* 获取会员信息
|
||||
*
|
||||
* GET /api/member/info
|
||||
* header: { "Authorization": "Bearer xxx" }
|
||||
*/
|
||||
@Operation(summary = "获取会员信息", description = "根据当前登录用户获取会员基本信息")
|
||||
public Mono<ServerResponse> getMemberInfo(ServerRequest request) {
|
||||
|
||||
Long memberId = authUtil.getMemberIdOrThrow(request);
|
||||
@@ -56,19 +53,7 @@ public class MemberHandler {
|
||||
.bodyValue(info));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新会员信息
|
||||
*
|
||||
* PUT /api/member/info
|
||||
* header: { "Authorization": "Bearer xxx" }
|
||||
* Body: {
|
||||
* "nickname": "新昵称",
|
||||
* "gender": 1,
|
||||
* "birthday": "2000-01-01",
|
||||
* "avatar": "https://example.com/avatar.jpg",
|
||||
* "address": "北京市朝阳区"
|
||||
* }
|
||||
*/
|
||||
@Operation(summary = "更新会员信息", description = "更新会员昵称、性别、生日、头像、地址等信息")
|
||||
public Mono<ServerResponse> updateMemberInfo(ServerRequest request) {
|
||||
|
||||
Long memberId = authUtil.getMemberIdOrThrow(request);
|
||||
@@ -82,11 +67,7 @@ public class MemberHandler {
|
||||
.bodyValue(info));
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定手机号(微信小程序)
|
||||
* header: { "Authorization": "Bearer xxx" }
|
||||
* POST /api/member/phone/bind?code=PHONE_CODE
|
||||
*/
|
||||
@Operation(summary = "绑定手机号", description = "通过微信小程序手机号code绑定会员手机号")
|
||||
public Mono<ServerResponse> bindPhone(ServerRequest request) {
|
||||
|
||||
Long memberId = authUtil.getMemberIdOrThrow(request);
|
||||
@@ -103,12 +84,7 @@ public class MemberHandler {
|
||||
.bodyValue(success));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询服务号关注状态
|
||||
*
|
||||
* GET /api/member/subscribe/status
|
||||
*
|
||||
*/
|
||||
@Operation(summary = "查询服务号关注状态", description = "查询会员是否关注微信服务号")
|
||||
public Mono<ServerResponse> checkSubscribeStatus(ServerRequest request) {
|
||||
|
||||
Long memberId = authUtil.getMemberIdOrThrow(request);
|
||||
@@ -123,14 +99,7 @@ public class MemberHandler {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员更新手机号
|
||||
*
|
||||
* POST /api/admin/member/123/phone
|
||||
* header: { "Authorization": "Bearer xxx" }
|
||||
* Body: { "phone": "13800138000" }
|
||||
*
|
||||
*/
|
||||
@Operation(summary = "管理员更新手机号", description = "后台管理员为会员更新手机号")
|
||||
public Mono<ServerResponse> adminUpdatePhone(ServerRequest request) {
|
||||
|
||||
Long adminId = authUtil.getMemberIdOrThrow(request);
|
||||
@@ -162,13 +131,7 @@ public class MemberHandler {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 前台查看会员信息
|
||||
*
|
||||
* GET /api/admin/member/{id}
|
||||
* header: { "Authorization": "xxx" }
|
||||
*
|
||||
*/
|
||||
@Operation(summary = "管理员查看会员详情", description = "后台管理员查看指定会员的详细信息")
|
||||
public Mono<ServerResponse> adminGetMemberInfo(ServerRequest request) {
|
||||
|
||||
Long adminId = authUtil.getMemberIdOrThrow(request);
|
||||
@@ -179,20 +142,24 @@ public class MemberHandler {
|
||||
|
||||
log.info("前台查看会员信息, adminId: {}, memberId: {}", adminId, memberId);
|
||||
|
||||
// TODO 多表查询:会员信息、团课信息、会员卡信息
|
||||
|
||||
return memberService.getMemberDetail(memberId)
|
||||
.flatMap(detail -> {
|
||||
if (detail.getPhone() != null && !detail.getPhone().isEmpty()) {
|
||||
try {
|
||||
String decryptedPhone = AesUtil.decrypt(detail.getPhone());
|
||||
detail.setPhone(WechatPhoneUtil.maskPhone(decryptedPhone));
|
||||
} catch (Exception e) {
|
||||
log.error("手机号解密失败, memberId: {}", detail.getId(), e);
|
||||
detail.setPhone(null);
|
||||
}
|
||||
}
|
||||
return ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue("成功");
|
||||
.bodyValue(detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 前台编辑会员信息
|
||||
*
|
||||
* PUT /api/admin/member/{id}
|
||||
* header: { "Authorization": "xxx" }
|
||||
* Body:{"字段","值"}
|
||||
*/
|
||||
@Operation(summary = "管理员编辑会员信息", description = "后台管理员编辑会员信息")
|
||||
public Mono<ServerResponse> adminUpdateMemberInfo(ServerRequest request) {
|
||||
|
||||
Long adminId = authUtil.getMemberIdOrThrow(request);
|
||||
@@ -201,42 +168,35 @@ public class MemberHandler {
|
||||
long memberId = NumberUtils.toLong(memberIdStr, 0L);
|
||||
if(memberId <= 0L) throw new IllegalArgumentException("会员ID格式错误");
|
||||
|
||||
// TODO: 补充签到记录
|
||||
log.info("前台编辑会员信息, adminId: {}, memberId: {}", adminId, memberId);
|
||||
|
||||
// TODO 多表查询:会员信息、团课信息、会员卡信息
|
||||
|
||||
return ServerResponse.ok()
|
||||
return request.bodyToMono(UpdateMemberInfoDto.class)
|
||||
.flatMap(updateDto -> memberService.adminUpdateMemberInfo(memberId, updateDto))
|
||||
.flatMap(detail -> ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue("成功");
|
||||
.bodyValue(detail));
|
||||
}
|
||||
|
||||
/**
|
||||
* 前台搜索会员列表
|
||||
*
|
||||
* GET /api/admin/members?searchValue=手机号/姓名/会员号&filter=男/女&pageNum=1&pageSize=10
|
||||
* header: { "Authorization": "Bearer xxx" }
|
||||
*/
|
||||
@Operation(summary = "搜索会员列表", description = "后台管理员按关键词搜索会员,支持性别筛选和分页")
|
||||
public Mono<ServerResponse> searchMembers(ServerRequest request) {
|
||||
|
||||
Long adminId = authUtil.getMemberIdOrThrow(request);
|
||||
|
||||
String keyword = request.queryParam("searchValue").orElse(null);
|
||||
String filter = request.queryParam("filter").orElse(null);
|
||||
int pageNum = NumberUtils.toInt(request.queryParam("pageNum").orElse("1"), 1);
|
||||
int pageSize = NumberUtils.toInt(request.queryParam("pageSize").orElse("10"), 10);
|
||||
Integer pageNum = NumberUtils.toInt(request.queryParam("pageNum").orElse("1"), 1);
|
||||
Integer pageSize = NumberUtils.toInt(request.queryParam("pageSize").orElse("10"), 10);
|
||||
|
||||
log.info("前台搜索会员列表, adminId: {}, keyword: {}, filter: {}, pageNum: {}, pageSize: {}",
|
||||
adminId, keyword, filter, pageNum, pageSize);
|
||||
log.info("前台搜索会员列表, adminId: {}, keyword: {},pageNum: {}, pageSize: {}",
|
||||
adminId, keyword, pageNum, pageSize);
|
||||
|
||||
return memberService.searchMember(new SearchMemberDto(keyword, filter, pageNum, pageSize))
|
||||
return memberService.searchMember(new SearchMemberDto(keyword, pageNum, pageSize))
|
||||
.map(member -> {
|
||||
// 解密手机号
|
||||
if (member.getPhone() != null && !member.getPhone().isEmpty()) {
|
||||
try {
|
||||
String secretKey = wechatProperties.getPhoneEncryption().getSecretKey();
|
||||
String iv = wechatProperties.getPhoneEncryption().getIv();
|
||||
String decryptedPhone = AesUtil.decrypt(member.getPhone(), secretKey, iv);
|
||||
member.setPhone(decryptedPhone);
|
||||
String decryptedPhone = AesUtil.decrypt(member.getPhone());
|
||||
member.setPhone(WechatPhoneUtil.maskPhone(decryptedPhone));
|
||||
} catch (Exception e) {
|
||||
log.error("手机号解密失败, memberId: {}", member.getId(), e);
|
||||
member.setPhone(null);
|
||||
@@ -249,12 +209,7 @@ public class MemberHandler {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 前台查看会员列表
|
||||
*
|
||||
* GET /api/admin/members/all?pageNum=1&pageSize=10
|
||||
* header: { "Authorization": "Bearer xxx" }
|
||||
*/
|
||||
@Operation(summary = "查看会员列表", description = "后台管理员分页查看所有会员列表")
|
||||
public Mono<ServerResponse> getAllMembers(ServerRequest request) {
|
||||
|
||||
Long adminId = authUtil.getMemberIdOrThrow(request);
|
||||
@@ -263,16 +218,15 @@ public class MemberHandler {
|
||||
int pageSize = NumberUtils.toInt(request.queryParam("pageSize").orElse("10"), 10);
|
||||
|
||||
log.info("前台查看会员列表, adminId: {}, pageNum: {}, pageSize: {}", adminId, pageNum, pageSize);
|
||||
// TODO: 补充签到记录
|
||||
|
||||
return memberService.findAll(pageNum, pageSize)
|
||||
.map(member -> {
|
||||
// 解密手机号
|
||||
if (member.getPhone() != null && !member.getPhone().isEmpty()) {
|
||||
try {
|
||||
String secretKey = wechatProperties.getPhoneEncryption().getSecretKey();
|
||||
String iv = wechatProperties.getPhoneEncryption().getIv();
|
||||
String decryptedPhone = AesUtil.decrypt(member.getPhone(), secretKey, iv);
|
||||
member.setPhone(decryptedPhone);
|
||||
String decryptedPhone = AesUtil.decrypt(member.getPhone());
|
||||
member.setPhone(WechatPhoneUtil.maskPhone(decryptedPhone));
|
||||
} catch (Exception e) {
|
||||
log.error("手机号解密失败, memberId: {}", member.getId(), e);
|
||||
member.setPhone(null);
|
||||
|
||||
+6
-17
@@ -2,6 +2,8 @@ package cn.novalon.gym.manage.member.handler;
|
||||
|
||||
import cn.novalon.gym.manage.member.dto.WechatLoginDto;
|
||||
import cn.novalon.gym.manage.member.service.WechatAuthService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -20,20 +22,13 @@ import reactor.core.publisher.Mono;
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "微信认证", description = "微信小程序登录、公众号回调等")
|
||||
public class WechatAuthHandler {
|
||||
|
||||
private final WechatAuthService wechatAuthService;
|
||||
private final WechatOfficialEventHandler wechatOfficialEventHandler;
|
||||
|
||||
/**
|
||||
* 小程序更新
|
||||
*
|
||||
* POST /api/member/auth/miniapp/login
|
||||
* Body: {"code": "wx_login_code"}
|
||||
*
|
||||
* @param request ServerRequest
|
||||
* @return Mono<ServerResponse> 登录响应
|
||||
*/
|
||||
@Operation(summary = "微信小程序登录", description = "通过微信小程序code获取session_key,完成会员登录或注册")
|
||||
public Mono<ServerResponse> miniappLogin(ServerRequest request) {
|
||||
log.info("收到小程序登录请求");
|
||||
|
||||
@@ -50,18 +45,12 @@ public class WechatAuthHandler {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 公众号回调
|
||||
*
|
||||
* POST /api/member/auth/mp/callback
|
||||
* Body: <xml><Event>subscribe</Event><FromUserName>openid</FromUserName></xml>
|
||||
*
|
||||
*/
|
||||
@Operation(summary = "微信公众号回调", description = "处理微信公众号事件(关注、取消关注等)")
|
||||
public Mono<ServerResponse> mpCallback(ServerRequest request) {
|
||||
return wechatOfficialEventHandler.handleEvent(request);
|
||||
}
|
||||
|
||||
// 验证微信公众号签名
|
||||
@Operation(summary = "验证微信公众号签名", description = "微信公众号服务器验证,返回echostr")
|
||||
public Mono<ServerResponse> verifyMpSignature(ServerRequest request) {
|
||||
return wechatOfficialEventHandler.verifySignature(request);
|
||||
}
|
||||
|
||||
-3
@@ -40,9 +40,6 @@ public class WechatOfficialEventHandler {
|
||||
.flatMap(xmlBody -> {
|
||||
log.info("收到微信公众号事件 {}", xmlBody);
|
||||
|
||||
// TODO: 将XML解析为WechatOfficialEventDto
|
||||
// 目前简化处理直接获取openId和event
|
||||
|
||||
String openId = extractOpenId(xmlBody);
|
||||
String event = extractEvent(xmlBody);
|
||||
|
||||
|
||||
+40
-1
@@ -1,7 +1,9 @@
|
||||
package cn.novalon.gym.manage.member.repository;
|
||||
|
||||
import cn.novalon.gym.manage.member.entity.Member;
|
||||
import cn.novalon.gym.manage.member.vo.MemberCardInfoVO;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.r2dbc.repository.Query;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -30,7 +32,44 @@ public interface IMemberRepository extends R2dbcRepository<Member, Long> {
|
||||
|
||||
/**
|
||||
* 分页查询所有会员
|
||||
* 方法名 findAllBy 是 Spring Data 的约定,表示按条件查询所有
|
||||
*/
|
||||
Flux<Member> findAllBy(Pageable pageable);
|
||||
|
||||
/**
|
||||
* 查询会员的所有卡片
|
||||
*/
|
||||
@Query("SELECT " +
|
||||
" r.id, " +
|
||||
" r.member_card_record_id, " +
|
||||
" r.member_id, " +
|
||||
" r.member_card_id, " +
|
||||
" r.status, " +
|
||||
" r.remaining_times, " +
|
||||
" r.remaining_amount, " +
|
||||
" r.expire_time, " +
|
||||
" r.purchase_time, " +
|
||||
" r.source_order_id, " +
|
||||
" r.created_at, " +
|
||||
" r.updated_at, " +
|
||||
" r.version, " +
|
||||
" r.card_composition, " +
|
||||
" c.id AS card_id, " +
|
||||
" c.member_card_id, " +
|
||||
" c.member_card_name, " +
|
||||
" c.member_card_type, " +
|
||||
" c.member_card_price, " +
|
||||
" c.member_card_validity_days, " +
|
||||
" c.member_card_total_times, " +
|
||||
" c.member_card_amount, " +
|
||||
" c.member_card_status, " +
|
||||
" c.extra_config, " +
|
||||
" c.created_at AS card_created_at, " +
|
||||
" c.updated_at AS card_updated_at " +
|
||||
"FROM member_card_record r " +
|
||||
"LEFT JOIN member_card c ON r.member_card_id = c.id " +
|
||||
"WHERE r.member_id = :memberId " +
|
||||
"AND r.deleted_at IS NULL " +
|
||||
"AND c.deleted_at IS NULL " +
|
||||
"ORDER BY r.created_at DESC")
|
||||
Flux<MemberCardInfoVO> findCardRecordsWithCardInfoByMemberId(Long memberId);
|
||||
}
|
||||
|
||||
+18
@@ -4,6 +4,7 @@ import cn.novalon.gym.manage.member.dto.SearchMemberDto;
|
||||
import cn.novalon.gym.manage.member.dto.UpdateMemberInfoDto;
|
||||
import cn.novalon.gym.manage.member.entity.Member;
|
||||
import cn.novalon.gym.manage.member.es.entity.MemberES;
|
||||
import cn.novalon.gym.manage.member.vo.MemberDetailVO;
|
||||
import cn.novalon.gym.manage.member.vo.MemberInfoVO;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -58,4 +59,21 @@ public interface MemberService {
|
||||
* @return 所有会员信息
|
||||
*/
|
||||
Flux<Member> findAll(Integer pageNum, Integer pageSize);
|
||||
|
||||
/**
|
||||
* 前台管理端获取会员详情(含会员卡信息)
|
||||
*
|
||||
* @param memberId 会员ID
|
||||
* @return 会员详情
|
||||
*/
|
||||
Mono<MemberDetailVO> getMemberDetail(Long memberId);
|
||||
|
||||
/**
|
||||
* 前台管理端编辑会员信息
|
||||
*
|
||||
* @param memberId 会员ID
|
||||
* @param updateDto 更新信息DTO
|
||||
* @return 更新后的会员详情
|
||||
*/
|
||||
Mono<Boolean> adminUpdateMemberInfo(Long memberId, UpdateMemberInfoDto updateDto);
|
||||
}
|
||||
|
||||
+46
-5
@@ -3,6 +3,8 @@ package cn.novalon.gym.manage.member.service.impl;
|
||||
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
|
||||
import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
|
||||
import cn.novalon.gym.manage.member.service.IMemberCardRecordService;
|
||||
import cn.novalon.gym.manage.member.util.RedisUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -16,17 +18,35 @@ import java.time.LocalDateTime;
|
||||
* @author 付嘉
|
||||
* @date 2026-05-27
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MemberCardRecordServiceImpl implements IMemberCardRecordService {
|
||||
private final MemberCardRecordRepository memberCardRecordRepository;
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
public MemberCardRecordServiceImpl(MemberCardRecordRepository memberCardRecordRepository) {
|
||||
private static final String MEMBER_CARD_RECORD_CACHE_PREFIX = "member:card:record:";
|
||||
private static final long CACHE_EXPIRE_SECONDS = 300;
|
||||
|
||||
public MemberCardRecordServiceImpl(MemberCardRecordRepository memberCardRecordRepository, RedisUtil redisUtil) {
|
||||
this.memberCardRecordRepository = memberCardRecordRepository;
|
||||
this.redisUtil = redisUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MemberCardRecord> findById(Long recordId) {
|
||||
return memberCardRecordRepository.findById(recordId);
|
||||
String cacheKey = MEMBER_CARD_RECORD_CACHE_PREFIX + recordId;
|
||||
Object cached = redisUtil.get(cacheKey);
|
||||
if (cached != null && cached instanceof MemberCardRecord) {
|
||||
log.debug("从缓存获取会员卡记录, recordId: {}", recordId);
|
||||
return Mono.just((MemberCardRecord) cached);
|
||||
}
|
||||
|
||||
return memberCardRecordRepository.findById(recordId)
|
||||
.doOnSuccess(record -> {
|
||||
if (record != null) {
|
||||
redisUtil.setWithExpire(cacheKey, record, CACHE_EXPIRE_SECONDS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -53,17 +73,32 @@ public class MemberCardRecordServiceImpl implements IMemberCardRecordService {
|
||||
|
||||
@Override
|
||||
public Mono<Integer> deductUsage(Long recordId, Integer deductTimes, Double deductAmount) {
|
||||
return memberCardRecordRepository.deductUsage(recordId, deductTimes, deductAmount);
|
||||
return memberCardRecordRepository.deductUsage(recordId, deductTimes, deductAmount)
|
||||
.doOnSuccess(updated -> {
|
||||
if (updated > 0) {
|
||||
clearRecordCache(recordId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Integer> renewCard(Long recordId, Integer addTimes, Double addAmount, LocalDateTime newExpireTime) {
|
||||
return memberCardRecordRepository.renewCard(recordId, addTimes, addAmount, newExpireTime);
|
||||
return memberCardRecordRepository.renewCard(recordId, addTimes, addAmount, newExpireTime)
|
||||
.doOnSuccess(updated -> {
|
||||
if (updated > 0) {
|
||||
clearRecordCache(recordId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Integer> updateStatus(Long recordId, String status) {
|
||||
return memberCardRecordRepository.updateStatus(recordId, status);
|
||||
return memberCardRecordRepository.updateStatus(recordId, status)
|
||||
.doOnSuccess(updated -> {
|
||||
if (updated > 0) {
|
||||
clearRecordCache(recordId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,4 +115,10 @@ public class MemberCardRecordServiceImpl implements IMemberCardRecordService {
|
||||
public Flux<MemberCardRecord> findExpiredCards() {
|
||||
return memberCardRecordRepository.findExpiredCards();
|
||||
}
|
||||
|
||||
private void clearRecordCache(Long recordId) {
|
||||
String cacheKey = MEMBER_CARD_RECORD_CACHE_PREFIX + recordId;
|
||||
redisUtil.delete(cacheKey);
|
||||
log.debug("清除会员卡记录缓存, recordId: {}", recordId);
|
||||
}
|
||||
}
|
||||
|
||||
+33
-3
@@ -15,6 +15,7 @@ import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
|
||||
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
|
||||
import cn.novalon.gym.manage.member.service.IMemberCardService;
|
||||
import cn.novalon.gym.manage.member.service.IMemberCardTransactionService;
|
||||
import cn.novalon.gym.manage.member.util.RedisUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -39,6 +40,10 @@ public class MemberCardServiceImpl implements IMemberCardService {
|
||||
private final DistributedLockService distributedLockService;
|
||||
private final ExpirationReminderService expirationReminderService;
|
||||
private final RefundSagaHandler refundSagaHandler;
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
private static final String MEMBER_CARD_CACHE_PREFIX = "member:card:";
|
||||
private static final long CACHE_EXPIRE_SECONDS = 300;
|
||||
|
||||
public MemberCardServiceImpl(MemberCardRepository memberCardRepository,
|
||||
MemberCardRecordRepository recordRepository,
|
||||
@@ -46,7 +51,8 @@ public class MemberCardServiceImpl implements IMemberCardService {
|
||||
MemberCardStateMachine stateMachine,
|
||||
DistributedLockService distributedLockService,
|
||||
ExpirationReminderService expirationReminderService,
|
||||
RefundSagaHandler refundSagaHandler) {
|
||||
RefundSagaHandler refundSagaHandler,
|
||||
RedisUtil redisUtil) {
|
||||
this.memberCardRepository = memberCardRepository;
|
||||
this.recordRepository = recordRepository;
|
||||
this.transactionService = transactionService;
|
||||
@@ -54,11 +60,24 @@ public class MemberCardServiceImpl implements IMemberCardService {
|
||||
this.distributedLockService = distributedLockService;
|
||||
this.expirationReminderService = expirationReminderService;
|
||||
this.refundSagaHandler = refundSagaHandler;
|
||||
this.redisUtil = redisUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MemberCard> findByMemberCardIdAndDeletedAtIsNull(Long memberCardId) {
|
||||
return memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(memberCardId);
|
||||
String cacheKey = MEMBER_CARD_CACHE_PREFIX + memberCardId;
|
||||
Object cached = redisUtil.get(cacheKey);
|
||||
if (cached != null && cached instanceof MemberCard) {
|
||||
log.debug("从缓存获取会员卡信息, memberCardId: {}", memberCardId);
|
||||
return Mono.just((MemberCard) cached);
|
||||
}
|
||||
|
||||
return memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(memberCardId)
|
||||
.doOnSuccess(card -> {
|
||||
if (card != null) {
|
||||
redisUtil.setWithExpire(cacheKey, card, CACHE_EXPIRE_SECONDS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,7 +114,12 @@ public class MemberCardServiceImpl implements IMemberCardService {
|
||||
|
||||
@Override
|
||||
public Mono<MemberCard> save(MemberCard entity) {
|
||||
return memberCardRepository.save(entity);
|
||||
return memberCardRepository.save(entity)
|
||||
.doOnSuccess(saved -> {
|
||||
if (saved.getMemberCardId() != null) {
|
||||
clearCardCache(saved.getMemberCardId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -329,4 +353,10 @@ public class MemberCardServiceImpl implements IMemberCardService {
|
||||
|
||||
return transactionService.createTransaction(transaction);
|
||||
}
|
||||
|
||||
private void clearCardCache(Long memberCardId) {
|
||||
String cacheKey = MEMBER_CARD_CACHE_PREFIX + memberCardId;
|
||||
redisUtil.delete(cacheKey);
|
||||
log.debug("清除会员卡缓存, memberCardId: {}", memberCardId);
|
||||
}
|
||||
}
|
||||
|
||||
+161
-20
@@ -4,20 +4,32 @@ import cn.novalon.gym.manage.common.exception.ConflictException;
|
||||
import cn.novalon.gym.manage.common.exception.ErrorCode;
|
||||
import cn.novalon.gym.manage.common.exception.NotFoundException;
|
||||
import cn.novalon.gym.manage.common.exception.SystemException;
|
||||
import cn.novalon.gym.manage.member.config.WechatProperties;
|
||||
import cn.novalon.gym.manage.common.util.HtmlEscapeUtil;
|
||||
import cn.novalon.gym.manage.member.dto.SearchMemberDto;
|
||||
import cn.novalon.gym.manage.member.dto.UpdateMemberInfoDto;
|
||||
import cn.novalon.gym.manage.member.entity.Member;
|
||||
import cn.novalon.gym.manage.member.enums.GenderEnum;
|
||||
import cn.novalon.gym.manage.member.enums.MemberCardType;
|
||||
import cn.novalon.gym.manage.member.es.entity.MemberES;
|
||||
import cn.novalon.gym.manage.member.es.repository.MemberESRepository;
|
||||
import cn.novalon.gym.manage.member.repository.IMemberRepository;
|
||||
import cn.novalon.gym.manage.member.service.MemberService;
|
||||
import cn.novalon.gym.manage.member.util.AesUtil;
|
||||
import cn.novalon.gym.manage.member.util.BeanConvertUtil;
|
||||
import cn.novalon.gym.manage.member.util.EsSyncUtils;
|
||||
import cn.novalon.gym.manage.member.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.member.vo.MemberCardInfoVO;
|
||||
import cn.novalon.gym.manage.member.vo.MemberDetailVO;
|
||||
import cn.novalon.gym.manage.member.vo.MemberInfoVO;
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
import co.elastic.clients.elasticsearch.core.IndexResponse;
|
||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import co.elastic.clients.transport.rest_client.RestClientTransport;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -26,6 +38,10 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 会员服务实现
|
||||
@@ -41,10 +57,14 @@ public class MemberServiceImpl implements MemberService {
|
||||
private final IMemberRepository memberRepository;
|
||||
private final MemberESRepository memberESRepository;
|
||||
private final EsSyncUtils esSyncUtils;
|
||||
private final WechatProperties wechatProperties;
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
private EsSyncUtils.EntitySyncer<Member, MemberES, String> memberSyncer;
|
||||
|
||||
private static final String MEMBER_INFO_CACHE_PREFIX = "member:info:";
|
||||
private static final String MEMBER_DETAIL_CACHE_PREFIX = "member:detail:";
|
||||
private static final long CACHE_EXPIRE_SECONDS = 300;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
this.memberSyncer = esSyncUtils.bind(Member.class, MemberES.class, memberESRepository);
|
||||
@@ -52,12 +72,23 @@ public class MemberServiceImpl implements MemberService {
|
||||
|
||||
@Override
|
||||
public Mono<MemberInfoVO> getMemberInfo(Long memberId) {
|
||||
String cacheKey = MEMBER_INFO_CACHE_PREFIX + memberId;
|
||||
|
||||
return redisUtil.get(cacheKey, MemberInfoVO.class)
|
||||
.flatMap(cached -> {
|
||||
if (cached != null) {
|
||||
log.debug("从缓存获取会员信息, memberId: {}", memberId);
|
||||
return Mono.just(cached);
|
||||
}
|
||||
return memberRepository.findById(memberId)
|
||||
.map(this::buildMemberInfoResponse)
|
||||
.flatMap(vo -> redisUtil.setWithExpire(cacheKey, vo, CACHE_EXPIRE_SECONDS)
|
||||
.then(Mono.just(vo)))
|
||||
.switchIfEmpty(Mono.error(() -> {
|
||||
log.error("会员不存在: memberId={}", memberId);
|
||||
throw new NotFoundException(ErrorCode.NOT_FOUND_USER, "会员不存在");
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,10 +98,10 @@ public class MemberServiceImpl implements MemberService {
|
||||
return memberRepository.findById(memberId)
|
||||
.flatMap(member -> {
|
||||
if (updateDto.getNickname() != null) {
|
||||
member.setNickname(updateDto.getNickname());
|
||||
member.setNickname(HtmlEscapeUtil.escape(updateDto.getNickname()));
|
||||
}
|
||||
if (updateDto.getGender() != null) {
|
||||
member.setGender(updateDto.getGender());
|
||||
member.setGender(updateDto.getGender().getCode());
|
||||
}
|
||||
if (updateDto.getBirthday() != null) {
|
||||
member.setBirthday(updateDto.getBirthday());
|
||||
@@ -79,12 +110,16 @@ public class MemberServiceImpl implements MemberService {
|
||||
member.setAvatar(updateDto.getAvatar());
|
||||
}
|
||||
if (updateDto.getAddress() != null) {
|
||||
member.setAddress(updateDto.getAddress());
|
||||
member.setAddress(HtmlEscapeUtil.escape(updateDto.getAddress()));
|
||||
}
|
||||
|
||||
return memberRepository.save(member);
|
||||
})
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.flatMap(savedMember -> {
|
||||
memberSyncer.sync(savedMember);
|
||||
return clearMemberCache(memberId)
|
||||
.then(Mono.just(savedMember));
|
||||
})
|
||||
.map(savedMember -> {
|
||||
log.info("会员信息更新成功, memberId: {}", savedMember.getId());
|
||||
return buildMemberInfoResponse(savedMember);
|
||||
@@ -99,11 +134,14 @@ public class MemberServiceImpl implements MemberService {
|
||||
String phone = member.getPhone();
|
||||
String maskedPhone = phone != null ? phone.replace(phone.substring(3, 7), "****") : null;
|
||||
|
||||
GenderEnum genderEnum = GenderEnum.fromCode(member.getGender());
|
||||
|
||||
return MemberInfoVO.builder()
|
||||
.id(member.getId())
|
||||
.nickname(member.getNickname())
|
||||
.phone(maskedPhone)
|
||||
.gender(member.getGender())
|
||||
.gender(genderEnum)
|
||||
.genderDesc(genderEnum.getDesc())
|
||||
.birthday(member.getBirthday())
|
||||
.avatar(member.getAvatar())
|
||||
.hasPhone(phone != null)
|
||||
@@ -117,9 +155,7 @@ public class MemberServiceImpl implements MemberService {
|
||||
|
||||
String encryptedPhone;
|
||||
try {
|
||||
String secretKey = wechatProperties.getPhoneEncryption().getSecretKey();
|
||||
String iv = wechatProperties.getPhoneEncryption().getIv();
|
||||
encryptedPhone = AesUtil.encrypt(phone, secretKey, iv);
|
||||
encryptedPhone = AesUtil.encrypt(phone);
|
||||
log.info("手机号加密成功");
|
||||
} catch (Exception e) {
|
||||
log.error("手机号加密失败", e);
|
||||
@@ -145,9 +181,8 @@ public class MemberServiceImpl implements MemberService {
|
||||
|
||||
@Override
|
||||
public Flux<MemberES> searchMember(SearchMemberDto searchMemberDto) {
|
||||
log.info("搜索会员, searchValue: {}, filter: {}, pageNum: {}, pageSize: {}",
|
||||
log.info("搜索会员, searchValue: {}, pageNum: {}, pageSize: {}",
|
||||
searchMemberDto.getSearchValue(),
|
||||
searchMemberDto.getFilter(),
|
||||
searchMemberDto.getPageNum(),
|
||||
searchMemberDto.getPageSize());
|
||||
|
||||
@@ -155,22 +190,23 @@ public class MemberServiceImpl implements MemberService {
|
||||
|
||||
if (searchValue != null && searchValue.matches("^1[3-9]\\d{9}$")) {
|
||||
log.debug("搜索值为手机号格式,进行加密处理");
|
||||
String secretKey = wechatProperties.getPhoneEncryption().getSecretKey();
|
||||
String iv = wechatProperties.getPhoneEncryption().getIv();
|
||||
searchValue = AesUtil.encrypt(searchValue,secretKey,iv);
|
||||
searchValue = AesUtil.encrypt(searchValue);
|
||||
}
|
||||
|
||||
Pageable pageable = PageRequest.of(
|
||||
searchMemberDto.getPageNum() - 1,
|
||||
searchMemberDto.getPageSize(),
|
||||
Sort.by(Sort.Direction.DESC, "update_at")
|
||||
searchMemberDto.getPageSize()
|
||||
);
|
||||
|
||||
return memberESRepository.findByMemberNoOrPhoneOrNicknameContainingAndGender(
|
||||
if (searchValue == null) {
|
||||
log.warn("搜索值为空,返回空结果");
|
||||
return Flux.empty();
|
||||
}
|
||||
|
||||
return memberESRepository.findByMemberNoOrPhoneOrNicknameContaining(
|
||||
searchValue,
|
||||
searchValue,
|
||||
searchValue,
|
||||
searchMemberDto.getFilter() ,
|
||||
pageable
|
||||
);
|
||||
}
|
||||
@@ -187,6 +223,99 @@ public class MemberServiceImpl implements MemberService {
|
||||
return memberRepository.findAllBy(pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MemberDetailVO> getMemberDetail(Long memberId) {
|
||||
log.info("查询会员详情, memberId: {}", memberId);
|
||||
|
||||
String cacheKey = MEMBER_DETAIL_CACHE_PREFIX + memberId;
|
||||
|
||||
return redisUtil.get(cacheKey, MemberDetailVO.class)
|
||||
.flatMap(cached -> {
|
||||
if (cached != null) {
|
||||
log.debug("从缓存获取会员详情, memberId: {}", memberId);
|
||||
return Mono.just(cached);
|
||||
}
|
||||
return memberRepository.findById(memberId)
|
||||
.zipWith(
|
||||
memberRepository.findCardRecordsWithCardInfoByMemberId(memberId)
|
||||
.collectList(),
|
||||
(baseInfo, cardList) -> {
|
||||
MemberDetailVO memberDetailVO = BeanConvertUtil.toBean(baseInfo, MemberDetailVO.class);
|
||||
|
||||
GenderEnum genderEnum = GenderEnum.fromCode(baseInfo.getGender());
|
||||
memberDetailVO.setGenderDesc(genderEnum.getDesc());
|
||||
|
||||
List<MemberCardInfoVO> enrichedCards = cardList.stream()
|
||||
.peek(vo -> {
|
||||
if (vo.getMemberCardType() != null) {
|
||||
try {
|
||||
MemberCardType cardType = MemberCardType.valueOf(vo.getMemberCardType());
|
||||
vo.setMemberCardTypeDesc(cardType.getDesc());
|
||||
} catch (IllegalArgumentException e) {
|
||||
vo.setMemberCardTypeDesc(vo.getMemberCardType());
|
||||
}
|
||||
}
|
||||
if (vo.getMemberCardStatus() != null) {
|
||||
vo.setMemberCardStatusDesc(vo.getMemberCardStatus() == 1 ? "上架" : "下架");
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
memberDetailVO.setMemberCards(enrichedCards);
|
||||
|
||||
long activeCount = enrichedCards.stream()
|
||||
.filter(card -> card.getMemberCardStatus() != null && card.getMemberCardStatus() == 1)
|
||||
.count();
|
||||
memberDetailVO.setActiveCardCount((int) activeCount);
|
||||
memberDetailVO.setInactiveCardCount(enrichedCards.size() - (int) activeCount);
|
||||
|
||||
return memberDetailVO;
|
||||
}
|
||||
)
|
||||
.flatMap(vo -> redisUtil.setWithExpire(cacheKey, vo, CACHE_EXPIRE_SECONDS)
|
||||
.then(Mono.just(vo)));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> adminUpdateMemberInfo(Long memberId, UpdateMemberInfoDto updateDto) {
|
||||
log.info("前台管理端编辑会员信息, memberId: {}", memberId);
|
||||
|
||||
return memberRepository.findById(memberId)
|
||||
.switchIfEmpty(Mono.error(() -> {
|
||||
log.error("会员不存在: memberId={}", memberId);
|
||||
throw new NotFoundException(ErrorCode.NOT_FOUND_USER, "会员不存在");
|
||||
}))
|
||||
.flatMap(member -> {
|
||||
if (updateDto.getNickname() != null) {
|
||||
member.setNickname(HtmlEscapeUtil.escape(updateDto.getNickname()));
|
||||
}
|
||||
if (updateDto.getGender() != null) {
|
||||
member.setGender(updateDto.getGender().getCode());
|
||||
}
|
||||
if (updateDto.getBirthday() != null) {
|
||||
member.setBirthday(updateDto.getBirthday());
|
||||
}
|
||||
if (updateDto.getAvatar() != null) {
|
||||
member.setAvatar(updateDto.getAvatar());
|
||||
}
|
||||
if (updateDto.getAddress() != null) {
|
||||
member.setAddress(HtmlEscapeUtil.escape(updateDto.getAddress()));
|
||||
}
|
||||
|
||||
return memberRepository.save(member);
|
||||
})
|
||||
.flatMap(savedMember -> {
|
||||
memberSyncer.sync(savedMember);
|
||||
return clearMemberCache(memberId)
|
||||
.then(Mono.just(true));
|
||||
})
|
||||
.onErrorResume(e -> {
|
||||
log.error("编辑会员信息失败, memberId: {}, error: {}", memberId, e.getMessage(), e);
|
||||
return Mono.just(false);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Boolean> updateMemberPhone(Long memberId, String encryptedPhone) {
|
||||
return memberRepository.findById(memberId)
|
||||
.flatMap(member -> {
|
||||
@@ -194,7 +323,11 @@ public class MemberServiceImpl implements MemberService {
|
||||
member.setLastLoginAt(LocalDateTime.now());
|
||||
|
||||
return memberRepository.save(member)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.flatMap(savedMember -> {
|
||||
memberSyncer.sync(savedMember);
|
||||
return clearMemberCache(memberId)
|
||||
.then(Mono.just(savedMember));
|
||||
})
|
||||
.map(savedMember -> {
|
||||
log.info("手机号录入成功, memberId: {}", savedMember.getId());
|
||||
return true;
|
||||
@@ -205,4 +338,12 @@ public class MemberServiceImpl implements MemberService {
|
||||
throw new NotFoundException(ErrorCode.NOT_FOUND_USER, "会员不存在");
|
||||
}));
|
||||
}
|
||||
|
||||
private Mono<Long> clearMemberCache(Long memberId) {
|
||||
String infoCacheKey = MEMBER_INFO_CACHE_PREFIX + memberId;
|
||||
String detailCacheKey = MEMBER_DETAIL_CACHE_PREFIX + memberId;
|
||||
return redisUtil.delete(infoCacheKey)
|
||||
.then(redisUtil.delete(detailCacheKey))
|
||||
.doOnSuccess(result -> log.debug("清除会员缓存, memberId: {}", memberId));
|
||||
}
|
||||
}
|
||||
+52
-15
@@ -1,9 +1,11 @@
|
||||
package cn.novalon.gym.manage.member.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.util.HtmlEscapeUtil;
|
||||
import cn.novalon.gym.manage.member.entity.RefundApplication;
|
||||
import cn.novalon.gym.manage.member.enums.RefundStatus;
|
||||
import cn.novalon.gym.manage.member.repository.RefundApplicationRepository;
|
||||
import cn.novalon.gym.manage.member.service.IRefundApplicationService;
|
||||
import cn.novalon.gym.manage.member.util.RedisUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -21,9 +23,14 @@ import java.time.LocalDateTime;
|
||||
public class RefundApplicationServiceImpl implements IRefundApplicationService {
|
||||
|
||||
private final RefundApplicationRepository refundApplicationRepository;
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
public RefundApplicationServiceImpl(RefundApplicationRepository refundApplicationRepository) {
|
||||
private static final String REFUND_APPLICATION_CACHE_PREFIX = "member:refund:";
|
||||
private static final long CACHE_EXPIRE_SECONDS = 300;
|
||||
|
||||
public RefundApplicationServiceImpl(RefundApplicationRepository refundApplicationRepository, RedisUtil redisUtil) {
|
||||
this.refundApplicationRepository = refundApplicationRepository;
|
||||
this.redisUtil = redisUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -38,14 +45,16 @@ public class RefundApplicationServiceImpl implements IRefundApplicationService {
|
||||
.then(Mono.defer(() -> {
|
||||
RefundApplication application = RefundApplication.builder()
|
||||
.recordId(recordId)
|
||||
.reason(reason)
|
||||
.reason(HtmlEscapeUtil.escape(reason))
|
||||
.status(RefundStatus.PENDING)
|
||||
.applyTime(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
return refundApplicationRepository.save(application)
|
||||
.doOnSuccess(app -> log.info("创建退款申请成功: applicationId={}, recordId={}",
|
||||
app.getId(), recordId));
|
||||
.doOnSuccess(app -> {
|
||||
log.info("创建退款申请成功: applicationId={}, recordId={}", app.getId(), recordId);
|
||||
clearRefundCache(recordId);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -54,14 +63,19 @@ public class RefundApplicationServiceImpl implements IRefundApplicationService {
|
||||
return refundApplicationRepository.findById(applicationId)
|
||||
.switchIfEmpty(Mono.error(new RuntimeException("退款申请不存在")))
|
||||
.flatMap(application -> {
|
||||
if (!"PENDING".equals(application.getStatus())) {
|
||||
if (application.getStatus() != RefundStatus.PENDING) {
|
||||
return Mono.error(new RuntimeException("退款申请状态不正确,当前状态: " + application.getStatus()));
|
||||
}
|
||||
|
||||
return refundApplicationRepository.approve(applicationId, "APPROVED", auditorId, remark)
|
||||
.thenReturn(application)
|
||||
.doOnSuccess(app -> log.info("批准退款申请成功: applicationId={}, auditorId={}",
|
||||
applicationId, auditorId));
|
||||
return refundApplicationRepository.approve(applicationId, "APPROVED", auditorId, HtmlEscapeUtil.escape(remark))
|
||||
.flatMap(updatedRows -> {
|
||||
if (updatedRows == 0) {
|
||||
return Mono.error(new RuntimeException("批准退款申请失败"));
|
||||
}
|
||||
clearRefundCache(application.getRecordId());
|
||||
log.info("批准退款申请成功: applicationId={}, auditorId={}", applicationId, auditorId);
|
||||
return refundApplicationRepository.findById(applicationId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,19 +84,42 @@ public class RefundApplicationServiceImpl implements IRefundApplicationService {
|
||||
return refundApplicationRepository.findById(applicationId)
|
||||
.switchIfEmpty(Mono.error(new RuntimeException("退款申请不存在")))
|
||||
.flatMap(application -> {
|
||||
if (!"PENDING".equals(application.getStatus())) {
|
||||
if (application.getStatus() != RefundStatus.PENDING) {
|
||||
return Mono.error(new RuntimeException("退款申请状态不正确,当前状态: " + application.getStatus()));
|
||||
}
|
||||
|
||||
return refundApplicationRepository.approve(applicationId, "REJECTED", auditorId, remark)
|
||||
.thenReturn(application)
|
||||
.doOnSuccess(app -> log.info("拒绝退款申请成功: applicationId={}, auditorId={}",
|
||||
applicationId, auditorId));
|
||||
return refundApplicationRepository.approve(applicationId, "REJECTED", auditorId, HtmlEscapeUtil.escape(remark))
|
||||
.flatMap(updatedRows -> {
|
||||
if (updatedRows == 0) {
|
||||
return Mono.error(new RuntimeException("拒绝退款申请失败"));
|
||||
}
|
||||
clearRefundCache(application.getRecordId());
|
||||
log.info("拒绝退款申请成功: applicationId={}, auditorId={}", applicationId, auditorId);
|
||||
return refundApplicationRepository.findById(applicationId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<RefundApplication> findByRecordId(Long recordId) {
|
||||
return refundApplicationRepository.findByRecordId(recordId);
|
||||
String cacheKey = REFUND_APPLICATION_CACHE_PREFIX + recordId;
|
||||
Object cached = redisUtil.get(cacheKey);
|
||||
if (cached != null && cached instanceof RefundApplication) {
|
||||
log.debug("从缓存获取退款申请, recordId: {}", recordId);
|
||||
return Mono.just((RefundApplication) cached);
|
||||
}
|
||||
|
||||
return refundApplicationRepository.findByRecordId(recordId)
|
||||
.doOnSuccess(application -> {
|
||||
if (application != null) {
|
||||
redisUtil.setWithExpire(cacheKey, application, CACHE_EXPIRE_SECONDS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void clearRefundCache(Long recordId) {
|
||||
String cacheKey = REFUND_APPLICATION_CACHE_PREFIX + recordId;
|
||||
redisUtil.delete(cacheKey);
|
||||
log.debug("清除退款申请缓存, recordId: {}", recordId);
|
||||
}
|
||||
}
|
||||
|
||||
+18
-2
@@ -4,6 +4,7 @@ import cn.novalon.gym.manage.common.exception.ErrorCode;
|
||||
import cn.novalon.gym.manage.common.exception.SystemException;
|
||||
import cn.novalon.gym.manage.member.config.WechatProperties;
|
||||
import cn.novalon.gym.manage.member.service.WechatApiService;
|
||||
import cn.novalon.gym.manage.member.util.RedisUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -30,6 +31,10 @@ import java.util.Map;
|
||||
public class WechatApiServiceImpl implements WechatApiService {
|
||||
|
||||
private final WechatProperties wechatProperties;
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
private static final String ACCESS_TOKEN_CACHE_PREFIX = "wechat:access_token:";
|
||||
private static final long ACCESS_TOKEN_EXPIRE_SECONDS = 7000; // 比官方过期时间短100秒
|
||||
|
||||
private final WebClient webClient = WebClient.builder()
|
||||
.baseUrl("https://api.weixin.qq.com")
|
||||
@@ -147,6 +152,15 @@ public class WechatApiServiceImpl implements WechatApiService {
|
||||
public Mono<String> getAccessToken(String appType) {
|
||||
log.debug("获取access_token, appType: {}", appType);
|
||||
|
||||
String cacheKey = ACCESS_TOKEN_CACHE_PREFIX + appType;
|
||||
|
||||
return redisUtil.get(cacheKey, String.class)
|
||||
.flatMap(cachedToken -> {
|
||||
if (cachedToken != null) {
|
||||
log.debug("从缓存获取access_token, appType: {}", appType);
|
||||
return Mono.just(cachedToken);
|
||||
}
|
||||
|
||||
String appId, appSecret;
|
||||
if ("miniapp".equals(appType)) {
|
||||
appId = wechatProperties.getMiniapp().getAppId();
|
||||
@@ -165,18 +179,20 @@ public class WechatApiServiceImpl implements WechatApiService {
|
||||
.build())
|
||||
.retrieve()
|
||||
.bodyToMono(Map.class)
|
||||
.map(response -> {
|
||||
.flatMap(response -> {
|
||||
if (response.containsKey("access_token")) {
|
||||
String accessToken = (String) response.get("access_token");
|
||||
Integer expiresIn = (Integer) response.get("expires_in");
|
||||
log.info("获取access_token成功, expires_in: {}s", expiresIn);
|
||||
return accessToken;
|
||||
return redisUtil.setWithExpire(cacheKey, accessToken, ACCESS_TOKEN_EXPIRE_SECONDS)
|
||||
.then(Mono.just(accessToken));
|
||||
} else {
|
||||
String errmsg = (String) response.get("errmsg");
|
||||
log.error("获取access_token失败: {}", errmsg);
|
||||
throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "获取access_token失败: " + errmsg);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+34
-13
@@ -4,6 +4,7 @@ import cn.novalon.gym.manage.common.exception.ConflictException;
|
||||
import cn.novalon.gym.manage.common.exception.ErrorCode;
|
||||
import cn.novalon.gym.manage.common.exception.NotFoundException;
|
||||
import cn.novalon.gym.manage.common.exception.SystemException;
|
||||
import cn.novalon.gym.manage.common.util.HtmlEscapeUtil;
|
||||
import cn.novalon.gym.manage.member.config.WechatProperties;
|
||||
import cn.novalon.gym.manage.member.dto.WechatLoginDto;
|
||||
import cn.novalon.gym.manage.member.entity.Member;
|
||||
@@ -15,6 +16,7 @@ import cn.novalon.gym.manage.member.service.WechatAuthService;
|
||||
import cn.novalon.gym.manage.member.util.AesUtil;
|
||||
import cn.novalon.gym.manage.member.util.EsSyncUtils;
|
||||
import cn.novalon.gym.manage.member.util.MemberNoGenerator;
|
||||
import cn.novalon.gym.manage.member.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.member.util.WechatPhoneUtil;
|
||||
import cn.novalon.gym.manage.member.vo.WechatLoginVO;
|
||||
import cn.novalon.gym.manage.sys.security.JwtTokenProvider;
|
||||
@@ -42,14 +44,17 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
|
||||
private final WechatApiService wechatApiService;
|
||||
private final IMemberRepository memberRepository;
|
||||
private final WechatProperties wechatProperties;
|
||||
private final WechatPhoneUtil wechatPhoneUtil;
|
||||
private final MemberESRepository memberESRepository;
|
||||
private final EsSyncUtils esSyncUtils;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
private EsSyncUtils.EntitySyncer<Member, MemberES, String> memberSyncer;
|
||||
|
||||
private static final String MEMBER_INFO_CACHE_PREFIX = "member:info:";
|
||||
private static final long CACHE_EXPIRE_SECONDS = 300;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
this.memberSyncer = esSyncUtils.bind(Member.class, MemberES.class, memberESRepository);
|
||||
@@ -79,7 +84,10 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
member.setLastLoginAt(LocalDateTime.now());
|
||||
|
||||
return memberRepository.save(member)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.doOnSuccess(saved -> {
|
||||
memberSyncer.sync(saved);
|
||||
clearMemberCache(saved.getId());
|
||||
})
|
||||
.flatMap(savedMember -> {
|
||||
WechatLoginVO response = buildLoginResponse(savedMember, false, sessionKey);
|
||||
return Mono.just(response);
|
||||
@@ -89,7 +97,10 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
member.setLastLoginAt(LocalDateTime.now());
|
||||
|
||||
return memberRepository.save(member)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.doOnSuccess(saved -> {
|
||||
memberSyncer.sync(saved);
|
||||
clearMemberCache(saved.getId());
|
||||
})
|
||||
.flatMap(savedMember -> {
|
||||
WechatLoginVO response = buildLoginResponse(savedMember, false, sessionKey);
|
||||
return Mono.just(response);
|
||||
@@ -105,7 +116,10 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
member.setLastLoginAt(LocalDateTime.now());
|
||||
|
||||
return memberRepository.save(member)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.doOnSuccess(saved -> {
|
||||
memberSyncer.sync(saved);
|
||||
clearMemberCache(saved.getId());
|
||||
})
|
||||
.flatMap(savedMember -> {
|
||||
WechatLoginVO response = buildLoginResponse(savedMember, false, sessionKey);
|
||||
return Mono.just(response);
|
||||
@@ -124,7 +138,10 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
member.setLastLoginAt(LocalDateTime.now());
|
||||
|
||||
return memberRepository.save(member)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.doOnSuccess(saved -> {
|
||||
memberSyncer.sync(saved);
|
||||
clearMemberCache(saved.getId());
|
||||
})
|
||||
.flatMap(savedMember -> {
|
||||
WechatLoginVO response = buildLoginResponse(savedMember, false, sessionKey);
|
||||
return Mono.just(response);
|
||||
@@ -185,7 +202,10 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
member.setPhone(encryptedPhone);
|
||||
member.setLastLoginAt(LocalDateTime.now());
|
||||
return memberRepository.save(member)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.doOnSuccess(saved -> {
|
||||
memberSyncer.sync(saved);
|
||||
clearMemberCache(saved.getId());
|
||||
})
|
||||
.map(savedMember -> {
|
||||
log.info("更新会员手机号成功, memberId: {}", savedMember.getId());
|
||||
return true;
|
||||
@@ -197,12 +217,15 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
}));
|
||||
}
|
||||
|
||||
private void clearMemberCache(Long memberId) {
|
||||
String cacheKey = MEMBER_INFO_CACHE_PREFIX + memberId;
|
||||
redisUtil.delete(cacheKey);
|
||||
log.debug("清除会员缓存, memberId: {}", memberId);
|
||||
}
|
||||
|
||||
private String encryptPhone(String phoneNumber) {
|
||||
try {
|
||||
String secretKey = wechatProperties.getPhoneEncryption().getSecretKey();
|
||||
String iv = wechatProperties.getPhoneEncryption().getIv();
|
||||
|
||||
String encryptedPhone = AesUtil.encrypt(phoneNumber, secretKey, iv);
|
||||
String encryptedPhone = AesUtil.encrypt(phoneNumber);
|
||||
|
||||
log.debug("手机号加密成功");
|
||||
return encryptedPhone;
|
||||
@@ -214,10 +237,8 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
|
||||
public String decryptPhone(String encryptedPhone) {
|
||||
try {
|
||||
String secretKey = wechatProperties.getPhoneEncryption().getSecretKey();
|
||||
String iv = wechatProperties.getPhoneEncryption().getIv();
|
||||
|
||||
String phoneNumber = AesUtil.decrypt(encryptedPhone, secretKey, iv);
|
||||
String phoneNumber = AesUtil.decrypt(encryptedPhone);
|
||||
|
||||
log.debug("手机号解密成功");
|
||||
return phoneNumber;
|
||||
|
||||
+55
-14
@@ -1,5 +1,6 @@
|
||||
package cn.novalon.gym.manage.member.service.impl;
|
||||
|
||||
import cn.novalon.gym.manage.common.util.HtmlEscapeUtil;
|
||||
import cn.novalon.gym.manage.member.config.WechatProperties;
|
||||
import cn.novalon.gym.manage.member.entity.Member;
|
||||
import cn.novalon.gym.manage.member.es.entity.MemberES;
|
||||
@@ -7,6 +8,7 @@ import cn.novalon.gym.manage.member.es.repository.MemberESRepository;
|
||||
import cn.novalon.gym.manage.member.repository.IMemberRepository;
|
||||
import cn.novalon.gym.manage.member.service.WechatOfficialService;
|
||||
import cn.novalon.gym.manage.member.util.EsSyncUtils;
|
||||
import cn.novalon.gym.manage.member.util.RedisUtil;
|
||||
import cn.novalon.gym.manage.member.vo.WechatUserInfoVO;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -40,11 +42,16 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
|
||||
private final MemberESRepository memberESRepository;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper()
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
private final EsSyncUtils esSyncUtils;
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
private EsSyncUtils.EntitySyncer<Member, MemberES, String> memberSyncer;
|
||||
|
||||
private static final String ACCESS_TOKEN_CACHE_PREFIX = "wechat:access_token:";
|
||||
private static final String MEMBER_INFO_CACHE_PREFIX = "member:info:";
|
||||
private static final long ACCESS_TOKEN_EXPIRE_SECONDS = 7000;
|
||||
private static final long CACHE_EXPIRE_SECONDS = 300;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
this.memberSyncer = esSyncUtils.bind(Member.class, MemberES.class, memberESRepository);
|
||||
@@ -74,24 +81,30 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
|
||||
existingMember.setOfficialOpenId(openId);
|
||||
|
||||
if (existingMember.getNickname() == null || existingMember.getNickname().isEmpty()) {
|
||||
existingMember.setNickname(userInfo.getNickname());
|
||||
existingMember.setNickname(HtmlEscapeUtil.escape(userInfo.getNickname()));
|
||||
}
|
||||
|
||||
if (existingMember.getAvatar() == null || existingMember.getAvatar().isEmpty()) {
|
||||
existingMember.setAvatar(userInfo.getHeadimgurl());
|
||||
existingMember.setAvatar(HtmlEscapeUtil.escape(userInfo.getHeadimgurl()));
|
||||
}
|
||||
|
||||
return memberRepository.save(existingMember)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.flatMap(saved -> {
|
||||
memberSyncer.sync(saved);
|
||||
return clearMemberCache(saved.getId())
|
||||
.then(sendWelcomeMessage(openId));
|
||||
});
|
||||
} else {
|
||||
log.info("老用户关注服务号: memberId={}", existingMember.getId());
|
||||
existingMember.setSubscribed(true);
|
||||
existingMember.setLastLoginAt(LocalDateTime.now());
|
||||
|
||||
return memberRepository.save(existingMember)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.flatMap(saved -> {
|
||||
memberSyncer.sync(saved);
|
||||
return clearMemberCache(saved.getId())
|
||||
.then(sendWelcomeMessage(openId));
|
||||
});
|
||||
}
|
||||
})
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
@@ -104,7 +117,10 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
|
||||
existingMember.setLastLoginAt(LocalDateTime.now());
|
||||
|
||||
return memberRepository.save(existingMember)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.doOnSuccess(saved -> {
|
||||
memberSyncer.sync(saved);
|
||||
clearMemberCache(saved.getId());
|
||||
})
|
||||
.then(sendWelcomeMessage(openId));
|
||||
})
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
@@ -122,7 +138,10 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
|
||||
existingMember.setLastLoginAt(LocalDateTime.now());
|
||||
|
||||
return memberRepository.save(existingMember)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.doOnSuccess(saved -> {
|
||||
memberSyncer.sync(saved);
|
||||
clearMemberCache(saved.getId());
|
||||
})
|
||||
.then(sendWelcomeMessage(openId));
|
||||
})
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
@@ -148,8 +167,10 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
|
||||
member.setSubscribed(false);
|
||||
member.setLastLoginAt(LocalDateTime.now());
|
||||
return memberRepository.save(member)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.then();
|
||||
.flatMap(saved -> {
|
||||
memberSyncer.sync(saved);
|
||||
return clearMemberCache(saved.getId()).then();
|
||||
});
|
||||
})
|
||||
.then()
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
@@ -213,7 +234,11 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
|
||||
member.setOfficialOpenId(officialOpenId);
|
||||
}
|
||||
return memberRepository.save(member)
|
||||
.doOnSuccess(memberSyncer::sync)
|
||||
.flatMap(saved -> {
|
||||
memberSyncer.sync(saved);
|
||||
return clearMemberCache(saved.getId())
|
||||
.then(Mono.just(saved));
|
||||
})
|
||||
.map(savedMember -> {
|
||||
log.info("关联成功, memberId: {}", savedMember.getId());
|
||||
return true;
|
||||
@@ -268,10 +293,17 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
|
||||
|
||||
/**
|
||||
* 获取微信AccessToken
|
||||
*
|
||||
* TODO: 应该使用缓存,避免频繁请求
|
||||
*/
|
||||
private Mono<String> getAccessToken() {
|
||||
String cacheKey = ACCESS_TOKEN_CACHE_PREFIX + "mp";
|
||||
|
||||
return redisUtil.get(cacheKey, String.class)
|
||||
.flatMap(cachedToken -> {
|
||||
if (cachedToken != null) {
|
||||
log.debug("从缓存获取服务号access_token");
|
||||
return Mono.just(cachedToken);
|
||||
}
|
||||
|
||||
String appId = wechatProperties.getMp().getAppId();
|
||||
String appSecret = wechatProperties.getMp().getAppSecret();
|
||||
|
||||
@@ -285,11 +317,14 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.retrieve()
|
||||
.bodyToMono(Map.class)
|
||||
.map(response -> {
|
||||
.flatMap(response -> {
|
||||
if (response.containsKey("errcode")) {
|
||||
throw new RuntimeException("获取AccessToken失败: " + response.get("errmsg"));
|
||||
}
|
||||
return (String) response.get("access_token");
|
||||
String accessToken = (String) response.get("access_token");
|
||||
return redisUtil.setWithExpire(cacheKey, accessToken, ACCESS_TOKEN_EXPIRE_SECONDS)
|
||||
.then(Mono.just(accessToken));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -335,4 +370,10 @@ public class WechatOfficialServiceImpl implements WechatOfficialService {
|
||||
return Mono.empty(); // 即使发送失败也不影响主流程
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Long> clearMemberCache(Long memberId) {
|
||||
String cacheKey = MEMBER_INFO_CACHE_PREFIX + memberId;
|
||||
return redisUtil.delete(cacheKey)
|
||||
.doOnSuccess(result -> log.debug("清除会员缓存, memberId: {}", memberId));
|
||||
}
|
||||
}
|
||||
|
||||
+31
-10
@@ -1,12 +1,24 @@
|
||||
package cn.novalon.gym.manage.member.util;
|
||||
|
||||
import cn.novalon.gym.manage.member.config.WechatProperties;
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
import co.elastic.clients.elasticsearch.core.IndexResponse;
|
||||
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||
import co.elastic.clients.transport.rest_client.RestClientTransport;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.checkerframework.checker.units.qual.K;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AES加密工具类
|
||||
@@ -16,24 +28,35 @@ import java.util.Base64;
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AesUtil {
|
||||
|
||||
private static final String ALGORITHM = "AES";
|
||||
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
|
||||
|
||||
private static String KEY;
|
||||
private static String IV;
|
||||
|
||||
@Autowired
|
||||
public void setWechatProperties(WechatProperties props) {
|
||||
KEY = props.getPhoneEncryption().getSecretKey(); // 从配置类读取
|
||||
IV = props.getPhoneEncryption().getIv();
|
||||
if(KEY == null || IV == null) throw new RuntimeException("请配置AES密钥和偏移量");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* AES 解密
|
||||
*
|
||||
* @param encryptedData 加密数据,Base64编码
|
||||
* @param key AES密钥,Base64编码(32字节)
|
||||
* @param iv 初始化向量IV,Base64编码(16字节)
|
||||
* @return 解密后的字符串
|
||||
*/
|
||||
public static String decrypt(String encryptedData, String key, String iv) {
|
||||
public static String decrypt(String encryptedData) {
|
||||
|
||||
try {
|
||||
byte[] dataByte = Base64.getDecoder().decode(encryptedData);
|
||||
byte[] keyByte = Base64.getDecoder().decode(key);
|
||||
byte[] ivByte = Base64.getDecoder().decode(iv);
|
||||
byte[] keyByte = Base64.getDecoder().decode(KEY);
|
||||
byte[] ivByte = Base64.getDecoder().decode(IV);
|
||||
|
||||
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(keyByte, ALGORITHM);
|
||||
@@ -52,15 +75,13 @@ public class AesUtil {
|
||||
* AES 加密
|
||||
*
|
||||
* @param data 原始数据
|
||||
* @param key AES密钥,Base64编码(32字节)
|
||||
* @param iv 初始化向量IV,Base64编码(16字节)
|
||||
* @return Base64编码的加密数据
|
||||
*/
|
||||
public static String encrypt(String data, String key, String iv) {
|
||||
public static String encrypt(String data) {
|
||||
try {
|
||||
byte[] dataByte = data.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] keyByte = Base64.getDecoder().decode(key);
|
||||
byte[] ivByte = Base64.getDecoder().decode(iv);
|
||||
byte[] keyByte = Base64.getDecoder().decode(KEY);
|
||||
byte[] ivByte = Base64.getDecoder().decode(IV);
|
||||
|
||||
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(keyByte, ALGORITHM);
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ public class WechatPhoneUtil {
|
||||
* @param phone 手机号
|
||||
* @return 脱敏后的手机号,如:138****8000
|
||||
*/
|
||||
private String maskPhone(String phone) {
|
||||
public static String maskPhone(String phone) {
|
||||
if (phone == null || phone.length() < 7) {
|
||||
return "***";
|
||||
}
|
||||
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package cn.novalon.gym.manage.member.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 会员卡类型响应 VO
|
||||
*
|
||||
* @author 付嘉
|
||||
* @date 2026-05-27
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MemberCardInfoVO {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 会员卡ID
|
||||
*/
|
||||
private Long memberCardId;
|
||||
|
||||
/**
|
||||
* 会员卡名称
|
||||
*/
|
||||
private String memberCardName;
|
||||
|
||||
/**
|
||||
* 会员卡类型:TIME_CARD-时长卡, COUNT_CARD-次卡, STORED_VALUE_CARD-储值卡
|
||||
*/
|
||||
private String memberCardType;
|
||||
|
||||
/**
|
||||
* 卡类型描述
|
||||
*/
|
||||
private String memberCardTypeDesc;
|
||||
|
||||
/**
|
||||
* 会员卡价格
|
||||
*/
|
||||
private BigDecimal memberCardPrice;
|
||||
|
||||
/**
|
||||
* 有效天数(时长卡用)
|
||||
*/
|
||||
private Integer memberCardValidityDays;
|
||||
|
||||
/**
|
||||
* 总次数(次卡用)
|
||||
*/
|
||||
private Integer memberCardTotalTimes;
|
||||
|
||||
/**
|
||||
* 面额(储值卡用)
|
||||
*/
|
||||
private BigDecimal memberCardAmount;
|
||||
|
||||
/**
|
||||
* 状态:0-下架, 1-上架
|
||||
*/
|
||||
private Integer memberCardStatus;
|
||||
|
||||
/**
|
||||
* 状态描述
|
||||
*/
|
||||
private String memberCardStatusDesc;
|
||||
|
||||
/**
|
||||
* 扩展配置(JSON格式)
|
||||
*/
|
||||
private String extraConfig;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package cn.novalon.gym.manage.member.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 会员详情 VO(管理端使用)
|
||||
*
|
||||
* @author 付嘉
|
||||
* @date 2026-05-27
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MemberDetailVO {
|
||||
|
||||
// ==================== 会员基础信息 ====================
|
||||
|
||||
/**
|
||||
* 会员ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 会员编号
|
||||
*/
|
||||
private String memberNo;
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 手机号(脱敏显示)
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 性别描述
|
||||
*/
|
||||
private String genderDesc;
|
||||
|
||||
/**
|
||||
* 生日
|
||||
*/
|
||||
private Date birthday;
|
||||
|
||||
/**
|
||||
* 地址
|
||||
*/
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 头像URL
|
||||
*/
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 是否关注服务号
|
||||
*/
|
||||
private Boolean subscribed;
|
||||
|
||||
/**
|
||||
* 最后登录时间
|
||||
*/
|
||||
private LocalDateTime lastLoginAt;
|
||||
|
||||
/**
|
||||
* 注册时间
|
||||
*/
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
// ==================== 会员卡信息 ====================
|
||||
|
||||
/**
|
||||
* 会员持有的卡列表
|
||||
*/
|
||||
private List<MemberCardInfoVO> memberCards;
|
||||
|
||||
/**
|
||||
* 有效会员卡数量
|
||||
*/
|
||||
private Integer activeCardCount;
|
||||
|
||||
/**
|
||||
* 过期/用完会员卡数量
|
||||
*/
|
||||
private Integer inactiveCardCount;
|
||||
}
|
||||
+7
-2
@@ -1,10 +1,12 @@
|
||||
package cn.novalon.gym.manage.member.vo;
|
||||
|
||||
import cn.novalon.gym.manage.member.enums.GenderEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@@ -30,10 +32,13 @@ public class MemberInfoVO {
|
||||
private String phone;
|
||||
|
||||
// 性别
|
||||
private Integer gender;
|
||||
private GenderEnum gender;
|
||||
|
||||
// 性别描述
|
||||
private String genderDesc;
|
||||
|
||||
// 生日
|
||||
private Date birthday;
|
||||
private LocalDate birthday;
|
||||
|
||||
// 头像
|
||||
private String avatar;
|
||||
|
||||
@@ -17,7 +17,7 @@ CREATE TABLE IF NOT EXISTS member_user (
|
||||
nickname VARCHAR(100), -- 昵称
|
||||
phone VARCHAR(255), -- 手机号(AES加密存储)
|
||||
gender INTEGER DEFAULT 0, -- 性别:0-未知,1-男,2-女
|
||||
birthday TIMESTAMP, -- 生日
|
||||
birthday DATE, -- 生日
|
||||
address VARCHAR(500), -- 地址
|
||||
avatar VARCHAR(500), -- 头像URL
|
||||
subscribed BOOLEAN DEFAULT FALSE, -- 是否关注服务号
|
||||
|
||||
@@ -2,21 +2,23 @@
|
||||
wechat:
|
||||
# Mock模式:true=使用模拟数据(开发测试),false=调用真实微信API(生产环境)
|
||||
mock-enabled: false
|
||||
|
||||
miniapp:
|
||||
app-id: wx4d480112b426100b
|
||||
app-secret: 78548f0c0ff66c73d3e8b071897eb1e5
|
||||
app-id: ${WECHAT_MINIAPP_APP_ID}
|
||||
app-secret: ${WECHAT_MINIAPP_SECRET}
|
||||
|
||||
mp:
|
||||
app-id: wx6f138c9aacc8a0e8
|
||||
app-secret: 5df2e315e9268e96a43bb2cce1d2270b
|
||||
token: test_token
|
||||
aes-key: ${WECHAT_MP_AESKEY:test_aes_key}
|
||||
# 服务器回调地址(微信服务器推送事件的URL)
|
||||
callback-url: https://1me240209tk74.vicp.fun/api/member/auth/mp/callback
|
||||
app-id: ${WECHAT_MP_APP_ID}
|
||||
app-secret: ${WECHAT_MP_SECRET}
|
||||
token: ${WECHAT_MP_TOKEN}
|
||||
aes-key: ${WECHAT_MP_AESKEY}
|
||||
callback-url: ${WECHAT_MP_CALLBACK_URL}
|
||||
|
||||
# 手机号加密配置
|
||||
phone-encryption:
|
||||
secret-key: nVnA99iBfyK0IE6SkcUYdVAaVrezyn2sLRdLfkIyWnY=
|
||||
iv: LMpG6Ih9mmfEAALOCeIJBw==
|
||||
secret-key: ${PHONE_ENCRYPTION_SECRET_KEY}
|
||||
iv: ${PHONE_ENCRYPTION_IV}
|
||||
|
||||
spring:
|
||||
elasticsearch:
|
||||
uris: http://localhost:9200 # ES 服务器地址(支持多个,逗号分隔)
|
||||
uris: http://localhost:9200
|
||||
@@ -139,6 +139,10 @@
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
+8
-2
@@ -10,6 +10,8 @@ import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDeta
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
|
||||
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
|
||||
@@ -17,9 +19,13 @@ import java.util.List;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = "cn.novalon.gym.manage", exclude = {
|
||||
ReactiveUserDetailsServiceAutoConfiguration.class })
|
||||
@EnableR2dbcRepositories(basePackages = { "cn.novalon.gym.manage.db.dao",
|
||||
@EnableR2dbcRepositories(basePackages = {
|
||||
"cn.novalon.gym.manage.db.dao",
|
||||
"cn.novalon.gym.manage.sys.audit.repository" ,
|
||||
"cn.novalon.gym.manage.gymmembercard.dao"})
|
||||
"cn.novalon.gym.manage.gymmembercard.dao",
|
||||
"cn.novalon.gym.manage.member.repository"
|
||||
})
|
||||
@EnableReactiveElasticsearchRepositories(basePackages = "cn.novalon.gym.manage.member.es.repository")
|
||||
public class ManageApplication {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ManageApplication.class);
|
||||
|
||||
@@ -15,8 +15,8 @@ spring:
|
||||
- org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
|
||||
r2dbc:
|
||||
url: r2dbc:postgresql://${DB_HOST:localhost}:${DB_PORT:55432}/${DB_NAME:manage_system}
|
||||
username: ${DB_USERNAME:postgres}
|
||||
password: ${DB_PASSWORD:postgres}
|
||||
username: ${DB_USERNAME:novalon}
|
||||
password: ${DB_PASSWORD:novalon123}
|
||||
pool:
|
||||
initial-size: 10
|
||||
max-size: 50
|
||||
@@ -25,8 +25,8 @@ spring:
|
||||
acquire-timeout: 5s
|
||||
datasource:
|
||||
url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:55432}/${DB_NAME:manage_system}
|
||||
username: ${DB_USERNAME:postgres}
|
||||
password: ${DB_PASSWORD:postgres}
|
||||
username: ${DB_USERNAME:novalon}
|
||||
password: ${DB_PASSWORD:novalon123}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
flyway:
|
||||
enabled: false
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package cn.novalon.gym.manage.member.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* Redis 配置类(响应式版本)
|
||||
*
|
||||
* @author 付嘉
|
||||
* @date 2026-05-29
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
/**
|
||||
* 配置 ReactiveRedisTemplate
|
||||
*/
|
||||
@Bean
|
||||
public ReactiveRedisTemplate<String, Object> reactiveRedisTemplate(
|
||||
ReactiveRedisConnectionFactory connectionFactory) {
|
||||
|
||||
// 配置序列化上下文
|
||||
RedisSerializationContext<String, Object> serializationContext =
|
||||
RedisSerializationContext.<String, Object>newSerializationContext()
|
||||
.key(StringRedisSerializer.UTF_8)
|
||||
.value(new GenericJackson2JsonRedisSerializer())
|
||||
.hashKey(StringRedisSerializer.UTF_8)
|
||||
.hashValue(new GenericJackson2JsonRedisSerializer())
|
||||
.build();
|
||||
|
||||
return new ReactiveRedisTemplate<>(connectionFactory, serializationContext);
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package cn.novalon.gym.manage.common.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* HTML 转义工具类
|
||||
* 防止 XSS 注入攻击
|
||||
*
|
||||
* @author 付嘉
|
||||
* @date 2026-05-29
|
||||
*/
|
||||
public class HtmlEscapeUtil {
|
||||
|
||||
private static final Map<Character, String> ESCAPE_MAP = new HashMap<>();
|
||||
private static final Map<String, Character> UNESCAPE_MAP = new HashMap<>();
|
||||
private static final Pattern HTML_PATTERN = Pattern.compile("<[^>]*>");
|
||||
|
||||
static {
|
||||
// HTML 特殊字符转义映射
|
||||
ESCAPE_MAP.put('&', "&");
|
||||
ESCAPE_MAP.put('<', "<");
|
||||
ESCAPE_MAP.put('>', ">");
|
||||
ESCAPE_MAP.put('"', """);
|
||||
ESCAPE_MAP.put('\'', "'");
|
||||
|
||||
// 反向映射
|
||||
UNESCAPE_MAP.put("&", '&');
|
||||
UNESCAPE_MAP.put("<", '<');
|
||||
UNESCAPE_MAP.put(">", '>');
|
||||
UNESCAPE_MAP.put(""", '"');
|
||||
UNESCAPE_MAP.put("'", '\'');
|
||||
}
|
||||
|
||||
/**
|
||||
* 转义 HTML 特殊字符
|
||||
*
|
||||
* @param input 原始字符串
|
||||
* @return 转义后的字符串
|
||||
*/
|
||||
public static String escape(String input) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (char c : input.toCharArray()) {
|
||||
String escaped = ESCAPE_MAP.get(c);
|
||||
if (escaped != null) {
|
||||
result.append(escaped);
|
||||
} else {
|
||||
result.append(c);
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 反转义 HTML 特殊字符
|
||||
*
|
||||
* @param input 转义后的字符串
|
||||
* @return 原始字符串
|
||||
*/
|
||||
public static String unescape(String input) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
String result = input;
|
||||
for (Map.Entry<String, Character> entry : UNESCAPE_MAP.entrySet()) {
|
||||
result = result.replace(entry.getKey(), String.valueOf(entry.getValue()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除所有 HTML 标签
|
||||
*
|
||||
* @param input 原始字符串
|
||||
* @return 移除标签后的字符串
|
||||
*/
|
||||
public static String stripHtmlTags(String input) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return input;
|
||||
}
|
||||
return HTML_PATTERN.matcher(input).replaceAll("");
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全转义(转义 + 移除标签)
|
||||
*
|
||||
* @param input 原始字符串
|
||||
* @return 安全字符串
|
||||
*/
|
||||
public static String sanitize(String input) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return input;
|
||||
}
|
||||
// 先移除 HTML 标签,再转义特殊字符
|
||||
String noTags = stripHtmlTags(input);
|
||||
return escape(noTags);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否包含 HTML 标签
|
||||
*
|
||||
* @param input 原始字符串
|
||||
* @return true-包含, false-不包含
|
||||
*/
|
||||
public static boolean containsHtmlTags(String input) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return HTML_PATTERN.matcher(input).find();
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package cn.novalon.gym.manage.member.util;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Redis 工具类(响应式版本)
|
||||
*
|
||||
* @author liwentao
|
||||
* @date 2026/5/15
|
||||
*/
|
||||
@Component
|
||||
public class RedisUtil {
|
||||
|
||||
@Autowired
|
||||
private ReactiveRedisTemplate<String, Object> reactiveRedisTemplate;
|
||||
|
||||
/**
|
||||
* 设置值
|
||||
*/
|
||||
public Mono<Boolean> set(String key, Object value) {
|
||||
return reactiveRedisTemplate.opsForValue().set(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置值并指定过期时间(秒)
|
||||
*/
|
||||
public Mono<Boolean> setWithExpire(String key, Object value, long timeoutSeconds) {
|
||||
return reactiveRedisTemplate.opsForValue().set(key, value, Duration.ofSeconds(timeoutSeconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取值
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Mono<T> get(String key, Class<T> clazz) {
|
||||
return reactiveRedisTemplate.opsForValue().get(key)
|
||||
.map(obj -> clazz.isInstance(obj) ? (T) obj : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取值(返回 Object)
|
||||
*/
|
||||
public Mono<Object> get(String key) {
|
||||
return reactiveRedisTemplate.opsForValue().get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除key
|
||||
*/
|
||||
public Mono<Long> delete(String key) {
|
||||
return reactiveRedisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断key是否存在
|
||||
*/
|
||||
public Mono<Boolean> hasKey(String key) {
|
||||
return reactiveRedisTemplate.hasKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置过期时间(秒)
|
||||
*/
|
||||
public Mono<Boolean> expire(String key, long timeoutSeconds) {
|
||||
return reactiveRedisTemplate.expire(key, Duration.ofSeconds(timeoutSeconds));
|
||||
}
|
||||
}
|
||||
@@ -222,6 +222,12 @@
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Redis响应式支持(会员卡模块需要) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
unpackage/
|
||||
.hbuilderx/
|
||||
.DS_Store
|
||||
@@ -0,0 +1,28 @@
|
||||
<script>
|
||||
export default {
|
||||
onLaunch: function() {
|
||||
console.log('App Launch')
|
||||
},
|
||||
onShow: function() {
|
||||
console.log('App Show')
|
||||
},
|
||||
onHide: function() {
|
||||
console.log('App Hide')
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import 'common/style/base.css';
|
||||
/*每个页面公共css */
|
||||
.app-container {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
max-width: 430px;
|
||||
margin: 0 auto;
|
||||
background-color: var(--bg-light);
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* ============================================
|
||||
* 健身房管理系统小程序 - 全局配色变量
|
||||
* 主题:活力运动风格
|
||||
* 主色调:深蓝专业 + 活力橙热情
|
||||
* 兼容暗色/浅色模式基础,保证可访问性
|
||||
* ============================================
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* ========== 主品牌色 ========== */
|
||||
--primary-dark: #0B2B4B; /* 深蓝主色 - 用于头部导航栏、重要按钮、品牌标识,体现专业信赖感 */
|
||||
--primary-deep: #1A4A6F; /* 中深蓝色 - 用于hover状态、次级按钮、图标点缀,增加层次感 */
|
||||
--primary-light: #2C6288; /* 浅蓝色(预留)- 用于选中态或辅助背景,保持和谐渐变 */
|
||||
|
||||
/* ========== 强调/行动色 ========== */
|
||||
--accent-orange: #FF6B35; /* 活力橙 - 主要CTA按钮、会员标识、高亮徽章、关键数据,刺激行动力 */
|
||||
--accent-orange-light: #FF8C5A; /* 浅橙色 - hover轻量背景、渐变辅助,带来温暖运动感 */
|
||||
--accent-orange-dark: #E55A2B; /* 深橙色 - 按压状态或重要警告,保持色彩体系完整 */
|
||||
|
||||
/* ========== 背景色系 ========== */
|
||||
--bg-light: #F9FAFE; /* 全局浅灰蓝背景 - 柔和且提升深蓝/橙色的视觉舒适度 */
|
||||
--bg-white: #FFFFFF; /* 纯白卡片背景 - 用于内容卡片、表单区域,提高可读性与层次感 */
|
||||
--bg-gray: #F2F5F9; /* 浅灰辅助背景 - 分割区域或禁用态背景 */
|
||||
|
||||
/* ========== 文本色系 ========== */
|
||||
--text-dark: #1E2A3A; /* 主要文字 - 标题、正文,保证高对比度 */
|
||||
--text-muted: #5E6F8D; /* 辅助文字 - 次要信息、占位符,保持易读性 */
|
||||
--text-light: #8A99B4; /* 更浅文字 - 提示语、时间戳,但需注意与背景对比 */
|
||||
--text-inverse: #FFFFFF; /* 反白文字 - 深色/橙色背景上的文字 */
|
||||
|
||||
/* ========== 边框/分割线 ========== */
|
||||
--border-light: #E9EDF2; /* 浅边框 - 卡片分割、列表边界,细腻柔和 */
|
||||
--border-focus: #FF6B35; /* 聚焦边框 - 输入框选中或强调区域,使用橙色点缀 */
|
||||
|
||||
/* ========== 状态颜色(功能性) ========== */
|
||||
--success-green: #2ECC71; /* 成功绿 - 已完成课程、健康打卡 */
|
||||
--warning-amber: #F39C12; /* 警示橙黄 - 提醒、到期提示 */
|
||||
--error-red: #E74C3C; /* 错误红 - 异常情况或取消预约 */
|
||||
--info-blue: #3498DB; /* 信息蓝 - 提示气泡、帮助文字 */
|
||||
|
||||
/* ========== 渐变色 (提升活力感) ========== */
|
||||
--gradient-orange: linear-gradient(135deg, #FF6B35 0%, #FF8C5A 100%); /* 橙色渐变 - 会员按钮、重要徽章 */
|
||||
--gradient-blue: linear-gradient(135deg, #0B2B4B 0%, #1A4A6F 100%); /* 深蓝渐变 - 头部banner或特别卡片 */
|
||||
--gradient-subtle: linear-gradient(120deg, #F9FAFE 0%, #FFFFFF 100%); /* 微弱渐变 - 增加细节精致度 */
|
||||
|
||||
/* ========== 阴影层级 ========== */
|
||||
--shadow-sm: 0 8px 20px rgba(0, 0, 0, 0.03), 0 2px 6px rgba(0, 0, 0, 0.05); /* 卡片小阴影 轻量浮起 */
|
||||
--shadow-md: 0 12px 28px rgba(0, 0, 0, 0.08); /* 中等阴影 - 弹窗或下拉菜单 */
|
||||
--shadow-lg: 0 20px 35px rgba(0, 0, 0, 0.12); /* 大阴影 - 模态框、悬浮元素 */
|
||||
--shadow-orange-glow: 0 4px 12px rgba(255, 107, 53, 0.25); /* 橙色光晕 - 增强CTA吸引力 */
|
||||
|
||||
/* ========== 圆角规范 (柔和运动风) ========== */
|
||||
--radius-sm: 12px; /* 小组件、标签圆角 */
|
||||
--radius-md: 20px; /* 标准卡片圆角 */
|
||||
--radius-lg: 28px; /* 大容器、头部卡片圆角 */
|
||||
--radius-full: 999px; /* 胶囊按钮、头像完全圆角 */
|
||||
|
||||
/* ========== 布局与间距 ========== */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-sm: 8px;
|
||||
--spacing-md: 16px;
|
||||
--spacing-lg: 24px;
|
||||
--spacing-xl: 32px;
|
||||
|
||||
/* ========== 字体 (移动端优先) ========== */
|
||||
--font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||
--font-size-xs: 0.7rem; /* 辅助标注 */
|
||||
--font-size-sm: 0.8rem; /* 次要文字 */
|
||||
--font-size-base: 0.9rem; /* 正文基准 */
|
||||
--font-size-md: 1rem; /* 小标题 */
|
||||
--font-size-lg: 1.2rem; /* 卡片标题 */
|
||||
--font-size-xl: 1.4rem; /* 大数字/欢迎语 */
|
||||
--font-weight-regular: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-bold: 700;
|
||||
--font-weight-extrabold: 800;
|
||||
}
|
||||
|
||||
/* ========== 暗色模式适配(可选,保持品牌一致性) ========== */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
/* 暗色模式下微调背景与文字,保留品牌色核心 */
|
||||
--bg-light: #121826;
|
||||
--bg-white: #1E2636;
|
||||
--bg-gray: #0F141F;
|
||||
--text-dark: #EDF2F7;
|
||||
--text-muted: #9AA9C1;
|
||||
--border-light: #2A3346;
|
||||
--shadow-sm: 0 8px 20px rgba(0, 0, 0, 0.4);
|
||||
/* 保留主色深蓝与橙色不变,但可适当提高对比 */
|
||||
--primary-dark: #123A5E; /* 亮一点保证深色背景可见度 */
|
||||
--accent-orange: #FF7846; /* 稍微提亮橙色 */
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 辅助类 (方便开发直接复用) ========== */
|
||||
.bg-primary {
|
||||
background-color: var(--primary-dark);
|
||||
}
|
||||
.bg-accent {
|
||||
background-color: var(--accent-orange);
|
||||
}
|
||||
.text-primary {
|
||||
color: var(--primary-dark);
|
||||
}
|
||||
.text-accent {
|
||||
color: var(--accent-orange);
|
||||
}
|
||||
.btn-orange {
|
||||
background: var(--gradient-orange);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: var(--radius-full);
|
||||
padding: 10px 20px;
|
||||
font-weight: var(--font-weight-bold);
|
||||
box-shadow: var(--shadow-orange-glow);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.btn-orange:active {
|
||||
transform: scale(0.97);
|
||||
background: var(--accent-orange-dark);
|
||||
}
|
||||
.card-default {
|
||||
background: var(--bg-white);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-sm);
|
||||
border: 1px solid var(--border-light);
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<!-- 底部导航栏容器 -->
|
||||
<view class="tab-bar">
|
||||
<!-- 导航栏项 -->
|
||||
<view
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
:class="['tab-item', { active: activeTab === index }]"
|
||||
@click="activeTab = index"
|
||||
>
|
||||
<!-- 导航栏图标 -->
|
||||
<image :src="activeTab === index ? tab.iconActive : tab.icon" mode="aspectFit" class="tab-icon" />
|
||||
<!-- 导航栏标签文字 -->
|
||||
<text class="tab-label">{{ tab.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
// 当前激活的导航栏索引
|
||||
const activeTab = ref(0)
|
||||
|
||||
// 导航栏数据列表
|
||||
const tabs = [
|
||||
{
|
||||
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/home.png',
|
||||
iconActive: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/active/home.png',
|
||||
label: '首页',
|
||||
},
|
||||
{
|
||||
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/course.png',
|
||||
iconActive: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/active/course.png',
|
||||
label: '课程'
|
||||
},
|
||||
{
|
||||
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/train.png',
|
||||
iconActive: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/active/train.png',
|
||||
label: '训练' ,
|
||||
},
|
||||
{
|
||||
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/discover.png',
|
||||
iconActive: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/active/discover.png',
|
||||
label: '发现',
|
||||
},
|
||||
{
|
||||
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/profile.png',
|
||||
iconActive: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/tabBar/active/profile.png',
|
||||
label: '我的',
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 底部导航栏容器样式 */
|
||||
.tab-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 120rpx;
|
||||
background: #1A4A6F;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
}
|
||||
|
||||
/* 导航栏项样式 */
|
||||
.tab-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
}
|
||||
|
||||
/* 导航栏图标样式 */
|
||||
.tab-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
/* 导航栏标签文字样式 */
|
||||
.tab-label {
|
||||
font-size: 22rpx;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
/* 导航栏激活状态文字样式 */
|
||||
.tab-item.active .tab-label {
|
||||
color: #f97316;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<!-- 轮播图容器 -->
|
||||
<view class="banner-container">
|
||||
<!-- 轮播图组件 -->
|
||||
<swiper
|
||||
class="banner-swiper"
|
||||
:circular="true"
|
||||
:autoplay="true"
|
||||
:interval="4000"
|
||||
:duration="500"
|
||||
:indicator-dots="false"
|
||||
@change="onSwiperChange"
|
||||
>
|
||||
<!-- 轮播项 -->
|
||||
<swiper-item v-for="(banner, index) in banners" :key="index">
|
||||
<!-- 轮播内容 -->
|
||||
<view class="banner-content">
|
||||
<!-- 轮播图片 -->
|
||||
<image :src="banner.image" mode="aspectFill" class="banner-image" />
|
||||
<!-- 图片遮罩层 -->
|
||||
<view class="banner-overlay"></view>
|
||||
<!-- 轮播文字信息 -->
|
||||
<view class="banner-text">
|
||||
<text class="banner-title">{{ banner.title }}</text>
|
||||
<text class="banner-subtitle">{{ banner.subtitle }}</text>
|
||||
<text class="banner-desc">{{ banner.desc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<!-- 轮播指示器点 -->
|
||||
<view class="banner-dots">
|
||||
<view
|
||||
v-for="(_, index) in banners"
|
||||
:key="index"
|
||||
:class="['dot', { active: currentIndex === index }]"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
// 轮播图数据列表
|
||||
const banners = [
|
||||
{
|
||||
image: 'https://images.unsplash.com/photo-1534438327276-14e5300c3a48?w=800&q=80',
|
||||
title: '突破自我',
|
||||
subtitle: '超越极限',
|
||||
desc: '科学训练 · 遇见更好的自己'
|
||||
},
|
||||
{
|
||||
image: 'https://images.unsplash.com/photo-1517836357463-d25dfeac3438?w=800&q=80',
|
||||
title: '专业指导',
|
||||
subtitle: '高效训练',
|
||||
desc: '私人定制 · 专属健身方案'
|
||||
},
|
||||
{
|
||||
image: 'https://images.unsplash.com/photo-1571019614242-c5c5dee9f50b?w=800&q=80',
|
||||
title: '健康生活',
|
||||
subtitle: '从这里开始',
|
||||
desc: '全方位 · 打造完美体态'
|
||||
}
|
||||
]
|
||||
|
||||
// 当前轮播索引,用于控制指示器激活状态
|
||||
const currentIndex = ref(0)
|
||||
|
||||
// 轮播图切换时的回调函数,更新当前索引
|
||||
const onSwiperChange = (e) => {
|
||||
currentIndex.value = e.detail.current
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 轮播图容器样式 */
|
||||
.banner-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 轮播图组件样式 */
|
||||
.banner-swiper {
|
||||
width: 100%;
|
||||
height: 360rpx;
|
||||
}
|
||||
|
||||
/* 轮播内容容器样式 */
|
||||
.banner-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 轮播图片样式 */
|
||||
.banner-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 图片遮罩层样式,添加渐变效果 */
|
||||
.banner-overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, rgba(11, 43, 75, 0.85) 0%, rgba(26, 74, 111, 0.6) 100%);
|
||||
}
|
||||
|
||||
/* 轮播文字信息容器样式 */
|
||||
.banner-text {
|
||||
position: absolute;
|
||||
left: 32rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 轮播标题样式 */
|
||||
.banner-title {
|
||||
display: block;
|
||||
font-size: 48rpx;
|
||||
font-weight: 800;
|
||||
color: #ffffff;
|
||||
margin-bottom: 8rpx;
|
||||
text-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* 轮播副标题样式 */
|
||||
.banner-subtitle {
|
||||
display: block;
|
||||
font-size: 56rpx;
|
||||
font-weight: 800;
|
||||
color: #f97316;
|
||||
margin-bottom: 16rpx;
|
||||
text-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* 轮播描述文字样式 */
|
||||
.banner-desc {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/* 轮播指示器容器样式 */
|
||||
.banner-dots {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
/* 轮播指示器点样式 */
|
||||
.dot {
|
||||
width: 48rpx;
|
||||
height: 8rpx;
|
||||
border-radius: 9999rpx;
|
||||
background: #d1d5db;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 轮播指示器激活状态样式 */
|
||||
.dot.active {
|
||||
width: 64rpx;
|
||||
background: #f97316;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<!-- 快捷入口容器 -->
|
||||
<view class="quick-entry">
|
||||
<!-- 快捷入口项 -->
|
||||
<view
|
||||
v-for="(item, index) in entries"
|
||||
:key="index"
|
||||
class="entry-item"
|
||||
>
|
||||
<!-- 入口图标容器 -->
|
||||
<view :class="['entry-icon', { accent: item.accent }]">
|
||||
<!-- 入口图标图片 -->
|
||||
<image :src="item.icon" mode="aspectFit" class="icon-img" />
|
||||
</view>
|
||||
<!-- 入口标题 -->
|
||||
<text class="entry-title">{{ item.title }}</text>
|
||||
<!-- 入口描述 -->
|
||||
<text class="entry-desc">{{ item.desc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 快捷入口数据列表
|
||||
const entries = [
|
||||
{
|
||||
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/icons/course.png',
|
||||
title: '找课程',
|
||||
desc: '精品课程',
|
||||
accent: false ,
|
||||
},
|
||||
{
|
||||
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/icons/plan.png',
|
||||
title: '训练计划',
|
||||
desc: '个性定制',
|
||||
accent: true ,
|
||||
},
|
||||
{
|
||||
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/icons/data.png',
|
||||
title: '健身数据',
|
||||
desc: '记录分析',
|
||||
accent: false ,
|
||||
},
|
||||
{
|
||||
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/icons/message.png',
|
||||
title: '消息',
|
||||
desc: '通知消息',
|
||||
accent: true,
|
||||
},
|
||||
{
|
||||
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/icons/checkIn.png',
|
||||
title: '签到',
|
||||
desc: '打卡签到',
|
||||
accent: false,
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 快捷入口容器样式 */
|
||||
.quick-entry {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx 24rpx;
|
||||
background: #ffffff;
|
||||
margin: 24rpx;
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
/* 快捷入口项样式 */
|
||||
.entry-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 入口图标容器样式 */
|
||||
.entry-icon {
|
||||
width: 104rpx;
|
||||
height: 104rpx;
|
||||
border-radius: 20rpx;
|
||||
background: #072A4E;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
/* 入口图标图片样式 */
|
||||
.icon-img {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
}
|
||||
|
||||
/* 入口图标强调色样式(橙色背景) */
|
||||
.entry-icon.accent {
|
||||
background: #FC5A15;
|
||||
}
|
||||
|
||||
/* 入口标题样式 */
|
||||
.entry-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #1a202c;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
/* 入口描述文字样式 */
|
||||
.entry-desc {
|
||||
font-size: 22rpx;
|
||||
color: #94a3b8;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,291 @@
|
||||
<template>
|
||||
<!-- 推荐课程容器 -->
|
||||
<view class="recommend-courses">
|
||||
<!-- 区域标题栏 -->
|
||||
<view class="section-header">
|
||||
<!-- 区域标题 -->
|
||||
<text class="section-title">推荐课程</text>
|
||||
<!-- 查看更多按钮 -->
|
||||
<view class="view-more">
|
||||
<text>查看更多</text>
|
||||
<text class="arrow">
|
||||
<uni-icons type="right" size="20" color="#94a3b8"/>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 课程横向滚动容器 -->
|
||||
<scroll-view class="courses-scroll" scroll-x="true" :show-scrollbar="false">
|
||||
<!-- 课程列表 -->
|
||||
<view class="courses-list">
|
||||
<!-- 课程卡片 -->
|
||||
<view
|
||||
v-for="(course, index) in courses"
|
||||
:key="index"
|
||||
class="course-card"
|
||||
>
|
||||
<!-- 课程图片区域 -->
|
||||
<view class="course-image">
|
||||
<!-- 课程封面图片 -->
|
||||
<image :src="course.image" mode="aspectFill" class="img" />
|
||||
<!-- 图片渐变遮罩 -->
|
||||
<view class="course-overlay"></view>
|
||||
<!-- 课程标签 -->
|
||||
<text :class="['course-tag', course.tagType]">{{ course.tag }}</text>
|
||||
<!-- 课程信息区域 -->
|
||||
<view class="course-info">
|
||||
<!-- 课程名称 -->
|
||||
<text class="course-name">{{ course.name }}</text>
|
||||
<!-- 课程元信息(时长、难度) -->
|
||||
<view class="course-meta">
|
||||
<!-- 时长信息 -->
|
||||
<view class="meta-item">
|
||||
<text class="meta-icon">
|
||||
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/time.png"/>
|
||||
</text>
|
||||
<text>{{ course.duration }}</text>
|
||||
</view>
|
||||
<!-- 难度信息 -->
|
||||
<view class="meta-item">
|
||||
<text class="meta-icon">
|
||||
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/intensity.png"/>
|
||||
</text>
|
||||
<text>{{ course.level }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 课程底部区域 -->
|
||||
<view class="course-footer">
|
||||
<!-- 参与人数信息 -->
|
||||
<view class="participants">
|
||||
<text class="fire-icon">
|
||||
<image src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/hot.png"/>
|
||||
</text>
|
||||
<text>{{ course.participants }}人参与</text>
|
||||
</view>
|
||||
<!-- 去参与按钮 -->
|
||||
<view class="join-btn">
|
||||
<text>去参与</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 推荐课程数据列表
|
||||
const courses = [
|
||||
{
|
||||
image: 'https://images.unsplash.com/photo-1534438327276-14e5300c3a48?w=400&q=80',
|
||||
tag: '限时免费',
|
||||
tagType: 'free',
|
||||
name: 'HIIT高强度燃脂',
|
||||
duration: '30分钟',
|
||||
level: '中级',
|
||||
participants: '4587'
|
||||
},
|
||||
{
|
||||
image: 'https://images.unsplash.com/photo-1583454110551-21f2fa2afe61?w=400&q=80',
|
||||
tag: '人气TOP',
|
||||
tagType: 'hot',
|
||||
name: '力量进阶训练',
|
||||
duration: '45分钟',
|
||||
level: '高级',
|
||||
participants: '6231'
|
||||
},
|
||||
{
|
||||
image: 'https://images.unsplash.com/photo-1544367567-0f2fcb009e0b?w=400&q=80',
|
||||
tag: '新课上线',
|
||||
tagType: 'new',
|
||||
name: '瑜伽·身心平衡',
|
||||
duration: '60分钟',
|
||||
level: '初级',
|
||||
participants: '3210'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* 推荐课程容器样式 */
|
||||
.recommend-courses {
|
||||
padding: 0 24rpx;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
/* 区域标题栏样式 */
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
/* 区域标题样式 */
|
||||
.section-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #1a202c;
|
||||
}
|
||||
|
||||
/* 查看更多按钮样式 */
|
||||
.view-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
font-size: 26rpx;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
/* 箭头图标样式 */
|
||||
.arrow {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
/* 课程横向滚动容器样式 */
|
||||
.courses-scroll {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 课程列表样式 */
|
||||
.courses-list {
|
||||
display: inline-flex;
|
||||
gap: 48rpx;
|
||||
}
|
||||
|
||||
/* 课程卡片样式 */
|
||||
.course-card {
|
||||
width: 320rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* 课程图片区域样式 */
|
||||
.course-image {
|
||||
height: 280rpx;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
/* 课程封面图片样式 */
|
||||
.img {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 图片渐变遮罩样式 */
|
||||
.course-overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(to top, rgba(0,0,0,0.7) 0%, transparent 60%);
|
||||
}
|
||||
|
||||
/* 课程标签样式 */
|
||||
.course-tag {
|
||||
position: absolute;
|
||||
top: 16rpx;
|
||||
right: 16rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
background: #f97316;
|
||||
}
|
||||
|
||||
/* 课程信息区域样式 */
|
||||
.course-info {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 课程名称样式 */
|
||||
.course-name {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
/* 课程元信息容器样式 */
|
||||
.course-meta {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 课程元信息项样式 */
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
gap: 6rpx;
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/* 元信息图标样式 */
|
||||
.meta-icon {
|
||||
font-size: 20rpx;
|
||||
image{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 25rpx;
|
||||
height: 25rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 课程底部区域样式 */
|
||||
.course-footer {
|
||||
padding: 16rpx 10rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 参与人数信息样式 */
|
||||
.participants {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
font-size: 22rpx;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
/* 火热图标样式 */
|
||||
.fire-icon {
|
||||
font-size: 24rpx;
|
||||
image{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 去参与按钮样式 */
|
||||
.join-btn {
|
||||
padding: 12rpx 28rpx;
|
||||
background: transparent;
|
||||
border: 2rpx solid #f97316;
|
||||
border-radius: 9999rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
color: #f97316;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,213 @@
|
||||
<template>
|
||||
<!-- 今日推荐容器 -->
|
||||
<view class="today-recommend">
|
||||
<!-- 区域标题栏 -->
|
||||
<view class="section-header">
|
||||
<!-- 区域标题 -->
|
||||
<text class="section-title">今日推荐</text>
|
||||
<!-- 查看更多按钮 -->
|
||||
<view class="view-more">
|
||||
<text>查看更多</text>
|
||||
<text class="arrow">
|
||||
<uni-icons type="right" size="20" color="#94a3b8"></uni-icons>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 推荐列表 -->
|
||||
<view class="recommend-list">
|
||||
<!-- 推荐项 -->
|
||||
<view
|
||||
v-for="(item, index) in recommends"
|
||||
:key="index"
|
||||
class="recommend-item"
|
||||
>
|
||||
<!-- 推荐项图片 -->
|
||||
<image :src="item.image" mode="aspectFill" class="item-image" />
|
||||
<!-- 推荐项内容区域 -->
|
||||
<view class="item-content">
|
||||
<!-- 推荐项标题 -->
|
||||
<text class="item-title">{{ item.title }}</text>
|
||||
<!-- 推荐项标签列表 -->
|
||||
<view class="item-tags">
|
||||
<text v-for="(tag, tagIndex) in item.tags" :key="tagIndex" class="tag">{{ tag }}</text>
|
||||
</view>
|
||||
<!-- 推荐项描述 -->
|
||||
<text class="item-desc">{{ item.desc }}</text>
|
||||
</view>
|
||||
<!-- 推荐项操作区域 -->
|
||||
<view class="item-action">
|
||||
<!-- 开始训练按钮 -->
|
||||
<view class="start-btn">
|
||||
<text class="start-btn-text">开始训练</text>
|
||||
</view>
|
||||
<!-- 参与人数 -->
|
||||
<text class="participants">{{ item.participants }}人参与</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 今日推荐数据列表
|
||||
const recommends = [
|
||||
{
|
||||
image: 'https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?w=300&q=80',
|
||||
title: '晨间活力唤醒跑',
|
||||
tags: ['20分钟', '初级'],
|
||||
desc: '唤醒身体,开启活力一天',
|
||||
participants: '2784'
|
||||
},
|
||||
{
|
||||
image: 'https://images.unsplash.com/photo-1581009146145-b5ef050c149a?w=300&q=80',
|
||||
title: '全身力量塑形',
|
||||
tags: ['50分钟', '中级'],
|
||||
desc: '全身综合训练,塑造完美线条',
|
||||
participants: '4126'
|
||||
},
|
||||
{
|
||||
image: 'https://images.unsplash.com/photo-1490645935967-10de6ba17061?w=300&q=80',
|
||||
title: '蛋白增肌饮食指南',
|
||||
tags: ['营养饮食', '12分钟'],
|
||||
desc: '科学饮食搭配,助力肌肉增长',
|
||||
participants: '1865'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 今日推荐容器样式 */
|
||||
.today-recommend {
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
/* 区域标题栏样式 */
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
/* 区域标题样式 */
|
||||
.section-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #1a202c;
|
||||
}
|
||||
|
||||
/* 查看更多按钮样式 */
|
||||
.view-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
font-size: 26rpx;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
/* 箭头图标样式 */
|
||||
.arrow {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
/* 推荐列表样式 */
|
||||
.recommend-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
/* 推荐项卡片样式 */
|
||||
.recommend-item {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
/* 推荐项图片样式 */
|
||||
.item-image {
|
||||
width: 200rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 推荐项内容区域样式 */
|
||||
.item-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 推荐项标题样式 */
|
||||
.item-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #1a202c;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
/* 推荐项标签列表样式 */
|
||||
.item-tags {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
/* 推荐项标签样式 */
|
||||
.tag {
|
||||
padding: 6rpx 16rpx;
|
||||
background: #f1f5f9;
|
||||
border-radius: 8rpx;
|
||||
font-size: 22rpx;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* 推荐项描述文字样式 */
|
||||
.item-desc {
|
||||
font-size: 24rpx;
|
||||
color: #94a3b8;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 推荐项操作区域样式 */
|
||||
.item-action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 开始训练按钮样式 */
|
||||
.start-btn {
|
||||
padding: 16rpx 28rpx;
|
||||
background: linear-gradient(135deg, #f97316 0%, #fb923c 100%);
|
||||
border-radius: 9999rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(249, 115, 22, 0.4);
|
||||
}
|
||||
|
||||
/* 开始训练按钮文字样式 */
|
||||
.start-btn-text {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 参与人数文字样式 */
|
||||
.participants {
|
||||
font-size: 22rpx;
|
||||
color: #94a3b8;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,570 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
<title>健身房管理系统 | 动感配色方案</title>
|
||||
<!-- 引入Font Awesome 6 (免费图标库,增强视觉) -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #F5F7FC; /* 整体背景柔和灰白,衬托主色 */
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||
padding: 24px 20px 48px;
|
||||
color: #1E2A3A;
|
||||
}
|
||||
|
||||
/* 主色调变量定义 – 活力运动风格
|
||||
主色: 动感深蓝 (#0B2B4B) – 权威、专业、沉稳
|
||||
辅色: 活力橙 (#FF6B35) – 热情、能量、行动召唤
|
||||
背景浅色: #F9FAFE, 卡片白, 强调蓝绿渐变点缀
|
||||
*/
|
||||
:root {
|
||||
--primary-dark: #0B2B4B; /* 深蓝主色,用于头部、重要按钮、边框强调 */
|
||||
--primary-deep: #1A4A6F; /* 中深蓝,用于hover、次级强调 */
|
||||
--accent-orange: #FF6B35; /* 活力橙,CTA、会员卡片、高亮标签 */
|
||||
--accent-orange-light: #FF8C5A;
|
||||
--bg-light: #F9FAFE;
|
||||
--card-white: #FFFFFF;
|
||||
--text-dark: #1E2A3A;
|
||||
--text-muted: #5E6F8D;
|
||||
--border-light: #E9EDF2;
|
||||
--success-green: #2ECC71;
|
||||
--warning-amber: #F39C12;
|
||||
--shadow-sm: 0 8px 20px rgba(0, 0, 0, 0.03), 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 12px 28px rgba(0, 0, 0, 0.08);
|
||||
--gradient-orange: linear-gradient(135deg, #FF6B35 0%, #FF8C5A 100%);
|
||||
--gradient-blue: linear-gradient(135deg, #0B2B4B 0%, #1A4A6F 100%);
|
||||
}
|
||||
|
||||
/* 整体容器 */
|
||||
.demo-container {
|
||||
max-width: 450px;
|
||||
margin: 0 auto;
|
||||
background: var(--bg-light);
|
||||
border-radius: 36px;
|
||||
box-shadow: var(--shadow-md);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 状态栏模拟 (小程序顶部风格) */
|
||||
.status-bar {
|
||||
background: var(--primary-dark);
|
||||
padding: 12px 20px 6px;
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 主头部导航 */
|
||||
.main-header {
|
||||
background: var(--primary-dark);
|
||||
padding: 12px 20px 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.logo-area h2 {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.logo-area h2 i {
|
||||
color: var(--accent-orange);
|
||||
font-size: 1.7rem;
|
||||
}
|
||||
|
||||
.logo-area p {
|
||||
font-size: 0.7rem;
|
||||
opacity: 0.8;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.header-actions i {
|
||||
font-size: 1.4rem;
|
||||
background: rgba(255,255,255,0.15);
|
||||
padding: 8px;
|
||||
border-radius: 30px;
|
||||
margin-left: 8px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* 欢迎卡片 + 活力橙强调 */
|
||||
.welcome-card {
|
||||
background: white;
|
||||
border-radius: 28px;
|
||||
padding: 18px 20px;
|
||||
margin-top: 12px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.welcome-text h4 {
|
||||
font-size: 1rem;
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.welcome-text h3 {
|
||||
font-size: 1.4rem;
|
||||
margin-top: 4px;
|
||||
color: var(--primary-dark);
|
||||
}
|
||||
|
||||
.badge-member {
|
||||
background: var(--gradient-orange);
|
||||
padding: 8px 16px;
|
||||
border-radius: 40px;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
box-shadow: 0 4px 8px rgba(255,107,53,0.2);
|
||||
}
|
||||
|
||||
/* 主要指标卡片区 */
|
||||
.stats-grid {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 20px;
|
||||
background: var(--bg-light);
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--card-white);
|
||||
flex: 1;
|
||||
border-radius: 24px;
|
||||
padding: 14px 0;
|
||||
text-align: center;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: all 0.2s;
|
||||
border: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.stat-card i {
|
||||
font-size: 1.8rem;
|
||||
color: var(--accent-orange);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 800;
|
||||
color: var(--primary-dark);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* 功能菜单 grid */
|
||||
.menu-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 12px;
|
||||
padding: 8px 20px 20px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
background: var(--card-white);
|
||||
border-radius: 20px;
|
||||
padding: 12px 6px;
|
||||
text-align: center;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid var(--border-light);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.menu-item i {
|
||||
font-size: 1.8rem;
|
||||
color: var(--primary-deep);
|
||||
margin-bottom: 6px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.menu-item span {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.menu-item:active {
|
||||
transform: scale(0.96);
|
||||
background: #FEF5F0;
|
||||
}
|
||||
|
||||
/* 课程/热门活动区域 */
|
||||
.section-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px 20px 4px;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.section-title h3 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary-dark);
|
||||
}
|
||||
|
||||
.section-title a {
|
||||
font-size: 0.75rem;
|
||||
color: var(--accent-orange);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.class-list {
|
||||
padding: 8px 20px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.class-card {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
border-left: 5px solid var(--accent-orange);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.class-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: rgba(255,107,53,0.1);
|
||||
border-radius: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.class-icon i {
|
||||
font-size: 1.8rem;
|
||||
color: var(--accent-orange);
|
||||
}
|
||||
|
||||
.class-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.class-info h4 {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.class-info p {
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-muted);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.class-time {
|
||||
font-size: 0.75rem;
|
||||
background: #F0F3F9;
|
||||
padding: 4px 10px;
|
||||
border-radius: 40px;
|
||||
font-weight: 600;
|
||||
color: var(--primary-deep);
|
||||
}
|
||||
|
||||
/* 底部导航栏 (Tab Bar 模拟) */
|
||||
.bottom-tab {
|
||||
background: white;
|
||||
backdrop-filter: blur(0px);
|
||||
border-top: 1px solid var(--border-light);
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 10px 20px 22px;
|
||||
border-radius: 0 0 36px 36px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
transition: 0.1s;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.tab-item i {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.tab-item span {
|
||||
font-size: 0.7rem;
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: var(--accent-orange);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 教练推荐卡片额外装饰 */
|
||||
.trainer-spot {
|
||||
background: var(--gradient-blue);
|
||||
margin: 0 20px 20px;
|
||||
border-radius: 28px;
|
||||
padding: 16px 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.trainer-info h4 {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.trainer-info h3 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.trainer-btn {
|
||||
background: var(--accent-orange);
|
||||
border: none;
|
||||
padding: 8px 18px;
|
||||
border-radius: 50px;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* 配色展示条 / 设计说明 */
|
||||
.color-palette-show {
|
||||
max-width: 450px;
|
||||
margin: 28px auto 0;
|
||||
background: white;
|
||||
border-radius: 32px;
|
||||
padding: 18px 20px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.color-title {
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
color: var(--primary-dark);
|
||||
font-size: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.color-swatches {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.swatch {
|
||||
flex: 1;
|
||||
min-width: 70px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.swatch-circle {
|
||||
height: 50px;
|
||||
border-radius: 16px;
|
||||
margin-bottom: 8px;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.swatch span {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 20px 0 8px;
|
||||
border: none;
|
||||
height: 1px;
|
||||
background: var(--border-light);
|
||||
}
|
||||
|
||||
.note {
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-muted);
|
||||
text-align: center;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
i, .tab-item, .menu-item, .stat-card {
|
||||
cursor: default;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="demo-container">
|
||||
<!-- 模拟小程序状态栏/胶囊区 -->
|
||||
<div class="status-bar">
|
||||
<span>9:41</span>
|
||||
<span><i class="fas fa-signal"></i> <i class="fas fa-battery-full"></i></span>
|
||||
</div>
|
||||
|
||||
<!-- 主头部深蓝区 -->
|
||||
<div class="main-header">
|
||||
<div class="header-top">
|
||||
<div class="logo-area">
|
||||
<h2><i class="fas fa-dumbbell"></i> IRONPULSE</h2>
|
||||
<p>智能健身 · 即刻燃动</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<i class="fas fa-bell"></i>
|
||||
<i class="fas fa-user-circle"></i>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 用户欢迎卡片,橙色高亮会员 -->
|
||||
<div class="welcome-card">
|
||||
<div class="welcome-text">
|
||||
<h4>欢迎回来,</h4>
|
||||
<h3>张峻铭 <span style="font-size: 0.8rem;">💪</span></h3>
|
||||
</div>
|
||||
<div class="badge-member">MVP 黑金会员</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 关键指标 活力橙点缀 -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<i class="fas fa-fire"></i>
|
||||
<div class="stat-number">1,280</div>
|
||||
<div class="stat-label">今日卡路里</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<i class="fas fa-clock"></i>
|
||||
<div class="stat-number">126</div>
|
||||
<div class="stat-label">运动分钟</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<i class="fas fa-calendar-check"></i>
|
||||
<div class="stat-number">12</div>
|
||||
<div class="stat-label">本月课程</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 功能区 网格菜单(四大模块) -->
|
||||
<div class="menu-grid">
|
||||
<div class="menu-item"><i class="fas fa-qrcode"></i><span>签到</span></div>
|
||||
<div class="menu-item"><i class="fas fa-chalkboard-user"></i><span>团课预约</span></div>
|
||||
<div class="menu-item"><i class="fas fa-chart-line"></i><span>体测报告</span></div>
|
||||
<div class="menu-item"><i class="fas fa-cog"></i><span>我的会籍</span></div>
|
||||
</div>
|
||||
|
||||
<!-- 热门课程板块 (动态橙边风格) -->
|
||||
<div class="section-title">
|
||||
<h3><i class="fas fa-heartbeat" style="color: var(--accent-orange); margin-right: 6px;"></i> 热门团课</h3>
|
||||
<a href="#">查看全部 →</a>
|
||||
</div>
|
||||
<div class="class-list">
|
||||
<div class="class-card">
|
||||
<div class="class-icon"><i class="fas fa-bicycle"></i></div>
|
||||
<div class="class-info">
|
||||
<h4>极速燃脂 · 动感单车</h4>
|
||||
<p>张教练 | 综合有氧</p>
|
||||
</div>
|
||||
<div class="class-time">19:30 满员</div>
|
||||
</div>
|
||||
<div class="class-card">
|
||||
<div class="class-icon"><i class="fas fa-hand-fist"></i></div>
|
||||
<div class="class-info">
|
||||
<h4>搏击风暴 · 泰拳基础</h4>
|
||||
<p>李娜 | 格斗区</p>
|
||||
</div>
|
||||
<div class="class-time">18:00 火热</div>
|
||||
</div>
|
||||
<div class="class-card">
|
||||
<div class="class-icon"><i class="fas fa-person-walking"></i></div>
|
||||
<div class="class-info">
|
||||
<h4>普拉提核心唤醒</h4>
|
||||
<p>Elena | 瑜伽室</p>
|
||||
</div>
|
||||
<div class="class-time">明早 09:30</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 教练推荐 使用深蓝渐变+橙色按钮 -->
|
||||
<div class="trainer-spot">
|
||||
<div class="trainer-info">
|
||||
<h4>🌟 明星私教推荐</h4>
|
||||
<h3>Alex 王 · 增肌塑形专家</h3>
|
||||
<p style="font-size: 0.7rem; opacity:0.85;">剩余3个时段可约</p>
|
||||
</div>
|
||||
<div class="trainer-btn">立即预约</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部tab栏 当前选中“首页”凸显橙色-->
|
||||
<div class="bottom-tab">
|
||||
<div class="tab-item active"><i class="fas fa-home"></i><span>首页</span></div>
|
||||
<div class="tab-item"><i class="fas fa-calendar-alt"></i><span>日程</span></div>
|
||||
<div class="tab-item"><i class="fas fa-chart-simple"></i><span>数据</span></div>
|
||||
<div class="tab-item"><i class="fas fa-user"></i><span>我的</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 配色方案展示区,直观看到颜色搭配,帮助设计决策 -->
|
||||
<div class="color-palette-show">
|
||||
<div class="color-title">
|
||||
<i class="fas fa-palette" style="color: #FF6B35;"></i> 健身房管理系统 · 能量配色方案
|
||||
</div>
|
||||
<div class="color-swatches">
|
||||
<div class="swatch">
|
||||
<div class="swatch-circle" style="background: #0B2B4B;"></div>
|
||||
<span>深蓝主色 #0B2B4B<br>专业信赖</span>
|
||||
</div>
|
||||
<div class="swatch">
|
||||
<div class="swatch-circle" style="background: #FF6B35;"></div>
|
||||
<span>活力橙 #FF6B35<br>行动/热情</span>
|
||||
</div>
|
||||
<div class="swatch">
|
||||
<div class="swatch-circle" style="background: #1A4A6F;"></div>
|
||||
<span>深邃辅助 #1A4A6F</span>
|
||||
</div>
|
||||
<div class="swatch">
|
||||
<div class="swatch-circle" style="background: #F9FAFE; border:1px solid #E2E8F0;"></div>
|
||||
<span>浅色背景 #F9FAFE</span>
|
||||
</div>
|
||||
<div class="swatch">
|
||||
<div class="swatch-circle" style="background: #FFFFFF; border:1px solid #E2E8F0;"></div>
|
||||
<span>卡片白 #FFFFFF</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="note">
|
||||
<i class="fas fa-lightbulb" style="color: #FF6B35;"></i> 设计理念:深蓝色传递专业与稳定感,活力橙色提升运动热情与CTA转化。<br>
|
||||
圆润卡片 + 强烈对比,适合健身管理小程序的年轻、力量与现代氛围。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 额外注释:颜色可访问性强,橙蓝互补但明度舒适 -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script>
|
||||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
||||
CSS.supports('top: constant(a)'))
|
||||
document.write(
|
||||
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
||||
</script>
|
||||
<title></title>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
import App from './App'
|
||||
|
||||
// #ifndef VUE3
|
||||
import Vue from 'vue'
|
||||
import './uni.promisify.adaptor'
|
||||
Vue.config.productionTip = false
|
||||
App.mpType = 'app'
|
||||
const app = new Vue({
|
||||
...App
|
||||
})
|
||||
app.$mount()
|
||||
// #endif
|
||||
|
||||
// #ifdef VUE3
|
||||
import { createSSRApp } from 'vue'
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
return {
|
||||
app
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name" : "gym-manage-uniapp",
|
||||
"appid": "",
|
||||
"description": "Gym Management System Mobile App",
|
||||
"appid" : "__UNI__1F1874C",
|
||||
"description" : "",
|
||||
"versionName" : "1.0.0",
|
||||
"versionCode" : "100",
|
||||
"transformPx" : false,
|
||||
/* 5+App特有相关 */
|
||||
"app-plus" : {
|
||||
"usingComponents" : true,
|
||||
"nvueStyleCompiler" : "uni-app",
|
||||
@@ -15,8 +16,11 @@
|
||||
"autoclose" : true,
|
||||
"delay" : 0
|
||||
},
|
||||
/* 模块配置 */
|
||||
"modules" : {},
|
||||
/* 应用发布信息 */
|
||||
"distribute" : {
|
||||
/* android打包配置 */
|
||||
"android" : {
|
||||
"permissions" : [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
@@ -36,13 +40,17 @@
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
]
|
||||
},
|
||||
/* ios打包配置 */
|
||||
"ios" : {},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs" : {}
|
||||
}
|
||||
},
|
||||
/* 快应用特有相关 */
|
||||
"quickapp" : {},
|
||||
/* 小程序特有相关 */
|
||||
"mp-weixin" : {
|
||||
"appid": "",
|
||||
"appid" : "wx8278fdbc9f158915",
|
||||
"setting" : {
|
||||
"urlCheck" : false
|
||||
},
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "gym-manage-uniapp",
|
||||
"version": "1.0.0",
|
||||
"description": "Gym Management System Mobile App",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev:mp-weixin": "uni -p mp-weixin",
|
||||
"build:mp-weixin": "uni build -p mp-weixin",
|
||||
"dev:h5": "uni",
|
||||
"build:h5": "uni build"
|
||||
},
|
||||
"keywords": [
|
||||
"uniapp",
|
||||
"gym",
|
||||
"management"
|
||||
],
|
||||
"author": "Novalon",
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -1,28 +1,17 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页"
|
||||
"navigationBarTitleText": "健身房"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "健身房管理系统",
|
||||
"navigationBarTitleText": "uni-app",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#7A7E83",
|
||||
"selectedColor": "#007AFF",
|
||||
"borderStyle": "black",
|
||||
"backgroundColor": "#F8F8F8",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"text": "首页"
|
||||
}
|
||||
]
|
||||
}
|
||||
"uniIdRouter": {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<view class="home-page">
|
||||
<!-- Banner轮播 -->
|
||||
<BannerSwiper />
|
||||
|
||||
<!-- 功能入口 -->
|
||||
<QuickEntry />
|
||||
|
||||
<!-- 推荐课程 -->
|
||||
<RecommendCourses />
|
||||
|
||||
<!-- 今日推荐 -->
|
||||
<TodayRecommend />
|
||||
|
||||
<!-- 底部占位 -->
|
||||
<view class="bottom-placeholder"></view>
|
||||
|
||||
<!-- 底部导航 -->
|
||||
<TabBar />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import BannerSwiper from '@/components/index/BannerSwiper.vue'
|
||||
import QuickEntry from '@/components/index/QuickEntry.vue'
|
||||
import RecommendCourses from '@/components/index/RecommendCourses.vue'
|
||||
import TodayRecommend from '@/components/index/TodayRecommend.vue'
|
||||
import TabBar from '@/components/TabBar.vue'
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.home-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f0f4f8;
|
||||
padding-bottom: 160rpx;
|
||||
}
|
||||
|
||||
.bottom-placeholder {
|
||||
height: 40rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -1,40 +0,0 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<text class="title">健身房管理系统</text>
|
||||
<text class="subtitle">欢迎使用 UniApp 移动端</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
title: '健身房管理系统'
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
console.log('Index page loaded')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,13 @@
|
||||
uni.addInterceptor({
|
||||
returnValue (res) {
|
||||
if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
|
||||
return res;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
res.then((res) => {
|
||||
if (!res) return resolve(res)
|
||||
return res[0] ? reject(res[0]) : resolve(res[1])
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 这里是uni-app内置的常用样式变量
|
||||
*
|
||||
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
|
||||
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||
*
|
||||
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
|
||||
*/
|
||||
|
||||
/* 颜色变量 */
|
||||
|
||||
/* 行为相关颜色 */
|
||||
$uni-color-primary: #007aff;
|
||||
$uni-color-success: #4cd964;
|
||||
$uni-color-warning: #f0ad4e;
|
||||
$uni-color-error: #dd524d;
|
||||
|
||||
/* 文字基本颜色 */
|
||||
$uni-text-color:#333;//基本色
|
||||
$uni-text-color-inverse:#fff;//反色
|
||||
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
|
||||
$uni-text-color-placeholder: #808080;
|
||||
$uni-text-color-disable:#c0c0c0;
|
||||
|
||||
/* 背景颜色 */
|
||||
$uni-bg-color:#ffffff;
|
||||
$uni-bg-color-grey:#f8f8f8;
|
||||
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
|
||||
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
|
||||
|
||||
/* 边框颜色 */
|
||||
$uni-border-color:#c8c7cc;
|
||||
|
||||
/* 尺寸变量 */
|
||||
|
||||
/* 文字尺寸 */
|
||||
$uni-font-size-sm:12px;
|
||||
$uni-font-size-base:14px;
|
||||
$uni-font-size-lg:16px;
|
||||
|
||||
/* 图片尺寸 */
|
||||
$uni-img-size-sm:20px;
|
||||
$uni-img-size-base:26px;
|
||||
$uni-img-size-lg:40px;
|
||||
|
||||
/* Border Radius */
|
||||
$uni-border-radius-sm: 2px;
|
||||
$uni-border-radius-base: 3px;
|
||||
$uni-border-radius-lg: 6px;
|
||||
$uni-border-radius-circle: 50%;
|
||||
|
||||
/* 水平间距 */
|
||||
$uni-spacing-row-sm: 5px;
|
||||
$uni-spacing-row-base: 10px;
|
||||
$uni-spacing-row-lg: 15px;
|
||||
|
||||
/* 垂直间距 */
|
||||
$uni-spacing-col-sm: 4px;
|
||||
$uni-spacing-col-base: 8px;
|
||||
$uni-spacing-col-lg: 12px;
|
||||
|
||||
/* 透明度 */
|
||||
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||
|
||||
/* 文章场景相关 */
|
||||
$uni-color-title: #2C405A; // 文章标题颜色
|
||||
$uni-font-size-title:20px;
|
||||
$uni-color-subtitle: #555555; // 二级标题颜色
|
||||
$uni-font-size-subtitle:26px;
|
||||
$uni-color-paragraph: #3F536E; // 文章段落颜色
|
||||
$uni-font-size-paragraph:15px;
|
||||
@@ -0,0 +1,44 @@
|
||||
## 2.0.12(2025-08-26)
|
||||
- 优化 uni-app x 下 size 类型问题
|
||||
## 2.0.11(2025-08-18)
|
||||
- 修复 图标点击事件返回
|
||||
## 2.0.9(2024-01-12)
|
||||
fix: 修复图标大小默认值错误的问题
|
||||
## 2.0.8(2023-12-14)
|
||||
- 修复 项目未使用 ts 情况下,打包报错的bug
|
||||
## 2.0.7(2023-12-14)
|
||||
- 修复 size 属性为 string 时,不加单位导致尺寸异常的bug
|
||||
## 2.0.6(2023-12-11)
|
||||
- 优化 兼容老版本icon类型,如 top ,bottom 等
|
||||
## 2.0.5(2023-12-11)
|
||||
- 优化 兼容老版本icon类型,如 top ,bottom 等
|
||||
## 2.0.4(2023-12-06)
|
||||
- 优化 uni-app x 下示例项目图标排序
|
||||
## 2.0.3(2023-12-06)
|
||||
- 修复 nvue下引入组件报错的bug
|
||||
## 2.0.2(2023-12-05)
|
||||
-优化 size 属性支持单位
|
||||
## 2.0.1(2023-12-05)
|
||||
- 新增 uni-app x 支持定义图标
|
||||
## 1.3.5(2022-01-24)
|
||||
- 优化 size 属性可以传入不带单位的字符串数值
|
||||
## 1.3.4(2022-01-24)
|
||||
- 优化 size 支持其他单位
|
||||
## 1.3.3(2022-01-17)
|
||||
- 修复 nvue 有些图标不显示的bug,兼容老版本图标
|
||||
## 1.3.2(2021-12-01)
|
||||
- 优化 示例可复制图标名称
|
||||
## 1.3.1(2021-11-23)
|
||||
- 优化 兼容旧组件 type 值
|
||||
## 1.3.0(2021-11-19)
|
||||
- 新增 更多图标
|
||||
- 优化 自定义图标使用方式
|
||||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-icons](https://uniapp.dcloud.io/component/uniui/uni-icons)
|
||||
## 1.1.7(2021-11-08)
|
||||
## 1.2.0(2021-07-30)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 1.1.5(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 1.1.4(2021-02-05)
|
||||
- 调整为uni_modules目录规范
|
||||
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<text class="uni-icons" :style="styleObj">
|
||||
<slot>{{unicode}}</slot>
|
||||
</text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fontData, IconsDataItem } from './uniicons_file'
|
||||
|
||||
/**
|
||||
* Icons 图标
|
||||
* @description 用于展示 icon 图标
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=28
|
||||
* @property {Number} size 图标大小
|
||||
* @property {String} type 图标图案,参考示例
|
||||
* @property {String} color 图标颜色
|
||||
* @property {String} customPrefix 自定义图标
|
||||
* @event {Function} click 点击 Icon 触发事件
|
||||
*/
|
||||
export default {
|
||||
name: "uni-icons",
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#333333'
|
||||
},
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 16
|
||||
},
|
||||
fontFamily: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
unicode() : string {
|
||||
let codes = fontData.find((item : IconsDataItem) : boolean => { return item.font_class == this.type })
|
||||
if (codes !== null) {
|
||||
return codes.unicode
|
||||
}
|
||||
return ''
|
||||
},
|
||||
iconSize() : string {
|
||||
const size = this.size
|
||||
if (typeof size == 'string') {
|
||||
const reg = /^[0-9]*$/g
|
||||
return reg.test(size as string) ? '' + size + 'px' : '' + size;
|
||||
// return '' + this.size
|
||||
}
|
||||
return this.getFontSize(size as number)
|
||||
},
|
||||
styleObj() : UTSJSONObject {
|
||||
if (this.fontFamily !== '') {
|
||||
return { color: this.color, fontSize: this.iconSize, fontFamily: this.fontFamily }
|
||||
}
|
||||
return { color: this.color, fontSize: this.iconSize }
|
||||
}
|
||||
},
|
||||
created() { },
|
||||
methods: {
|
||||
/**
|
||||
* 字体大小
|
||||
*/
|
||||
getFontSize(size : number) : string {
|
||||
return size + 'px';
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@font-face {
|
||||
font-family: UniIconsFontFamily;
|
||||
src: url('./uniicons.ttf');
|
||||
}
|
||||
|
||||
.uni-icons {
|
||||
font-family: UniIconsFontFamily;
|
||||
font-size: 18px;
|
||||
font-style: normal;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<text :style="styleObj" class="uni-icons" @click="_onClick">{{unicode}}</text>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<text :style="styleObj" class="uni-icons" :class="['uniui-'+type,customPrefix,customPrefix?type:'']" @click="_onClick">
|
||||
<slot></slot>
|
||||
</text>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fontData } from './uniicons_file_vue.js';
|
||||
|
||||
const getVal = (val) => {
|
||||
const reg = /^[0-9]*$/g
|
||||
return (typeof val === 'number' || reg.test(val)) ? val + 'px' : val;
|
||||
}
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
var domModule = weex.requireModule('dom');
|
||||
import iconUrl from './uniicons.ttf'
|
||||
domModule.addRule('fontFace', {
|
||||
'fontFamily': "uniicons",
|
||||
'src': "url('" + iconUrl + "')"
|
||||
});
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* Icons 图标
|
||||
* @description 用于展示 icons 图标
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=28
|
||||
* @property {Number} size 图标大小
|
||||
* @property {String} type 图标图案,参考示例
|
||||
* @property {String} color 图标颜色
|
||||
* @property {String} customPrefix 自定义图标
|
||||
* @event {Function} click 点击 Icon 触发事件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniIcons',
|
||||
emits: ['click'],
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#333333'
|
||||
},
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 16
|
||||
},
|
||||
customPrefix: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
fontFamily: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
icons: fontData
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
unicode() {
|
||||
let code = this.icons.find(v => v.font_class === this.type)
|
||||
if (code) {
|
||||
return code.unicode
|
||||
}
|
||||
return ''
|
||||
},
|
||||
iconSize() {
|
||||
return getVal(this.size)
|
||||
},
|
||||
styleObj() {
|
||||
if (this.fontFamily !== '') {
|
||||
return `color: ${this.color}; font-size: ${this.iconSize}; font-family: ${this.fontFamily};`
|
||||
}
|
||||
return `color: ${this.color}; font-size: ${this.iconSize};`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
_onClick(e) {
|
||||
this.$emit('click', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* #ifndef APP-NVUE */
|
||||
@import './uniicons.css';
|
||||
|
||||
@font-face {
|
||||
font-family: uniicons;
|
||||
src: url('./uniicons.ttf');
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.uni-icons {
|
||||
font-family: uniicons;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,664 @@
|
||||
|
||||
.uniui-cart-filled:before {
|
||||
content: "\e6d0";
|
||||
}
|
||||
|
||||
.uniui-gift-filled:before {
|
||||
content: "\e6c4";
|
||||
}
|
||||
|
||||
.uniui-color:before {
|
||||
content: "\e6cf";
|
||||
}
|
||||
|
||||
.uniui-wallet:before {
|
||||
content: "\e6b1";
|
||||
}
|
||||
|
||||
.uniui-settings-filled:before {
|
||||
content: "\e6ce";
|
||||
}
|
||||
|
||||
.uniui-auth-filled:before {
|
||||
content: "\e6cc";
|
||||
}
|
||||
|
||||
.uniui-shop-filled:before {
|
||||
content: "\e6cd";
|
||||
}
|
||||
|
||||
.uniui-staff-filled:before {
|
||||
content: "\e6cb";
|
||||
}
|
||||
|
||||
.uniui-vip-filled:before {
|
||||
content: "\e6c6";
|
||||
}
|
||||
|
||||
.uniui-plus-filled:before {
|
||||
content: "\e6c7";
|
||||
}
|
||||
|
||||
.uniui-folder-add-filled:before {
|
||||
content: "\e6c8";
|
||||
}
|
||||
|
||||
.uniui-color-filled:before {
|
||||
content: "\e6c9";
|
||||
}
|
||||
|
||||
.uniui-tune-filled:before {
|
||||
content: "\e6ca";
|
||||
}
|
||||
|
||||
.uniui-calendar-filled:before {
|
||||
content: "\e6c0";
|
||||
}
|
||||
|
||||
.uniui-notification-filled:before {
|
||||
content: "\e6c1";
|
||||
}
|
||||
|
||||
.uniui-wallet-filled:before {
|
||||
content: "\e6c2";
|
||||
}
|
||||
|
||||
.uniui-medal-filled:before {
|
||||
content: "\e6c3";
|
||||
}
|
||||
|
||||
.uniui-fire-filled:before {
|
||||
content: "\e6c5";
|
||||
}
|
||||
|
||||
.uniui-refreshempty:before {
|
||||
content: "\e6bf";
|
||||
}
|
||||
|
||||
.uniui-location-filled:before {
|
||||
content: "\e6af";
|
||||
}
|
||||
|
||||
.uniui-person-filled:before {
|
||||
content: "\e69d";
|
||||
}
|
||||
|
||||
.uniui-personadd-filled:before {
|
||||
content: "\e698";
|
||||
}
|
||||
|
||||
.uniui-arrowthinleft:before {
|
||||
content: "\e6d2";
|
||||
}
|
||||
|
||||
.uniui-arrowthinup:before {
|
||||
content: "\e6d3";
|
||||
}
|
||||
|
||||
.uniui-arrowthindown:before {
|
||||
content: "\e6d4";
|
||||
}
|
||||
|
||||
.uniui-back:before {
|
||||
content: "\e6b9";
|
||||
}
|
||||
|
||||
.uniui-forward:before {
|
||||
content: "\e6ba";
|
||||
}
|
||||
|
||||
.uniui-arrow-right:before {
|
||||
content: "\e6bb";
|
||||
}
|
||||
|
||||
.uniui-arrow-left:before {
|
||||
content: "\e6bc";
|
||||
}
|
||||
|
||||
.uniui-arrow-up:before {
|
||||
content: "\e6bd";
|
||||
}
|
||||
|
||||
.uniui-arrow-down:before {
|
||||
content: "\e6be";
|
||||
}
|
||||
|
||||
.uniui-arrowthinright:before {
|
||||
content: "\e6d1";
|
||||
}
|
||||
|
||||
.uniui-down:before {
|
||||
content: "\e6b8";
|
||||
}
|
||||
|
||||
.uniui-bottom:before {
|
||||
content: "\e6b8";
|
||||
}
|
||||
|
||||
.uniui-arrowright:before {
|
||||
content: "\e6d5";
|
||||
}
|
||||
|
||||
.uniui-right:before {
|
||||
content: "\e6b5";
|
||||
}
|
||||
|
||||
.uniui-up:before {
|
||||
content: "\e6b6";
|
||||
}
|
||||
|
||||
.uniui-top:before {
|
||||
content: "\e6b6";
|
||||
}
|
||||
|
||||
.uniui-left:before {
|
||||
content: "\e6b7";
|
||||
}
|
||||
|
||||
.uniui-arrowup:before {
|
||||
content: "\e6d6";
|
||||
}
|
||||
|
||||
.uniui-eye:before {
|
||||
content: "\e651";
|
||||
}
|
||||
|
||||
.uniui-eye-filled:before {
|
||||
content: "\e66a";
|
||||
}
|
||||
|
||||
.uniui-eye-slash:before {
|
||||
content: "\e6b3";
|
||||
}
|
||||
|
||||
.uniui-eye-slash-filled:before {
|
||||
content: "\e6b4";
|
||||
}
|
||||
|
||||
.uniui-info-filled:before {
|
||||
content: "\e649";
|
||||
}
|
||||
|
||||
.uniui-reload:before {
|
||||
content: "\e6b2";
|
||||
}
|
||||
|
||||
.uniui-micoff-filled:before {
|
||||
content: "\e6b0";
|
||||
}
|
||||
|
||||
.uniui-map-pin-ellipse:before {
|
||||
content: "\e6ac";
|
||||
}
|
||||
|
||||
.uniui-map-pin:before {
|
||||
content: "\e6ad";
|
||||
}
|
||||
|
||||
.uniui-location:before {
|
||||
content: "\e6ae";
|
||||
}
|
||||
|
||||
.uniui-starhalf:before {
|
||||
content: "\e683";
|
||||
}
|
||||
|
||||
.uniui-star:before {
|
||||
content: "\e688";
|
||||
}
|
||||
|
||||
.uniui-star-filled:before {
|
||||
content: "\e68f";
|
||||
}
|
||||
|
||||
.uniui-calendar:before {
|
||||
content: "\e6a0";
|
||||
}
|
||||
|
||||
.uniui-fire:before {
|
||||
content: "\e6a1";
|
||||
}
|
||||
|
||||
.uniui-medal:before {
|
||||
content: "\e6a2";
|
||||
}
|
||||
|
||||
.uniui-font:before {
|
||||
content: "\e6a3";
|
||||
}
|
||||
|
||||
.uniui-gift:before {
|
||||
content: "\e6a4";
|
||||
}
|
||||
|
||||
.uniui-link:before {
|
||||
content: "\e6a5";
|
||||
}
|
||||
|
||||
.uniui-notification:before {
|
||||
content: "\e6a6";
|
||||
}
|
||||
|
||||
.uniui-staff:before {
|
||||
content: "\e6a7";
|
||||
}
|
||||
|
||||
.uniui-vip:before {
|
||||
content: "\e6a8";
|
||||
}
|
||||
|
||||
.uniui-folder-add:before {
|
||||
content: "\e6a9";
|
||||
}
|
||||
|
||||
.uniui-tune:before {
|
||||
content: "\e6aa";
|
||||
}
|
||||
|
||||
.uniui-auth:before {
|
||||
content: "\e6ab";
|
||||
}
|
||||
|
||||
.uniui-person:before {
|
||||
content: "\e699";
|
||||
}
|
||||
|
||||
.uniui-email-filled:before {
|
||||
content: "\e69a";
|
||||
}
|
||||
|
||||
.uniui-phone-filled:before {
|
||||
content: "\e69b";
|
||||
}
|
||||
|
||||
.uniui-phone:before {
|
||||
content: "\e69c";
|
||||
}
|
||||
|
||||
.uniui-email:before {
|
||||
content: "\e69e";
|
||||
}
|
||||
|
||||
.uniui-personadd:before {
|
||||
content: "\e69f";
|
||||
}
|
||||
|
||||
.uniui-chatboxes-filled:before {
|
||||
content: "\e692";
|
||||
}
|
||||
|
||||
.uniui-contact:before {
|
||||
content: "\e693";
|
||||
}
|
||||
|
||||
.uniui-chatbubble-filled:before {
|
||||
content: "\e694";
|
||||
}
|
||||
|
||||
.uniui-contact-filled:before {
|
||||
content: "\e695";
|
||||
}
|
||||
|
||||
.uniui-chatboxes:before {
|
||||
content: "\e696";
|
||||
}
|
||||
|
||||
.uniui-chatbubble:before {
|
||||
content: "\e697";
|
||||
}
|
||||
|
||||
.uniui-upload-filled:before {
|
||||
content: "\e68e";
|
||||
}
|
||||
|
||||
.uniui-upload:before {
|
||||
content: "\e690";
|
||||
}
|
||||
|
||||
.uniui-weixin:before {
|
||||
content: "\e691";
|
||||
}
|
||||
|
||||
.uniui-compose:before {
|
||||
content: "\e67f";
|
||||
}
|
||||
|
||||
.uniui-qq:before {
|
||||
content: "\e680";
|
||||
}
|
||||
|
||||
.uniui-download-filled:before {
|
||||
content: "\e681";
|
||||
}
|
||||
|
||||
.uniui-pyq:before {
|
||||
content: "\e682";
|
||||
}
|
||||
|
||||
.uniui-sound:before {
|
||||
content: "\e684";
|
||||
}
|
||||
|
||||
.uniui-trash-filled:before {
|
||||
content: "\e685";
|
||||
}
|
||||
|
||||
.uniui-sound-filled:before {
|
||||
content: "\e686";
|
||||
}
|
||||
|
||||
.uniui-trash:before {
|
||||
content: "\e687";
|
||||
}
|
||||
|
||||
.uniui-videocam-filled:before {
|
||||
content: "\e689";
|
||||
}
|
||||
|
||||
.uniui-spinner-cycle:before {
|
||||
content: "\e68a";
|
||||
}
|
||||
|
||||
.uniui-weibo:before {
|
||||
content: "\e68b";
|
||||
}
|
||||
|
||||
.uniui-videocam:before {
|
||||
content: "\e68c";
|
||||
}
|
||||
|
||||
.uniui-download:before {
|
||||
content: "\e68d";
|
||||
}
|
||||
|
||||
.uniui-help:before {
|
||||
content: "\e679";
|
||||
}
|
||||
|
||||
.uniui-navigate-filled:before {
|
||||
content: "\e67a";
|
||||
}
|
||||
|
||||
.uniui-plusempty:before {
|
||||
content: "\e67b";
|
||||
}
|
||||
|
||||
.uniui-smallcircle:before {
|
||||
content: "\e67c";
|
||||
}
|
||||
|
||||
.uniui-minus-filled:before {
|
||||
content: "\e67d";
|
||||
}
|
||||
|
||||
.uniui-micoff:before {
|
||||
content: "\e67e";
|
||||
}
|
||||
|
||||
.uniui-closeempty:before {
|
||||
content: "\e66c";
|
||||
}
|
||||
|
||||
.uniui-clear:before {
|
||||
content: "\e66d";
|
||||
}
|
||||
|
||||
.uniui-navigate:before {
|
||||
content: "\e66e";
|
||||
}
|
||||
|
||||
.uniui-minus:before {
|
||||
content: "\e66f";
|
||||
}
|
||||
|
||||
.uniui-image:before {
|
||||
content: "\e670";
|
||||
}
|
||||
|
||||
.uniui-mic:before {
|
||||
content: "\e671";
|
||||
}
|
||||
|
||||
.uniui-paperplane:before {
|
||||
content: "\e672";
|
||||
}
|
||||
|
||||
.uniui-close:before {
|
||||
content: "\e673";
|
||||
}
|
||||
|
||||
.uniui-help-filled:before {
|
||||
content: "\e674";
|
||||
}
|
||||
|
||||
.uniui-paperplane-filled:before {
|
||||
content: "\e675";
|
||||
}
|
||||
|
||||
.uniui-plus:before {
|
||||
content: "\e676";
|
||||
}
|
||||
|
||||
.uniui-mic-filled:before {
|
||||
content: "\e677";
|
||||
}
|
||||
|
||||
.uniui-image-filled:before {
|
||||
content: "\e678";
|
||||
}
|
||||
|
||||
.uniui-locked-filled:before {
|
||||
content: "\e668";
|
||||
}
|
||||
|
||||
.uniui-info:before {
|
||||
content: "\e669";
|
||||
}
|
||||
|
||||
.uniui-locked:before {
|
||||
content: "\e66b";
|
||||
}
|
||||
|
||||
.uniui-camera-filled:before {
|
||||
content: "\e658";
|
||||
}
|
||||
|
||||
.uniui-chat-filled:before {
|
||||
content: "\e659";
|
||||
}
|
||||
|
||||
.uniui-camera:before {
|
||||
content: "\e65a";
|
||||
}
|
||||
|
||||
.uniui-circle:before {
|
||||
content: "\e65b";
|
||||
}
|
||||
|
||||
.uniui-checkmarkempty:before {
|
||||
content: "\e65c";
|
||||
}
|
||||
|
||||
.uniui-chat:before {
|
||||
content: "\e65d";
|
||||
}
|
||||
|
||||
.uniui-circle-filled:before {
|
||||
content: "\e65e";
|
||||
}
|
||||
|
||||
.uniui-flag:before {
|
||||
content: "\e65f";
|
||||
}
|
||||
|
||||
.uniui-flag-filled:before {
|
||||
content: "\e660";
|
||||
}
|
||||
|
||||
.uniui-gear-filled:before {
|
||||
content: "\e661";
|
||||
}
|
||||
|
||||
.uniui-home:before {
|
||||
content: "\e662";
|
||||
}
|
||||
|
||||
.uniui-home-filled:before {
|
||||
content: "\e663";
|
||||
}
|
||||
|
||||
.uniui-gear:before {
|
||||
content: "\e664";
|
||||
}
|
||||
|
||||
.uniui-smallcircle-filled:before {
|
||||
content: "\e665";
|
||||
}
|
||||
|
||||
.uniui-map-filled:before {
|
||||
content: "\e666";
|
||||
}
|
||||
|
||||
.uniui-map:before {
|
||||
content: "\e667";
|
||||
}
|
||||
|
||||
.uniui-refresh-filled:before {
|
||||
content: "\e656";
|
||||
}
|
||||
|
||||
.uniui-refresh:before {
|
||||
content: "\e657";
|
||||
}
|
||||
|
||||
.uniui-cloud-upload:before {
|
||||
content: "\e645";
|
||||
}
|
||||
|
||||
.uniui-cloud-download-filled:before {
|
||||
content: "\e646";
|
||||
}
|
||||
|
||||
.uniui-cloud-download:before {
|
||||
content: "\e647";
|
||||
}
|
||||
|
||||
.uniui-cloud-upload-filled:before {
|
||||
content: "\e648";
|
||||
}
|
||||
|
||||
.uniui-redo:before {
|
||||
content: "\e64a";
|
||||
}
|
||||
|
||||
.uniui-images-filled:before {
|
||||
content: "\e64b";
|
||||
}
|
||||
|
||||
.uniui-undo-filled:before {
|
||||
content: "\e64c";
|
||||
}
|
||||
|
||||
.uniui-more:before {
|
||||
content: "\e64d";
|
||||
}
|
||||
|
||||
.uniui-more-filled:before {
|
||||
content: "\e64e";
|
||||
}
|
||||
|
||||
.uniui-undo:before {
|
||||
content: "\e64f";
|
||||
}
|
||||
|
||||
.uniui-images:before {
|
||||
content: "\e650";
|
||||
}
|
||||
|
||||
.uniui-paperclip:before {
|
||||
content: "\e652";
|
||||
}
|
||||
|
||||
.uniui-settings:before {
|
||||
content: "\e653";
|
||||
}
|
||||
|
||||
.uniui-search:before {
|
||||
content: "\e654";
|
||||
}
|
||||
|
||||
.uniui-redo-filled:before {
|
||||
content: "\e655";
|
||||
}
|
||||
|
||||
.uniui-list:before {
|
||||
content: "\e644";
|
||||
}
|
||||
|
||||
.uniui-mail-open-filled:before {
|
||||
content: "\e63a";
|
||||
}
|
||||
|
||||
.uniui-hand-down-filled:before {
|
||||
content: "\e63c";
|
||||
}
|
||||
|
||||
.uniui-hand-down:before {
|
||||
content: "\e63d";
|
||||
}
|
||||
|
||||
.uniui-hand-up-filled:before {
|
||||
content: "\e63e";
|
||||
}
|
||||
|
||||
.uniui-hand-up:before {
|
||||
content: "\e63f";
|
||||
}
|
||||
|
||||
.uniui-heart-filled:before {
|
||||
content: "\e641";
|
||||
}
|
||||
|
||||
.uniui-mail-open:before {
|
||||
content: "\e643";
|
||||
}
|
||||
|
||||
.uniui-heart:before {
|
||||
content: "\e639";
|
||||
}
|
||||
|
||||
.uniui-loop:before {
|
||||
content: "\e633";
|
||||
}
|
||||
|
||||
.uniui-pulldown:before {
|
||||
content: "\e632";
|
||||
}
|
||||
|
||||
.uniui-scan:before {
|
||||
content: "\e62a";
|
||||
}
|
||||
|
||||
.uniui-bars:before {
|
||||
content: "\e627";
|
||||
}
|
||||
|
||||
.uniui-checkbox:before {
|
||||
content: "\e62b";
|
||||
}
|
||||
|
||||
.uniui-checkbox-filled:before {
|
||||
content: "\e62c";
|
||||
}
|
||||
|
||||
.uniui-shop:before {
|
||||
content: "\e62f";
|
||||
}
|
||||
|
||||
.uniui-headphones:before {
|
||||
content: "\e630";
|
||||
}
|
||||
|
||||
.uniui-cart:before {
|
||||
content: "\e631";
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,664 @@
|
||||
|
||||
export type IconsData = {
|
||||
id : string
|
||||
name : string
|
||||
font_family : string
|
||||
css_prefix_text : string
|
||||
description : string
|
||||
glyphs : Array<IconsDataItem>
|
||||
}
|
||||
|
||||
export type IconsDataItem = {
|
||||
font_class : string
|
||||
unicode : string
|
||||
}
|
||||
|
||||
|
||||
export const fontData = [
|
||||
{
|
||||
"font_class": "arrow-down",
|
||||
"unicode": "\ue6be"
|
||||
},
|
||||
{
|
||||
"font_class": "arrow-left",
|
||||
"unicode": "\ue6bc"
|
||||
},
|
||||
{
|
||||
"font_class": "arrow-right",
|
||||
"unicode": "\ue6bb"
|
||||
},
|
||||
{
|
||||
"font_class": "arrow-up",
|
||||
"unicode": "\ue6bd"
|
||||
},
|
||||
{
|
||||
"font_class": "auth",
|
||||
"unicode": "\ue6ab"
|
||||
},
|
||||
{
|
||||
"font_class": "auth-filled",
|
||||
"unicode": "\ue6cc"
|
||||
},
|
||||
{
|
||||
"font_class": "back",
|
||||
"unicode": "\ue6b9"
|
||||
},
|
||||
{
|
||||
"font_class": "bars",
|
||||
"unicode": "\ue627"
|
||||
},
|
||||
{
|
||||
"font_class": "calendar",
|
||||
"unicode": "\ue6a0"
|
||||
},
|
||||
{
|
||||
"font_class": "calendar-filled",
|
||||
"unicode": "\ue6c0"
|
||||
},
|
||||
{
|
||||
"font_class": "camera",
|
||||
"unicode": "\ue65a"
|
||||
},
|
||||
{
|
||||
"font_class": "camera-filled",
|
||||
"unicode": "\ue658"
|
||||
},
|
||||
{
|
||||
"font_class": "cart",
|
||||
"unicode": "\ue631"
|
||||
},
|
||||
{
|
||||
"font_class": "cart-filled",
|
||||
"unicode": "\ue6d0"
|
||||
},
|
||||
{
|
||||
"font_class": "chat",
|
||||
"unicode": "\ue65d"
|
||||
},
|
||||
{
|
||||
"font_class": "chat-filled",
|
||||
"unicode": "\ue659"
|
||||
},
|
||||
{
|
||||
"font_class": "chatboxes",
|
||||
"unicode": "\ue696"
|
||||
},
|
||||
{
|
||||
"font_class": "chatboxes-filled",
|
||||
"unicode": "\ue692"
|
||||
},
|
||||
{
|
||||
"font_class": "chatbubble",
|
||||
"unicode": "\ue697"
|
||||
},
|
||||
{
|
||||
"font_class": "chatbubble-filled",
|
||||
"unicode": "\ue694"
|
||||
},
|
||||
{
|
||||
"font_class": "checkbox",
|
||||
"unicode": "\ue62b"
|
||||
},
|
||||
{
|
||||
"font_class": "checkbox-filled",
|
||||
"unicode": "\ue62c"
|
||||
},
|
||||
{
|
||||
"font_class": "checkmarkempty",
|
||||
"unicode": "\ue65c"
|
||||
},
|
||||
{
|
||||
"font_class": "circle",
|
||||
"unicode": "\ue65b"
|
||||
},
|
||||
{
|
||||
"font_class": "circle-filled",
|
||||
"unicode": "\ue65e"
|
||||
},
|
||||
{
|
||||
"font_class": "clear",
|
||||
"unicode": "\ue66d"
|
||||
},
|
||||
{
|
||||
"font_class": "close",
|
||||
"unicode": "\ue673"
|
||||
},
|
||||
{
|
||||
"font_class": "closeempty",
|
||||
"unicode": "\ue66c"
|
||||
},
|
||||
{
|
||||
"font_class": "cloud-download",
|
||||
"unicode": "\ue647"
|
||||
},
|
||||
{
|
||||
"font_class": "cloud-download-filled",
|
||||
"unicode": "\ue646"
|
||||
},
|
||||
{
|
||||
"font_class": "cloud-upload",
|
||||
"unicode": "\ue645"
|
||||
},
|
||||
{
|
||||
"font_class": "cloud-upload-filled",
|
||||
"unicode": "\ue648"
|
||||
},
|
||||
{
|
||||
"font_class": "color",
|
||||
"unicode": "\ue6cf"
|
||||
},
|
||||
{
|
||||
"font_class": "color-filled",
|
||||
"unicode": "\ue6c9"
|
||||
},
|
||||
{
|
||||
"font_class": "compose",
|
||||
"unicode": "\ue67f"
|
||||
},
|
||||
{
|
||||
"font_class": "contact",
|
||||
"unicode": "\ue693"
|
||||
},
|
||||
{
|
||||
"font_class": "contact-filled",
|
||||
"unicode": "\ue695"
|
||||
},
|
||||
{
|
||||
"font_class": "down",
|
||||
"unicode": "\ue6b8"
|
||||
},
|
||||
{
|
||||
"font_class": "bottom",
|
||||
"unicode": "\ue6b8"
|
||||
},
|
||||
{
|
||||
"font_class": "download",
|
||||
"unicode": "\ue68d"
|
||||
},
|
||||
{
|
||||
"font_class": "download-filled",
|
||||
"unicode": "\ue681"
|
||||
},
|
||||
{
|
||||
"font_class": "email",
|
||||
"unicode": "\ue69e"
|
||||
},
|
||||
{
|
||||
"font_class": "email-filled",
|
||||
"unicode": "\ue69a"
|
||||
},
|
||||
{
|
||||
"font_class": "eye",
|
||||
"unicode": "\ue651"
|
||||
},
|
||||
{
|
||||
"font_class": "eye-filled",
|
||||
"unicode": "\ue66a"
|
||||
},
|
||||
{
|
||||
"font_class": "eye-slash",
|
||||
"unicode": "\ue6b3"
|
||||
},
|
||||
{
|
||||
"font_class": "eye-slash-filled",
|
||||
"unicode": "\ue6b4"
|
||||
},
|
||||
{
|
||||
"font_class": "fire",
|
||||
"unicode": "\ue6a1"
|
||||
},
|
||||
{
|
||||
"font_class": "fire-filled",
|
||||
"unicode": "\ue6c5"
|
||||
},
|
||||
{
|
||||
"font_class": "flag",
|
||||
"unicode": "\ue65f"
|
||||
},
|
||||
{
|
||||
"font_class": "flag-filled",
|
||||
"unicode": "\ue660"
|
||||
},
|
||||
{
|
||||
"font_class": "folder-add",
|
||||
"unicode": "\ue6a9"
|
||||
},
|
||||
{
|
||||
"font_class": "folder-add-filled",
|
||||
"unicode": "\ue6c8"
|
||||
},
|
||||
{
|
||||
"font_class": "font",
|
||||
"unicode": "\ue6a3"
|
||||
},
|
||||
{
|
||||
"font_class": "forward",
|
||||
"unicode": "\ue6ba"
|
||||
},
|
||||
{
|
||||
"font_class": "gear",
|
||||
"unicode": "\ue664"
|
||||
},
|
||||
{
|
||||
"font_class": "gear-filled",
|
||||
"unicode": "\ue661"
|
||||
},
|
||||
{
|
||||
"font_class": "gift",
|
||||
"unicode": "\ue6a4"
|
||||
},
|
||||
{
|
||||
"font_class": "gift-filled",
|
||||
"unicode": "\ue6c4"
|
||||
},
|
||||
{
|
||||
"font_class": "hand-down",
|
||||
"unicode": "\ue63d"
|
||||
},
|
||||
{
|
||||
"font_class": "hand-down-filled",
|
||||
"unicode": "\ue63c"
|
||||
},
|
||||
{
|
||||
"font_class": "hand-up",
|
||||
"unicode": "\ue63f"
|
||||
},
|
||||
{
|
||||
"font_class": "hand-up-filled",
|
||||
"unicode": "\ue63e"
|
||||
},
|
||||
{
|
||||
"font_class": "headphones",
|
||||
"unicode": "\ue630"
|
||||
},
|
||||
{
|
||||
"font_class": "heart",
|
||||
"unicode": "\ue639"
|
||||
},
|
||||
{
|
||||
"font_class": "heart-filled",
|
||||
"unicode": "\ue641"
|
||||
},
|
||||
{
|
||||
"font_class": "help",
|
||||
"unicode": "\ue679"
|
||||
},
|
||||
{
|
||||
"font_class": "help-filled",
|
||||
"unicode": "\ue674"
|
||||
},
|
||||
{
|
||||
"font_class": "home",
|
||||
"unicode": "\ue662"
|
||||
},
|
||||
{
|
||||
"font_class": "home-filled",
|
||||
"unicode": "\ue663"
|
||||
},
|
||||
{
|
||||
"font_class": "image",
|
||||
"unicode": "\ue670"
|
||||
},
|
||||
{
|
||||
"font_class": "image-filled",
|
||||
"unicode": "\ue678"
|
||||
},
|
||||
{
|
||||
"font_class": "images",
|
||||
"unicode": "\ue650"
|
||||
},
|
||||
{
|
||||
"font_class": "images-filled",
|
||||
"unicode": "\ue64b"
|
||||
},
|
||||
{
|
||||
"font_class": "info",
|
||||
"unicode": "\ue669"
|
||||
},
|
||||
{
|
||||
"font_class": "info-filled",
|
||||
"unicode": "\ue649"
|
||||
},
|
||||
{
|
||||
"font_class": "left",
|
||||
"unicode": "\ue6b7"
|
||||
},
|
||||
{
|
||||
"font_class": "link",
|
||||
"unicode": "\ue6a5"
|
||||
},
|
||||
{
|
||||
"font_class": "list",
|
||||
"unicode": "\ue644"
|
||||
},
|
||||
{
|
||||
"font_class": "location",
|
||||
"unicode": "\ue6ae"
|
||||
},
|
||||
{
|
||||
"font_class": "location-filled",
|
||||
"unicode": "\ue6af"
|
||||
},
|
||||
{
|
||||
"font_class": "locked",
|
||||
"unicode": "\ue66b"
|
||||
},
|
||||
{
|
||||
"font_class": "locked-filled",
|
||||
"unicode": "\ue668"
|
||||
},
|
||||
{
|
||||
"font_class": "loop",
|
||||
"unicode": "\ue633"
|
||||
},
|
||||
{
|
||||
"font_class": "mail-open",
|
||||
"unicode": "\ue643"
|
||||
},
|
||||
{
|
||||
"font_class": "mail-open-filled",
|
||||
"unicode": "\ue63a"
|
||||
},
|
||||
{
|
||||
"font_class": "map",
|
||||
"unicode": "\ue667"
|
||||
},
|
||||
{
|
||||
"font_class": "map-filled",
|
||||
"unicode": "\ue666"
|
||||
},
|
||||
{
|
||||
"font_class": "map-pin",
|
||||
"unicode": "\ue6ad"
|
||||
},
|
||||
{
|
||||
"font_class": "map-pin-ellipse",
|
||||
"unicode": "\ue6ac"
|
||||
},
|
||||
{
|
||||
"font_class": "medal",
|
||||
"unicode": "\ue6a2"
|
||||
},
|
||||
{
|
||||
"font_class": "medal-filled",
|
||||
"unicode": "\ue6c3"
|
||||
},
|
||||
{
|
||||
"font_class": "mic",
|
||||
"unicode": "\ue671"
|
||||
},
|
||||
{
|
||||
"font_class": "mic-filled",
|
||||
"unicode": "\ue677"
|
||||
},
|
||||
{
|
||||
"font_class": "micoff",
|
||||
"unicode": "\ue67e"
|
||||
},
|
||||
{
|
||||
"font_class": "micoff-filled",
|
||||
"unicode": "\ue6b0"
|
||||
},
|
||||
{
|
||||
"font_class": "minus",
|
||||
"unicode": "\ue66f"
|
||||
},
|
||||
{
|
||||
"font_class": "minus-filled",
|
||||
"unicode": "\ue67d"
|
||||
},
|
||||
{
|
||||
"font_class": "more",
|
||||
"unicode": "\ue64d"
|
||||
},
|
||||
{
|
||||
"font_class": "more-filled",
|
||||
"unicode": "\ue64e"
|
||||
},
|
||||
{
|
||||
"font_class": "navigate",
|
||||
"unicode": "\ue66e"
|
||||
},
|
||||
{
|
||||
"font_class": "navigate-filled",
|
||||
"unicode": "\ue67a"
|
||||
},
|
||||
{
|
||||
"font_class": "notification",
|
||||
"unicode": "\ue6a6"
|
||||
},
|
||||
{
|
||||
"font_class": "notification-filled",
|
||||
"unicode": "\ue6c1"
|
||||
},
|
||||
{
|
||||
"font_class": "paperclip",
|
||||
"unicode": "\ue652"
|
||||
},
|
||||
{
|
||||
"font_class": "paperplane",
|
||||
"unicode": "\ue672"
|
||||
},
|
||||
{
|
||||
"font_class": "paperplane-filled",
|
||||
"unicode": "\ue675"
|
||||
},
|
||||
{
|
||||
"font_class": "person",
|
||||
"unicode": "\ue699"
|
||||
},
|
||||
{
|
||||
"font_class": "person-filled",
|
||||
"unicode": "\ue69d"
|
||||
},
|
||||
{
|
||||
"font_class": "personadd",
|
||||
"unicode": "\ue69f"
|
||||
},
|
||||
{
|
||||
"font_class": "personadd-filled",
|
||||
"unicode": "\ue698"
|
||||
},
|
||||
{
|
||||
"font_class": "personadd-filled-copy",
|
||||
"unicode": "\ue6d1"
|
||||
},
|
||||
{
|
||||
"font_class": "phone",
|
||||
"unicode": "\ue69c"
|
||||
},
|
||||
{
|
||||
"font_class": "phone-filled",
|
||||
"unicode": "\ue69b"
|
||||
},
|
||||
{
|
||||
"font_class": "plus",
|
||||
"unicode": "\ue676"
|
||||
},
|
||||
{
|
||||
"font_class": "plus-filled",
|
||||
"unicode": "\ue6c7"
|
||||
},
|
||||
{
|
||||
"font_class": "plusempty",
|
||||
"unicode": "\ue67b"
|
||||
},
|
||||
{
|
||||
"font_class": "pulldown",
|
||||
"unicode": "\ue632"
|
||||
},
|
||||
{
|
||||
"font_class": "pyq",
|
||||
"unicode": "\ue682"
|
||||
},
|
||||
{
|
||||
"font_class": "qq",
|
||||
"unicode": "\ue680"
|
||||
},
|
||||
{
|
||||
"font_class": "redo",
|
||||
"unicode": "\ue64a"
|
||||
},
|
||||
{
|
||||
"font_class": "redo-filled",
|
||||
"unicode": "\ue655"
|
||||
},
|
||||
{
|
||||
"font_class": "refresh",
|
||||
"unicode": "\ue657"
|
||||
},
|
||||
{
|
||||
"font_class": "refresh-filled",
|
||||
"unicode": "\ue656"
|
||||
},
|
||||
{
|
||||
"font_class": "refreshempty",
|
||||
"unicode": "\ue6bf"
|
||||
},
|
||||
{
|
||||
"font_class": "reload",
|
||||
"unicode": "\ue6b2"
|
||||
},
|
||||
{
|
||||
"font_class": "right",
|
||||
"unicode": "\ue6b5"
|
||||
},
|
||||
{
|
||||
"font_class": "scan",
|
||||
"unicode": "\ue62a"
|
||||
},
|
||||
{
|
||||
"font_class": "search",
|
||||
"unicode": "\ue654"
|
||||
},
|
||||
{
|
||||
"font_class": "settings",
|
||||
"unicode": "\ue653"
|
||||
},
|
||||
{
|
||||
"font_class": "settings-filled",
|
||||
"unicode": "\ue6ce"
|
||||
},
|
||||
{
|
||||
"font_class": "shop",
|
||||
"unicode": "\ue62f"
|
||||
},
|
||||
{
|
||||
"font_class": "shop-filled",
|
||||
"unicode": "\ue6cd"
|
||||
},
|
||||
{
|
||||
"font_class": "smallcircle",
|
||||
"unicode": "\ue67c"
|
||||
},
|
||||
{
|
||||
"font_class": "smallcircle-filled",
|
||||
"unicode": "\ue665"
|
||||
},
|
||||
{
|
||||
"font_class": "sound",
|
||||
"unicode": "\ue684"
|
||||
},
|
||||
{
|
||||
"font_class": "sound-filled",
|
||||
"unicode": "\ue686"
|
||||
},
|
||||
{
|
||||
"font_class": "spinner-cycle",
|
||||
"unicode": "\ue68a"
|
||||
},
|
||||
{
|
||||
"font_class": "staff",
|
||||
"unicode": "\ue6a7"
|
||||
},
|
||||
{
|
||||
"font_class": "staff-filled",
|
||||
"unicode": "\ue6cb"
|
||||
},
|
||||
{
|
||||
"font_class": "star",
|
||||
"unicode": "\ue688"
|
||||
},
|
||||
{
|
||||
"font_class": "star-filled",
|
||||
"unicode": "\ue68f"
|
||||
},
|
||||
{
|
||||
"font_class": "starhalf",
|
||||
"unicode": "\ue683"
|
||||
},
|
||||
{
|
||||
"font_class": "trash",
|
||||
"unicode": "\ue687"
|
||||
},
|
||||
{
|
||||
"font_class": "trash-filled",
|
||||
"unicode": "\ue685"
|
||||
},
|
||||
{
|
||||
"font_class": "tune",
|
||||
"unicode": "\ue6aa"
|
||||
},
|
||||
{
|
||||
"font_class": "tune-filled",
|
||||
"unicode": "\ue6ca"
|
||||
},
|
||||
{
|
||||
"font_class": "undo",
|
||||
"unicode": "\ue64f"
|
||||
},
|
||||
{
|
||||
"font_class": "undo-filled",
|
||||
"unicode": "\ue64c"
|
||||
},
|
||||
{
|
||||
"font_class": "up",
|
||||
"unicode": "\ue6b6"
|
||||
},
|
||||
{
|
||||
"font_class": "top",
|
||||
"unicode": "\ue6b6"
|
||||
},
|
||||
{
|
||||
"font_class": "upload",
|
||||
"unicode": "\ue690"
|
||||
},
|
||||
{
|
||||
"font_class": "upload-filled",
|
||||
"unicode": "\ue68e"
|
||||
},
|
||||
{
|
||||
"font_class": "videocam",
|
||||
"unicode": "\ue68c"
|
||||
},
|
||||
{
|
||||
"font_class": "videocam-filled",
|
||||
"unicode": "\ue689"
|
||||
},
|
||||
{
|
||||
"font_class": "vip",
|
||||
"unicode": "\ue6a8"
|
||||
},
|
||||
{
|
||||
"font_class": "vip-filled",
|
||||
"unicode": "\ue6c6"
|
||||
},
|
||||
{
|
||||
"font_class": "wallet",
|
||||
"unicode": "\ue6b1"
|
||||
},
|
||||
{
|
||||
"font_class": "wallet-filled",
|
||||
"unicode": "\ue6c2"
|
||||
},
|
||||
{
|
||||
"font_class": "weibo",
|
||||
"unicode": "\ue68b"
|
||||
},
|
||||
{
|
||||
"font_class": "weixin",
|
||||
"unicode": "\ue691"
|
||||
}
|
||||
] as IconsDataItem[]
|
||||
|
||||
// export const fontData = JSON.parse<IconsDataItem>(fontDataJson)
|
||||
@@ -0,0 +1,649 @@
|
||||
|
||||
export const fontData = [
|
||||
{
|
||||
"font_class": "arrow-down",
|
||||
"unicode": "\ue6be"
|
||||
},
|
||||
{
|
||||
"font_class": "arrow-left",
|
||||
"unicode": "\ue6bc"
|
||||
},
|
||||
{
|
||||
"font_class": "arrow-right",
|
||||
"unicode": "\ue6bb"
|
||||
},
|
||||
{
|
||||
"font_class": "arrow-up",
|
||||
"unicode": "\ue6bd"
|
||||
},
|
||||
{
|
||||
"font_class": "auth",
|
||||
"unicode": "\ue6ab"
|
||||
},
|
||||
{
|
||||
"font_class": "auth-filled",
|
||||
"unicode": "\ue6cc"
|
||||
},
|
||||
{
|
||||
"font_class": "back",
|
||||
"unicode": "\ue6b9"
|
||||
},
|
||||
{
|
||||
"font_class": "bars",
|
||||
"unicode": "\ue627"
|
||||
},
|
||||
{
|
||||
"font_class": "calendar",
|
||||
"unicode": "\ue6a0"
|
||||
},
|
||||
{
|
||||
"font_class": "calendar-filled",
|
||||
"unicode": "\ue6c0"
|
||||
},
|
||||
{
|
||||
"font_class": "camera",
|
||||
"unicode": "\ue65a"
|
||||
},
|
||||
{
|
||||
"font_class": "camera-filled",
|
||||
"unicode": "\ue658"
|
||||
},
|
||||
{
|
||||
"font_class": "cart",
|
||||
"unicode": "\ue631"
|
||||
},
|
||||
{
|
||||
"font_class": "cart-filled",
|
||||
"unicode": "\ue6d0"
|
||||
},
|
||||
{
|
||||
"font_class": "chat",
|
||||
"unicode": "\ue65d"
|
||||
},
|
||||
{
|
||||
"font_class": "chat-filled",
|
||||
"unicode": "\ue659"
|
||||
},
|
||||
{
|
||||
"font_class": "chatboxes",
|
||||
"unicode": "\ue696"
|
||||
},
|
||||
{
|
||||
"font_class": "chatboxes-filled",
|
||||
"unicode": "\ue692"
|
||||
},
|
||||
{
|
||||
"font_class": "chatbubble",
|
||||
"unicode": "\ue697"
|
||||
},
|
||||
{
|
||||
"font_class": "chatbubble-filled",
|
||||
"unicode": "\ue694"
|
||||
},
|
||||
{
|
||||
"font_class": "checkbox",
|
||||
"unicode": "\ue62b"
|
||||
},
|
||||
{
|
||||
"font_class": "checkbox-filled",
|
||||
"unicode": "\ue62c"
|
||||
},
|
||||
{
|
||||
"font_class": "checkmarkempty",
|
||||
"unicode": "\ue65c"
|
||||
},
|
||||
{
|
||||
"font_class": "circle",
|
||||
"unicode": "\ue65b"
|
||||
},
|
||||
{
|
||||
"font_class": "circle-filled",
|
||||
"unicode": "\ue65e"
|
||||
},
|
||||
{
|
||||
"font_class": "clear",
|
||||
"unicode": "\ue66d"
|
||||
},
|
||||
{
|
||||
"font_class": "close",
|
||||
"unicode": "\ue673"
|
||||
},
|
||||
{
|
||||
"font_class": "closeempty",
|
||||
"unicode": "\ue66c"
|
||||
},
|
||||
{
|
||||
"font_class": "cloud-download",
|
||||
"unicode": "\ue647"
|
||||
},
|
||||
{
|
||||
"font_class": "cloud-download-filled",
|
||||
"unicode": "\ue646"
|
||||
},
|
||||
{
|
||||
"font_class": "cloud-upload",
|
||||
"unicode": "\ue645"
|
||||
},
|
||||
{
|
||||
"font_class": "cloud-upload-filled",
|
||||
"unicode": "\ue648"
|
||||
},
|
||||
{
|
||||
"font_class": "color",
|
||||
"unicode": "\ue6cf"
|
||||
},
|
||||
{
|
||||
"font_class": "color-filled",
|
||||
"unicode": "\ue6c9"
|
||||
},
|
||||
{
|
||||
"font_class": "compose",
|
||||
"unicode": "\ue67f"
|
||||
},
|
||||
{
|
||||
"font_class": "contact",
|
||||
"unicode": "\ue693"
|
||||
},
|
||||
{
|
||||
"font_class": "contact-filled",
|
||||
"unicode": "\ue695"
|
||||
},
|
||||
{
|
||||
"font_class": "down",
|
||||
"unicode": "\ue6b8"
|
||||
},
|
||||
{
|
||||
"font_class": "bottom",
|
||||
"unicode": "\ue6b8"
|
||||
},
|
||||
{
|
||||
"font_class": "download",
|
||||
"unicode": "\ue68d"
|
||||
},
|
||||
{
|
||||
"font_class": "download-filled",
|
||||
"unicode": "\ue681"
|
||||
},
|
||||
{
|
||||
"font_class": "email",
|
||||
"unicode": "\ue69e"
|
||||
},
|
||||
{
|
||||
"font_class": "email-filled",
|
||||
"unicode": "\ue69a"
|
||||
},
|
||||
{
|
||||
"font_class": "eye",
|
||||
"unicode": "\ue651"
|
||||
},
|
||||
{
|
||||
"font_class": "eye-filled",
|
||||
"unicode": "\ue66a"
|
||||
},
|
||||
{
|
||||
"font_class": "eye-slash",
|
||||
"unicode": "\ue6b3"
|
||||
},
|
||||
{
|
||||
"font_class": "eye-slash-filled",
|
||||
"unicode": "\ue6b4"
|
||||
},
|
||||
{
|
||||
"font_class": "fire",
|
||||
"unicode": "\ue6a1"
|
||||
},
|
||||
{
|
||||
"font_class": "fire-filled",
|
||||
"unicode": "\ue6c5"
|
||||
},
|
||||
{
|
||||
"font_class": "flag",
|
||||
"unicode": "\ue65f"
|
||||
},
|
||||
{
|
||||
"font_class": "flag-filled",
|
||||
"unicode": "\ue660"
|
||||
},
|
||||
{
|
||||
"font_class": "folder-add",
|
||||
"unicode": "\ue6a9"
|
||||
},
|
||||
{
|
||||
"font_class": "folder-add-filled",
|
||||
"unicode": "\ue6c8"
|
||||
},
|
||||
{
|
||||
"font_class": "font",
|
||||
"unicode": "\ue6a3"
|
||||
},
|
||||
{
|
||||
"font_class": "forward",
|
||||
"unicode": "\ue6ba"
|
||||
},
|
||||
{
|
||||
"font_class": "gear",
|
||||
"unicode": "\ue664"
|
||||
},
|
||||
{
|
||||
"font_class": "gear-filled",
|
||||
"unicode": "\ue661"
|
||||
},
|
||||
{
|
||||
"font_class": "gift",
|
||||
"unicode": "\ue6a4"
|
||||
},
|
||||
{
|
||||
"font_class": "gift-filled",
|
||||
"unicode": "\ue6c4"
|
||||
},
|
||||
{
|
||||
"font_class": "hand-down",
|
||||
"unicode": "\ue63d"
|
||||
},
|
||||
{
|
||||
"font_class": "hand-down-filled",
|
||||
"unicode": "\ue63c"
|
||||
},
|
||||
{
|
||||
"font_class": "hand-up",
|
||||
"unicode": "\ue63f"
|
||||
},
|
||||
{
|
||||
"font_class": "hand-up-filled",
|
||||
"unicode": "\ue63e"
|
||||
},
|
||||
{
|
||||
"font_class": "headphones",
|
||||
"unicode": "\ue630"
|
||||
},
|
||||
{
|
||||
"font_class": "heart",
|
||||
"unicode": "\ue639"
|
||||
},
|
||||
{
|
||||
"font_class": "heart-filled",
|
||||
"unicode": "\ue641"
|
||||
},
|
||||
{
|
||||
"font_class": "help",
|
||||
"unicode": "\ue679"
|
||||
},
|
||||
{
|
||||
"font_class": "help-filled",
|
||||
"unicode": "\ue674"
|
||||
},
|
||||
{
|
||||
"font_class": "home",
|
||||
"unicode": "\ue662"
|
||||
},
|
||||
{
|
||||
"font_class": "home-filled",
|
||||
"unicode": "\ue663"
|
||||
},
|
||||
{
|
||||
"font_class": "image",
|
||||
"unicode": "\ue670"
|
||||
},
|
||||
{
|
||||
"font_class": "image-filled",
|
||||
"unicode": "\ue678"
|
||||
},
|
||||
{
|
||||
"font_class": "images",
|
||||
"unicode": "\ue650"
|
||||
},
|
||||
{
|
||||
"font_class": "images-filled",
|
||||
"unicode": "\ue64b"
|
||||
},
|
||||
{
|
||||
"font_class": "info",
|
||||
"unicode": "\ue669"
|
||||
},
|
||||
{
|
||||
"font_class": "info-filled",
|
||||
"unicode": "\ue649"
|
||||
},
|
||||
{
|
||||
"font_class": "left",
|
||||
"unicode": "\ue6b7"
|
||||
},
|
||||
{
|
||||
"font_class": "link",
|
||||
"unicode": "\ue6a5"
|
||||
},
|
||||
{
|
||||
"font_class": "list",
|
||||
"unicode": "\ue644"
|
||||
},
|
||||
{
|
||||
"font_class": "location",
|
||||
"unicode": "\ue6ae"
|
||||
},
|
||||
{
|
||||
"font_class": "location-filled",
|
||||
"unicode": "\ue6af"
|
||||
},
|
||||
{
|
||||
"font_class": "locked",
|
||||
"unicode": "\ue66b"
|
||||
},
|
||||
{
|
||||
"font_class": "locked-filled",
|
||||
"unicode": "\ue668"
|
||||
},
|
||||
{
|
||||
"font_class": "loop",
|
||||
"unicode": "\ue633"
|
||||
},
|
||||
{
|
||||
"font_class": "mail-open",
|
||||
"unicode": "\ue643"
|
||||
},
|
||||
{
|
||||
"font_class": "mail-open-filled",
|
||||
"unicode": "\ue63a"
|
||||
},
|
||||
{
|
||||
"font_class": "map",
|
||||
"unicode": "\ue667"
|
||||
},
|
||||
{
|
||||
"font_class": "map-filled",
|
||||
"unicode": "\ue666"
|
||||
},
|
||||
{
|
||||
"font_class": "map-pin",
|
||||
"unicode": "\ue6ad"
|
||||
},
|
||||
{
|
||||
"font_class": "map-pin-ellipse",
|
||||
"unicode": "\ue6ac"
|
||||
},
|
||||
{
|
||||
"font_class": "medal",
|
||||
"unicode": "\ue6a2"
|
||||
},
|
||||
{
|
||||
"font_class": "medal-filled",
|
||||
"unicode": "\ue6c3"
|
||||
},
|
||||
{
|
||||
"font_class": "mic",
|
||||
"unicode": "\ue671"
|
||||
},
|
||||
{
|
||||
"font_class": "mic-filled",
|
||||
"unicode": "\ue677"
|
||||
},
|
||||
{
|
||||
"font_class": "micoff",
|
||||
"unicode": "\ue67e"
|
||||
},
|
||||
{
|
||||
"font_class": "micoff-filled",
|
||||
"unicode": "\ue6b0"
|
||||
},
|
||||
{
|
||||
"font_class": "minus",
|
||||
"unicode": "\ue66f"
|
||||
},
|
||||
{
|
||||
"font_class": "minus-filled",
|
||||
"unicode": "\ue67d"
|
||||
},
|
||||
{
|
||||
"font_class": "more",
|
||||
"unicode": "\ue64d"
|
||||
},
|
||||
{
|
||||
"font_class": "more-filled",
|
||||
"unicode": "\ue64e"
|
||||
},
|
||||
{
|
||||
"font_class": "navigate",
|
||||
"unicode": "\ue66e"
|
||||
},
|
||||
{
|
||||
"font_class": "navigate-filled",
|
||||
"unicode": "\ue67a"
|
||||
},
|
||||
{
|
||||
"font_class": "notification",
|
||||
"unicode": "\ue6a6"
|
||||
},
|
||||
{
|
||||
"font_class": "notification-filled",
|
||||
"unicode": "\ue6c1"
|
||||
},
|
||||
{
|
||||
"font_class": "paperclip",
|
||||
"unicode": "\ue652"
|
||||
},
|
||||
{
|
||||
"font_class": "paperplane",
|
||||
"unicode": "\ue672"
|
||||
},
|
||||
{
|
||||
"font_class": "paperplane-filled",
|
||||
"unicode": "\ue675"
|
||||
},
|
||||
{
|
||||
"font_class": "person",
|
||||
"unicode": "\ue699"
|
||||
},
|
||||
{
|
||||
"font_class": "person-filled",
|
||||
"unicode": "\ue69d"
|
||||
},
|
||||
{
|
||||
"font_class": "personadd",
|
||||
"unicode": "\ue69f"
|
||||
},
|
||||
{
|
||||
"font_class": "personadd-filled",
|
||||
"unicode": "\ue698"
|
||||
},
|
||||
{
|
||||
"font_class": "personadd-filled-copy",
|
||||
"unicode": "\ue6d1"
|
||||
},
|
||||
{
|
||||
"font_class": "phone",
|
||||
"unicode": "\ue69c"
|
||||
},
|
||||
{
|
||||
"font_class": "phone-filled",
|
||||
"unicode": "\ue69b"
|
||||
},
|
||||
{
|
||||
"font_class": "plus",
|
||||
"unicode": "\ue676"
|
||||
},
|
||||
{
|
||||
"font_class": "plus-filled",
|
||||
"unicode": "\ue6c7"
|
||||
},
|
||||
{
|
||||
"font_class": "plusempty",
|
||||
"unicode": "\ue67b"
|
||||
},
|
||||
{
|
||||
"font_class": "pulldown",
|
||||
"unicode": "\ue632"
|
||||
},
|
||||
{
|
||||
"font_class": "pyq",
|
||||
"unicode": "\ue682"
|
||||
},
|
||||
{
|
||||
"font_class": "qq",
|
||||
"unicode": "\ue680"
|
||||
},
|
||||
{
|
||||
"font_class": "redo",
|
||||
"unicode": "\ue64a"
|
||||
},
|
||||
{
|
||||
"font_class": "redo-filled",
|
||||
"unicode": "\ue655"
|
||||
},
|
||||
{
|
||||
"font_class": "refresh",
|
||||
"unicode": "\ue657"
|
||||
},
|
||||
{
|
||||
"font_class": "refresh-filled",
|
||||
"unicode": "\ue656"
|
||||
},
|
||||
{
|
||||
"font_class": "refreshempty",
|
||||
"unicode": "\ue6bf"
|
||||
},
|
||||
{
|
||||
"font_class": "reload",
|
||||
"unicode": "\ue6b2"
|
||||
},
|
||||
{
|
||||
"font_class": "right",
|
||||
"unicode": "\ue6b5"
|
||||
},
|
||||
{
|
||||
"font_class": "scan",
|
||||
"unicode": "\ue62a"
|
||||
},
|
||||
{
|
||||
"font_class": "search",
|
||||
"unicode": "\ue654"
|
||||
},
|
||||
{
|
||||
"font_class": "settings",
|
||||
"unicode": "\ue653"
|
||||
},
|
||||
{
|
||||
"font_class": "settings-filled",
|
||||
"unicode": "\ue6ce"
|
||||
},
|
||||
{
|
||||
"font_class": "shop",
|
||||
"unicode": "\ue62f"
|
||||
},
|
||||
{
|
||||
"font_class": "shop-filled",
|
||||
"unicode": "\ue6cd"
|
||||
},
|
||||
{
|
||||
"font_class": "smallcircle",
|
||||
"unicode": "\ue67c"
|
||||
},
|
||||
{
|
||||
"font_class": "smallcircle-filled",
|
||||
"unicode": "\ue665"
|
||||
},
|
||||
{
|
||||
"font_class": "sound",
|
||||
"unicode": "\ue684"
|
||||
},
|
||||
{
|
||||
"font_class": "sound-filled",
|
||||
"unicode": "\ue686"
|
||||
},
|
||||
{
|
||||
"font_class": "spinner-cycle",
|
||||
"unicode": "\ue68a"
|
||||
},
|
||||
{
|
||||
"font_class": "staff",
|
||||
"unicode": "\ue6a7"
|
||||
},
|
||||
{
|
||||
"font_class": "staff-filled",
|
||||
"unicode": "\ue6cb"
|
||||
},
|
||||
{
|
||||
"font_class": "star",
|
||||
"unicode": "\ue688"
|
||||
},
|
||||
{
|
||||
"font_class": "star-filled",
|
||||
"unicode": "\ue68f"
|
||||
},
|
||||
{
|
||||
"font_class": "starhalf",
|
||||
"unicode": "\ue683"
|
||||
},
|
||||
{
|
||||
"font_class": "trash",
|
||||
"unicode": "\ue687"
|
||||
},
|
||||
{
|
||||
"font_class": "trash-filled",
|
||||
"unicode": "\ue685"
|
||||
},
|
||||
{
|
||||
"font_class": "tune",
|
||||
"unicode": "\ue6aa"
|
||||
},
|
||||
{
|
||||
"font_class": "tune-filled",
|
||||
"unicode": "\ue6ca"
|
||||
},
|
||||
{
|
||||
"font_class": "undo",
|
||||
"unicode": "\ue64f"
|
||||
},
|
||||
{
|
||||
"font_class": "undo-filled",
|
||||
"unicode": "\ue64c"
|
||||
},
|
||||
{
|
||||
"font_class": "up",
|
||||
"unicode": "\ue6b6"
|
||||
},
|
||||
{
|
||||
"font_class": "top",
|
||||
"unicode": "\ue6b6"
|
||||
},
|
||||
{
|
||||
"font_class": "upload",
|
||||
"unicode": "\ue690"
|
||||
},
|
||||
{
|
||||
"font_class": "upload-filled",
|
||||
"unicode": "\ue68e"
|
||||
},
|
||||
{
|
||||
"font_class": "videocam",
|
||||
"unicode": "\ue68c"
|
||||
},
|
||||
{
|
||||
"font_class": "videocam-filled",
|
||||
"unicode": "\ue689"
|
||||
},
|
||||
{
|
||||
"font_class": "vip",
|
||||
"unicode": "\ue6a8"
|
||||
},
|
||||
{
|
||||
"font_class": "vip-filled",
|
||||
"unicode": "\ue6c6"
|
||||
},
|
||||
{
|
||||
"font_class": "wallet",
|
||||
"unicode": "\ue6b1"
|
||||
},
|
||||
{
|
||||
"font_class": "wallet-filled",
|
||||
"unicode": "\ue6c2"
|
||||
},
|
||||
{
|
||||
"font_class": "weibo",
|
||||
"unicode": "\ue68b"
|
||||
},
|
||||
{
|
||||
"font_class": "weixin",
|
||||
"unicode": "\ue691"
|
||||
}
|
||||
]
|
||||
|
||||
// export const fontData = JSON.parse<IconsDataItem>(fontDataJson)
|
||||
@@ -0,0 +1,111 @@
|
||||
{
|
||||
"id": "uni-icons",
|
||||
"displayName": "uni-icons 图标",
|
||||
"version": "2.0.12",
|
||||
"description": "图标组件,用于展示移动端常见的图标,可自定义颜色、大小。",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
"uniui",
|
||||
"icon",
|
||||
"图标"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.2.14",
|
||||
"uni-app": "^4.08",
|
||||
"uni-app-x": "^4.61"
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue",
|
||||
"darkmode": "x",
|
||||
"i18n": "x",
|
||||
"widescreen": "x"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
"uni-scss"
|
||||
],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "x",
|
||||
"aliyun": "x",
|
||||
"alipay": "x"
|
||||
},
|
||||
"client": {
|
||||
"uni-app": {
|
||||
"vue": {
|
||||
"vue2": "√",
|
||||
"vue3": "√"
|
||||
},
|
||||
"web": {
|
||||
"safari": "√",
|
||||
"chrome": "√"
|
||||
},
|
||||
"app": {
|
||||
"vue": "√",
|
||||
"nvue": "-",
|
||||
"android": {
|
||||
"extVersion": "",
|
||||
"minVersion": "29"
|
||||
},
|
||||
"ios": "√",
|
||||
"harmony": "√"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": "√",
|
||||
"alipay": "√",
|
||||
"toutiao": "√",
|
||||
"baidu": "√",
|
||||
"kuaishou": "-",
|
||||
"jd": "-",
|
||||
"harmony": "-",
|
||||
"qq": "√",
|
||||
"lark": "-"
|
||||
},
|
||||
"quickapp": {
|
||||
"huawei": "√",
|
||||
"union": "√"
|
||||
}
|
||||
},
|
||||
"uni-app-x": {
|
||||
"web": {
|
||||
"safari": "√",
|
||||
"chrome": "√"
|
||||
},
|
||||
"app": {
|
||||
"android": {
|
||||
"extVersion": "",
|
||||
"minVersion": "29"
|
||||
},
|
||||
"ios": "√",
|
||||
"harmony": "√"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": "√"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
## Icons 图标
|
||||
> **组件名:uni-icons**
|
||||
> 代码块: `uIcons`
|
||||
|
||||
用于展示 icons 图标 。
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-icons)
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||
@@ -0,0 +1,8 @@
|
||||
## 1.0.3(2022-01-21)
|
||||
- 优化 组件示例
|
||||
## 1.0.2(2021-11-22)
|
||||
- 修复 / 符号在 vue 不同版本兼容问题引起的报错问题
|
||||
## 1.0.1(2021-11-22)
|
||||
- 修复 vue3中scss语法兼容问题
|
||||
## 1.0.0(2021-11-18)
|
||||
- init
|
||||
@@ -0,0 +1 @@
|
||||
@import './styles/index.scss';
|
||||
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"id": "uni-scss",
|
||||
"displayName": "uni-scss 辅助样式",
|
||||
"version": "1.0.3",
|
||||
"description": "uni-sass是uni-ui提供的一套全局样式 ,通过一些简单的类名和sass变量,实现简单的页面布局操作,比如颜色、边距、圆角等。",
|
||||
"keywords": [
|
||||
"uni-scss",
|
||||
"uni-ui",
|
||||
"辅助样式"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.0"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"JS SDK",
|
||||
"通用 SDK"
|
||||
],
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "n",
|
||||
"联盟": "n"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
`uni-sass` 是 `uni-ui`提供的一套全局样式 ,通过一些简单的类名和`sass`变量,实现简单的页面布局操作,比如颜色、边距、圆角等。
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-sass)
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||
@@ -0,0 +1,7 @@
|
||||
@import './setting/_variables.scss';
|
||||
@import './setting/_border.scss';
|
||||
@import './setting/_color.scss';
|
||||
@import './setting/_space.scss';
|
||||
@import './setting/_radius.scss';
|
||||
@import './setting/_text.scss';
|
||||
@import './setting/_styles.scss';
|
||||
@@ -0,0 +1,3 @@
|
||||
.uni-border {
|
||||
border: 1px $uni-border-1 solid;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
// TODO 暂时不需要 class ,需要用户使用变量实现 ,如果使用类名其实并不推荐
|
||||
// @mixin get-styles($k,$c) {
|
||||
// @if $k == size or $k == weight{
|
||||
// font-#{$k}:#{$c}
|
||||
// }@else{
|
||||
// #{$k}:#{$c}
|
||||
// }
|
||||
// }
|
||||
$uni-ui-color:(
|
||||
// 主色
|
||||
primary: $uni-primary,
|
||||
primary-disable: $uni-primary-disable,
|
||||
primary-light: $uni-primary-light,
|
||||
// 辅助色
|
||||
success: $uni-success,
|
||||
success-disable: $uni-success-disable,
|
||||
success-light: $uni-success-light,
|
||||
warning: $uni-warning,
|
||||
warning-disable: $uni-warning-disable,
|
||||
warning-light: $uni-warning-light,
|
||||
error: $uni-error,
|
||||
error-disable: $uni-error-disable,
|
||||
error-light: $uni-error-light,
|
||||
info: $uni-info,
|
||||
info-disable: $uni-info-disable,
|
||||
info-light: $uni-info-light,
|
||||
// 中性色
|
||||
main-color: $uni-main-color,
|
||||
base-color: $uni-base-color,
|
||||
secondary-color: $uni-secondary-color,
|
||||
extra-color: $uni-extra-color,
|
||||
// 背景色
|
||||
bg-color: $uni-bg-color,
|
||||
// 边框颜色
|
||||
border-1: $uni-border-1,
|
||||
border-2: $uni-border-2,
|
||||
border-3: $uni-border-3,
|
||||
border-4: $uni-border-4,
|
||||
// 黑色
|
||||
black:$uni-black,
|
||||
// 白色
|
||||
white:$uni-white,
|
||||
// 透明
|
||||
transparent:$uni-transparent
|
||||
) !default;
|
||||
@each $key, $child in $uni-ui-color {
|
||||
.uni-#{"" + $key} {
|
||||
color: $child;
|
||||
}
|
||||
.uni-#{"" + $key}-bg {
|
||||
background-color: $child;
|
||||
}
|
||||
}
|
||||
.uni-shadow-sm {
|
||||
box-shadow: $uni-shadow-sm;
|
||||
}
|
||||
.uni-shadow-base {
|
||||
box-shadow: $uni-shadow-base;
|
||||
}
|
||||
.uni-shadow-lg {
|
||||
box-shadow: $uni-shadow-lg;
|
||||
}
|
||||
.uni-mask {
|
||||
background-color:$uni-mask;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
@mixin radius($r,$d:null ,$important: false){
|
||||
$radius-value:map-get($uni-radius, $r) if($important, !important, null);
|
||||
// Key exists within the $uni-radius variable
|
||||
@if (map-has-key($uni-radius, $r) and $d){
|
||||
@if $d == t {
|
||||
border-top-left-radius:$radius-value;
|
||||
border-top-right-radius:$radius-value;
|
||||
}@else if $d == r {
|
||||
border-top-right-radius:$radius-value;
|
||||
border-bottom-right-radius:$radius-value;
|
||||
}@else if $d == b {
|
||||
border-bottom-left-radius:$radius-value;
|
||||
border-bottom-right-radius:$radius-value;
|
||||
}@else if $d == l {
|
||||
border-top-left-radius:$radius-value;
|
||||
border-bottom-left-radius:$radius-value;
|
||||
}@else if $d == tl {
|
||||
border-top-left-radius:$radius-value;
|
||||
}@else if $d == tr {
|
||||
border-top-right-radius:$radius-value;
|
||||
}@else if $d == br {
|
||||
border-bottom-right-radius:$radius-value;
|
||||
}@else if $d == bl {
|
||||
border-bottom-left-radius:$radius-value;
|
||||
}
|
||||
}@else{
|
||||
border-radius:$radius-value;
|
||||
}
|
||||
}
|
||||
|
||||
@each $key, $child in $uni-radius {
|
||||
@if($key){
|
||||
.uni-radius-#{"" + $key} {
|
||||
@include radius($key)
|
||||
}
|
||||
}@else{
|
||||
.uni-radius {
|
||||
@include radius($key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@each $direction in t, r, b, l,tl, tr, br, bl {
|
||||
@each $key, $child in $uni-radius {
|
||||
@if($key){
|
||||
.uni-radius-#{"" + $direction}-#{"" + $key} {
|
||||
@include radius($key,$direction,false)
|
||||
}
|
||||
}@else{
|
||||
.uni-radius-#{$direction} {
|
||||
@include radius($key,$direction,false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
@mixin fn($space,$direction,$size,$n) {
|
||||
@if $n {
|
||||
#{$space}-#{$direction}: #{$size*$uni-space-root}px
|
||||
} @else {
|
||||
#{$space}-#{$direction}: #{-$size*$uni-space-root}px
|
||||
}
|
||||
}
|
||||
@mixin get-styles($direction,$i,$space,$n){
|
||||
@if $direction == t {
|
||||
@include fn($space, top,$i,$n);
|
||||
}
|
||||
@if $direction == r {
|
||||
@include fn($space, right,$i,$n);
|
||||
}
|
||||
@if $direction == b {
|
||||
@include fn($space, bottom,$i,$n);
|
||||
}
|
||||
@if $direction == l {
|
||||
@include fn($space, left,$i,$n);
|
||||
}
|
||||
@if $direction == x {
|
||||
@include fn($space, left,$i,$n);
|
||||
@include fn($space, right,$i,$n);
|
||||
}
|
||||
@if $direction == y {
|
||||
@include fn($space, top,$i,$n);
|
||||
@include fn($space, bottom,$i,$n);
|
||||
}
|
||||
@if $direction == a {
|
||||
@if $n {
|
||||
#{$space}:#{$i*$uni-space-root}px;
|
||||
} @else {
|
||||
#{$space}:#{-$i*$uni-space-root}px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@each $orientation in m,p {
|
||||
$space: margin;
|
||||
@if $orientation == m {
|
||||
$space: margin;
|
||||
} @else {
|
||||
$space: padding;
|
||||
}
|
||||
@for $i from 0 through 16 {
|
||||
@each $direction in t, r, b, l, x, y, a {
|
||||
.uni-#{$orientation}#{$direction}-#{$i} {
|
||||
@include get-styles($direction,$i,$space,true);
|
||||
}
|
||||
.uni-#{$orientation}#{$direction}-n#{$i} {
|
||||
@include get-styles($direction,$i,$space,false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/* #ifndef APP-NVUE */
|
||||
|
||||
$-color-white:#fff;
|
||||
$-color-black:#000;
|
||||
@mixin base-style($color) {
|
||||
color: #fff;
|
||||
background-color: $color;
|
||||
border-color: mix($-color-black, $color, 8%);
|
||||
&:not([hover-class]):active {
|
||||
background: mix($-color-black, $color, 10%);
|
||||
border-color: mix($-color-black, $color, 20%);
|
||||
color: $-color-white;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
@mixin is-color($color) {
|
||||
@include base-style($color);
|
||||
&[loading] {
|
||||
@include base-style($color);
|
||||
&::before {
|
||||
margin-right:5px;
|
||||
}
|
||||
}
|
||||
&[disabled] {
|
||||
&,
|
||||
&[loading],
|
||||
&:not([hover-class]):active {
|
||||
color: $-color-white;
|
||||
border-color: mix(darken($color,10%), $-color-white);
|
||||
background-color: mix($color, $-color-white);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@mixin base-plain-style($color) {
|
||||
color:$color;
|
||||
background-color: mix($-color-white, $color, 90%);
|
||||
border-color: mix($-color-white, $color, 70%);
|
||||
&:not([hover-class]):active {
|
||||
background: mix($-color-white, $color, 80%);
|
||||
color: $color;
|
||||
outline: none;
|
||||
border-color: mix($-color-white, $color, 50%);
|
||||
}
|
||||
}
|
||||
@mixin is-plain($color){
|
||||
&[plain] {
|
||||
@include base-plain-style($color);
|
||||
&[loading] {
|
||||
@include base-plain-style($color);
|
||||
&::before {
|
||||
margin-right:5px;
|
||||
}
|
||||
}
|
||||
&[disabled] {
|
||||
&,
|
||||
&:active {
|
||||
color: mix($-color-white, $color, 40%);
|
||||
background-color: mix($-color-white, $color, 90%);
|
||||
border-color: mix($-color-white, $color, 80%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.uni-btn {
|
||||
margin: 5px;
|
||||
color: #393939;
|
||||
border:1px solid #ccc;
|
||||
font-size: 16px;
|
||||
font-weight: 200;
|
||||
background-color: #F9F9F9;
|
||||
// TODO 暂时处理边框隐藏一边的问题
|
||||
overflow: visible;
|
||||
&::after{
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:not([type]),&[type=default] {
|
||||
color: #999;
|
||||
&[loading] {
|
||||
background: none;
|
||||
&::before {
|
||||
margin-right:5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
&[disabled]{
|
||||
color: mix($-color-white, #999, 60%);
|
||||
&,
|
||||
&[loading],
|
||||
&:active {
|
||||
color: mix($-color-white, #999, 60%);
|
||||
background-color: mix($-color-white,$-color-black , 98%);
|
||||
border-color: mix($-color-white, #999, 85%);
|
||||
}
|
||||
}
|
||||
|
||||
&[plain] {
|
||||
color: #999;
|
||||
background: none;
|
||||
border-color: $uni-border-1;
|
||||
&:not([hover-class]):active {
|
||||
background: none;
|
||||
color: mix($-color-white, $-color-black, 80%);
|
||||
border-color: mix($-color-white, $-color-black, 90%);
|
||||
outline: none;
|
||||
}
|
||||
&[disabled]{
|
||||
&,
|
||||
&[loading],
|
||||
&:active {
|
||||
background: none;
|
||||
color: mix($-color-white, #999, 60%);
|
||||
border-color: mix($-color-white, #999, 85%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not([hover-class]):active {
|
||||
color: mix($-color-white, $-color-black, 50%);
|
||||
}
|
||||
|
||||
&[size=mini] {
|
||||
font-size: 16px;
|
||||
font-weight: 200;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
&.uni-btn-small {
|
||||
font-size: 14px;
|
||||
}
|
||||
&.uni-btn-mini {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&.uni-btn-radius {
|
||||
border-radius: 999px;
|
||||
}
|
||||
&[type=primary] {
|
||||
@include is-color($uni-primary);
|
||||
@include is-plain($uni-primary)
|
||||
}
|
||||
&[type=success] {
|
||||
@include is-color($uni-success);
|
||||
@include is-plain($uni-success)
|
||||
}
|
||||
&[type=error] {
|
||||
@include is-color($uni-error);
|
||||
@include is-plain($uni-error)
|
||||
}
|
||||
&[type=warning] {
|
||||
@include is-color($uni-warning);
|
||||
@include is-plain($uni-warning)
|
||||
}
|
||||
&[type=info] {
|
||||
@include is-color($uni-info);
|
||||
@include is-plain($uni-info)
|
||||
}
|
||||
}
|
||||
/* #endif */
|
||||
@@ -0,0 +1,24 @@
|
||||
@mixin get-styles($k,$c) {
|
||||
@if $k == size or $k == weight{
|
||||
font-#{$k}:#{$c}
|
||||
}@else{
|
||||
#{$k}:#{$c}
|
||||
}
|
||||
}
|
||||
|
||||
@each $key, $child in $uni-headings {
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-#{$key} {
|
||||
@each $k, $c in $child {
|
||||
@include get-styles($k,$c)
|
||||
}
|
||||
}
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
.container .uni-#{$key} {
|
||||
@each $k, $c in $child {
|
||||
@include get-styles($k,$c)
|
||||
}
|
||||
}
|
||||
/* #endif */
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// @use "sass:math";
|
||||
@import '../tools/functions.scss';
|
||||
// 间距基础倍数
|
||||
$uni-space-root: 2 !default;
|
||||
// 边框半径默认值
|
||||
$uni-radius-root:5px !default;
|
||||
$uni-radius: () !default;
|
||||
// 边框半径断点
|
||||
$uni-radius: map-deep-merge(
|
||||
(
|
||||
0: 0,
|
||||
// TODO 当前版本暂时不支持 sm 属性
|
||||
// 'sm': math.div($uni-radius-root, 2),
|
||||
null: $uni-radius-root,
|
||||
'lg': $uni-radius-root * 2,
|
||||
'xl': $uni-radius-root * 6,
|
||||
'pill': 9999px,
|
||||
'circle': 50%
|
||||
),
|
||||
$uni-radius
|
||||
);
|
||||
// 字体家族
|
||||
$body-font-family: 'Roboto', sans-serif !default;
|
||||
// 文本
|
||||
$heading-font-family: $body-font-family !default;
|
||||
$uni-headings: () !default;
|
||||
$letterSpacing: -0.01562em;
|
||||
$uni-headings: map-deep-merge(
|
||||
(
|
||||
'h1': (
|
||||
size: 32px,
|
||||
weight: 300,
|
||||
line-height: 50px,
|
||||
// letter-spacing:-0.01562em
|
||||
),
|
||||
'h2': (
|
||||
size: 28px,
|
||||
weight: 300,
|
||||
line-height: 40px,
|
||||
// letter-spacing: -0.00833em
|
||||
),
|
||||
'h3': (
|
||||
size: 24px,
|
||||
weight: 400,
|
||||
line-height: 32px,
|
||||
// letter-spacing: normal
|
||||
),
|
||||
'h4': (
|
||||
size: 20px,
|
||||
weight: 400,
|
||||
line-height: 30px,
|
||||
// letter-spacing: 0.00735em
|
||||
),
|
||||
'h5': (
|
||||
size: 16px,
|
||||
weight: 400,
|
||||
line-height: 24px,
|
||||
// letter-spacing: normal
|
||||
),
|
||||
'h6': (
|
||||
size: 14px,
|
||||
weight: 500,
|
||||
line-height: 18px,
|
||||
// letter-spacing: 0.0125em
|
||||
),
|
||||
'subtitle': (
|
||||
size: 12px,
|
||||
weight: 400,
|
||||
line-height: 20px,
|
||||
// letter-spacing: 0.00937em
|
||||
),
|
||||
'body': (
|
||||
font-size: 14px,
|
||||
font-weight: 400,
|
||||
line-height: 22px,
|
||||
// letter-spacing: 0.03125em
|
||||
),
|
||||
'caption': (
|
||||
'size': 12px,
|
||||
'weight': 400,
|
||||
'line-height': 20px,
|
||||
// 'letter-spacing': 0.03333em,
|
||||
// 'text-transform': false
|
||||
)
|
||||
),
|
||||
$uni-headings
|
||||
);
|
||||
|
||||
|
||||
|
||||
// 主色
|
||||
$uni-primary: #2979ff !default;
|
||||
$uni-primary-disable:lighten($uni-primary,20%) !default;
|
||||
$uni-primary-light: lighten($uni-primary,25%) !default;
|
||||
|
||||
// 辅助色
|
||||
// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。
|
||||
$uni-success: #18bc37 !default;
|
||||
$uni-success-disable:lighten($uni-success,20%) !default;
|
||||
$uni-success-light: lighten($uni-success,25%) !default;
|
||||
|
||||
$uni-warning: #f3a73f !default;
|
||||
$uni-warning-disable:lighten($uni-warning,20%) !default;
|
||||
$uni-warning-light: lighten($uni-warning,25%) !default;
|
||||
|
||||
$uni-error: #e43d33 !default;
|
||||
$uni-error-disable:lighten($uni-error,20%) !default;
|
||||
$uni-error-light: lighten($uni-error,25%) !default;
|
||||
|
||||
$uni-info: #8f939c !default;
|
||||
$uni-info-disable:lighten($uni-info,20%) !default;
|
||||
$uni-info-light: lighten($uni-info,25%) !default;
|
||||
|
||||
// 中性色
|
||||
// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。
|
||||
$uni-main-color: #3a3a3a !default; // 主要文字
|
||||
$uni-base-color: #6a6a6a !default; // 常规文字
|
||||
$uni-secondary-color: #909399 !default; // 次要文字
|
||||
$uni-extra-color: #c7c7c7 !default; // 辅助说明
|
||||
|
||||
// 边框颜色
|
||||
$uni-border-1: #F0F0F0 !default;
|
||||
$uni-border-2: #EDEDED !default;
|
||||
$uni-border-3: #DCDCDC !default;
|
||||
$uni-border-4: #B9B9B9 !default;
|
||||
|
||||
// 常规色
|
||||
$uni-black: #000000 !default;
|
||||
$uni-white: #ffffff !default;
|
||||
$uni-transparent: rgba($color: #000000, $alpha: 0) !default;
|
||||
|
||||
// 背景色
|
||||
$uni-bg-color: #f7f7f7 !default;
|
||||
|
||||
/* 水平间距 */
|
||||
$uni-spacing-sm: 8px !default;
|
||||
$uni-spacing-base: 15px !default;
|
||||
$uni-spacing-lg: 30px !default;
|
||||
|
||||
// 阴影
|
||||
$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5) !default;
|
||||
$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;
|
||||
$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5) !default;
|
||||
|
||||
// 蒙版
|
||||
$uni-mask: rgba($color: #000000, $alpha: 0.4) !default;
|
||||
@@ -0,0 +1,19 @@
|
||||
// 合并 map
|
||||
@function map-deep-merge($parent-map, $child-map){
|
||||
$result: $parent-map;
|
||||
@each $key, $child in $child-map {
|
||||
$parent-has-key: map-has-key($result, $key);
|
||||
$parent-value: map-get($result, $key);
|
||||
$parent-type: type-of($parent-value);
|
||||
$child-type: type-of($child);
|
||||
$parent-is-map: $parent-type == map;
|
||||
$child-is-map: $child-type == map;
|
||||
|
||||
@if (not $parent-has-key) or ($parent-type != $child-type) or (not ($parent-is-map and $child-is-map)){
|
||||
$result: map-merge($result, ( $key: $child ));
|
||||
}@else {
|
||||
$result: map-merge($result, ( $key: map-deep-merge($parent-value, $child) ));
|
||||
}
|
||||
}
|
||||
@return $result;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
// 间距基础倍数
|
||||
$uni-space-root: 2;
|
||||
// 边框半径默认值
|
||||
$uni-radius-root:5px;
|
||||
// 主色
|
||||
$uni-primary: #2979ff;
|
||||
// 辅助色
|
||||
$uni-success: #4cd964;
|
||||
// 警告色
|
||||
$uni-warning: #f0ad4e;
|
||||
// 错误色
|
||||
$uni-error: #dd524d;
|
||||
// 描述色
|
||||
$uni-info: #909399;
|
||||
// 中性色
|
||||
$uni-main-color: #303133;
|
||||
$uni-base-color: #606266;
|
||||
$uni-secondary-color: #909399;
|
||||
$uni-extra-color: #C0C4CC;
|
||||
// 背景色
|
||||
$uni-bg-color: #f5f5f5;
|
||||
// 边框颜色
|
||||
$uni-border-1: #DCDFE6;
|
||||
$uni-border-2: #E4E7ED;
|
||||
$uni-border-3: #EBEEF5;
|
||||
$uni-border-4: #F2F6FC;
|
||||
|
||||
// 常规色
|
||||
$uni-black: #000000;
|
||||
$uni-white: #ffffff;
|
||||
$uni-transparent: rgba($color: #000000, $alpha: 0);
|
||||
@@ -0,0 +1,62 @@
|
||||
@import './styles/setting/_variables.scss';
|
||||
// 间距基础倍数
|
||||
$uni-space-root: 2;
|
||||
// 边框半径默认值
|
||||
$uni-radius-root:5px;
|
||||
|
||||
// 主色
|
||||
$uni-primary: #2979ff;
|
||||
$uni-primary-disable:mix(#fff,$uni-primary,50%);
|
||||
$uni-primary-light: mix(#fff,$uni-primary,80%);
|
||||
|
||||
// 辅助色
|
||||
// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。
|
||||
$uni-success: #18bc37;
|
||||
$uni-success-disable:mix(#fff,$uni-success,50%);
|
||||
$uni-success-light: mix(#fff,$uni-success,80%);
|
||||
|
||||
$uni-warning: #f3a73f;
|
||||
$uni-warning-disable:mix(#fff,$uni-warning,50%);
|
||||
$uni-warning-light: mix(#fff,$uni-warning,80%);
|
||||
|
||||
$uni-error: #e43d33;
|
||||
$uni-error-disable:mix(#fff,$uni-error,50%);
|
||||
$uni-error-light: mix(#fff,$uni-error,80%);
|
||||
|
||||
$uni-info: #8f939c;
|
||||
$uni-info-disable:mix(#fff,$uni-info,50%);
|
||||
$uni-info-light: mix(#fff,$uni-info,80%);
|
||||
|
||||
// 中性色
|
||||
// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。
|
||||
$uni-main-color: #3a3a3a; // 主要文字
|
||||
$uni-base-color: #6a6a6a; // 常规文字
|
||||
$uni-secondary-color: #909399; // 次要文字
|
||||
$uni-extra-color: #c7c7c7; // 辅助说明
|
||||
|
||||
// 边框颜色
|
||||
$uni-border-1: #F0F0F0;
|
||||
$uni-border-2: #EDEDED;
|
||||
$uni-border-3: #DCDCDC;
|
||||
$uni-border-4: #B9B9B9;
|
||||
|
||||
// 常规色
|
||||
$uni-black: #000000;
|
||||
$uni-white: #ffffff;
|
||||
$uni-transparent: rgba($color: #000000, $alpha: 0);
|
||||
|
||||
// 背景色
|
||||
$uni-bg-color: #f7f7f7;
|
||||
|
||||
/* 水平间距 */
|
||||
$uni-spacing-sm: 8px;
|
||||
$uni-spacing-base: 15px;
|
||||
$uni-spacing-lg: 30px;
|
||||
|
||||
// 阴影
|
||||
$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5);
|
||||
$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2);
|
||||
$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5);
|
||||
|
||||
// 蒙版
|
||||
$uni-mask: rgba($color: #000000, $alpha: 0.4);
|
||||
@@ -0,0 +1 @@
|
||||
// 任何第三方框架,包括图标库,字体等都需要写在这里,并附上网址。
|
||||
Reference in New Issue
Block a user