刘帅
2026-09-17 b3221701c387ab536adf66cf315c75d3b429fe04
feat&bug:文档问题修改及优化
5个文件已添加
28个文件已修改
792 ■■■■■ 已修改文件
cloud-server-account/src/main/java/com/dsh/account/controller/StudentController.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-course/src/main/java/com/dsh/course/controller/CourseController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-course/src/main/java/com/dsh/course/controller/CoursePackagePaymentController.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/course/feignClient/account/AppUserClient.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/course/mapper/RoleMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/course/mapper/UserMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/AccountAuditLogReader.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/DataStatisticsController.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/AccountAuditLogController.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/SensitiveDataController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/TStudentController.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/UserMgrController.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/guns/modular/system/service/impl/RoleServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/resources/mapper/RoleMapper.xml 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/resources/mapper/TStoreMapper.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/resources/mapper/UserMapper.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/WEB-INF/view/system/accountAuditLog/account_audit_log.html 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/WEB-INF/view/system/dataStatistics/operatorIncome.html 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/WEB-INF/view/system/dataStatistics/platformIncome.html 205 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/WEB-INF/view/system/dataStatistics/storeIncome.html 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/WEB-INF/view/system/tStudent/tStudent.html 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/WEB-INF/view/system/tStudent/tStudentEdit_first.html 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/WEB-INF/view/system/user/user_add.html 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/WEB-INF/view/system/user/user_edit.html 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/WEB-INF/view/system/user/user_edit1.html 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/WEB-INF/view/system/user/user_view.html 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/static/modular/system/accountAuditLog/account_audit_log.js 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/static/modular/system/tStudent/tStudent.js 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/static/modular/system/user/user.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/static/modular/system/user/user_info.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/controller/AccountAuditLogController.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/controller/BallController.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/V20260916__add_account_audit_log_management_menu.sql 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/controller/StudentController.java
@@ -73,6 +73,14 @@
    }
    /**
     * MyBatis-Plus 会将空集合拼成 IN (),ShardingSphere 无法解析该 SQL。
     * 空集合按无匹配用户处理,使用不可能的 ID 进行查询。
     */
    private List<Integer> normalizeQueryIds(List<Integer> ids) {
        return ids == null || ids.isEmpty() ? Collections.singletonList(-1) : ids;
    }
    /**
     * 查询玩湃惠民卡绑定学员列表信息
     *
     * @return
@@ -312,10 +320,35 @@
        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);
    }
@@ -835,6 +868,7 @@
    @ResponseBody
    @PostMapping("/student/userAndVipYys")
    public HashMap<String, Object> userAndVipYys(@RequestBody List<Integer> ids) {
        ids = normalizeQueryIds(ids);
        HashMap<String, Object> map = new HashMap<>();
        // 所有的
@@ -1230,6 +1264,7 @@
    @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)
@@ -1396,6 +1431,7 @@
    @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());
cloud-server-course/src/main/java/com/dsh/course/controller/CourseController.java
@@ -635,9 +635,12 @@
            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) {
cloud-server-course/src/main/java/com/dsh/course/controller/CoursePackagePaymentController.java
@@ -2662,7 +2662,8 @@
    @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);
        }
@@ -2714,7 +2715,8 @@
        map.put("allCourse",sum-sum1);
        //所有课包
        List<TCoursePackage> coursePackages = tcpService.list();
        List<TCoursePackage> coursePackages = tcpService.list(
                new LambdaQueryWrapper<TCoursePackage>().ne(TCoursePackage::getState, 3));
        // 年
@@ -2864,7 +2866,8 @@
        map.put("allCourse", sum - sum1);
        //所有课包
        List<TCoursePackage> coursePackages = tcpService.list();
        List<TCoursePackage> coursePackages = tcpService.list(
                new LambdaQueryWrapper<TCoursePackage>().ne(TCoursePackage::getState, 3));
        // 年
@@ -3011,7 +3014,8 @@
        map.put("allCourse", sum - sum1);
        //所有课包
        List<TCoursePackage> coursePackages = tcpService.list();
        List<TCoursePackage> coursePackages = tcpService.list(
                new LambdaQueryWrapper<TCoursePackage>().ne(TCoursePackage::getState, 3));
        // 年
cloud-server-management/src/main/java/com/dsh/course/feignClient/account/AppUserClient.java
@@ -32,6 +32,10 @@
    @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查询当前活动参与次数
     */
cloud-server-management/src/main/java/com/dsh/course/mapper/RoleMapper.java
@@ -41,7 +41,7 @@
     * @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);
    /**
     * 获取角色列表树
@@ -49,7 +49,7 @@
     * @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);
}
}
cloud-server-management/src/main/java/com/dsh/course/mapper/UserMapper.java
@@ -68,7 +68,7 @@
    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);
cloud-server-management/src/main/java/com/dsh/guns/modular/system/audit/AccountAuditLogReader.java
New file
@@ -0,0 +1,90 @@
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; }
    }
}
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/DataStatisticsController.java
@@ -181,7 +181,7 @@
        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);
            }
@@ -207,7 +207,7 @@
        // 根据运营商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);
@@ -476,23 +476,28 @@
        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);
@@ -500,13 +505,17 @@
                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);
@@ -527,6 +536,10 @@
        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()));
@@ -930,6 +943,10 @@
        //赛事
        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<>();
@@ -1052,6 +1069,10 @@
        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);
        //场地
@@ -1061,6 +1082,10 @@
        //赛事
        List<Integer> list2 = competitionClient.queryAppUserId(collect1);
        list.addAll(list2);
        // 下游会员统计同样使用 IN 查询,空用户列表按无匹配记录处理。
        if (list.isEmpty()) {
            list.add(-1);
        }
        //门票
        HashMap<String, Object> map = appUserClient.stuYys(list);
@@ -1155,7 +1180,7 @@
        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());
@@ -1187,7 +1212,9 @@
        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());
@@ -1212,7 +1239,7 @@
        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());
@@ -1298,7 +1325,7 @@
        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())){
@@ -1308,7 +1335,7 @@
        }
        List<TSiteType> list2 = siteTypeService.list();
        List<TSiteType> list2 = siteTypeService.list(new LambdaQueryWrapper<TSiteType>().ne(TSiteType::getState, 3));
        List<Map<String,Object>> mapList = new ArrayList<>();
@@ -1364,6 +1391,10 @@
        //赛事
        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);
@@ -1406,9 +1437,9 @@
        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())){
@@ -1418,7 +1449,7 @@
        }
        List<TSiteType> lists2 = siteTypeService.list();
        List<TSiteType> lists2 = siteTypeService.list(new LambdaQueryWrapper<TSiteType>().ne(TSiteType::getState, 3));
        List<Map<String,Object>> mapList = new ArrayList<>();
@@ -1549,7 +1580,7 @@
        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())){
@@ -1559,7 +1590,7 @@
        }
        List<TSiteType> list2 = siteTypeService.list();
        List<TSiteType> list2 = siteTypeService.list(new LambdaQueryWrapper<TSiteType>().ne(TSiteType::getState, 3));
        List<Map<String,Object>> mapList = new ArrayList<>();
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/AccountAuditLogController.java
New file
@@ -0,0 +1,42 @@
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);
    }
}
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/SensitiveDataController.java
@@ -50,6 +50,14 @@
        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);
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/TStudentController.java
@@ -33,6 +33,7 @@
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;
@@ -137,6 +138,7 @@
    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";
@@ -647,13 +649,17 @@
    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);
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/system/UserMgrController.java
@@ -1,6 +1,7 @@
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;
@@ -331,6 +332,7 @@
    @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);
@@ -340,7 +342,10 @@
        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;
        }
@@ -404,8 +409,7 @@
        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());
@@ -433,7 +437,7 @@
        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());
@@ -452,12 +456,11 @@
        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);
@@ -498,6 +501,14 @@
        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)
cloud-server-management/src/main/java/com/dsh/guns/modular/system/service/impl/RoleServiceImpl.java
@@ -1,9 +1,7 @@
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;
@@ -13,8 +11,6 @@
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;
@@ -68,26 +64,14 @@
    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());
    }
}
cloud-server-management/src/main/resources/mapper/RoleMapper.xml
@@ -42,16 +42,11 @@
        <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>
@@ -88,21 +83,27 @@
        <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
cloud-server-management/src/main/resources/mapper/TStoreMapper.xml
@@ -61,7 +61,7 @@
    </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>
@@ -72,7 +72,7 @@
    <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>
@@ -82,7 +82,7 @@
    <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}
@@ -99,7 +99,7 @@
    <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>
@@ -280,4 +280,4 @@
            #{item}
        </foreach>
    </select>
</mapper>
</mapper>
cloud-server-management/src/main/resources/mapper/UserMapper.xml
@@ -73,12 +73,8 @@
    <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 (
cloud-server-management/src/main/webapp/WEB-INF/view/system/accountAuditLog/account_audit_log.html
New file
@@ -0,0 +1,32 @@
@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>
@}
cloud-server-management/src/main/webapp/WEB-INF/view/system/dataStatistics/operatorIncome.html
@@ -226,7 +226,7 @@
                                    </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">
cloud-server-management/src/main/webapp/WEB-INF/view/system/dataStatistics/platformIncome.html
@@ -77,16 +77,46 @@
        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">
@@ -135,7 +165,12 @@
                                        <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>
@@ -239,7 +274,7 @@
                                    </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">
@@ -372,6 +407,51 @@
            + 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);
@@ -380,112 +460,29 @@
        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'));
@@ -631,21 +628,6 @@
        });
        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值的切换
@@ -663,7 +645,6 @@
        // 使用刚指定的配置项和数据显示图表。
        myChart.setOption(option);
        myChart1.setOption(optionBack);
        myChart7.setOption(option7);
cloud-server-management/src/main/webapp/WEB-INF/view/system/dataStatistics/storeIncome.html
@@ -192,7 +192,7 @@
                                    </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">
cloud-server-management/src/main/webapp/WEB-INF/view/system/tStudent/tStudent.html
@@ -67,6 +67,9 @@
                            @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>
@@ -75,7 +78,7 @@
        </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>
cloud-server-management/src/main/webapp/WEB-INF/view/system/tStudent/tStudentEdit_first.html
@@ -124,15 +124,14 @@
          "id": "u:e7cf50e4f755",
          "size": "md",
          "mode": "horizontal",
          "required": true,
          "placeholder": "手机号已加密;不修改请留空",
          "validationErrors": {
            "isPhoneNumber": "请输入正确的手机号"
          },
          "validateOnChange": true,
          "validations": {
            "isPhoneNumber": true
          },
          "value":`${item.phone!}`
          }
        },
        {
          "type": "input-text",
@@ -272,25 +271,7 @@
              "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"
            },
            {
@@ -331,6 +312,18 @@
        "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": [
@@ -353,4 +346,4 @@
</script>
</body>
</html>
</html>
cloud-server-management/src/main/webapp/WEB-INF/view/system/user/user_add.html
@@ -60,7 +60,7 @@
</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'
cloud-server-management/src/main/webapp/WEB-INF/view/system/user/user_edit.html
@@ -67,7 +67,7 @@
<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'
cloud-server-management/src/main/webapp/WEB-INF/view/system/user/user_edit1.html
@@ -51,7 +51,7 @@
<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'
cloud-server-management/src/main/webapp/WEB-INF/view/system/user/user_view.html
@@ -67,7 +67,7 @@
    </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'
cloud-server-management/src/main/webapp/static/modular/system/accountAuditLog/account_audit_log.js
New file
@@ -0,0 +1,42 @@
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();
});
cloud-server-management/src/main/webapp/static/modular/system/tStudent/tStudent.js
@@ -146,6 +146,26 @@
    }
};
/** 超级管理员查看学员完整手机号、身份证号;服务端会记录审计日志。 */
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('无权查看敏感信息或查询失败'); }
        });
    });
};
/**
cloud-server-management/src/main/webapp/static/modular/system/user/user.js
@@ -33,13 +33,11 @@
            },
            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},
cloud-server-management/src/main/webapp/static/modular/system/user/user_info.js
@@ -288,15 +288,18 @@
        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;
cloud-server-other/src/main/java/com/dsh/other/controller/AccountAuditLogController.java
@@ -7,7 +7,6 @@
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/** 内部审计写入及受控查询接口。 */
@RestController
@RequestMapping("/internal/audit-logs")
@@ -24,11 +23,13 @@
    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);
    }
cloud-server-other/src/main/java/com/dsh/other/controller/BallController.java
@@ -47,24 +47,28 @@
            // 获取到游戏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;
        }
sql/V20260916__add_account_audit_log_management_menu.sql
New file
@@ -0,0 +1,13 @@
-- 管理后台菜单:系统管理 → 操作日志。日志仅允许查询,默认授予超级管理员。
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';