| | |
| | | } |
| | | |
| | | /** |
| | | * MyBatis-Plus 会将空集合拼成 IN (),ShardingSphere 无法解析该 SQL。 |
| | | * 空集合按无匹配用户处理,使用不可能的 ID 进行查询。 |
| | | */ |
| | | private List<Integer> normalizeQueryIds(List<Integer> ids) { |
| | | return ids == null || ids.isEmpty() ? Collections.singletonList(-1) : ids; |
| | | } |
| | | |
| | | /** |
| | | * 查询玩湃惠民卡绑定学员列表信息 |
| | | * |
| | | * @return |
| | |
| | | return studentService.listOne(id); |
| | | } |
| | | |
| | | /** 仅供管理端超级管理员经受控接口查看学员完整敏感信息。 */ |
| | | @PostMapping("/internal/sensitive/student/{id}") |
| | | public Map<String, Object> sensitiveStudent(@PathVariable("id") Integer id, |
| | | @RequestHeader(value = "X-Internal-Sensitive-Access", required = false) String access) { |
| | | if (!"true".equalsIgnoreCase(access)) { |
| | | throw new IllegalStateException("Sensitive data access is restricted to the management service"); |
| | | } |
| | | TStudent student = studentService.getById(id); |
| | | if (student == null) return null; |
| | | Map<String, Object> result = new HashMap<String, Object>(); |
| | | result.put("id", student.getId()); |
| | | result.put("appUserId", student.getAppUserId()); |
| | | result.put("name", student.getName()); |
| | | result.put("phone", student.getPhone()); |
| | | result.put("idCard", student.getIdCard()); |
| | | return result; |
| | | } |
| | | |
| | | @ResponseBody |
| | | @PostMapping("/student/update") |
| | | public void update(@RequestBody TStudent tStudent) { |
| | | System.out.println("======tStudent======" + tStudent); |
| | | // 管理端不会回显加密手机号;空值代表未修改,必须保留数据库中的原始值。 |
| | | if (StringUtils.isEmpty(tStudent.getPhone()) && tStudent.getId() != null) { |
| | | TStudent originalStudent = studentService.getById(tStudent.getId()); |
| | | if (originalStudent != null) { |
| | | tStudent.setPhone(originalStudent.getPhone()); |
| | | } |
| | | } |
| | | studentService.updateById(tStudent); |
| | | } |
| | | |
| | |
| | | @ResponseBody |
| | | @PostMapping("/student/userAndVipYys") |
| | | public HashMap<String, Object> userAndVipYys(@RequestBody List<Integer> ids) { |
| | | ids = normalizeQueryIds(ids); |
| | | HashMap<String, Object> map = new HashMap<>(); |
| | | |
| | | // 所有的 |
| | |
| | | |
| | | @PostMapping("/student/queryUserAgeYys") |
| | | public HashMap<String, Object> queryUserAgeYys(@RequestBody List<Integer> ids) { |
| | | ids = normalizeQueryIds(ids); |
| | | HashMap<String, Object> map = new HashMap<>(); |
| | | List<TAppUser> list = appUserService.list(new LambdaQueryWrapper<TAppUser>().in(TAppUser::getId, ids) |
| | | .isNotNull(TAppUser::getPhone) |
| | |
| | | |
| | | @PostMapping("/student/queryUserAgeYys1") |
| | | public HashMap<String, Object> queryUserAgeYys1(@RequestBody List<Integer> ids) { |
| | | ids = normalizeQueryIds(ids); |
| | | HashMap<String, Object> map = new HashMap<>(); |
| | | List<TAppUser> list = appUserService.list(new LambdaQueryWrapper<TAppUser>().in(TAppUser::getId, ids)); |
| | | List<Integer> collect = list.stream().map(TAppUser::getId).collect(Collectors.toList()); |
| | |
| | | collect.add(-1); |
| | | } |
| | | // |
| | | List<TCoursePackage> list2 = coursePackageService.list(new LambdaQueryWrapper<TCoursePackage>().in(TCoursePackage::getId, collect)); |
| | | List<TCoursePackage> list2 = coursePackageService.list(new LambdaQueryWrapper<TCoursePackage>() |
| | | .in(TCoursePackage::getId, collect) |
| | | .ne(TCoursePackage::getState, 3)); |
| | | // 课类型 |
| | | List<TCoursePackageType> list1 = coursePackageTypeService.list(); |
| | | List<TCoursePackageType> list1 = coursePackageTypeService.list( |
| | | new LambdaQueryWrapper<TCoursePackageType>().ne(TCoursePackageType::getState, 3)); |
| | | |
| | | for (CoursePackageOrder tCoursePackagePayment : list) { |
| | | for (TCoursePackage tCoursePackage : list2) { |
| | |
| | | @PostMapping("/coursePackagePayment/queryCourseData") |
| | | public List<Map<String, Object>> queryCourseData(@RequestBody List<Integer> ids) { |
| | | // 找出课程类型 找出剩余的课时数 |
| | | List<TCoursePackageType> list = coursePackageTypeService.list(); |
| | | List<TCoursePackageType> list = coursePackageTypeService.list( |
| | | new LambdaQueryWrapper<TCoursePackageType>().ne(TCoursePackageType::getState, 3)); |
| | | if (ids.size() == 0) { |
| | | ids.add(-1); |
| | | } |
| | |
| | | map.put("allCourse",sum-sum1); |
| | | |
| | | //所有课包 |
| | | List<TCoursePackage> coursePackages = tcpService.list(); |
| | | List<TCoursePackage> coursePackages = tcpService.list( |
| | | new LambdaQueryWrapper<TCoursePackage>().ne(TCoursePackage::getState, 3)); |
| | | |
| | | |
| | | // 年 |
| | |
| | | map.put("allCourse", sum - sum1); |
| | | |
| | | //所有课包 |
| | | List<TCoursePackage> coursePackages = tcpService.list(); |
| | | List<TCoursePackage> coursePackages = tcpService.list( |
| | | new LambdaQueryWrapper<TCoursePackage>().ne(TCoursePackage::getState, 3)); |
| | | |
| | | |
| | | // 年 |
| | |
| | | map.put("allCourse", sum - sum1); |
| | | |
| | | //所有课包 |
| | | List<TCoursePackage> coursePackages = tcpService.list(); |
| | | List<TCoursePackage> coursePackages = tcpService.list( |
| | | new LambdaQueryWrapper<TCoursePackage>().ne(TCoursePackage::getState, 3)); |
| | | |
| | | |
| | | // 年 |
| | |
| | | @PostMapping("/internal/sensitive/app-user/{id}") |
| | | Map<String, Object> sensitiveAppUser(@PathVariable("id") Integer id); |
| | | |
| | | /** Only invoked by the super-administrator sensitive student-data query. */ |
| | | @PostMapping("/internal/sensitive/student/{id}") |
| | | Map<String, Object> sensitiveStudent(@PathVariable("id") Integer id); |
| | | |
| | | /** |
| | | * 根据介绍有礼id查询当前活动参与次数 |
| | | */ |
| | |
| | | * @return |
| | | * @date 2017年2月18日 上午10:32:04 |
| | | */ |
| | | List<ZTreeNode> roleTreeList(@Param("type") Integer type, @Param("id") Integer id, @Param("storeIds") List<Integer> storeIds); |
| | | List<ZTreeNode> roleTreeList(@Param("type") Integer type, @Param("id") Integer id, @Param("creatorId") Integer creatorId); |
| | | |
| | | /** |
| | | * 获取角色列表树 |
| | |
| | | * @return |
| | | * @date 2017年2月18日 上午10:32:04 |
| | | */ |
| | | List<ZTreeNode> roleTreeListByRoleId(@Param("array") String[] roleId, @Param("type") Integer type, @Param("id") Integer id); |
| | | List<ZTreeNode> roleTreeListByRoleId(@Param("array") String[] roleId, @Param("type") Integer type, @Param("id") Integer id, @Param("creatorId") Integer creatorId); |
| | | |
| | | |
| | | } |
| | | } |
| | |
| | | |
| | | void deleteStoreRelations(@Param("userId") Integer userId); |
| | | |
| | | void insertStoreRelations(@Param("userId") Integer userId, @Param("storeIds") List<Integer> storeIds); |
| | | void insertStoreRelation(@Param("id") Long id, @Param("userId") Integer userId, @Param("storeId") Integer storeId); |
| | | |
| | | List<Integer> getStoreIdsByUserId(@Param("userId") Integer userId); |
| | | |
| New file |
| | |
| | | package com.dsh.guns.modular.system.audit; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.cloud.client.ServiceInstance; |
| | | import org.springframework.cloud.client.discovery.DiscoveryClient; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.io.InputStream; |
| | | import java.net.HttpURLConnection; |
| | | import java.net.URL; |
| | | import java.net.URLEncoder; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.util.Collections; |
| | | import java.util.ArrayList; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** 从 other 服务读取统一账号审计日志,不经公网网关暴露内部接口。 */ |
| | | @Slf4j |
| | | @Component |
| | | public class AccountAuditLogReader { |
| | | @Resource private DiscoveryClient discoveryClient; |
| | | |
| | | public AuditLogPage page(long current, long size, String userAccount, String operationType) { |
| | | try { |
| | | List<ServiceInstance> instances = discoveryClient.getInstances("mb-cloud-other"); |
| | | if (instances == null || instances.isEmpty()) return AuditLogPage.empty(); |
| | | StringBuilder url = new StringBuilder(instances.get(0).getUri().toString()) |
| | | .append("/internal/audit-logs?current=").append(current) |
| | | .append("&size=").append(Math.min(size, 200)); |
| | | appendQuery(url, "userAccount", userAccount); |
| | | appendQuery(url, "operationType", operationType); |
| | | HttpURLConnection connection = (HttpURLConnection) new URL(url.toString()).openConnection(); |
| | | connection.setConnectTimeout(2000); |
| | | connection.setReadTimeout(5000); |
| | | connection.setRequestMethod("GET"); |
| | | try (InputStream input = connection.getInputStream()) { |
| | | JSONObject body = JSON.parseObject(new String(readAll(input), StandardCharsets.UTF_8)); |
| | | JSONArray records = body.getJSONArray("records"); |
| | | return new AuditLogPage(toRecords(records), body.getLongValue("total")); |
| | | } finally { |
| | | connection.disconnect(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("读取账号审计日志失败", e); |
| | | return AuditLogPage.empty(); |
| | | } |
| | | } |
| | | |
| | | private static void appendQuery(StringBuilder url, String name, String value) throws Exception { |
| | | if (value != null && !value.trim().isEmpty()) { |
| | | url.append('&').append(name).append('=').append(URLEncoder.encode(value.trim(), "UTF-8")); |
| | | } |
| | | } |
| | | |
| | | private static byte[] readAll(InputStream input) throws Exception { |
| | | byte[] buffer = new byte[4096]; |
| | | int length; |
| | | java.io.ByteArrayOutputStream output = new java.io.ByteArrayOutputStream(); |
| | | while ((length = input.read(buffer)) != -1) output.write(buffer, 0, length); |
| | | return output.toByteArray(); |
| | | } |
| | | |
| | | @SuppressWarnings("unchecked") |
| | | private static List<Map<String, Object>> toRecords(JSONArray records) { |
| | | if (records == null || records.isEmpty()) return Collections.emptyList(); |
| | | List<Map<String, Object>> result = new ArrayList<Map<String, Object>>(records.size()); |
| | | for (Object record : records) { |
| | | if (record instanceof Map) { |
| | | result.add(new LinkedHashMap<String, Object>((Map<String, Object>) record)); |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | public static class AuditLogPage { |
| | | private final List<Map<String, Object>> records; |
| | | private final long total; |
| | | |
| | | AuditLogPage(List<Map<String, Object>> records, long total) { this.records = records; this.total = total; } |
| | | static AuditLogPage empty() { return new AuditLogPage(Collections.<Map<String, Object>>emptyList(), 0); } |
| | | public List<Map<String, Object>> getRecords() { return records; } |
| | | public long getTotal() { return total; } |
| | | } |
| | | } |
| | |
| | | if(objectId>0){ |
| | | operatorId=objectId; |
| | | if(storeId==null){ |
| | | cityManagerId= storeService.list(new QueryWrapper<TStore>().eq("cityManagerId", objectId)).stream().map(TStore::getId).collect(Collectors.toList()); |
| | | cityManagerId= storeService.list(new QueryWrapper<TStore>().eq("cityManagerId", objectId).ne("state", 3)).stream().map(TStore::getId).collect(Collectors.toList()); |
| | | }else { |
| | | cityManagerId.add(storeId); |
| | | } |
| | |
| | | // 根据运营商ID 查询运营商下的所有门店 |
| | | if (storeId==null){ |
| | | if (operatorId!=null){ |
| | | List<TStore> stores = storeService.list(new QueryWrapper<TStore>().eq("operatorId", operatorId)); |
| | | List<TStore> stores = storeService.list(new QueryWrapper<TStore>().eq("operatorId", operatorId).ne("state", 3)); |
| | | // 拿到该运营商下的门店id集合 |
| | | storeIds = stores.stream().map(TStore::getId).collect(Collectors.toList()); |
| | | query.setStoresIds(storeIds); |
| | |
| | | int currentYear = today.getYear(); |
| | | List<IncomeStatisticsVO> yearly = new ArrayList<>(); |
| | | List<Object> years = new ArrayList<>(); |
| | | List<String> yearLabels = new ArrayList<>(); |
| | | for (int year = currentYear - 9; year <= currentYear; year++) { |
| | | final int statisticYear = year; |
| | | yearly.add(sumIncomeStatistics(incomeRecords, record -> getRecordDate(record) |
| | | .getYear() == statisticYear)); |
| | | years.add(year); |
| | | yearLabels.add(String.valueOf(year)); |
| | | } |
| | | |
| | | List<IncomeStatisticsVO> monthly = new ArrayList<>(); |
| | | List<String> monthLabels = new ArrayList<>(); |
| | | for (int month = 1; month <= 12; month++) { |
| | | final int statisticMonth = month; |
| | | monthly.add(sumIncomeStatistics(incomeRecords, record -> { |
| | | LocalDate date = getRecordDate(record); |
| | | return date.getYear() == currentYear && date.getMonthValue() == statisticMonth; |
| | | })); |
| | | monthLabels.add(String.format("%d-%02d", currentYear, month)); |
| | | } |
| | | |
| | | List<IncomeStatisticsVO> weekly = new ArrayList<>(); |
| | | List<String> weekLabels = new ArrayList<>(); |
| | | for (int weekIndex = 3; weekIndex >= 0; weekIndex--) { |
| | | LocalDate startDate = today.minusDays(weekIndex * 7L + 6L); |
| | | LocalDate endDate = today.minusDays(weekIndex * 7L); |
| | |
| | | LocalDate date = getRecordDate(record); |
| | | return !date.isBefore(startDate) && !date.isAfter(endDate); |
| | | })); |
| | | weekLabels.add(startDate.format(DateTimeFormatter.ISO_LOCAL_DATE) |
| | | + " 至 " + endDate.format(DateTimeFormatter.ISO_LOCAL_DATE)); |
| | | } |
| | | |
| | | List<IncomeStatisticsVO> daily = new ArrayList<>(); |
| | | List<String> dayLabels = new ArrayList<>(); |
| | | for (int dayIndex = 6; dayIndex >= 0; dayIndex--) { |
| | | LocalDate statisticDate = today.minusDays(dayIndex); |
| | | daily.add(sumIncomeStatistics(incomeRecords, record -> getRecordDate(record) |
| | | .equals(statisticDate))); |
| | | dayLabels.add(statisticDate.format(DateTimeFormatter.ISO_LOCAL_DATE)); |
| | | } |
| | | |
| | | IncomeStatisticsVO total = sumIncomeStatistics(incomeRecords, record -> true); |
| | |
| | | result.put("weekIncomeStatistics", weekly); |
| | | result.put("dayIncomeStatistics", daily); |
| | | result.put("year", years); |
| | | result.put("yearLabels", yearLabels); |
| | | result.put("monthLabels", monthLabels); |
| | | result.put("weekLabels", weekLabels); |
| | | result.put("dayLabels", dayLabels); |
| | | // 兼容既有图表消费字段,值调整为总营收金额。 |
| | | result.put("yearIncome", yearly.stream().map(IncomeStatisticsVO::getTotalIncomeAmount) |
| | | .collect(Collectors.toList())); |
| | |
| | | //赛事 |
| | | List<Integer> list2 = competitionClient.queryAppUserId(collect1); |
| | | list.addAll(list2); |
| | | // 没有消费用户时,避免 MyBatis-Plus 生成 userId IN () 的非法 SQL。 |
| | | if (list.isEmpty()) { |
| | | list.add(-1); |
| | | } |
| | | //门票 |
| | | |
| | | HashMap<String, Object> map = new HashMap<>(); |
| | |
| | | id = resolveOperatorId(id); |
| | | List<TStore> list4 = storeService.list(new QueryWrapper<TStore>().eq("operatorId", id).eq("state", 1)); |
| | | List<Integer> collect1 = list4.stream().map(TStore::getId).collect(Collectors.toList()); |
| | | // 运营商没有门店时,后续查询不能生成 storeId IN ()。 |
| | | if (collect1.isEmpty()) { |
| | | collect1.add(-1); |
| | | } |
| | | //课程 |
| | | List<Integer> list = coursePackagePaymentClient.queryPaymentCoursePackage(collect1); |
| | | //场地 |
| | |
| | | //赛事 |
| | | List<Integer> list2 = competitionClient.queryAppUserId(collect1); |
| | | list.addAll(list2); |
| | | // 下游会员统计同样使用 IN 查询,空用户列表按无匹配记录处理。 |
| | | if (list.isEmpty()) { |
| | | list.add(-1); |
| | | } |
| | | //门票 |
| | | |
| | | HashMap<String, Object> map = appUserClient.stuYys(list); |
| | |
| | | List<Integer> userPt = appUserClient.getUserPt(Arrays.asList(1)); |
| | | HashMap<String, Object> map = competitionClient.actPt(userPt); |
| | | |
| | | List<TStore> list = storeService.list(new LambdaQueryWrapper<TStore>().eq(TStore::getOperatorId, 0)); |
| | | List<TStore> list = storeService.list(new LambdaQueryWrapper<TStore>().eq(TStore::getOperatorId, 0).ne(TStore::getState, 3)); |
| | | long l1=0; |
| | | for (TStore tStore : list) { |
| | | l1 += getStoreOperatingHours(tStore, new Date()); |
| | |
| | | competionVO.setStoreIds(collect1); |
| | | HashMap<String, Object> map = competitionClient.actYys(competionVO); |
| | | |
| | | List<TStore> lists = storeService.list(new LambdaQueryWrapper<TStore>().eq(TStore::getOperatorId, id)); |
| | | List<TStore> lists = storeService.list(new LambdaQueryWrapper<TStore>() |
| | | .eq(TStore::getOperatorId, id) |
| | | .eq(TStore::getState, 1)); |
| | | long l1=0; |
| | | for (TStore tStore : lists) { |
| | | l1 += getStoreOperatingHours(tStore, new Date()); |
| | |
| | | |
| | | HashMap<String, Object> map = competitionClient.actPt(list); |
| | | |
| | | List<TStore> lists = storeService.list(new LambdaQueryWrapper<TStore>().eq(TStore::getOperatorId, 0)); |
| | | List<TStore> lists = storeService.list(new LambdaQueryWrapper<TStore>().eq(TStore::getOperatorId, 0).ne(TStore::getState, 3)); |
| | | long l1=0; |
| | | for (TStore tStore : lists) { |
| | | l1 += getStoreOperatingHours(tStore, new Date()); |
| | |
| | | if(collect.size()==0){ |
| | | collect.add(-1); |
| | | } |
| | | List<TSite> list3 = siteService.list(new LambdaQueryWrapper<TSite>().in(TSite::getId, collect)); |
| | | List<TSite> list3 = siteService.list(new LambdaQueryWrapper<TSite>().in(TSite::getId, collect).ne(TSite::getState, 3)); |
| | | for (SiteBooking siteBooking : list1) { |
| | | for (TSite tSite : list3) { |
| | | if(siteBooking.getSiteId().equals(tSite.getId())){ |
| | |
| | | } |
| | | |
| | | |
| | | List<TSiteType> list2 = siteTypeService.list(); |
| | | List<TSiteType> list2 = siteTypeService.list(new LambdaQueryWrapper<TSiteType>().ne(TSiteType::getState, 3)); |
| | | |
| | | List<Map<String,Object>> mapList = new ArrayList<>(); |
| | | |
| | |
| | | //赛事 |
| | | List<Integer> list2 = competitionClient.queryAppUserId(collect1); |
| | | list.addAll(list2); |
| | | // 没有消费用户时,避免 MyBatis-Plus 生成 appUserId IN () 的非法 SQL。 |
| | | if (list.isEmpty()) { |
| | | list.add(-1); |
| | | } |
| | | //门票 |
| | | |
| | | QueryDataFee queryDataFee = new QueryDataFee(time, list); |
| | |
| | | |
| | | List<Integer> collects = list1.stream().map(SiteBooking::getSiteId).collect(Collectors.toList()); |
| | | if(collects.size()==0){ |
| | | collect.add(-1); |
| | | collects.add(-1); |
| | | } |
| | | List<TSite> list3 = siteService.list(new LambdaQueryWrapper<TSite>().in(TSite::getId, collects)); |
| | | List<TSite> list3 = siteService.list(new LambdaQueryWrapper<TSite>().in(TSite::getId, collects).ne(TSite::getState, 3)); |
| | | for (SiteBooking siteBooking : list1) { |
| | | for (TSite tSite : list3) { |
| | | if(siteBooking.getSiteId().equals(tSite.getId())){ |
| | |
| | | } |
| | | |
| | | |
| | | List<TSiteType> lists2 = siteTypeService.list(); |
| | | List<TSiteType> lists2 = siteTypeService.list(new LambdaQueryWrapper<TSiteType>().ne(TSiteType::getState, 3)); |
| | | |
| | | List<Map<String,Object>> mapList = new ArrayList<>(); |
| | | |
| | |
| | | if(collect.size()==0){ |
| | | collect.add(-1); |
| | | } |
| | | List<TSite> list3 = siteService.list(new LambdaQueryWrapper<TSite>().in(TSite::getId, collect)); |
| | | List<TSite> list3 = siteService.list(new LambdaQueryWrapper<TSite>().in(TSite::getId, collect).ne(TSite::getState, 3)); |
| | | for (SiteBooking siteBooking : list1) { |
| | | for (TSite tSite : list3) { |
| | | if(siteBooking.getSiteId().equals(tSite.getId())){ |
| | |
| | | } |
| | | |
| | | |
| | | List<TSiteType> list2 = siteTypeService.list(); |
| | | List<TSiteType> list2 = siteTypeService.list(new LambdaQueryWrapper<TSiteType>().ne(TSiteType::getState, 3)); |
| | | |
| | | List<Map<String,Object>> mapList = new ArrayList<>(); |
| | | |
| New file |
| | |
| | | package com.dsh.guns.modular.system.controller.system; |
| | | |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.dsh.guns.core.base.controller.BaseController; |
| | | import com.dsh.guns.core.common.annotion.Permission; |
| | | import com.dsh.guns.core.common.constant.Const; |
| | | import com.dsh.guns.core.common.constant.factory.PageFactory; |
| | | import com.dsh.guns.modular.system.audit.AccountAuditLogReader; |
| | | import org.springframework.stereotype.Controller; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.ResponseBody; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.Map; |
| | | |
| | | /** 管理后台统一账号操作日志。 */ |
| | | @Controller |
| | | @RequestMapping("/accountAuditLog") |
| | | public class AccountAuditLogController extends BaseController { |
| | | private static final String PREFIX = "/system/accountAuditLog/"; |
| | | |
| | | @Resource private AccountAuditLogReader accountAuditLogReader; |
| | | |
| | | @RequestMapping("") |
| | | @Permission(Const.ADMIN_NAME) |
| | | public String index() { |
| | | return PREFIX + "account_audit_log.html"; |
| | | } |
| | | |
| | | @RequestMapping("/list") |
| | | @Permission(Const.ADMIN_NAME) |
| | | @ResponseBody |
| | | public Object list(@RequestParam(required = false) String userAccount, |
| | | @RequestParam(required = false) String operationType) { |
| | | Page<Map<String, Object>> page = new PageFactory<Map<String, Object>>().defaultPage(); |
| | | AccountAuditLogReader.AuditLogPage result = accountAuditLogReader.page(page.getCurrent(), page.getSize(), userAccount, operationType); |
| | | page.setRecords(result.getRecords()); |
| | | page.setTotal(result.getTotal()); |
| | | return super.packForBT(page); |
| | | } |
| | | } |
| | |
| | | return result; |
| | | } |
| | | |
| | | @GetMapping("/student/{id}") |
| | | public Map<String, Object> student(@PathVariable Integer id) { |
| | | assertAdmin(); |
| | | Map<String, Object> result = appUserClient.sensitiveStudent(id); |
| | | writeAudit("student", id, result == null ? "NOT_FOUND" : "SUCCESS"); |
| | | return result; |
| | | } |
| | | |
| | | private void assertAdmin() { |
| | | if (UserExt.getUser() == null || !"admin".equalsIgnoreCase(UserExt.getUser().getAccount())) { |
| | | throw new GunsException(BizExceptionEnum.NO_PERMITION); |
| | |
| | | import com.dsh.guns.modular.system.util.BigDecimalToChineseAmountUtil; |
| | | import com.dsh.guns.modular.system.util.ResultUtil; |
| | | import io.swagger.models.auth.In; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.apache.ibatis.annotations.Param; |
| | | import org.bouncycastle.math.raw.Mod; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | |
| | | public String index(Model model) { |
| | | Integer objectType = UserExt.getUser().getObjectType(); |
| | | model.addAttribute("objectType",objectType); |
| | | model.addAttribute("isSuperAdmin", "admin".equalsIgnoreCase(UserExt.getUser().getAccount())); |
| | | List<TCity> list = cityService.list(new LambdaQueryWrapper<TCity>().eq(TCity::getParentId, 0)); |
| | | model.addAttribute("list",list); |
| | | return PREFIX + "tStudent.html"; |
| | |
| | | |
| | | public ResultUtil update(@RequestBody TStudent tStudent) { |
| | | List<String> strings = new ArrayList<>(); |
| | | if (!tStudent.getImage1().isEmpty()){ |
| | | // 编辑页不回显加密手机号;未填写表示保持原手机号,避免把空值或密文写回账户库。 |
| | | if (StringUtils.isBlank(tStudent.getPhone())) { |
| | | tStudent.setPhone(null); |
| | | } |
| | | if (StringUtils.isNotBlank(tStudent.getImage1())){ |
| | | strings.add(tStudent.getImage1()); |
| | | } |
| | | if (!tStudent.getImage2().isEmpty()){ |
| | | if (StringUtils.isNotBlank(tStudent.getImage2())){ |
| | | strings.add(tStudent.getImage2()); |
| | | } |
| | | if (!tStudent.getImage3().isEmpty()){ |
| | | if (StringUtils.isNotBlank(tStudent.getImage3())){ |
| | | strings.add(tStudent.getImage3()); |
| | | } |
| | | String result = String.join(";", strings); |
| | |
| | | package com.dsh.guns.modular.system.controller.system; |
| | | |
| | | import cn.hutool.crypto.SecureUtil; |
| | | import com.baomidou.mybatisplus.core.toolkit.IdWorker; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | |
| | | @BussinessLog(value = "添加管理员", key = "account", dict = UserDict.class) |
| | | @Permission(Const.ADMIN_NAME) |
| | | @ResponseBody |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Object add(@Valid UserDto user, BindingResult result) { |
| | | if (result.hasErrors()) { |
| | | throw new GunsException(BizExceptionEnum.REQUEST_NULL); |
| | |
| | | if (StringUtils.isEmpty(user.getPhone())) { |
| | | return 5002; |
| | | } |
| | | User theUser = userService.getOne(new QueryWrapper<User>().eq("phone", user.getPhone())); |
| | | // 已逻辑删除的员工(status=3)不占用手机号,可用同一手机号重新创建员工。 |
| | | User theUser = userService.getOne(new QueryWrapper<User>() |
| | | .eq("phone", user.getPhone()) |
| | | .ne("status", ManagerStatus.DELETED.getCode())); |
| | | if (theUser != null || userService.getByAccount(user.getPhone()) != null){ |
| | | return 5001; |
| | | } |
| | |
| | | objectUser.setVersion(UserExt.getUser().getId()); |
| | | this.userService.save(objectUser); |
| | | if (!assignedStores.isEmpty()) { |
| | | userMapper.deleteStoreRelations(objectUser.getId()); |
| | | userMapper.insertStoreRelations(objectUser.getId(), assignedStores.stream().map(TStore::getId).collect(Collectors.toList())); |
| | | replaceStoreRelations(objectUser.getId(), assignedStores); |
| | | // 各所选门店同步负责人账号;旧系统仍可通过 storeStaffId 查询主负责人。 |
| | | for (TStore store : assignedStores) { |
| | | store.setStoreStaffId(objectUser.getId()); |
| | |
| | | if (user.getPhone() != null && user.getPhone().contains("*")) { |
| | | // The edit view displays a masked phone. Treat an unchanged masked |
| | | // value as "keep original", never as a value to persist. |
| | | } else if (!oldUser.getPhone().equals(user.getPhone())){ |
| | | } else if (!Objects.equals(oldUser.getPhone(), user.getPhone())) { |
| | | User phone = userService.getOne(new QueryWrapper<User>().eq("phone", user.getPhone())); |
| | | if (phone == null){ |
| | | oldUser.setPhone(user.getPhone()); |
| | |
| | | if (user.getStoreIds() != null) { |
| | | List<TStore> assignedStores = validateStores(user.getStoreIds()); |
| | | if (assignedStores == null) return ResultUtil.error("所选门店不存在、已停用或不在当前账号管理范围内"); |
| | | userMapper.deleteStoreRelations(oldUser.getId()); |
| | | replaceStoreRelations(oldUser.getId(), assignedStores); |
| | | if (!assignedStores.isEmpty()) { |
| | | oldUser.setObjectType(3); |
| | | oldUser.setObjectId(assignedStores.get(0).getId()); |
| | | oldUser.setRoleid("3"); |
| | | userMapper.insertStoreRelations(oldUser.getId(), assignedStores.stream().map(TStore::getId).collect(Collectors.toList())); |
| | | for (TStore store : assignedStores) { |
| | | store.setStoreStaffId(oldUser.getId()); |
| | | storeService.updateById(store); |
| | |
| | | return stores; |
| | | } |
| | | |
| | | /** 每条关联记录单独生成雪花主键,避免 MyBatis foreach 复用同一个绑定变量。 */ |
| | | private void replaceStoreRelations(Integer userId, List<TStore> stores) { |
| | | userMapper.deleteStoreRelations(userId); |
| | | for (TStore store : stores) { |
| | | userMapper.insertStoreRelation(IdWorker.getId(), userId, store.getId()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 删除管理员(逻辑删除) |
| | | * Delete administrator (logical deletion) |
| | |
| | | package com.dsh.guns.modular.system.service.impl; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import javax.annotation.Resource; |
| | | |
| | |
| | | import com.dsh.course.model.node.ZTreeNode; |
| | | import com.dsh.guns.config.UserExt; |
| | | import com.dsh.guns.modular.system.controller.util.Convert; |
| | | import com.dsh.guns.modular.system.model.TStore; |
| | | import com.dsh.guns.modular.system.service.IStoreService; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | |
| | | public int deleteRolesById(Integer roleId) { |
| | | return this.baseMapper.deleteRolesById(roleId); |
| | | } |
| | | @Resource |
| | | private IStoreService storeService; |
| | | @Override |
| | | public List<ZTreeNode> roleTreeList(Integer type,Integer id) { |
| | | List<Integer> storeIds = new ArrayList<>(); |
| | | if (type==2){ |
| | | // 查询运营商下的门店ids |
| | | List<Integer> collect = storeService.lambdaQuery().eq(TStore::getOperatorId, id).list() |
| | | .stream().map(TStore::getId).collect(Collectors.toList()); |
| | | if (collect.isEmpty()){ |
| | | storeIds.add(-1); |
| | | } |
| | | storeIds.addAll(collect); |
| | | } |
| | | return this.baseMapper.roleTreeList(type,id,storeIds); |
| | | return this.baseMapper.roleTreeList(type, id, UserExt.getUser().getId()); |
| | | } |
| | | |
| | | @Override |
| | | public List<ZTreeNode> roleTreeListByRoleId(String[] roleId,Integer type,Integer id) { |
| | | return this.baseMapper.roleTreeListByRoleId(roleId,type,id); |
| | | return this.baseMapper.roleTreeListByRoleId(roleId, type, id, UserExt.getUser().getId()); |
| | | } |
| | | |
| | | } |
| | |
| | | <if test="type==3"> |
| | | and objType =3 and objId =#{id} |
| | | </if> |
| | | <if test="type == 1"> |
| | | and objType = 1 |
| | | </if> |
| | | <if test="type == 2"> |
| | | and (objType = 2 and objId =#{id} |
| | | or |
| | | <if test="storeIds != null and storeIds.size()>0"> |
| | | objId IN |
| | | <foreach collection="storeIds" separator="," item="id" open="(" close=")"> |
| | | #{id} |
| | | </foreach> |
| | | </if> |
| | | ) |
| | | and version = #{creatorId} |
| | | </if> |
| | | </select> |
| | | |
| | |
| | | <foreach collection="array" index="index" item="i" open="(" separator="," close=")"> |
| | | #{i} |
| | | </foreach> |
| | | <if test="type==3"> |
| | | and objType =3 and objId =#{id} |
| | | </if> |
| | | <if test="type == 2"> |
| | | and (objType = 2 and objId =#{id} |
| | | or |
| | | <if test="storeIds != null and storeIds.size()>0"> |
| | | objId IN |
| | | <foreach collection="storeIds" separator="," item="id" open="(" close=")"> |
| | | #{id} |
| | | </foreach> |
| | | </if> |
| | | ) |
| | | <if test="type==3"> |
| | | and objType =3 and objId =#{id} |
| | | </if> |
| | | <if test="type == 1"> |
| | | and objType = 1 |
| | | </if> |
| | | <if test="type == 2"> |
| | | and version = #{creatorId} |
| | | </if> |
| | | ) r1 ON r.ID = r1.ID |
| | | <where> |
| | | <if test="type==3"> |
| | | and r.objType = 3 and r.objId = #{id} |
| | | </if> |
| | | <if test="type == 1"> |
| | | and r.objType = 1 |
| | | </if> |
| | | <if test="type==2"> |
| | | and r.version = #{creatorId} |
| | | </if> |
| | | </where> |
| | | ORDER BY |
| | | pId, |
| | | num ASC |
| | |
| | | </select> |
| | | <select id="queryProvince" resultType="map"> |
| | | select * from ( |
| | | select provinceCode as code, province as name from t_store where state = 1 |
| | | select provinceCode as code, MIN(province) as name from t_store where state = 1 |
| | | <if test="null != cityCode and '' != cityCode"> |
| | | and cityCode = #{cityCode} |
| | | </if> |
| | |
| | | |
| | | <select id="queryCity" resultType="map"> |
| | | select * from ( |
| | | select cityCode as code, city as name from t_store where state = 1 and provinceCode = #{provinceCode} |
| | | select cityCode as code, MIN(city) as name from t_store where state = 1 and provinceCode = #{provinceCode} |
| | | <if test="null != cityCode and '' != cityCode"> |
| | | and cityCode = #{cityCode} |
| | | </if> |
| | |
| | | <select id="queryCity2" resultType="map"> |
| | | |
| | | select * from ( |
| | | select cityCode as code, city as name |
| | | select cityCode as code, MIN(city) as name |
| | | from t_store where state = 1 and provinceCode = #{provinceCode} |
| | | <if test="null != cityCode and '' != cityCode"> |
| | | and cityCode = #{cityCode} |
| | |
| | | |
| | | <select id="queryCity1" resultType="map"> |
| | | select * from ( |
| | | select cityCode as code, city as name from t_store where state = 1 |
| | | select cityCode as code, MIN(city) as name from t_store where state = 1 |
| | | group by cityCode |
| | | ) as aa order by aa.code |
| | | </select> |
| | |
| | | #{item} |
| | | </foreach> |
| | | </select> |
| | | </mapper> |
| | | </mapper> |
| | |
| | | <delete id="deleteStoreRelations"> |
| | | delete from sys_user_store where userId = #{userId} |
| | | </delete> |
| | | <insert id="insertStoreRelations"> |
| | | insert into sys_user_store(id, userId, storeId) values |
| | | <foreach collection="storeIds" item="storeId" separator=","> |
| | | <bind name="relationId" value="@com.baomidou.mybatisplus.core.toolkit.IdWorker@getId()"/> |
| | | (#{relationId}, #{userId}, #{storeId}) |
| | | </foreach> |
| | | <insert id="insertStoreRelation"> |
| | | insert into sys_user_store(id, userId, storeId) values (#{id}, #{userId}, #{storeId}) |
| | | </insert> |
| | | <select id="getStoreIdsByUserId" resultType="java.lang.Integer"> |
| | | select distinct storeId from ( |
| New file |
| | |
| | | @layout("/common/_container.html"){ |
| | | <div class="row"> |
| | | <div class="col-sm-12"> |
| | | <div class="ibox float-e-margins"> |
| | | <div class="ibox-title"><h5>操作日志</h5></div> |
| | | <div class="ibox-content"> |
| | | <div class="row row-lg"><div class="col-sm-12"> |
| | | <div class="row" style="margin-bottom: 15px;"> |
| | | <div class="col-sm-3"> |
| | | <#NameCon id="userAccount" name="账号" placeholder="请输入账号" /> |
| | | </div> |
| | | <div class="col-sm-3"> |
| | | <#SelectCon id="operationType" name="操作类型"> |
| | | <option value="">请选择操作类型</option> |
| | | <option value="LOGIN">登录</option><option value="CREATE">新增</option> |
| | | <option value="UPDATE">修改</option><option value="DELETE">删除</option><option value="READ">查询</option> |
| | | <option value="VIEW_SENSITIVE">查看敏感信息</option><option value="STATUS_CHANGE">状态变更</option><option value="FEIGN_CALL">服务调用</option> |
| | | </#SelectCon> |
| | | </div> |
| | | <div class="col-sm-3"> |
| | | <#button name="搜索" icon="fa-search" clickFun="AccountAuditLog.search()"/> |
| | | <#button name="重置" icon="fa-trash" clickFun="AccountAuditLog.resetSearch()" space="true"/> |
| | | </div> |
| | | </div> |
| | | <table id="AccountAuditLogTable" data-mobile-responsive="true" data-click-to-select="false"></table> |
| | | </div></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <script src="${ctxPath}/modular/system/accountAuditLog/account_audit_log.js"></script> |
| | | @} |
| | |
| | | </br> |
| | | <div style="height: 25px;box-sizing: border-box;background:#f3f3f4;line-height: 25px;border:1px solid;margin-bottom: 20px;width: 500px"> |
| | | <div id="e1" class='table3' onclick="getContent2(1)" style="border-right: 1px solid #333;background-color: rgb(26, 179, 148);color: white;">活动次数</div> |
| | | <div id="e2" class='table3' onclick="getContent2(2)" style="border-right: 1px solid #333;">预定次数</div> |
| | | <div id="e2" class='table3' onclick="getContent2(2)" style="border-right: 1px solid #333;">活动报名人数</div> |
| | | </div> |
| | | </div> |
| | | <div class="col-sm-10"> |
| | |
| | | text-align: left; |
| | | } |
| | | .income-period-item { |
| | | min-height: 38px; |
| | | min-height: 110px; |
| | | padding: 9px 12px; |
| | | border-left: 3px solid #1ab394; |
| | | background: #f8fafc; |
| | | color: #606266; |
| | | line-height: 20px; |
| | | } |
| | | .income-period-title { |
| | | margin-bottom: 6px; |
| | | color: #303133; |
| | | font-weight: 600; |
| | | } |
| | | .income-period-records { |
| | | max-height: 185px; |
| | | overflow-y: auto; |
| | | } |
| | | .income-period-record { |
| | | padding: 4px 0; |
| | | border-top: 1px solid #edf0f5; |
| | | font-size: 13px; |
| | | } |
| | | .income-trend-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, minmax(480px, 1fr)); |
| | | gap: 18px; |
| | | width: 100%; |
| | | max-width: 1280px; |
| | | margin-bottom: 18px; |
| | | } |
| | | .income-trend-chart { |
| | | height: 300px; |
| | | padding: 8px; |
| | | border: 1px solid #e4e7ed; |
| | | border-radius: 6px; |
| | | background: #fff; |
| | | } |
| | | \@media (max-width: 992px) { |
| | | .income-overview { grid-template-columns: repeat(2, minmax(180px, 1fr)); } |
| | | .income-period-grid { grid-template-columns: 1fr; } |
| | | .income-trend-grid { grid-template-columns: 1fr; } |
| | | } |
| | | </style> |
| | | <div class="ibox float-e-margins"> |
| | |
| | | <span id="weekIncome" style="display:none"></span> |
| | | <span id="todayIncome" style="display:none"></span> |
| | | </div> |
| | | <div id="main" style="width: 1280px;height:500px;"></div> |
| | | <div class="income-trend-grid"> |
| | | <div id="incomeYearChart" class="income-trend-chart"></div> |
| | | <div id="incomeMonthChart" class="income-trend-chart"></div> |
| | | <div id="incomeWeekChart" class="income-trend-chart"></div> |
| | | <div id="incomeDayChart" class="income-trend-chart"></div> |
| | | </div> |
| | | </div> |
| | | <div class="col-sm-10"> |
| | | <h2>收入类型统计</h2> |
| | |
| | | </br> |
| | | <div style="height: 25px;box-sizing: border-box;background:#f3f3f4;line-height: 25px;border:1px solid;margin-bottom: 20px;width: 500px"> |
| | | <div id="e1" class='table3' onclick="getContent2(1)" style="border-right: 1px solid #333;background-color: rgb(26, 179, 148);color: white;">活动次数</div> |
| | | <div id="e2" class='table3' onclick="getContent2(2)" style="border-right: 1px solid #333;">预定次数</div> |
| | | <div id="e2" class='table3' onclick="getContent2(2)" style="border-right: 1px solid #333;">活动报名人数</div> |
| | | </div> |
| | | </div> |
| | | <div class="col-sm-10"> |
| | |
| | | + statistics.refundAmount; |
| | | } |
| | | |
| | | function renderIncomeStatisticsList(elementId, title, labels, statistics) { |
| | | var records = labels.map(function (label, index) { |
| | | var item = statistics[index]; |
| | | return '<div class="income-period-record"><strong>' + label + '</strong>:总营收金额 ' |
| | | + item.totalIncomeAmount + ',净收入金额 ' + item.netIncomeAmount |
| | | + ',优惠券抵扣金额 ' + item.couponDeductionAmount |
| | | + ',退款金额 ' + item.refundAmount + '</div>'; |
| | | }).join(''); |
| | | document.getElementById(elementId).innerHTML = '<div class="income-period-title">' + title |
| | | + '</div><div class="income-period-records">' + records + '</div>'; |
| | | } |
| | | |
| | | function renderIncomeTrendChart(elementId, title, labels, statistics) { |
| | | var chart = echarts.init(document.getElementById(elementId)); |
| | | chart.setOption({ |
| | | title: {text: title, left: 'center', textStyle: {fontSize: 15}}, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | formatter: function (params) { |
| | | var item = params[0]; |
| | | var detail = statistics[item.dataIndex]; |
| | | return labels[item.dataIndex] + '<br/>总营收:' + detail.totalIncomeAmount |
| | | + '<br/>净收入:' + detail.netIncomeAmount |
| | | + '<br/>优惠券抵扣:' + detail.couponDeductionAmount |
| | | + '<br/>退款:' + detail.refundAmount; |
| | | } |
| | | }, |
| | | grid: {left: '5%', right: '4%', bottom: '18%', containLabel: true}, |
| | | xAxis: { |
| | | type: 'category', |
| | | boundaryGap: false, |
| | | data: labels, |
| | | axisLabel: {rotate: labels.length > 7 ? 35 : 0, interval: 0} |
| | | }, |
| | | yAxis: {type: 'value', name: '金额(元)'}, |
| | | series: [{ |
| | | name: '总营收', |
| | | type: 'line', |
| | | smooth: true, |
| | | data: statistics.map(function (item) { return item.totalIncomeAmount; }), |
| | | areaStyle: {opacity: 0.12} |
| | | }] |
| | | }); |
| | | } |
| | | |
| | | $(function(){ |
| | | getContent(1); |
| | | getContent1(1); |
| | |
| | | |
| | | |
| | | var ajax = new $ax(Feng.ctxPath + "/data/getIncomeData", function(resp){ |
| | | yearX=resp.year |
| | | |
| | | |
| | | // 运营、教学等其他图表仍复用年度横轴。 |
| | | yearX = resp.year; |
| | | var list = resp.yearMount; |
| | | console.log(list); |
| | | var t1 = document.getElementById("totalIncome"); |
| | | var t2 = document.getElementById("yearIncome"); |
| | | var t3 = document.getElementById("monthIncome"); |
| | | var t4 = document.getElementById("weekIncome"); |
| | | var t5 = document.getElementById("todayIncome"); |
| | | var netIncomeAmount = document.getElementById("netIncomeAmount"); |
| | | var couponDeductionAmount = document.getElementById("couponDeductionAmount"); |
| | | var refundAmount = document.getElementById("refundAmount"); |
| | | t1.innerText = list[0] || 0; |
| | | t2.innerText = "年度营收:"+list[1]+" "; |
| | | t3.innerText = "月度营收:"+list[2]+" "; |
| | | t4.innerText = "周度营收:"+list[3]+" "; |
| | | t5.innerText = "日度营收:"+list[4]+" "; |
| | | netIncomeAmount.innerText = resp.incomeStatistics.netIncomeAmount || 0; |
| | | couponDeductionAmount.innerText = resp.incomeStatistics.couponDeductionAmount || 0; |
| | | refundAmount.innerText = resp.incomeStatistics.refundAmount || 0; |
| | | renderIncomeStatistics("yearIncomeStatistics", "年度", resp.yearIncomeStatistics[resp.yearIncomeStatistics.length - 1]); |
| | | renderIncomeStatistics("monthIncomeStatistics", "月度", resp.monthIncomeStatistics[new Date().getMonth()]); |
| | | renderIncomeStatistics("weekIncomeStatistics", "周度", resp.weekIncomeStatistics[resp.weekIncomeStatistics.length - 1]); |
| | | renderIncomeStatistics("dayIncomeStatistics", "日度", resp.dayIncomeStatistics[resp.dayIncomeStatistics.length - 1]); |
| | | var year = resp.yearIncome; |
| | | var month = resp.monthIncome; |
| | | var week = resp.weekIncome; |
| | | console.log("周度营收") |
| | | console.log(year) |
| | | console.log(week) |
| | | var days = week[0].length; |
| | | var day = resp.dayIncome; |
| | | option = { |
| | | tooltip: { |
| | | trigger: 'axis' |
| | | }, |
| | | legend: { |
| | | |
| | | data: ['年度营收', '月度营收','周度营收','日度营收'], |
| | | icon: 'stack', |
| | | selectedMode: 'single', // 单选 |
| | | selected: { |
| | | 年度营收: true, |
| | | 月度营收: false, |
| | | 周度营收: false, |
| | | 日度营收: false |
| | | } |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | containLabel: true |
| | | }, |
| | | toolbox: { |
| | | feature: { |
| | | saveAsImage: {} |
| | | } |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | boundaryGap: false, |
| | | data: yearX |
| | | }, |
| | | yAxis: { |
| | | type: 'value' |
| | | }, |
| | | series: [ |
| | | { |
| | | name: '年度营收', |
| | | type: 'line', |
| | | stack: 'Total', |
| | | data: [year[0],year[1],year[2],year[3],year[4],year[5],year[6],year[7],year[8],year[9],year[10]] |
| | | }, |
| | | { |
| | | name: '月度营收', |
| | | type: 'line', |
| | | stack: 'Total', |
| | | data: [month[0][0],month[0][1], month[0][2], month[0][3], month[0][4], |
| | | month[0][5], month[0][6], month[0][7], month[0][8], month[0][9], month[0][10], month[0][11]] |
| | | }, |
| | | { |
| | | name: '周度营收', |
| | | type: 'line', |
| | | stack: 'Total', |
| | | data: [ |
| | | week[0][0], week[0][1], week[0][2], week[0][3] |
| | | ] |
| | | }, |
| | | { |
| | | name: '日度营收', |
| | | type: 'line', |
| | | stack: 'Total', |
| | | data: [ |
| | | day[0][0], day[0][1],day[0][2], day[0][3], day[0][4], day[0][5], day[0][6] |
| | | ] |
| | | } |
| | | ] |
| | | }; |
| | | renderIncomeStatisticsList("yearIncomeStatistics", "历年营收明细", resp.yearLabels, resp.yearIncomeStatistics); |
| | | renderIncomeStatisticsList("monthIncomeStatistics", "本年各月营收明细", resp.monthLabels, resp.monthIncomeStatistics); |
| | | renderIncomeStatisticsList("weekIncomeStatistics", "近四周营收明细", resp.weekLabels, resp.weekIncomeStatistics); |
| | | renderIncomeStatisticsList("dayIncomeStatistics", "近七日营收明细", resp.dayLabels, resp.dayIncomeStatistics); |
| | | renderIncomeTrendChart("incomeYearChart", "多年营收趋势(" + resp.yearLabels[0] + " - " + resp.yearLabels[resp.yearLabels.length - 1] + ")", resp.yearLabels, resp.yearIncomeStatistics); |
| | | renderIncomeTrendChart("incomeMonthChart", "本年各月营收趋势", resp.monthLabels, resp.monthIncomeStatistics); |
| | | renderIncomeTrendChart("incomeWeekChart", "近四周营收趋势", resp.weekLabels, resp.weekIncomeStatistics); |
| | | renderIncomeTrendChart("incomeDayChart", "近七日营收趋势", resp.dayLabels, resp.dayIncomeStatistics); |
| | | }); |
| | | ajax.set("type",1); |
| | | ajax.start(); |
| | | |
| | | // 基于准备好的dom,初始化echarts实例 |
| | | var myChart = echarts.init(document.getElementById('main')); |
| | | var myChart1 = echarts.init(document.getElementById('refund')); |
| | | |
| | | var myChart7 = echarts.init(document.getElementById('courseData')); |
| | |
| | | }); |
| | | ajax4.start(); |
| | | |
| | | |
| | | myChart.on('legendselectchanged', obj => { |
| | | var options = myChart.getOption() |
| | | //这里是选择切换什么样的x轴,那么他会进行对Y值的切换 |
| | | if (obj.name == '年度营收') { |
| | | options.xAxis[0].data = yearX |
| | | } else if (obj.name == '月度营收') { |
| | | options.xAxis[0].data = monthX |
| | | } else if (obj.name == '周度营收') { |
| | | options.xAxis[0].data = weekX |
| | | } else if (obj.name == '日度营收') { |
| | | options.xAxis[0].data = dataX |
| | | } |
| | | myChart.setOption(options, true) |
| | | }) |
| | | myChart1.on('legendselectchanged', obj => { |
| | | var options = myChart1.getOption() |
| | | //这里是选择切换什么样的x轴,那么他会进行对Y值的切换 |
| | |
| | | |
| | | |
| | | // 使用刚指定的配置项和数据显示图表。 |
| | | myChart.setOption(option); |
| | | myChart1.setOption(optionBack); |
| | | |
| | | myChart7.setOption(option7); |
| | |
| | | </br> |
| | | <div style="height: 25px;box-sizing: border-box;background:#f3f3f4;line-height: 25px;border:1px solid;margin-bottom: 20px;width: 500px"> |
| | | <div id="e1" class='table3' onclick="getContent2(1)" style="border-right: 1px solid #333;background-color: rgb(26, 179, 148);color: white;">活动次数</div> |
| | | <div id="e2" class='table3' onclick="getContent2(2)" style="border-right: 1px solid #333;">预定次数</div> |
| | | <div id="e2" class='table3' onclick="getContent2(2)" style="border-right: 1px solid #333;">活动报名人数</div> |
| | | </div> |
| | | </div> |
| | | <div class="col-sm-10"> |
| | |
| | | @if(shiro.hasPermission("/tStudent/comit")){ |
| | | <#button name="学员评语" icon="fa-edit" clickFun="TStudent.openTStudentDetail2()" space="true"/> |
| | | @} |
| | | @if(isSuperAdmin){ |
| | | <#button name="查看敏感信息" icon="fa-eye" clickFun="TStudent.viewSensitiveData()" space="true"/> |
| | | @} |
| | | </div> |
| | | <#table id="TStudentTable"/> |
| | | </div> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <script src="${ctxPath}/modular/system/tStudent/tStudent.js"></script> |
| | | <script src="${ctxPath}/modular/system/tStudent/tStudent.js?v=20260916-3"></script> |
| | | <script src="${ctxPath}/modular/system/tShop/tShop.js"></script> |
| | | <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.4.0/jspdf.umd.min.js"> |
| | | </script> |
| | |
| | | "id": "u:e7cf50e4f755", |
| | | "size": "md", |
| | | "mode": "horizontal", |
| | | "required": true, |
| | | "placeholder": "手机号已加密;不修改请留空", |
| | | "validationErrors": { |
| | | "isPhoneNumber": "请输入正确的手机号" |
| | | }, |
| | | "validateOnChange": true, |
| | | "validations": { |
| | | "isPhoneNumber": true |
| | | }, |
| | | "value":`${item.phone!}` |
| | | } |
| | | }, |
| | | { |
| | | "type": "input-text", |
| | |
| | | "type": "button", |
| | | "id": "u:783ce71739db", |
| | | "label": "保存", |
| | | "onEvent": { |
| | | "click": { |
| | | "actions": [ |
| | | { |
| | | "componentId": "u:7c5f75a3b7b5", |
| | | "args": { |
| | | }, |
| | | "outputVar": "responseResult", |
| | | "actionType": "submit" |
| | | }, |
| | | { |
| | | "args": { |
| | | }, |
| | | "script": "parent.layer.close(window.parent.TStudent.layerIndex);\n\n window.parent.TStudent.table.refresh();", |
| | | "actionType": "custom" |
| | | } |
| | | ] |
| | | } |
| | | }, |
| | | "actionType": "submit", |
| | | "level": "info" |
| | | }, |
| | | { |
| | |
| | | "method": "post", |
| | | "url": "/tStudent/update", |
| | | "messages": { |
| | | "saveSuccess": "保存成功", |
| | | "saveFailed": "保存失败,请稍后重试" |
| | | } |
| | | }, |
| | | "onEvent": { |
| | | "submitSucc": { |
| | | "actions": [ |
| | | { |
| | | "actionType": "custom", |
| | | "script": "parent.layer.close(window.parent.TStudent.layerIndex);\nwindow.parent.TStudent.table.refresh();" |
| | | } |
| | | ] |
| | | } |
| | | }, |
| | | "actions": [ |
| | |
| | | </script> |
| | | |
| | | </body> |
| | | </html> |
| | | </html> |
| | |
| | | |
| | | |
| | | </div> |
| | | <script src="${ctxPath}/modular/system/user/user_info.js"></script> |
| | | <script src="${ctxPath}/modular/system/user/user_info.js?v=20260916-2"></script> |
| | | <script> |
| | | laydate.render({ |
| | | elem: '#birthday' |
| | |
| | | |
| | | |
| | | |
| | | <script src="${ctxPath}/modular/system/user/user_info.js"></script> |
| | | <script src="${ctxPath}/modular/system/user/user_info.js?v=20260916-2"></script> |
| | | <script> |
| | | laydate.render({ |
| | | elem: '#birthday' |
| | |
| | | |
| | | |
| | | |
| | | <script src="${ctxPath}/modular/system/user/user_info.js"></script> |
| | | <script src="${ctxPath}/modular/system/user/user_info.js?v=20260916-2"></script> |
| | | <script> |
| | | laydate.render({ |
| | | elem: '#birthday' |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <script src="${ctxPath}/modular/system/user/user_info.js"></script> |
| | | <script src="${ctxPath}/modular/system/user/user_info.js?v=20260916-2"></script> |
| | | <script> |
| | | laydate.render({ |
| | | elem: '#birthday' |
| New file |
| | |
| | | var AccountAuditLog = { |
| | | id: "AccountAuditLogTable", |
| | | table: null |
| | | }; |
| | | |
| | | AccountAuditLog.operationType = function (value) { |
| | | var names = {LOGIN: '登录', CREATE: '新增', UPDATE: '修改', DELETE: '删除', READ: '查询', VIEW_SENSITIVE: '查看敏感信息', STATUS_CHANGE: '状态变更', FEIGN_CALL: '服务调用'}; |
| | | return names[value] || value || '-'; |
| | | }; |
| | | |
| | | AccountAuditLog.result = function (value) { |
| | | var names = {REQUESTED: '请求中', SUCCESS: '成功', FAIL: '失败'}; |
| | | return names[value] || value || '-'; |
| | | }; |
| | | |
| | | AccountAuditLog.initColumn = function () { |
| | | return [ |
| | | {title: '账号', field: 'userAccount', align: 'center', valign: 'middle'}, |
| | | {title: '操作类型', field: 'operationType', align: 'center', valign: 'middle', formatter: AccountAuditLog.operationType}, |
| | | {title: '操作说明', field: 'operationName', align: 'center', valign: 'middle'}, |
| | | {title: '操作记录值', field: 'requestParams', align: 'left', valign: 'middle'}, |
| | | {title: '变更前记录', field: 'beforeData', align: 'left', valign: 'middle'}, |
| | | {title: '变更后记录', field: 'afterData', align: 'left', valign: 'middle'}, |
| | | {title: '结果', field: 'result', align: 'center', valign: 'middle', formatter: AccountAuditLog.result}, |
| | | {title: '时间', field: 'createdAt', align: 'center', valign: 'middle'}, |
| | | {title: 'IP地址', field: 'clientIp', align: 'center', valign: 'middle'} |
| | | ]; |
| | | }; |
| | | |
| | | AccountAuditLog.formParams = function () { |
| | | return {userAccount: $('#userAccount').val(), operationType: $('#operationType').val()}; |
| | | }; |
| | | |
| | | AccountAuditLog.search = function () { AccountAuditLog.table.refresh({query: AccountAuditLog.formParams()}); }; |
| | | AccountAuditLog.resetSearch = function () { $('#userAccount').val(''); $('#operationType').val(''); AccountAuditLog.search(); }; |
| | | |
| | | $(function () { |
| | | var table = new BSTable(AccountAuditLog.id, '/accountAuditLog/list', AccountAuditLog.initColumn()); |
| | | table.setPaginationType('server'); |
| | | table.setQueryParams(AccountAuditLog.formParams()); |
| | | AccountAuditLog.table = table.init(); |
| | | }); |
| | |
| | | } |
| | | }; |
| | | |
| | | /** 超级管理员查看学员完整手机号、身份证号;服务端会记录审计日志。 */ |
| | | TStudent.viewSensitiveData = function () { |
| | | if (!this.check()) return; |
| | | Feng.confirm('查看完整手机号和身份证号将记录审计日志,是否继续?', function () { |
| | | $.ajax({ |
| | | url: Feng.ctxPath + '/system/sensitive/student/' + TStudent.seItem.id, |
| | | type: 'GET', |
| | | success: function (data) { |
| | | if (!data) { Feng.info('未查询到学员信息'); return; } |
| | | var html = '<div style="padding:20px;line-height:28px">' |
| | | + '<div>学员姓名:' + (data.name || '') + '</div>' |
| | | + '<div>手机号:' + (data.phone || '') + '</div>' |
| | | + '<div>身份证号:' + (data.idCard || '') + '</div></div>'; |
| | | parent.layer.open({type: 1, title: '学员敏感信息(已记录审计日志)', area: ['420px', '250px'], content: html}); |
| | | }, |
| | | error: function () { Feng.error('无权查看敏感信息或查询失败'); } |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | |
| | | |
| | | /** |
| | |
| | | }, |
| | | sortable: true}, |
| | | {title: '生日', field: 'birthday', align: 'center', valign: 'middle', sortable: true}, |
| | | {title: language==1?'性别':(language==2?'Gender':'Jenis kelamin'), field: 'sexName', align: 'center', valign: 'middle', sortable: true, |
| | | {title: language==1?'性别':(language==2?'Gender':'Jenis kelamin'), field: 'sex', align: 'center', valign: 'middle', sortable: true, |
| | | formatter:function (data) { |
| | | if(data==1){ |
| | | return '男' |
| | | }else { |
| | | return '女' |
| | | } |
| | | if (data == 1) return '男'; |
| | | if (data == 2) return '女'; |
| | | return '-'; |
| | | } |
| | | }, |
| | | {title: '身份证号', field: 'idCard', align: 'center', valign: 'middle', sortable: true}, |
| | |
| | | Feng.info("请输入员工姓名") |
| | | return; |
| | | } |
| | | var phonePattern = /^1[3-9]\d{9}$/; |
| | | if (!phonePattern.test($("#phone").val())) { |
| | | Feng.info("请输入合法手机号") |
| | | return ; |
| | | } |
| | | if($("#phone").val()==''){ |
| | | var phone = $("#phone").val(); |
| | | if(phone == ''){ |
| | | Feng.info("请输入员工电话") |
| | | return; |
| | | } |
| | | // 编辑页未改动的手机号为脱敏值(如 138****0000),后端会保留原值; |
| | | // 仅在输入了完整新手机号时才校验格式。 |
| | | var phonePattern = /^1[3-9]\d{9}$/; |
| | | if (phone.indexOf('*') === -1 && !phonePattern.test(phone)) { |
| | | Feng.info("请输入合法手机号") |
| | | return ; |
| | | } |
| | | if($("#birthday").val()==''){ |
| | | Feng.info("请选择员工生日") |
| | | return; |
| | |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.annotation.Resource; |
| | | |
| | | /** 内部审计写入及受控查询接口。 */ |
| | | @RestController |
| | | @RequestMapping("/internal/audit-logs") |
| | |
| | | public Page<SysAccountAuditLog> page(@RequestParam(defaultValue = "1") long current, |
| | | @RequestParam(defaultValue = "20") long size, |
| | | @RequestParam(required = false) Long userId, |
| | | @RequestParam(required = false) String userAccount, |
| | | @RequestParam(required = false) String operationType) { |
| | | QueryWrapper<SysAccountAuditLog> query = new QueryWrapper<SysAccountAuditLog>() |
| | | .orderByDesc("created_at") |
| | | .orderByDesc("id"); |
| | | if (userId != null) query.eq("user_id", userId); |
| | | if (userAccount != null && !userAccount.trim().isEmpty()) query.like("user_account", userAccount.trim()); |
| | | if (operationType != null && !operationType.trim().isEmpty()) query.eq("operation_type", operationType); |
| | | return auditLogService.page(new Page<SysAccountAuditLog>(current, Math.min(size, 200)), query); |
| | | } |
| | |
| | | // 获取到游戏ids |
| | | List<Integer> gameIds = gameService.list(new QueryWrapper<Game>().eq("operationId", query.getObjectId())) |
| | | .stream().map(Game::getId).collect(Collectors.toList()); |
| | | // 运营商下没有智慧球场时,不能省略 gameId 条件后查询全部支付记录。 |
| | | if (gameIds.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | // 根据游戏ids查询智慧球场支付记录 |
| | | QueryWrapper<TGameRecord> wrapper = new QueryWrapper<TGameRecord>().eq("status", 1) |
| | | .ne("payType", 3) |
| | | .ne("payType", 4); |
| | | if(gameIds.size() > 0){ |
| | | wrapper.in("gameId", gameIds); |
| | | } |
| | | wrapper.in("gameId", gameIds); |
| | | List<TGameRecord> list = gameRecordService.list(wrapper); |
| | | return list; |
| | | }else{ |
| | | List<Integer> gameIds = gameService.list(new QueryWrapper<Game>().eq("storeId", query.getStoreId())) |
| | | .stream().map(Game::getId).collect(Collectors.toList()); |
| | | // 门店下没有智慧球场时,不能省略 gameId 条件后查询全部支付记录。 |
| | | if (gameIds.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | QueryWrapper<TGameRecord> wrapper = new QueryWrapper<TGameRecord>() |
| | | .eq("status", 1) |
| | | .in("payType", 1, 2); |
| | | if(gameIds.size() > 0){ |
| | | wrapper.in("gameId", gameIds); |
| | | } |
| | | wrapper.in("gameId", gameIds); |
| | | List<TGameRecord> list = gameRecordService.list(wrapper); |
| | | return list; |
| | | } |
| New file |
| | |
| | | -- 管理后台菜单:系统管理 → 操作日志。日志仅允许查询,默认授予超级管理员。 |
| | | INSERT INTO `sys_menu` (`code`, `pcode`, `pcodes`, `name`, `icon`, `url`, `num`, `levels`, `ismenu`, `tips`, `status`, `isopen`) |
| | | SELECT 'accountAuditLog', 'system', '[0],[system],', '操作日志', 'fa-list-alt', '/accountAuditLog', 998, 2, 1, '统一账号操作审计日志查询', 1, 1 |
| | | WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `code` = 'accountAuditLog'); |
| | | |
| | | INSERT INTO `sys_relation` (`menuid`, `roleid`) |
| | | SELECT m.id, 1 FROM `sys_menu` m |
| | | WHERE m.code = 'accountAuditLog' |
| | | AND NOT EXISTS (SELECT 1 FROM `sys_relation` r WHERE r.menuid = m.id AND r.roleid = 1); |
| | | |
| | | -- 回滚: |
| | | -- DELETE r FROM `sys_relation` r INNER JOIN `sys_menu` m ON r.menuid = m.id WHERE m.code = 'accountAuditLog'; |
| | | -- DELETE FROM `sys_menu` WHERE `code` = 'accountAuditLog'; |